Django开发简单Blog系统——上

前言

承接《Django入门》,本文参照慕课网《django入门与实践》课程,开发一个简单的博客系统。按照国际惯例,我们先学习一下django的基础知识。

模板引擎

Django默认使用DTL(Django Template Language)作为模板引擎,如果想要修改为其他模板引擎,直接在djsite/djsite/settings.py中修改TEMPLATES即可。详情可以参考The Django template language: for Python programmers

first template

1、在blog目录下创建templates目录。

2、在templates目录中创建index.html文件。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
first template!
</body>
</html>

3、修改blog/urls.py为:

1
2
3
4
5
6
7
from django.conf.urls import url
from . import views

urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'helloworld', views.hello, name='hello')
]

4、在views.py中添加方法:

1
2
def index(request):
return render(request, 'index.html')

5、测试访问
启动django,访问 http://localhost:8000/blog/ ,即可看到渲染好的页面。

DTL

1、修改index方法为:

1
2
def index(request):
return render(request, 'index.html',{'title': 'DTL'})

2、修改index.html为:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h2>{{title}}</h2>
<p>first template!</p>
</body>
</html>

3、测试访问
启动django,访问 http://localhost:8000/blog/ ,即可看到渲染好的页面。

不同应用下的templates目录会发生冲突,django按照INSTALLED_APP中的顺序查找templates。为了解决这个问题,我们需要在templates目录中加一层目录,以应用名为名。而模板,都放到这一层目录中。

4、在templates目录下,新建blog文件夹,把index.html移动到blog文件夹中。同时,修改index函数为:

1
2
def index(request):
return render(request, 'blog/index.html',{'title': 'DTL'})

增删查改

django默认使用db.sqlite3数据库,我们暂时不进行修改。

Model

1、在blog/models.py中添加一个类Article:

1
2
3
4
5
6
7
8
class Article(models.Model):
title = models.CharField(max_length=32, default='Title')
content = models.TextField(null=True)
# 参数 auto_now=True 表示自动添加隐藏的时间
pub_time = models.DateTimeField(null=True, auto_now=True)

def __str__(self):
return self.title

关于属性的配置,参考Model field reference

2、生成数据表
python manage.py makemigrations blog,创建model,生成的文件在blog/migrations目录下

python manage.py migrate,根据model生成数据库表

3、查看sql语句
python manage.py sqlmigrate blog 0001

4、下载安装SQLite Expert Personal,双击db.sqlite3文件即可查看编辑数据库。

5、使用SQLiteExpert,在blog_article表中添加数据。

查找数据

1、在blog/views.py中添加方法:

1
2
3
4
5
from . import models
def list(request):
articles = models.Article.objects.all()
article = models.Article.objects.get(pk=1)
return render(request, 'blog/list.html',{'articles':articles,'article':article})

2、在migrations/blog中添加list.html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>List</title>
</head>
<body>
<h2>第一篇文章</h2>
<h3>标题:{{article.title}}</h3>
<p>内容{{article.content}}</p>
<hr>
<h2>文章列表</h2>
<table>
<thead>
<th>标题</th>
<th>内容</th>
</thead>
<tbody>
{% for article in articles %}
<tr>
<td>{{article.title}}</td>
<td>{{article.content}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

3、修改blog/urls.py为:

1
2
3
4
5
6
7
8
9
from django.conf.urls import url
from . import views

urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^index$', views.index, name='index'),
url(r'^helloworld$', views.hello, name='hello'),
url(r'^list$',views.list, name='list')
]

4、测试访问
访问地址 http://localhost:8000/blog/list ,即可看到渲染后的效果。

增加数据

1、在blog/urls.py中添加:

1
url(r'^add$',views.add, name='add'),

2、在blog/views.py中添加方法:

1
2
3
4
5
6
7
8
9
import json
def add(request):
title = request.GET.get('title', 'defaultTitle')
content = request.GET.get('content', 'defaultContent')

article = models.Article.objects.create(title=title, content=content)

result = {'code': 0, 'ext': 'success', 'article_id': article.id}
return HttpResponse(json.dumps(result,ensure_ascii=False))

3、测试访问
访问地址 http://localhost:8000/blog/add?title=test&content=test ,即可看到添加成功的提示。

修改数据

1、在blog/urls.py中添加:

1
url(r'^edit$',views.edit, name='edit'),

2、在blog/views.py中添加方法:

1
2
3
4
5
6
7
8
9
10
11
12
def edit(request):
article_id = request.GET.get('id', 0)
title = request.GET.get('title', 'defaultTitle')
content = request.GET.get('content', 'defaultContent')

article = models.Article.objects.get(pk=article_id)
article.title = title
article.content = content
article.save()

result = {'code': 0, 'ext': 'success', 'article_id': article.id}
return HttpResponse(json.dumps(result,ensure_ascii=False))

3、测试访问
访问地址 http://localhost:8000/blog/edit?id=1&title=test&content=test222 ,即可看到修改成功的提示。

PS:修改数据和增加数据可以合成为一个接口,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def edit(request):
article_id = request.GET.get('id', '0')
title = request.GET.get('title', 'defaultTitle')
content = request.GET.get('content', 'defaultContent')
if article_id == '0':
article = models.Article.objects.create(title=title, content=content)
result = {'code': 0, 'ext': 'success', 'article_id': article.id}
return HttpResponse(json.dumps(result,ensure_ascii=False))

article = models.Article.objects.get(pk=article_id)
article.title = title
article.content = content
article.save()
result = {'code': 0, 'ext': 'success', 'article_id': article.id}
return HttpResponse(json.dumps(result,ensure_ascii=False))

删除数据

1、在blog/urls.py中添加:

1
url(r'^delete$',views.delete, name='delete'),

2、在blog/views.py中添加方法:

1
2
3
4
5
def delete(request):
article_id = request.GET.get('id', 0)
models.Article.objects.get(pk=article_id).delete()
result = {'code': 0, 'ext': 'success'}
return HttpResponse(json.dumps(result,ensure_ascii=False))

3、测试访问
访问地址 http://localhost:8000/blog/delete?id=1 ,即可看到删除成功的提示。

Model转JSON

要想最终得到一个json数据,前提是我们要拥有一个dict,所以Model转JSON问题就归结为怎样组装出一个dict。

示例一:在add方法中,我们返回的结果是json格式。如果想要把article(Model)也放进结果中,该怎么处理?参考Python JSONdjango的model对象转化成dict,修改代码如下:

1
2
3
4
5
6
7
8
9
10
from django.forms.models import model_to_dict
def add(request):
title = request.GET.get('title', 'defaultTitle')
content = request.GET.get('content', 'defaultContent')

article = models.Article.objects.create(title=title, content=content)
article = model_to_dict(article)

result = {'code': 0, 'ext': 'success','article': article}
return HttpResponse(json.dumps(result,ensure_ascii=False))

示例二:如果想要把articles(Models)也放进结果中,该怎么处理?参考django 返回json数据
首先,把Models序列化为json格式数据;然后,使用json.loads转换为dict格式数据;最后,把转换后的dict和其他dict格式数据组装到一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.core import serializers
def add(request):
title = request.GET.get('title', 'defaultTitle')
content = request.GET.get('content', 'defaultContent')

article = models.Article.objects.create(title=title, content=content)
article = model_to_dict(article)
articles = models.Article.objects.all()
json_data = serializers.serialize("json", articles)
dict_data = json.loads(json_data)
result = {
'code': 0,
'ext': 'success',
'article': article,
'articles': dict_data}
return HttpResponse(json.dumps(result, ensure_ascii=False))

sqlite清空表命令

delete from 'blog_article';
update sqlite_sequence set seq = 0 where name = 'blog_article';

POST问题

修改add方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def add(request):
title = request.POST.get('title', 'defaultTitle')
content = request.POST.get('content', 'defaultContent')

article = models.Article.objects.create(title=title, content=content)
article = model_to_dict(article)
articles = models.Article.objects.all()
json_data = serializers.serialize("json", articles)
dict_data = json.loads(json_data)
result = {
'code': 0,
'ext': 'success',
'article': article,
'articles': dict_data}
return HttpResponse(json.dumps(result, ensure_ascii=False))

使用postman发送post请求时遇到如下错误:

1
CSRF verification failed. Request aborted.

解决办法,使用csrf_exempt装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def add(request):
title = request.POST.get('title', 'defaultTitle')
content = request.POST.get('content', 'defaultContent')

article = models.Article.objects.create(title=title, content=content)
article = model_to_dict(article)
articles = models.Article.objects.all()
json_data = serializers.serialize("json", articles)
dict_data = json.loads(json_data)
result = {
'code': 0,
'ext': 'success',
'article': article,
'articles': dict_data}
return HttpResponse(json.dumps(result, ensure_ascii=False))

时间处理

修改时区

查看db.sqlite3数据库,可以看到通过接口添加的数据时间不对。
参考django时间的时区问题,修改settings.py:

1
2
3
USE_TZ = True

TIME_ZONE = 'Asia/Shanghai'

设置了USE_TZ=True,则存储到数据库中的时间永远是UTC时间。
设置了TIME_ZONE = ‘Asia/Shanghai’,能保证证模板时间的正确显示。

这时如果TIME_ZONE = ‘UTC’,用datetime.datetime.now()获取时间,django会把这个时间当成UTC时间存储到数据库中去。
如果修改设置为TIME_ZONE = ‘Asia/Shanghai’,用datetime.datetime.now()获取时间,django会把这个时间当成Asia/Shanghai时间,即东八区时间,然后django会把这个时间转成带时区UTC时间存储到数据库中去,而读的时候直接按UTC时间读出来,这就是很多人遇到的存储到数据库中的时间比本地时间会小8个小时的原因。

如果要获取当前时区时间,则使用django.utils.timezone.now()。

Model

参考django:DateTimeField如何自动设置为当前时间并且能被修改,我们来修改一下blog/models.py。

创建django的model时,有DateTimeField、DateField和TimeField三种类型可以用来创建日期字段,其值分别对应着datetime()、date()、time()三中对象。这三个field有着相同的参数auto_now和auto_now_add,表面上看起来很easy,但实际使用中很容易出错,下面是一些注意点。

DateTimeField.auto_now

这个参数的默认值为false,设置为true时,能够在保存该字段时,将其值设置为当前时间,并且每次修改model,都会自动更新。因此这个参数在需要存储“最后修改时间”的场景下,十分方便。需要注意的是,设置该参数为true时,并不简单地意味着字段的默认值为当前时间,而是指字段会被“强制”更新到当前时间,你无法程序中手动为字段赋值;如果使用django再带的admin管理器,那么该字段在admin中是只读的。

DateTimeField.auto_now_add

这个参数的默认值也为False,设置为True时,会在model对象第一次被创建时,将字段的值设置为创建时的时间,以后修改对象时,字段的值不会再更新。该属性通常被用在存储“创建时间”的场景下。与auto_now类似,auto_now_add也具有强制性,一旦被设置为True,就无法在程序中手动为字段赋值,在admin中字段也会成为只读的。

如何将创建时间设置为“默认当前”并且可修改

那么问题来了。实际场景中,往往既希望在对象的创建时间默认被设置为当前值,又希望能在日后修改它。怎么实现这种需求呢?

django中所有的model字段都拥有一个default参数,用来给字段设置默认值。可以用default=timezone.now来替换auto_now=True或auto_now_add=True。timezone.now对应着django.utils.timezone.now(),因此需要写成类似下面的形式:

1
2
3
4
5
6
7
8
9
from django.db import models
import django.utils.timezone as timezone
class Article(models.Model):
title = models.CharField(max_length=32, default='Title')
content = models.TextField(null=True)
pub_time = models.DateTimeField('发布日期', default=timezone.now)

def __str__(self):
return self.title

DateEncoder

经过上面的修改,时间是可以修改了,但是同时引入了另外一个问题,在add接口中,json.dumps()函数会报错:

1
TypeError: Object of type 'datetime' is not JSON serializable

这是因为json.dumps()函数无法解析datetime格式的数据。
问题来了,auto_now=True时,json.dumps()却可以解析,莫非此时不是datetime格式?
且不管它,我们先解决datetime转json问题。

1
2
3
4
5
6
7
8
9
10
import json  
import datetime
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(obj, date):
return obj.strftime('%Y-%m-%d')
else:
return json.JSONEncoder.default(self, obj)

在使用json.dumps()函数时,添加cls参数:

1
json.dumps(result, cls=DateEncoder, ensure_ascii=False)

页面渲染

设置好时区后,在页面渲染时,会自动转化成当前时区时间,例如Nov. 29, 2017, 1:49 p.m.

但是,这种格式不符合我们的阅读习惯,我们可以在渲染时改成自己喜欢的格式:

1
{{article.pub_time|date:"Y-m-d H:i:s"}}

此时,输出到页面的格式就变成了2017-11-29 13:49:44

json UTC处理

以add接口为例,从数据库中查询出的数据时间是UTC格式的,例如2017-11-29T05:49:44.092Z

思路一:
直接返回UTC格式数据给前端,前端来完成格式化,参考js格式化json传来的UTC格式的时间,或者使用支持UTC格式化的模板引擎。

思路二:
参考遍历QuerySet,给每一个pub_time转换格式:

1
2
3
4
5
6
7
8
9
10
import datetime
import time
def utc2local(utc_st):
# UTC时间转本地时间(+8:00)
now_stamp = time.time()
local_time = datetime.datetime.fromtimestamp(now_stamp)
utc_time = datetime.datetime.utcfromtimestamp(now_stamp)
offset = local_time - utc_time
local_st = utc_st + offset
return local_st

1
2
3
4
5
6
7
8
for item in articles:
# print(item.pub_time)
local_time = utc2local(item.pub_time)
# UTC_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
LOCAL_FORMAT = "%Y-%m-%d %H:%M:%S"
# print(local_time.strftime(LOCAL_FORMAT))
local_time_str = local_time.strftime(LOCAL_FORMAT)
item.pub_time = datetime.datetime.strptime(local_time_str, LOCAL_FORMAT)

这种方法返回的时间,格式为2017-11-29T13:49:44,还是有问题,多了个T。
我们为什么不把local_time_str赋值给item.pub_time呢?因为item.put_time限制数据类型为datetime。

思路三:
存储时,直接存储字符串格式的时间。修改blog/models.py如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.db import models

# Create your models here.


class Article(models.Model):
title = models.CharField(max_length=32, default='Title')
content = models.TextField(null=True)
# pub_time = models.DateTimeField('发布日期', default=timezone.now)
pub_time = models.CharField(max_length=64, default='')

def __str__(self):
return self.title

修改add和edit接口为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import json
from django.forms.models import model_to_dict
from django.views.decorators.csrf import csrf_exempt
import datetime
import time
from django.utils import timezone


@csrf_exempt
def add(request):
title = request.POST.get('title', 'defaultTitle')
content = request.POST.get('content', 'defaultContent')

pub_time = utc2local(timezone.now())
LOCAL_FORMAT = "%Y-%m-%d %H:%M:%S"
pub_time = pub_time.strftime(LOCAL_FORMAT)

article = models.Article.objects.create(title=title, content=content, pub_time=pub_time)
article = model_to_dict(article)

result = {
'code': 0,
'ext': 'success',
'article': article}
return HttpResponse(json.dumps(result, ensure_ascii=False))


@csrf_exempt
def edit(request):
article_id = request.POST.get('id', 0)
title = request.POST.get('title', 'defaultTitle')
content = request.POST.get('content', 'defaultContent')
pub_time = utc2local(timezone.now())
LOCAL_FORMAT = "%Y-%m-%d %H:%M:%S"
pub_time = pub_time.strftime(LOCAL_FORMAT)

article = models.Article.objects.get(pk=article_id)
article.title = title
article.content = content
article.pub_time = pub_time
article.save()

article = model_to_dict(article)

result = {
'code': 0,
'ext': 'success',
'article': article}
return HttpResponse(json.dumps(result, ensure_ascii=False))


def utc2local(utc_st):
# UTC时间转本地时间(+8:00)
now_stamp = time.time()
local_time = datetime.datetime.fromtimestamp(now_stamp)
utc_time = datetime.datetime.utcfromtimestamp(now_stamp)
offset = local_time - utc_time
local_st = utc_st + offset
return local_st

源码分享

https://github.com/voidking/djsite/releases/tag/v0.1.0

小结

至此,涉猎了django开发blog所需要的基本知识。下文中,将会在实战中学习django更高级的用法。

书签

django入门与实践

0%