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文件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
    first template!
</body>
</html>

3、修改blog/urls.py为:

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中添加方法:

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

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

DTL

1、修改index方法为:

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

2、修改index.html为:

<!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函数为:

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

增删查改

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

Model

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

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中添加方法:

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文件

<!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为:

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中添加:

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

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

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中添加:

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

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

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:修改数据和增加数据可以合成为一个接口,例如:

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中添加:

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

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

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,修改代码如下:

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格式数据组装到一起。

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方法为:

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请求时遇到如下错误:

CSRF verification failed. Request aborted.

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

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:

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(),因此需要写成类似下面的形式:

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()函数会报错:

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

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

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参数:

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

页面渲染

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

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

{{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转换格式:

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
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如下:

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接口为:

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入门与实践

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容