1. 前言承接《Django入门》,本文参照慕课网《django入门与实践》课程,开发一个简单的博客系统。按照国际惯例,我们先学习一下django的基础知识。
2. 模板引擎Django默认使用DTL(Django Template Language)作为模板引擎,如果想要修改为其他模板引擎,直接在djsite/djsite/settings.py中修改TEMPLATES即可。详情可以参考The Django template language: for Python programmers 。
2.1. first template1、在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 urlfrom . import viewsurlpatterns = [ 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/ ,即可看到渲染好的页面。
2.2. DTL1、修改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' })
3. 增删查改django默认使用db.sqlite3数据库,我们暂时不进行修改。
3.1. Model1、在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表中添加数据。
3.2. 查找数据1、在blog/views.py中添加方法:
1 2 3 4 5 from . import modelsdef 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 urlfrom . import viewsurlpatterns = [ 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 ,即可看到渲染后的效果。
3.3. 增加数据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 ,即可看到添加成功的提示。
3.4. 修改数据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 ))
3.5. 删除数据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 ,即可看到删除成功的提示。
3.6. Model转JSON要想最终得到一个json数据,前提是我们要拥有一个dict,所以Model转JSON问题就归结为怎样组装出一个dict。
示例一:在add方法中,我们返回的结果是json格式。如果想要把article(Model)也放进结果中,该怎么处理?参考Python JSON 和django的model对象转化成dict ,修改代码如下:
1 2 3 4 5 6 7 8 9 10 from django.forms.models import model_to_dictdef 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 serializersdef 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 ))
3.7. sqlite清空表命令delete from 'blog_article';
update sqlite_sequence set seq = 0 where name = 'blog_article';
4. 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 ))
5. 时间处理5.1. 修改时区查看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()。
5.2. 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 modelsimport django.utils.timezone as timezoneclass 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
5.3. 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 datetimeclass 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 )
5.4. 页面渲染设置好时区后,在页面渲染时,会自动转化成当前时区时间,例如Nov. 29, 2017, 1:49 p.m.
但是,这种格式不符合我们的阅读习惯,我们可以在渲染时改成自己喜欢的格式:
1 {{article.pub_time |date:"Y-m-d H:i:s" }}
此时,输出到页面的格式就变成了2017-11-29 13:49:44
5.5. 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 datetimeimport 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: local_time = utc2local(item.pub_time) LOCAL_FORMAT = "%Y -%m -%d %H :%M :%S " 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 modelsclass 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_dictfrom django.views.decorators.csrf import csrf_exemptimport 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
6. 源码分享https://github.com/voidking/djsite/releases/tag/v0.1.0
7. 小结至此,涉猎了django开发blog所需要的基本知识。下文中,将会在实战中学习django更高级的用法。
8. 书签django入门与实践