用django来开发自己的博客

[TOC]

Django的MVC模式/MTV模式

Django紧紧地遵循MVC模式,可以称得上是一种MVC框架。 以下是Django中M、V和C各自的含义:

  • M:数据存取部分,由django数据库层处理;
  • V:选择显示哪些数据要显示以及怎样显示的部分,由视图和模板处理;
  • C:根据用户输入委派视图的部分,由Django框架根据URLconf设置,对给定URL调用适当的Python函数。

由于C由框架自行处理,而Django里更关注的是模型(Model)、模板(Template)和视图(Views),Django也被称为MTV框架。在MTV开发模式中:

  • M:代表模型(Model),即数据存取层。该层处理与数据相关的所有事务:如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等;
  • T:代表模板(Template),即表现层。该层处理与表现相关的决定:如何在页面或其他类型文档中进行显示。
  • V:代表视图(View),即业务逻辑层。该层包含存取模型及调取恰当模板的相关逻辑。你可以把它看作模型与模板之间的桥梁。

(以上摘自《The Django Book》)

完整的开发过程

1. 创建开发环境

mkvirtualenv myblog  # 创建虚拟环境
pip install django  # 安装最新版的django-1.10.6

2. 创建项目和app

django-admin.py startproject myblog
django-admin.py startapp blog  # 一个项目可以包含多个app

当前目录结构如下图所示


目录结构1.png

目录结构介绍

  • blog: app的目录
  • migrations: 包含对模型定义与修改的迁移记录;
  • admin.py: 对 Django 站点管理的定制;
  • apps.py: 包含对 App 的配置;
  • models.py: 应包含定义的模型;
  • tests.py: 包含单元测试;
  • views.py: 应包含各种视图。
  • myblog: 整个项目的配置目录
  • settings.py: 项目的配置文件
  • urls.py: 总的urls配置文件
  • **wsgi.py **: 部署服务器时用到的文件

新定义的app加到settings.py中的INSTALL_APPS中
编辑setting文件,如下图


深度截图20170304154138.png

3. 连接数据库

暂时先用默认的sqlite3数据库,不需要特别的配置。以后需要再改成别的数据库。


4. 建立模型

  • 文章模型
class Article(models.Model):
    STATUS = (
        ('0', '发布'),
        ('1', '草稿')
    )
    title = models.CharField(max_length=64, unique=True, verbose_name='标题')
    abstract = models.TextField(verbose_name='摘要', max_length=54, blank=True, null=True, help_text="可选项,若为空格则摘取正文前54个字符")
    body = models.TextField(verbose_name='内容')
    # on_delete 当指向的表被删除时,将该项设为空
    category = models.ForeignKey('Category', verbose_name='分类', null=True, on_delete=models.SET_NULL)
    tags = models.ManyToManyField('Tag', verbose_name='标签', blank=True)
    url = models.CharField(max_length=255, verbose_name='链接', unique=True)
    status = models.CharField(default='0', max_length=1, choices=STATUS, verbose_name='文章状态')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    last_modified_time = models.DateTimeField(auto_now=True, verbose_name='最近修改时间')

    class Meta:
        # Meta 包含一系列选项,这里的ordering表示排序, - 表示逆序
        # 即当从数据库中取出文章时,以文章最后修改时间逆向排序
        verbose_name = '文章(Article)'
        verbose_name_plural = verbose_name
        ordering = ['-last_modified_time']

    def __str__(self):
        return self.title
  • 分类模型
class Category(models.Model):
    name = models.CharField(max_length=20, verbose_name='类名')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    last_modified_time = models.DateTimeField(auto_now=True, verbose_name='最近修改时间')

    class Meta:
        verbose_name = '分类(Category)'
        verbose_name_plural = verbose_name
        ordering = ['-created_time']

    def __str__(self):
        return self.name
  • 标签模型
class Tag(models.Model):
    name = models.CharField(max_length=20, verbose_name='标签名')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    last_modified_time = models.DateTimeField(auto_now=True, verbose_name='最近修改时间')

    class Meta:
        verbose_name = '标签(Tag)'
        verbose_name_plural = verbose_name
        ordering = ['-created_time']

    def __str__(self):
        return self.name
  • 评论模型
class Comment(models.Model):
    user_name = models.CharField(max_length=64, verbose_name='评论者名字')
    content = models.TextField(verbose_name='评论内容')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
    article = models.ForeignKey('Article', verbose_name='评论所属文章', on_delete=models.CASCADE)

    def __str__(self):
        return self.content[:20]

模型只是利用 Django 提供的 ORM 完成对实际表结构的映射,因此在完成模型定义后,我们需要将其真正同步到实际的数据库中去。新版本的 Django 中,该操作需要分成两步,

  1. 生成 migrations:
    python manage.py makemigrations blog
    深度截图20170304162344.png
  2. 进行数据库同步
    python manage.py migrate
    深度截图20170304162405.png

可以用python manage.py shell进入django shell对数据库进行操作
如下图所示

深度截图20170304164013.png


5. 设置后台管理

from django.contrib import admin
from .models import Article, Category, BlogComment, Tag

# Register your models here.

admin.site.register([Article, Category, BlogComment, Tag])

6. 设置分页

Django提供了一些类来帮助你管理分页的数据,这些类位于django/core/paginator.py中。
分页需要使用到的的 API ,Django 官方文档对此有十分详细的介绍,请参考django文档中分页教程
尽管可以把分页逻辑直接写在视图内,但是为了通用性,我们使用一点点 Django 更加高级的技巧——模板标签(TemplateTags)。

模板标签介绍

为了使用模板标签,Django 要求我们先建立一个 templatetags 文件夹,并在里面加上 init.py文件以指示 python 这是一个模块(python 把含有该问价的文件夹当做一个模块,具体请参考任何一个关于 python 模块的教程)。并且 templatetags 文件夹和你的 model.py,views.py 文件是同级的,也就是说你的目录结构看起来应该是这样:

app/
    __init__.py
    models.py
    templatetags/
        __init__.py
        app_extras.py
    views.py

分页代码

首先来回顾一下 Django 的模板系统是如何工作的,回想一下视图函数的工作流程,视图函数接收一个 Http 请求,经过一系列处理,通常情况下其会渲染某个模板文件,把模板文件中的一些用 {{ }} 包裹的变量替换成从该视图函数中相应变量的值。事实上在此过程中 Django 悄悄帮我们做了一些事情,它把视图函数中的变量的值封装在了一个 Context (一般翻译成上下文)对象中,只要模板文件中的变量在 Context 中有对应的值,它就会被相应的值替换。
因此,我们的程序可以这样做:首先把取到的文章列表(官方术语是一个 queryset)分页,用户请求第几页,我们就把第几页的文章列表传递给模板文件;另外还要根据上面的需求传递页码值给模板文件,这样只要把模板文件中的变量替换成我们传递过去的值,那么就达到本文开篇处那样的分页显示效果了。
paginate.py

from django import template
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
// 这是分页功能涉及的一些类和异常,官方文档对此有详细介绍。当然从命名也可以直接看出它们的用途:Paginator(分页),PageNotAnInteger(页码不是一个整数异常),EmptyPage(空的页码号异常)

register = template.Library()
// 这是定义模板标签要用到的

@register.simple_tag(takes_context=True)
// 这个装饰器表明这个函数是一个模板标签,takes_context = True 表示接收上下文对象,就是前面所说的封装了各种变量的 Context 对象。
def paginate(context, object_list, page_count):
    //context是Context 对象,object_list是你要分页的对象,page_count表示每页的数量
    left = 3 # 当前页码左边显示几个页码号 -1,比如3就显示2个
    right = 3 # 当前页码右边显示几个页码号 -1
    paginator = Paginator(object_list, page_count) # 通过object_list分页对象
    page = context['request'].GET.get('page') # 从 Http 请求中获取用户请求的页码号
    try:
        object_list = paginator.page(page) # 根据页码号获取第几页的数据
        context['current_page'] = int(page) # 把当前页封装进context(上下文)中
        pages = get_left(context['current_page'], left, paginator.num_pages) + get_right(context['current_page'], right, paginator.num_pages)
        // 调用了两个辅助函数,根据当前页得到了左右的页码号,比如设置成获取左右两边2个页码号,那么假如当前页是5,则 pages = [3,4,5,6,7],当然一些细节需要处理,比如如果当前页是2,那么获取的是pages = [1,2,3,4]
    except PageNotAnInteger:
        // 异常处理,如果用户传递的page值不是整数,则把第一页的值返回给他
        object_list = paginator.page(1)
        context['current_page'] = 1 # 当前页是1
        pages = get_right(context['current_page'], right, paginator.num_pages)
    except EmptyPage:
        // 如果用户传递的 page 值是一个空值,那么把最后一页的值返回给他
        object_list = paginator.page(paginator.num_pages)
        context['current_page'] = paginator.num_pages # 当前页是最后一页,num_pages的值是总分页数
        pages = get_left(context['current_page'], left, paginator.num_pages)
    context['article_list'] = object_list # 把获取到的分页的数据封装到上下文中
    context['pages'] = pages # 把页码号列表封装进去
    context['last_page'] = paginator.num_pages # 最后一页的页码号
    context['first_page'] = 1 # 第一页的页码号为1
    try:
        // 获取 pages 列表第一个值和最后一个值,主要用于在是否该插入省略号的判断,在模板文件中将会体会到它的用处。注意这里可能产生异常,因为pages可能是一个空列表,比如本身只有一个分页,那么pages就为空,因为我们永远不会获取页码为1的页码号(至少有1页,1的页码号已经固定写在模板文件中)
        context['pages_first'] = pages[0]
        context['pages_last'] = pages[-1] + 1
        // +1的原因是为了方便判断,在模板文件中将会体会到其作用。
    except IndexError:
        context['pages_first'] = 1 # 发生异常说明只有1页
        context['pages_last'] = 2 # 1 + 1 后的值
    return ''  # 必须加这个,否则首页会显示个None

def get_left(current_page, left, num_pages):
    """
    辅助函数,获取当前页码的值得左边两个页码值,要注意一些细节,比如不够两个那么最左取到2,为了方便处理,包含当前页码值,比如当前页码值为5,那么pages = [3,4,5]
    """
    if current_page == 1:
        return []
    elif current_page == num_pages:
        l = [i - 1 for i in range(current_page, current_page - left, -1) if i - 1 > 1]
        l.sort()
        return l
    l = [i for i in range(current_page, current_page - left, -1) if i > 1]
    l.sort()
    return l

def get_right(current_page, right, num_pages):
    """
    辅助函数,获取当前页码的值得右边两个页码值,要注意一些细节,比如不够两个那么最右取到最大页码值。不包含当前页码值。比如当前页码值为5,那么pages = [6,7]
    """
    if current_page == num_pages:
        return []
    return [i + 1 for i in range(current_page, current_page + right - 1) if i < num_pages - 1]

模板文件

templates/blog/pagination.html

<div id="pagenavi" class="noselect">
    {% if article_list.has_previous %} # 判断是否还有上一页,有的话要显示一个上一页按钮
        <a class="previous-page" href="?page={{ article_list.previous_page_number }}">
            <span class="icon-previous"></span>上一页
        </a>
    {% endif %}

    # 页码号为1永远显示
    {% if first_page == current_page %} # 当前页就是第一页
        <span class="first-page current">1</span>
    {% else %} # 否则的话,第一页是可以点击的,点击后通过?page=1的形式把页码号传递给视图函数
        <a href="?page=1" class="first-page">1</a>
    {% endif %}

    {% if pages_first > 2 %} # 2以前的页码号要被显示成省略号了
        <span>...</span>
    {% endif %}

    {% for page in pages %} # 通过for循环把pages中的值显示出来
        {% if page == current_page %} # 是否当前页,按钮会显示不同的样式
            <span class="current">{{ page }}</span>
        {% else %}
            <a href="?page={{ page }}">{{ page }}</a>
        {% endif %}
    {% endfor %}

    # pages最后一个值+1的值小于最大页码号,说明有页码号需要被省略号替换
    {% if pages_last < last_page %}
        <span>...</span>
    {% endif %}

    # 永远显示最后一页的页码号,如果只有一页则前面已经显示了1就不用再显示了
    {% if last_page != 1 %}
        {% if last_page == current_page %}
            <span class="current">{{ last_page }}</span>
        {% else %}
            <a href="?page={{ last_page }}">{{ last_page }}</a>
        {% endif %}
    {% endif %}

    # 还有下一页,则显示一个下一页按钮
    {% if article_list.has_next %}
        <a class="next-page" href="?page={{ article_list.next_page_number }}">
            下一页<span class="icon-next"></span>
        </a>
    {% endif %}
</div>

至此,整个分页功能就完成了。

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

推荐阅读更多精彩内容