vieyahn2017 / iBlog

44 stars 0 forks source link

5.29 Django使用总结(一个项目的整体流程) #140

Closed vieyahn2017 closed 4 years ago

vieyahn2017 commented 6 years ago

http://blog.csdn.net/liwang_30/article/details/78510322

1. 对于任一django项目,拿到功能需求时都要思考的问题

1)去数据库里存什么 2)制作什么样的页面 3) 定义什么样的URL 4)页面与数据库之间的信息交换方式 5)时间如何考虑 6)数据库中方法被调用时显示方式 7)在Admin后台中的显示

vieyahn2017 commented 4 years ago

目录:

1. 对于任一django项目,拿到功能需求时都要思考的问题

  1. HTML入门 2.1 文字类:h1~h6,p,a,label,span 2.2 组件类:table,input,form,button,textarea

3.页面美化 3.1 CSS 3.2 bootstrap

4.页面布局

5.基础模板的制作

6. 数据存入数据库

6.1 示例代码 6.2 数据库类的继承 6.3 数据库中的数据类型 6.4 数据库中的外键

7. 版块应用的创建

7.1 建立版块application 7.2 在settings.py中注册这个app 7.3 在models.py中建立block 7.3.1 创建版块数据表前的思考 7.3.2 代码分解 7.3.3 后台数据库中的显示 7.4 数据表在views中的引用 7.4.1 在views中导入数据表 7.4.2 定义函数及调用数据库数据,将数据传递至template 7.5 数据表在templates中的显示 7.6 页面的显示效果

8.文章列表的创建

8.1 创建前的思考 8.2 编写文章列表views 8.2.1 引入相关数据库 8.2.2 编写views函数 8.2.3 编写文章列表template 8.2.3.1 文章列表导航功能的模板 8.2.3.2 “创建文章”按钮的制作 8.2.3.3 文章列表的显示 8.2.3.4 分页功能的引入 8.3 文章列表页面的显示效果

  1. 分页的实现 9.1 分页的本质 9.2 django提供的分页工具 9.3 分页功能的页面分解 9.4 分页功能代码编写 9.5 分页的模板 9.6 分页功能的应用 9.6.1 分页功能的引入 9.6.2 功能的实现 9.6.3 功能的页面

10.文章的创建 10.1 创建前的思考 10.2 建立文章application及注册 10.3 文章数据库的创建 10.3.1 引入关联表 10.3.2 创建数据表 10.4 文章数据库在views中的引用 10.4.1 在views中导入数据表 10.4.2 定义类并接收数据、进行数据校验等处理、存入数据库及进行页面传递 10.5 文章在template中的显示 10.5.1 引入导航功能 10.5.2 文章创建的form表单

  1. 富文本编辑框功能 11.1 富文本编辑框介绍 11.2 富文本编辑框的安装 11.3 在settings.py中进行静态文件注册 11.4 在template中替换原有纯文本输入方式 11.5 富文本编辑框中文字输入的提交 11.6 关闭文章详情显示中的自动转义功能 11.7 文件上传功能概述 11.8 文件上传功能设置 11.8.1 设置后端URL地址 11.8.2 设置ueditor与后端接口 11.8.3 设置文件存储位置及访问路径

  2. forms校验器的使用 12.1 校验器功能的思考 12.2 校验器代码 12.2.1 引入校验器功能及数据表 12.2.2 定义校验器类 12.3 校验器在views中的引用 12.3.1 引入定义的校验器类 12.3.2 编写校验器代码 12.4 校验器在template中的引用

13.CBV与FBV 13.1 基于函数的views与基于类的views区别 13.2 CBV与FBV在使用上的差别 13.3 CBV与FBV在文章详情页面上的使用差别

  1. 时间的使用 14.1 在模板中改变时间格式 14.2 在models中设置时间 14.3 在models中数据表设置时间格式 14.4 在settings.py中设置时区

  2. GET/POST/CSRF的概念 15.1 网站相应流程 15.2 请求的格式 15.3 GET参数传递 15.4 POST参数传递 15.5 CSRF:跨站点请求伪造 15.5.1 CSRF产生原因 15.5.2 CSRF工作原理 15.5.3 CSRF的使用

16. URL管理

16.1 URL级联示意图 16.2 主URL配置 16.2.1 settings.py中主URL配置 16.2.2 主URL与子URL配置样式 16.3 子URL配置 16.3.1 常见子URL配置 16.3.2 子URL配置的特殊用法 16.4 在template中与URL正则进行匹配

17. 用户与注册

17.1 创建前的思考 17.2 用户注册的数据表建立 17.2.1 django系统USER表 17.2.2 用户注册数据表 17.2.3 使用django系统USER表的优势 17.2.4 在article app中Article数据表增加用户一项,引用User外键; 17.3 邮箱验证 17.3.1 邮箱验证功能概述 17.3.2 邮箱注册功能要点 17.4 发送邮件的配置 17.4.1 发送邮件的配置 17.4.2 设置邮件内容 17.4.3 注册template的编写 17.4.3.1 注册页面(register.html)的编写 17.4.3.2 注册成功请激活页面的编写 17.5 用户激活

  1. 用户登录与登出 18.1 创建前的思考 18.2 登录功能概述 18.3 登录功能URL 18.4 登录及登出功能跳转位置 18.5 登录及登出模板放置位置 18.6 登录功能template 18.7 登录功能显示效果 18.8 登出功能template 18.9 登出功能显示效果

19.重置密码与修改密码 19.1 创建前的思考 19.2 URL配置 19.3 重置密码流程 19.4 密码重置过程要点 19.5 密码重置代码 19.5.1 重置密码页面(registration/password_reset_form.html) 19.5.2 给用户发送邮件的主题(registration/password_reset_subject.txt) 19.5.3 给用户发送邮件的内容(registration/password_reset_email.html) 19.5.4 反馈发送成功的页面(registration/password_reset_done.html) 19.5.5 密码修改页面(registration/password_reset_confirm.html) 19.5.6 修改成功页面(registration/password_reset_complete.html)

  1. 用户头像上传功能 20.1 创建前的思考 20.2 URL配置 20.3 上传头像流程 20.4 页面制作 20.5 控制器 20.6 头像展示 20.7 models

  2. 为用户增加额外的属性 21.1 创建前的思考 21.2 创建额外属性的数据表 21.3 维护一一对应的关系 21.4 manage.py的作用 21.5 编写脚本 21.6 设置联锁删除

22. javascript介绍

  1. jQuery介绍
  2. JSON介绍

25. 文章评论功能

25.1 评论功能要点 25.2 控制器展示部分 25.3 页面展示部分 25.4 创建评论功能

  1. 回复评论功能 26.1 数据模型 26.2 URL及控制器 26.3 回复评论页面 26.4 锚点的概念 26.5 回复评论页面

27. 消息系统

27.1 消息系统要点 27.2 数据模型 27.3 URL及控制器 27.4 HTML显示要点 27.5 消息存入

28. cookie与session

28.1 cookie的含义 28.2 cookie的弱点 28.3 session:会话

  1. Admin的汉化 29.1 在settings里面更改语言 29.2 在models中更改admin语言 29.3 在app中admin.py设置语言 29.4 在app下创建apps.py并进行汉化

30.日志功能 30.1 日志功能概述 30.2 配置样例 30.3 日志使用简单举例

  1. 利用中间件简化任务 31.1 中间件的需求原因 31.2 中间件流程 31.3 中间件的编写(django.middleware) 31.4 中间件的配置 31.5 利用中间件记录所有异常

  2. nginx入门 32.1 域名的概念 32.2 域名的组成 32.3 计算机解析域名的方法 32.4 静态文件服务器与nginx 32.5 配置静态文件服务器 32.6 nginx常用命令

33.网站部署

33.1 代码拷贝至云服务器 33.2 上线后不显示图片的原因 33.3 线上安装nginx 33.4 在nginx重新配置后需同步在django中更改 33.5 设置ini文件,方便线上线下更改 33.6 线上部署后DEBUG=False对系统的影响 33.7 收集静态资源

  1. 高性能部署 34.1 修改默认的runserver 34.2 安装配置uwsgi 34.3 uwsgi配置补充 34.4 运行uwsgi 34.5 错误排查
vieyahn2017 commented 4 years ago

下面是正文。比较长

我这边不排版了

vieyahn2017 commented 4 years ago

1. 对于任一django项目,拿到功能需求时都要思考的问题

1)去数据库里存什么 2)制作什么样的页面 3) 定义什么样的URL 4)页面与数据库之间的信息交换方式 5)时间如何考虑 6)数据库中方法被调用时显示方式 7)在Admin后台中的显示

vieyahn2017 commented 4 years ago
  1. HTML入门 2.1 文字类:h1~h6,p,a,label,span

一级标题

二级标题

三级标题

四级标题

五级标题
六级标题

<a href=""> 1 2 3 4 5 6 7 8 9 10 11 对于div,span,label的区别,可参阅:

http://www.cnblogs.com/cmslby/p/5945059.html

简单来说,div是框架,可以包括span,span是组合文档的元素,label主要用来绑定表单元素.

2.2 组件类:table,input,form,button,textarea

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 2.3 布局类:div,br

1 2 3 4 5 6 7 8 3.页面美化 3.1 CSS CSS代码以三种方式代入进html文件中 方式1:特指特定的一个节点 方式2:选取一类节点 方式3:节点类型限制 方式4:层级限制 方式5:并集选取 3.2 bootstrap 步骤1:前往bootstrap官网下载文件,找到bootsrap.min.css文件并放入至django中的static文件夹下; 步骤2:在settings.py中注册静态文件路径

STATIC_URL = '/static/' STATICFILES_DIRS = (os.path.join(BASE_DIR,"static"),) #BASE_DIR,项目所在目录 1 2 步骤3:在template模板中写入

1 步骤4:前往bootstrap官网查看中意的样式然后拷贝进代码中并匹配自己的代码即可.

4.页面布局 4.1 页面布局的概念 页面中先定义行(row),然后定义列(colum); 一行分为12份 xs:小屏幕,md:中屏幕,

5.基础模板的制作 基础模板类似于基本页面,代码为:

# code {% block content %} {% endblock %} # code

1 2 3 4 5 6 子模板代码为:

{% extends 'base.html' %} {% block content %}

code

{% endblock %}

vieyahn2017 commented 4 years ago

6. 数据存入数据库

6.1 示例代码

from django.contrib.auth.models import User from django.db import models from blocks.models import Block import pytz

BEIJING_TZ = pytz.timezone('Asia/Shanghai')

class Article(models.Model): owner = models.ForeignKey(User, verbose_name='作者') block = models.ForeignKey(Block, verbose_name='版块ID') title = models.CharField('模块名称', max_length=100) content = models.CharField('模块描述', max_length=400000) status = models.IntegerField('状态', choices=((0, '正常'), (1, '删除'),(10, '精华')))

create_timestamp = models.DateTimeField('创建时间', auto_now_add=True)
last_update_timestamp = models.DateTimeField('修改时间', auto_now=True)

def __str__(self):
    return self.title

class Meta:
    verbose_name = '文章'
    verbose_name_plural = '文章'

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 6.2 数据库类的继承 从django.db中引入models,后面每一张数据表都会继承models 6.3 数据库中的数据类型 数据库中有如下类型: 1) CharField,字符类型 最常用的一种类型,存储字符串数据。 2) IntegerField,数字类型 存储数字,也可以如上图代码,存储类似与selectbox的内容 3) DateTimeField,时间类型 存储时间,其中在属性中设置auto_now_add=True 一般代表文章的创建时间,此时间不会变化,auto_now=True一般代表文章的最后更新时间,此时间会随着数据库被修改而变化。 6.4 数据库中的外键 数据库中的外键相当于数据库中每一张表的联系,比如文章的数据库需要连接到版块信息中,常用的外键如下: 1) ForeignKey,外键 代表此表与另外一张表建立了外键的联系,ForeignKey内部存储的实际上是int类型,即另外一张表的id. 2) OnetooneField:一对一关系 3) ManytoManyField:多对多关系 4) OnetoManyField:一对多关系 ———————————————— 版权声明:本文为CSDN博主「Leon_online」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liwang_30/article/details/78510322

vieyahn2017 commented 4 years ago

7. 版块应用的创建

7.1 建立版块application

1) 在terminal中进入manage.py所在目录下; 2) 利用python manage.py startapp +app名称(block)建立block这个app;

7.2 在settings.py中注册这个app 注意:后续章节会对app进行汉化,因此注册名称会发生变化。

INSTALLED_APPS = [

...

'blocks.apps.BlocksConfig',
# ...

] 1 2 3 4 5 7.3 在models.py中建立block 7.3.1 创建版块数据表前的思考

设想数据结构:需要版块名称,版块描述,版块所属管理员,版块状态; 必要的补充信息:被调用时返回的名称,在admin后台的汉化,时间的引入(此表不需要),外键的引用(此表不需要); 7.3.2 代码分解

from django.db import models

class Block(models.Model): name = models.CharField('模块名称', max_length=100) desc = models.CharField('模块描述', max_length=100) manager_name = models.CharField('模块管理员名称', max_length=100) status = models.IntegerField('状态', choices=((0, '正常'), (1, '删除')))

def __str__(self):
    return self.name

class Meta:
    verbose_name = '版块'
    verbose_name_plural = '版块'

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 导入django.db.models 建立数据表Block,继承models.Model 按照设想的数据结构,建立: 版块名称name(CharField类型,中文名称,最大长度); 版块描述desc(CharField,中文名称,最大长度); 管理员名称manager_name(CharField,中文名称,最大长度); 版块状态status(InterField,中文名称,选择信息) 被调用时的名称str:版块名称name 在admin后台显示:verbose_name和verbose_name_plural都是“版块” 7.3.3 后台数据库中的显示

数据库会建立一张blocks_block的数据表,其中blocks为app名称,models中建立的表名,数据库会自动生成一列id列,此列一般被ForeignKey引用。 1 7.4 数据表在views中的引用 7.4.1 在views中导入数据表

from blocks.models import Block 1 7.4.2 定义函数及调用数据库数据,将数据传递至template

def index(request): block_infos = Block.objects.all().filter(status=0).order_by('-id') if request.user.is_authenticated(): msg_cnt = Usermessage.objects.filter(status=0, owner=request.user).count() else: msg_cnt = 0 return render(request, "index.html",{'blocks':block_infos, 'msg_cnt': msg_cnt}) 1 2 3 4 5 6 7 7.5 数据表在templates中的显示 在页面中使用for循环的语法是{% for b in blocks%}; 通过b.id ,b.name及{{ }}语法调用blocks表中的数据;

<div class="col-xs-12 col-md-10">
    {% for b in blocks %}
        <div class="panel panel-default">
            <div class="panel-heading">
                <a href="/article/list/{{ b.id }}" style="font-size: 15px">{{ b.name }}</a>
                <span class="pull-right">{{ b.manager_name }}</span>
            </div>
            <div class="panel-body">{{ b.desc }}</div>
        </div>
    {% endfor %}
</div>

1 2 3 4 5 6 7 8 9 10 11 7.6 页面的显示效果

vieyahn2017 commented 4 years ago

8.文章列表的创建

8.1 创建前的思考

去数据库里什么:只是作为各版块中每一篇文章的显示功能,不涉及具体数据库操作;

定义一个什么样的页面:包含导航功能,显示目前所处路径,包含一个table,显示文章标题,文章内容概要,文章作者,创建时间,最后修改时间

页面切换:版块概要页面文章列表页面文章详情页面或创建文章页面

时间管理:不涉及时间的管理

汉化功能:不涉及汉化功能;

其他功能:在文章太多的情况下,分页功能;

8.2 编写文章列表views 8.2.1 引入相关数据库

版块数据表Block from blocks.models import Block 1 文章数据表 from .models import Article 1 8.2.2 编写views函数

def article_list(request, block_id): block_id = int(block_id) blocks = Block.objects.get(id=block_id) page_no = int(request.GET.get('page_no','1')) all_articles = Article.objects.filter(block=blocks,status=0).order_by('-id') page_articles,pagination_data = paginate_queryset(all_articles, page_no) return render(request, 'article_list.html', {'blocks':blocks, 'articles':page_articles, 'pagination_data': pagination_data}) 1 2 3 4 5 6 7 8 9 10 从网页上获取当前所属版块id(block_id=int(block_id)) 根据版块id获取Block数据表中数据(blocks=Block.objects.get(id=block_id) 获取文章:满足状态正常(status=0)且属于当前版块(block=blocks)的文章(all_artilces=Arcitle.objects.filter(block=blocks,status=0).order_by(‘-id’)) 将文章进行分页(分页的实现一节) 返回模板,代入版块数据,文章分页及文章数据 8.2.3 编写文章列表template 8.2.3.1 文章列表导航功能的模板

<ol class="breadcrumb">
    <li><a href="/">主页</a></li>
    <li class="active">{{ blocks.name }}</li>
</ol>

1 2 3 4 8.2.3.2 “创建文章”按钮的制作

<a href="/article/create/{{ blocks.id }}" type="button" class="btn btn-primary">发表文章</a>

1 8.2.3.3 文章列表的显示

<table class="table table-bordered">
    <thead>
        <tr>
            <th>标题</th>
            <th>作者</th>
            <th>创建时间</th>
            <th>最后更新时间</th>
        </tr>
    </thead>
    <tbody>
        {% for article in articles %}
            <tr>
                <td><a href="/article/articledetail/{{ article.id }}">{{ article.title }}</a></td>
                <td>{{ article.content }}</td>
                <td>{{ article.create_timestamp|date:'Y-m-d P' }}</td>
                <td>{{ article.last_update_timestamp|date:'Y-m-d P' }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 使用for语句,显示所有文章; 在文章标题设置连接,当点击文章标题,可以进入文章详情页面; 时间的格式化处理,将数据库中的时间以特定格式显示出来(|date:’Y-m-d P’); 8.2.3.4 分页功能的引入

{% include 'component/paginator.html' %}

1 详细讲解见“分页的实现”一节 8.3 文章列表页面的显示效果

vieyahn2017 commented 4 years ago

9. 分页的实现

9.1 分页的本质 获取所有文章,将所有文章按照指定数量进行分隔,当页面调取哪一块数据,分页器返回相应数据。

9.2 django提供的分页工具 Django分页工具:django.core.paginator.Paginator

from django.core.paginator import Paginator all_articles = Articles.objects.all().filter(status=0).order_by("-id") p = Paginator(all_articles,ARTICLE_COUNT_1PAGE) page = p.page(page_no) artilces_objs = page.object_list 1 2 3 4 5 6 9.3 分页功能的页面分解

分页总共涉及到7个变量,详见下表:

9.4 分页功能代码编写

from django.core.paginator import Paginator

def paginate_queryset(objs, page_no, cnt_per_page=5, half_show_length=5): p = Paginator(objs, cnt_per_page) #分页功能的实例化 if page_no > p.num_pages: #判断页数是否超过最大页数 page_no = p.num_pages if page_no <= 0: #判断页数是否小于最小页数 page_no = 1 page_links = [i for i in range(page_no - half_show_length, page_no + half_show_length + 1) if i > 0 and i <= p.num_pages] page = p.page(page_no) previous_link = page_links[0] - 1 next_link = page_links[-1] + 1 pagination_data = {'has_previous': previous_link>0, #有前页在views中做计算 'has_next': next_link <= p.num_pages, #有后页在views中做计算 'previous_link': previous_link, 'next_link': next_link, 'page_cnt': p.num_pages, 'current_no': page_no, 'page_links': page_links} return (page.object_list, pagination_data) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1) 不仅在文章列表用到分页功能,后续评论功能也会用到分页,因此将分页功能提取可复用的代码,因此新建paginator.py文件。 2) 导入django分页器from django.core.paginator import Paginator 3) 定义分页函数,参数包含objs(文章的列表数据),page_no(当前页面),count_per_page(每页的文章数量),half_show_length(页面上显示的页码范围); 4) 在9.3中讲到分页页面总共有7个变量,这7个变量都可以通过2个变量(count_per_page,half_show_length)来进行计算

5) 程序返回值为当前页的所有数据(列表格式),打包的页面数据(是否有前一页(has_previous),是否有后一页(has_next),前一页(previous_link),后一页(next_link),页数总计(page_cnt),当前页(current_no),页数列表(page_links)) 9.5 分页的模板

<nav aria-label="Page navigation">
    <ul class="pagination">
        {% if pagination_data.has_previous  %}  {# 判断是否有前一页 #}
            <li ><a aria-hidden="true" href="?page_no=1">首页</a></li>    {# 首页按钮 #}
            <li >   {# 前一页标志及链接 #}
                <a aria-hidden="true" href="?page_no={{ pagination_data.previous_link }}" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span></a>
            </li>
        {% endif %}
        {% for page in pagination_data.page_links %}
            {% ifequal page pagination_data.current_no %}   {# 判断页面与当前页面是否一致 #}
                <li class="active"><a href="?page_no={{ page }}">{{ page }}</a></li>
            {% else %}
                <li><a href="?page_no={{ page }}">{{ page }}</a></li>
            {% endifequal %}
        {% endfor %}
        {% if pagination_data.has_next %}   {# 判断是否有后一页 #}
            <li>    {# 后一页标志及链接 #}
              <a aria-hidden="true" href="?page_no={{ pagination_data.next_link }}" aria-label="Next">
                <span aria-hidden="true">&raquo;</span></a>
            </li>
            <li><a aria-hidden="true" href="?page_no={{ pagination_data.page_cnt }}">尾页</a></li>    {# 尾页按钮 #}
        {% endif %}
  </ul>
</nav>

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 分页模板主要分为三部分,第一是是否有前一页的判断及显示前一页,第二是显示页面范围并active当前页,第三是是否有后一页的判断。 分页的模板可以去bootstrap网站寻找,如下:

9.6 分页功能的应用 分页功能在文章列表页面得到了应用,下面是实际代码。 9.6.1 分页功能的引入

from utils.paginator import paginate_queryset 1 9.6.2 功能的实现

def article_list(request, block_id): block_id = int(block_id) blocks = Block.objects.get(id=block_id) page_no = int(request.GET.get('page_no','1')) all_articles = Article.objects.filter(block=blocks,status=0).order_by('-id') page_articles,pagination_data = paginate_queryset(all_articles, page_no) return render(request, 'article_list.html', {'blocks':blocks, 'articles':page_articles, 'pagination_data': pagination_data}) 1 2 3 4 5 6 7 8 9 要点:先从template中获取当前页面(page_no)及从models中获取所有文章(all_articles),然后将这两个数据传递至分页函数paginate_queryset中(每页显示文章数量count_per_page及页面上显示的页码范围half_show_length都已设定默认值(count_per_page=5,half_show_length=5)),由于函数返回了两个变量,一个是每页显示的文章(page.queryset),另外一个是分页相关数据(pagination_data),因此设置两个变量进行接收,将这两个变量直接传回至template中 9.6.3 功能的页面

<table class="table table-bordered">
    <thead>
        <tr>
            <th>标题</th>
            <th>作者</th>
            <th>创建时间</th>
            <th>最后更新时间</th>
        </tr>
    </thead>
    <tbody>
        {% for article in articles %}
            <tr>
                <td><a href="/article/articledetail/{{ article.id }}">{{ article.title }}</a></td>
                <td>{{ article.content }}</td>
                <td>{{ article.create_timestamp|date:'Y-m-d P' }}</td>
                <td>{{ article.last_update_timestamp|date:'Y-m-d P' }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>
{% include 'component/paginator.html' %}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 利用for函数遍历所有文章,利用table样式进行显示; 引入paginator.html文件并显示分页样式及文章列表分页设置;

vieyahn2017 commented 4 years ago

10.文章的创建

10.1 创建前的思考

去数据库里存什么:用户,文章标题,文章描述,文章标签,文章内容,所属版块,创建时间,修改时间,文章状态(删除,正常,精华),其中用户引入django中的用户外键,所属版块引入版块外键,创建时间和修改时间在系统后台加入。 定义一个什么样的页面:页面中含有form表单,包括文章标题,文章描述,文章标签,文章,可以加入导航,方便页面切换,设置保存或发表按钮。 用户在文章列表页面点击创建文章–>进入文章创建界面,编辑完后点击保存或发表–>返回文章列表页面。 创建时间的考虑:由于系统默认时间不是北京时间,需要进行时区调整。 汉化功能:考虑在admin中的汉化 10.2 建立文章application及注册 流程与版块一致,application名称为article; 10.3 文章数据库的创建 10.3.1 引入关联表

从User表中引入用户 from django.contrib.auth.models import User 1 从Block表中引入版块 from blocks.models import Block 1 11.3.2 创建数据表

class Article(models.Model): owner = models.ForeignKey(User, verbose_name='作者') block = models.ForeignKey(Block, verbose_name='版块ID') title = models.CharField('模块名称', max_length=100) content = models.CharField('模块描述', max_length=400000) status = models.IntegerField('状态', choices=((0, '正常'), (1, '删除'),(10, '精华')))

create_timestamp = models.DateTimeField('创建时间', auto_now_add=True)
last_update_timestamp = models.DateTimeField('修改时间', auto_now=True)

def __str__(self):
    return self.title

class Meta:
    verbose_name = '文章'
    verbose_name_plural = '文章'

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 owner是User表的外键(foreignkey); block是Block表的外键(foreignkey); title是文章标题(charfield); content是文章内容(charfield); status是文章状态(integerfield,选择项) create_timestamp是文章创建时间(datetimefield,auto_now_add=True) last_update_timestamp是文章最后更新时间 (datetimefield,auto_now=True) 10.4 文章数据库在views中的引用 10.4.1 在views中导入数据表 from .models import Article 1 11.4.2 定义类并接收数据、进行数据校验等处理、存入数据库及进行页面传递

class ArticleCreateView(View):

template_name = 'create.html'

def init_data(self, block_id):
    self.block_id = int(block_id)
    self.block = Block.objects.filter(id=self.block_id)

def get(self, request, block_id):
    # #提取成init_data
    # block_id = int(block_id)
    # block = Block.objects.filter(id=block_id)
    self.init_data(block_id)
    return render(request, self.template_name, {'blocks': self.block})

def post(self, request, block_id):
    # #下面两行代码get函数也有,因此需进行提取
    # block_id = int(block_id)
    # block = Block.objects.filter(id=block_id)
    self.init_data(block_id)
    form = ArticleForm(request.POST)
    if form.is_valid():
        article = form.save(commit=False)
        article.block_id = self.block_id
        article.owner = request.user
        article.status = 0
        article.save()
        return redirect('/article/list/%s' % block_id)
    else:
        return render(request, self.template_name, {'blocks': self.block, 'form': form})

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 定义初始数据:定义模板名称为create.html,从页面中获取版块id(block_id),根据版块id获取版块信息(block=Block.objects.filter(id=self.block_id)) 定义get请求:返回模板,和block数据 定义post请求:引入校验器(校验器的使用一节会详细介绍),如果校验器通过,则设定校验器中没有的article数据表数据,然后连同文章标题、内容一同存入数据库。 10.5 文章在template中的显示 10.5.1 引入导航功能

    <ol class="breadcrumb">
      <li><a href="/">主页</a></li>
      <li><a class="active" href="/article/list/{{ item.id }}">{{ item.name }}</a></li>
      <li>发表文章</li>
    </ol>

1 2 3 4 5 导航样式为:主页/版块名称/发表文章 点击“主页”、“版块名称”可进入相应页面中 10.5.2 文章创建的form表单

    <form action="/article/create/{{ item.id }}" class="form-horizontal" method="post">{% csrf_token %}
      <div class="form-group">
        <label class="col-sm-2 control-label">标题</label>
        <div class="col-sm-10">
          <input type="text" class="form-control" name="title" placeholder="标题" value="{{ form.title.value }}" />
        </div>
      </div>
      <div class="form-group">
        <label  class="col-sm-2 control-label">内容</label>
        <div class="col-sm-10">
            <script type="text/javascript" src="/static/ueditor/ueditor.config.js"></script>
            <script type="text/javascript" src="/static/ueditor/ueditor.all.js"></script>
            <script id="container" name="content" type="text/plain"></script>
            <input type="hidden" name="content" id="contentInput" />

{# #}

      {% for field in form %}
          {% if field.errors %}
              {% for error in field.errors %}
                  <div class="alert alert-danger">{{ field.label }}:{{ error }}</div>
              {% endfor %}
          {% endif %}
      {% endfor %}
    </form>

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 行号2-6为标题显示及文章标题输入框; 行号7-19为内容显示及内容输入框,其中行号15-17注释掉的是原有标准输入框,类型与文章标题类似,行号11-13为ueditor富文本输入框(详情可参阅富文本输入框一节) 行号22-26为发表文章的按钮; 行号27-37为校验器错误显示的部分(详情可参阅校验器一节);

vieyahn2017 commented 4 years ago

11. 富文本编辑框功能

11.1 富文本编辑框介绍 富文本编辑框实际上是可以对输入文章进行格式编辑,加入图片等多种操作。

11.2 富文本编辑框的安装 github上可以获取富文本编辑框的源代码。 11.3 在settings.py中进行静态文件注册

STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'), os.path.join(BASE_DIR, 'DjangoUeditor/static')) 1 2 11.4 在template中替换原有纯文本输入方式

      <div class="form-group">
        <label  class="col-sm-2 control-label">内容</label>
        <div class="col-sm-10">
            <script type="text/javascript" src="/static/ueditor/ueditor.config.js"></script>
            <script type="text/javascript" src="/static/ueditor/ueditor.all.js"></script>
            <script id="container" name="content" type="text/plain"></script>
            <input type="hidden" name="content" id="contentInput" />

{# #}

1 2 3 4 5 6 7 8 9 10 11 如上图,其中被标注的部分就是原纯文本输入方式,其中input框中的name,id都是固定写法,不需要更改,然后针对script中的type进行渲染并将用户输入内容contentInput转换成ue内容,代码如下 1

1 2 3 4 5 6 11.5 富文本编辑框中文字输入的提交 在将富文本编辑框内容输入后,将在javascipt中定义的setContent函数内容作为用户输入内容提交,代码如下:

1 2 3 4 5 11.6 关闭文章详情显示中的自动转义功能 在文章创建成功后,如果在文章详情页article_detail中进行展示,django会自动将数据库内容进行转义,如下图所示,比如将&lt转义为<,hh转义为a标签,

但实际上我们想要直接将内容展示到html页面,由html直接解析,因此我们需要暂时将django的转义关闭,

<table class="table table-bordered">
    <tr>
        <td style="width: 200px">
            作者:{{ article.owner.username }}
        </td>
        <td>
            <h2>{{ article.title }}</h2>
            {% autoescape off %}
            <div>{{ article.content }}</div>
            {% endautoescape %}
        </td>
    </tr>

1 2 3 4 5 6 7 8 9 10 11 12 11.7 文件上传功能概述 1) 前端与后端通过URL来连接,前端所有请求都通过URL来传递,URL会带有参数; 2) 前端传递图片信息至后端; 3)后端将图片写入系统硬盘上(配置路径); 4) 后端读取图片存储路径; 5) 后端传递图片访问URL至前端;

11.8 文件上传功能设置 12.8.1 设置后端URL地址 在djangoueditor中存在一个urls.py的文件,将此文件挂载到主urls中,形成连接。

图 Ueditor urls文件

图 ueditor/urls.py文件中的内容

图 在主urls中挂载Ueditor urls文件 12.8.2 设置ueditor与后端接口 在DjangoUeditor/static/editor/uditor.config.js文件中设置js与后端接口路径,即根据上一节内容拼接我们刚刚设置的url相对路径,http://request.get_host()/ueditor/controller/

12.8.2 设置文件存储位置及访问路径 在settings.py文件中设置用户上传文件的实际存储位置及访问路径:

提示:用户上传的内容称为MEDIA,程序员侧定义的文件称为STATIC。 ———————————————— 版权声明:本文为CSDN博主「Leon_online」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liwang_30/article/details/78510322

vieyahn2017 commented 4 years ago

12. forms校验器的使用

12.1 校验器功能的思考

引入校验器的目的:在views中一条一条编写异常提示太过麻烦,由于数据最终会存入到数据库中,如果可以从数据库数据直接校验输入数据,可以省去很多工作;

12.2 校验器代码 13.2.1 引入校验器功能及数据表 from django import forms from .models import Article 1 2 12.2.2 定义校验器类

直接从models里面引入数据 class ArticleForm(forms.ModelForm): class Meta: model = Article fields = ['title', 'content'] 1 2 3 4 5 6 校验器继承forms.ModelForm 定义元数据类class Meta 定义数据表,model = Article 定义校验的字段fields = [‘title’,’content’] 校验标题及内容字段,标题和内容字段在数据表中的定义如下: class Article(models.Model):

code

title = models.CharField('模块名称', max_length=100)
content = models.CharField('模块描述', max_length=400000)
# code

1 2 3 4 5 从上图可以看出,title最长为100,content最长为400000,若超过设定或为空都会报错; 1 可以看出除了文章标题title和内容content外,还有其他比如block和owner,status字段,这些字段需要在views中进行补全然后才能存入至文章数据表中。 12.3 校验器在views中的引用 12.3.1 引入定义的校验器类

from .forms import ArticleForm 1 12.3.2 编写校验器代码

def post(self, request, block_id):
    self.init_data(block_id)
    form = ArticleForm(request.POST)
    if form.is_valid():
        article = form.save(commit=False)
        article.block_id = self.block_id
        article.owner = request.user
        article.status = 0
        article.save()
        return redirect('/article/list/%s' % block_id)
    else:
        return render(request, self.template_name, {'blocks': self.block, 'form': form})

1 2 3 4 5 6 7 8 9 10 11 12 13 实例化校验器form=ArticleForm(request.POST); 校验器判断是否存在异常,如果存在异常则停留当前页面并代入form异常报警,如果正常则将用户输入的内容存入数据库; 12.4 校验器在template中的引用

{% for field in form %} {% if field.errors %} {% for error in field.errors %}

{{ field.label }}:{{ error }}
    {% endfor %}
{% endif %}

{% endfor %} 1 2 3 4 5 6 7 8 9 遍历form中的元素 判断form中errors字段是否为空 如果不为空则遍历显示错误信息,格式为:错误标签:错误内容

vieyahn2017 commented 4 years ago

14. 时间的使用

14.1 在模板中改变时间格式

14.2 在models中设置时间

BEIJING_TZ = pytz.timezone('Asia/Shanghai') 1 14.3 在models中数据表设置时间格式

create_timestamp = models.DateTimeField('创建时间', auto_now_add=True) last_update_timestamp = models.DateTimeField('修改时间', auto_now=True) 1 2 14.4 在settings.py中设置时区

TIME_ZONE = 'Asia/Shanghai'

vieyahn2017 commented 4 years ago

13.CBV与FBV

13.1 基于函数的views与基于类的views区别

区别1:FBV判断请求类型(if函数)并进行相应操作,CBV将请求继承分类(class)并进行相应操作。 区别2:FBV在url中直接引用,CBV在url中作为类引用(.asview())

13.2 CBV与FBV在使用上的差别 在简单代码中,FBV效率要高于CBV,但是在复杂的程序设计中CBV要比FBV更先进,下面这张图中从页面中获取默认值,FBV需要在GET和POST中都取,而完善后的CBV仅需要创建init函数即可对数据一次读取。

13.3 CBV与FBV在文章详情页面上的使用差别 在CBV的URL中正则必须叫pk(primary key),不能改名为其他。

vieyahn2017 commented 4 years ago
  1. 时间的使用 14.1 在模板中改变时间格式

14.2 在models中设置时间

BEIJING_TZ = pytz.timezone('Asia/Shanghai') 1 14.3 在models中数据表设置时间格式

create_timestamp = models.DateTimeField('创建时间', auto_now_add=True) last_update_timestamp = models.DateTimeField('修改时间', auto_now=True) 1 2 14.4 在settings.py中设置时区

TIME_ZONE = 'Asia/Shanghai'

vieyahn2017 commented 4 years ago

15. GET/POST/CSRF的概念

15.1 网站相应流程 当请求来的时候,首先经过URL,通过URL传递至每一个处理函数,如果没有找到处理函数,则经过django系统的处理函数打包成相应对象作为响应。

15.2 请求的格式

15.3 GET参数传递

15.4 POST参数传递

注意下图中input标签中name=’title’或’content’,这样才能根views里面的title和content对接上。

15.5 CSRF:跨站点请求伪造 15.5.1 CSRF产生原因 钓鱼网站通过仿照正规网站的表单诱导用户填写用户名和密码,获取用户私密信息. 15.5.2 CSRF工作原理

1)浏览器返回有form表单的页面,表单中含有一个隐藏字段,值为A,A为随机数,这个A每个请求不一样,服务器将A存储起来; 2)服务器返回给浏览器表单页面中含有随机数A,钓鱼网站无法获取; 3)用户在浏览器填写数据,点击提交,提交数据给服务器,服务器中加带随机数A; 4)服务器校验随机数A,如果不匹配则直接报错,如果正确则继续处理. 15.5.3 CSRF的使用 在页面上form表单中增加{% csrf_token %},则自动执行CSRF功能。

vieyahn2017 commented 4 years ago

16. URL管理

16.1 URL级联示意图 当系统中存在较多功能时,如果采用单一URL,所有操作将会在一起进行,多人协作的话不容易进行管理,因此在建立app时应也建立子URL,总URL与子URL进行协同管理。

16.2 主URL配置 16.2.1 settings.py中主URL配置

ROOT_URLCONF = 'forum.urls' 1 16.2.2 主URL与子URL配置样式

// 主url配置 from django.conf.urls import url,include from forum.views import htmltemplate,index,register,test

urlpatterns = [ url(r'^htmltemplate/$', htmltemplate), url(r'^article/',include('article.urls')), url(r'^comment/',include('comment.urls')), url(r'^message/',include('message.urls')), ]

// 子url配置 from django.conf.urls import url from django.contrib.auth.decorators import login_required from .views import article_list, ArticleCreateView, ArticleDetailView

urlpatterns = [ url(r'^list/(?P\d+)', article_list), url(r'^create/(?P\d+)', login_required(ArticleCreateView.as_view())), url(r'^articledetail/(?P\d+)',ArticleDetailView.as_view()), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 典型URL用法包括: url(r’^admin/’, index):首页 url(r’^$’, admin.site.urls):admin管理后台; url(r’^article/’, include(‘article.urls’):文章子URL,comment,message与此类似; url(r’^activate/(?P\w+)$’, activate):注册激活码链接页面,注册功能一节会详述 16.3 子URL配置 16.3.1 常见子URL配置 URL匹配页面中name=block_id的值,当点击含有block_id的链接时,跳转到相应页面,URL中的block值传递至views中进行处理。

16.3.2 子URL配置的特殊用法 除了文章列表外,article还应用了两个较特殊的URL配置,一个是文章创建功能,另外一个是文章详情页面功能;

urlpatterns = [ url(r'^list/(?P\d+)', article_list), url(r'^create/(?P\d+)', login_required(ArticleCreateView.as_view())), url(r'^articledetail/(?P\d+)',ArticleDetailView.as_view()), 1 2 3 4 文章创建功能:首先ArticleCreateView在views中以CBV方式创建,因此在引入时带有.as_view(),然后引入了django的decorator功能login_required,即需要登陆才能创建文章。 文章详情页面:由于显示models中数据表的属性在django中比较常见,因此django将此功能抽取成为统一功能,使用此功能在url中必须将关联name设置为pk,其在views中的代码如下: 1 2 引入detailview from django.views.generic import View, DetailView 1 编写文章详情页代码 class ArticleDetailView(DetailView): model = Article template_name = 'article_detail.html' context_object_name = 'article'

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    page_no = int(self.request.GET.get('page_no', 1))
    all_comments = Comment.normal_objects.filter(article=context['article'])
    comments,pagination_data = paginate_queryset(all_comments, page_no, cnt_per_page=3)
    context['comments'] = comments
    context['pagination_data'] = pagination_data
    return context

1 2 3 4 5 6 7 8 9 10 11 12 13 16.4 在template中与URL正则进行匹配

<div class="col-xs-12 col-md-10">
    {% for b in blocks %}
        <div class="panel panel-default">
            <div class="panel-heading">
                <a href="/article/list/{{ b.id }}" style="font-size: 15px">{{ b.name }}</a>
                <span class="pull-right">{{ b.manager_name }}</span>
            </div>
            <div class="panel-body">{{ b.desc }}</div>
        </div>
    {% endfor %}
</div>

1 2 3 4 5 6 7 8 9 10 11 在页面中a标签中加入href=”/article/list/{{ b.id }}”,其中b.id将作为URL中的block_id的值再传入到views中;

页面中的b.id传递至URL中的block_id中 url(r'^create/(?P\d+)', login_required(ArticleCreateView.as_view())), 1 views中获取URL中的匹配值并做相应处理

class ArticleCreateView(View):

def init_data(self, block_id):
    self.block_id = int(block_id)
    self.block = Block.objects.filter(id=self.block_id)

def get(self, request, block_id):
    // do something

def post(self, request, block_id):
    // do something
vieyahn2017 commented 4 years ago

17. 用户与注册

17.1 创建前的思考

是否新建APP:不需要,在主forum下即可完成功能; 去数据库里存什么:建立数据表,将USER表作为外键,另外存储邮件激活码,验证邮件有效时间,用户创建时间,用户更改时间, ; 定义一个什么样的页面:注册页面,验证邮件已发送页面,用户验证成功页面 ; 页面切换:主页的注册按键注册页面验证邮件已发送页面 ; #用户点击邮件中的注册链接用户验证成功页面; 定义什么样的URL:常规URL配置,无特殊匹配; 时间管理:验证邮件有效时间,用户创建时间,用户修改时间; 汉化功能:数据表中各字段的汉化; 其他辅助功能:无; 17.2 用户注册的数据表建立 17.2.1 django系统USER表 django里面自带了USER表,可以直接使用,常用的数据结构如下:

username password email is_active:是否激活,激活才能登陆 is_staff:是否是职员,是职员才能登陆admin is_superuser:是否是超级管理员,超值管理员拥有一切权限 17.2.2 用户注册数据表

from django.db import models from django.contrib.auth.models import User

class ActivateCode(models.Model): owner = models.ForeignKey(User, verbose_name='用户') code = models.CharField('激活码', max_length=100) expire_timestamp = models.DateTimeField() create_timestamp = models.DateTimeField(auto_now_add=True) last_update_timestamp = models.DateTimeField(auto_now=True) 1 2 3 4 5 6 7 8 9 10 用户注册数据表中建立了:

用户字段owner(User外键); 激活码字段code(文本类型); 验证过期时间字段expire_timestamp(时间类型); 创建时间字段create_timestamp(时间类型,不自动更新时间); 最后更新时间字段last_update_timestamp(时间类型,自动更新时间); 17.2.3 使用django系统USER表的优势 django内置的用户引入与程序员自己定义的用户表,两者区别是利用django内置的用户引入,存储的密码不会是明文,因此减少了泄漏的风险。 17.2.4 在article app中Article数据表增加用户一项,引用User外键;

class Article(models.Model): owner = models.ForeignKey(User, verbose_name='作者') // other model 1 2 3 17.3 邮箱验证 17.3.1 邮箱验证功能概述 邮箱验证功能整体流程图如下图所示,流程分为:

  1. 用户侧:POST提交注册信息;
  2. 后台侧:根据用户提交注册信息,生成激活码,将激活码写入数据库,然后利用系统设置的邮箱发送带有激活码的邮件至用户注册时使用的邮箱;
  3. 用户侧:用户登陆注册邮箱,点击激活URL,登陆到激活页面;
  4. 后台侧:从用户激活URL中提取激活码与数据库中激活码做比对,若成功则在数据库中设置用户状态位为有效,最后反馈用户注册成功页面;

17.3.2 邮箱注册功能要点 1)激活码的生成 激活码就相当于是一个随机字符串,自Python中产生随机字符串的库为uuid,然后使用str(uuid.uuid4()).replace(“-”, “”)将带有“-”的字符串合并为一串无需文本,将此文本存储为new_code。

def register(request): if request.method =="GET": return render(request,'register.html') else:

code

    user = User.objects.create_user(username=owner, email=email, password=password, is_active=False)
    user.save()

    new_code = str(uuid.uuid4()).replace('-', '')
    expire_time = timezone.now()+datetime.timedelta(days=2)
    code_record = ActivateCode(owner=user, code=new_code, expire_timestamp=expire_time)
    code_record.save()

1 2 3 4 5 6 7 8 9 10 11 12 2)激活码链接的生成 将激活码与URL前面的地址拼接成一个完整的激活码链接,获取主页面地址的命令为request.get_host(),因此整体链接就是”http://%s/activate/%s”%(request.get_host(),new_code)

active_link = 'http://%s/activate/%s' % (request.get_host(), new_code) active_email = '''点击这里激活''' % active_link send_mail(subject='测试激活邮件', message='点击链接激活:%s' % active_link, html_message=active_email, from_email='liwang_30@126.com', recipient_list=[email], fail_silently=False) 1 2 3 4 5 6 7 8 3)在URL中获取激活码 在URL中匹配激活链接,在http://request.get_host()/active/后面加入正则(?P\w+),这段激活码的name=code,形式就是多个字符,在views中会获取name,然后与数据库中存储的激活码进行比对。

urlpatterns = [

other url

url(r'^activate/(?P<code>\w+)$', activate),
# other url

1 2 3 4 def activate(request, code): query = ActivateCode.objects.filter(code=code, expire_timestamp__gte=timezone.now()) if query.count() > 0: code_record = query[0] code_record.owner.is_active = True code_record.owner.save() return render(request, 'success_hint.html', {'msg':'激活成功', 'hint':'去登录', 'link': '/'}) else: return render(request, 'success_hint.html', {'msg':'激活失败'}) 1 2 3 4 5 6 7 8 9 {% extends 'base.html' %} {% block content %}

<div class="col-md-6">
    <h2>{{ msg }}</h2>
    <a href="{{ link }}">{{ hint }}</a>
</div>
<div class="col-md-3"></div>

{% endblock %} 1 2 3 4 5 6 7 8 9 17.4 发送邮件的配置 17.4.1 发送邮件的配置 由于发送邮件配置涉及到后台系统侧邮箱,邮箱授权码,因此基于安全考虑,建立一个secret.py文件夹将发件人信息保存,然后在settings.py中进行引用;

EMAIL_USER_SSL = True EMAIL_USE_SSL = True EMAIL_HOST = 'smtp.126.com' EMAIL_PORT = 465 EMAIL_HOST_USER = 'liwang_30@126.com' EMAIL_HOST_PASSWORD = '**' DEFAULT_FROM_EMAIL = 'liwang_30@126.com' 1 2 3 4 5 6 7 注意:使用阿里云服务器,若采用126邮箱,则不能设置25端口,只能设置465端口,需要设置EMAIL_USER_SSL=True。 17.4.2 设置邮件内容 在forum.views.py中编写发送邮件的代码,首先导入必要的模块;

import uuid import datetime from django.contrib.auth.models import User from django.shortcuts import render, HttpResponse from django.core.mail import send_mail from django.utils import timezone from usercenter.models import ActivateCode 1 2 3 4 5 6 7 然后编写用户注册的代码,用户注册代码主要分为GET请求部分,POST请求部分,其中POST请求部分细分为接收用户输入信息、用户信息校验,将用户信息存储进User表,将注册码,过期时间,注册时间等内容存储进ActivateCode表,编写用户认证邮件。下面依次对各部分代码进行分析: 1 GET请求部分 定义注册功能register,设置error的初值。

def register(request): error = "" if request.method == 'GET': return render(request, 'register.html') 1 2 3 4 POST请求部分 // 获取页面上用户输入的信息 else: owner = request.POST['username'].strip() email = request.POST['email'].strip() password = request.POST['password'].strip() password_check = request.POST['password_check'].strip() 1 2 3 4 5 6 // 设置错误验证信息及返回错误页面 if not owner or not password or not email: error = "任何字段都不能为空" if password != password_check: error = "两次密码不一致" if User.objects.filter(username=owner).count() > 0: error="用户名已存在" 1 2 3 4 5 6 7 // 如果用户信息输入正确,则把用户输入信息存储进USER表中,同时设置激活状态为False if not error: user = User.objects.create_user(username=owner, email=email, password=password, is_active=False) user.save() 1 2 3 4 // 创建过期时间,用户建立时间,验证码并存入ActivateCode数据表 new_code = str(uuid.uuid4()).replace('-', '') expire_time = timezone.now()+datetime.timedelta(days=2) code_record = ActivateCode(owner=user, code=new_code, expire_timestamp=expire_time) code_record.save() 1 2 3 4 5 // 配置发送用户注册邮件相关配置 active_link = 'http://%s/activate/%s' % (request.get_host(), new_code) active_email = '''点击这里激活''' % active_link send_mail(subject='测试激活邮件', message='点击链接激活:%s' % active_link, html_message=active_email, from_email='liwang_30@126.com', recipient_list=[email], fail_silently=False) 1 2 3 4 5 6 7 8 9 邮件发送后反馈邮件发送成功的页面

    else:
        return render(request, "register.html",{"error": error})
    return render(request, 'success_hint.html', {'msg': '注册成功,请前往您的邮箱完成验证!'})

1 2 3 17.4.3 注册template的编写 17.4.3.1 注册页面(register.html)的编写 1) 首先定义页面布局,页面采用3:6:3的方式,主要内容在6中。

{% extends 'base.html' %} {% block content %}
# register code
{% endblock %}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2) 编写注册页面信息 1

{% if error %}
{{ error }}
{% endif %}
注册
{% csrf_token %}
用户名

邮箱

密码

确认密码

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 校验用户输入内容,如果输入内容出错则输出错误信息; 定义panel,panel标题为“注册”,内容为form表单,输入用户名,邮箱,密码及密码确认。 用户名name=username,邮箱name=email,密码name=password,确认密码name=password_check.传递用户输入信息至后台系统。 17.4.3.2 注册成功请激活页面的编写 当用户正确完成注册流程后,页面返回注册成功信息,如下图:

{% extends 'base.html' %} {% block content %}

<div class="col-md-6">
    <h2>{{ msg }}</h2>
    <a href="{{ link }}">{{ hint }}</a>
</div>
<div class="col-md-3"></div>

{% endblock %} 1 2 3 4 5 6 7 8 9 其中,msg引入views中的信息为:

    return render(request, 'success_hint.html', {'msg': '注册成功,请前往您的邮箱完成验证!'})

1 17.5 用户激活 1) 当用户点击了邮箱中的激活链接,则跳转到激活页面,URL会提取激活链接中的激活码并传入views

url(r'^activate/(?P<code>\w+)$', activate),  # 注册时带有激活码的激活连接页面

1 2) 逻辑提取models中的激活码

class ActivateCode(models.Model): owner = models.ForeignKey(User, verbose_name='用户') code = models.CharField('激活码', max_length=100) expire_timestamp = models.DateTimeField() create_timestamp = models.DateTimeField(auto_now_add=True) last_update_timestamp = models.DateTimeField(auto_now=True) 1 2 3 4 5 6 3) 在views中进行比较逻辑

// 将从页面中得到的激活码在models中进行筛选,如果count>0,则将用户表中is_active设置为True并返回激活成功页面,如果count=0,则说明数据库中没有相应的用户激活信息,返回错误提示页面。 def activate(request, code): query = ActivateCode.objects.filter(code=code, expire_timestamp__gte=timezone.now()) if query.count() > 0: code_record = query[0] code_record.owner.is_active = True code_record.owner.save() return render(request, 'success_hint.html', {'msg':'激活成功', 'hint':'去登录', 'link': '/'}) else: return render(request, 'success_hint.html', {'msg':'激活失败'}) 1 2 3 4 5 6 7 8 9 10

vieyahn2017 commented 4 years ago

18. 用户登录与登出

18.1 创建前的思考 去数据库里存什么:使用已有的USER数据库表,无需再创建数据库表 ; 定义一个什么样的页面:登录页面,登出页面 ; 页面切换:主页的登录按键–>登录页面–>跳转指定页面 ; 主页的登出按键–>登出页面–>跳转指定页面; 定义什么样的URL:常规URL配置,无特殊匹配; 时间管理:不涉及时间的管理 汉化功能:不涉及汉化功能; 其他辅助功能:无; 18.2 登录功能概述 django已经为我们定义好了一套登录系统,系统名字就是django.contrib.auth.urls,挂载在根目录下面,默认的登陆名称就是login 定义一个URL: /accounts/login

GET请求 返回登录页面,包含用户名,密码输入框;

POST请求 获取用户名、密码 验证用户名、密码(失败则返回页面,提示错误) 在浏览器中设置cookie,设置session记录用户登录状态; return重定向到指定页面 LOGIN_REDIRECT_URL = “/” 默认是/accounts/profile

18.3 登录功能URL

url(r'^accounts/', include('django.contrib.auth.urls')),  # 用户登录页面,修改密码页面,密码重置页面URL

1 18.4 登录及登出功能跳转位置 登录及登出功能均在主页中设置,登录及登出需要判断当前用户的认证状态,如果当前用户为已登录,则显示登出按键,如果当前用户未认证,则显示登录按键,实际代码如下:

  <div class="well">
        {% if user.is_authenticated %}
            <p>{{ user.username }},欢迎来到论坛.</p>
            <a href="/accounts/logout">登出</a>&nbsp;<a href="/accounts/password_change/">修改密码</a>
            {% if msg_cnt %}
                <a href="/message/list"><span class="badge">{{ msg_cnt }}</span></a>
            {% endif %}
            {% if user.userprofile.avatar %}
                <img src="{{ user.userprofile.avatar }}" alt="头像文字" style="width: 100%; height: 150px;" />
            {% else %}
                <a href="/usercenter/uploadavatar/">
                您还没有头像,去上传
                </a>
            {% endif %}
        {% else %}
            <p>匿名用户,请<a href="/accounts/login">登陆</a></p>
        {% endif %}
    </div>

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 判断登录状态为if user.is_authenticated; 如果已认证,则跳转到登出页面,href=/accounts/logout/ 如果未认证,则跳转到登录页面,href=/accounts/login/ 18.5 登录及登出模板放置位置

登录及登出的模板均放置在templates/registration/中。 18.6 登录功能template 登录功能template主要涉及两方面内容,一是接收用户输入,二是显示错误信息。 登录功能实际代码如下:

页面标题,bootstrap引用,template基本样式; <!DOCTYPE html>

用户登陆 {% extends 'base.html' %} 1 2 3 4 5 6 7 8 9 用户信息验证 {% block content %}
{% if form.non_field_errors %} {% for error in form.non_field_errors %}
{{ error }}
{% endfor %} {% endif %} {% if form.usernames.errors %} {% for error in form.usernames.errors%}
用户名:{{ error }}
{% endfor %} {% endif %} {% if form.password.errors %} {% for error in form.password.errors%}
密码:{{ error }}
{% endfor %} {% endif %}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 用户信息验证包括三方面内容,,如果发生错误则在页面上以alert alert-danger的样式显示: 1) 非指定错误:form.non_field_error; 2) 用户名错误:form.usernames.errors; 3) 密码错误:form.password.errors; 1 2 3 4 接收用户信息
登录
{% csrf_token %}
用户名

密码

{# #}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1)页面分为3:6:3; 2) 页面采用panel样式,panel-heading是“登录”;panel-body内部嵌入了form表单,接收用户输入信息; 3) form表单中method是post,action匹配url规则中login名称; 4)用户名id是id_username,name是username,密码id是id_password,name是password; 1 2 3 4 18.7 登录功能显示效果 18.8 登出功能template 与登录功能相比,登出功能涉及参数较少,仅需在首页判断用户为已登录状态后,点击登出按钮,弹出登出相关页面并显示登录按钮即可。 {% block content %}

登出成功

重新登陆?
{% endblock %} 1 2 3 4 5 6 7 8 9 10 11 12 18.9 登出功能显示效果
vieyahn2017 commented 4 years ago

19.重置密码与修改密码

19.1 创建前的思考 去数据库里存什么:引用已有的USER表,无需新建数据表; 定义一个什么样的页面:申请密码重置页面,提交完成页面,重置邮件主题,重置邮件内容,密码重置页面,密码重置完成页面; 页面切换:申请密码重置页面–>提交完成页面; 密码重置页面–>重置完成页面; 定义什么样的URL:django中已设定URL,需参照django格式; 时间管理:django中已设置时间管理。 汉化功能:不涉及汉化功能; 其他辅助功能:无;

19.2 URL配置

url(r'^accounts/', include('django.contrib.auth.urls')),  # 用户登录页面,修改密码页面,密码重置页面URL

1 密码重置功能不涉及到views的编写,点击进入django.contrib.auth.urls,可以看到:

urlpatterns = [ url(r'^login/$', views.login, name='login'), url(r'^logout/$', views.logout, name='logout'), url(r'^password_change/$', views.password_change, name='password_change'), url(r'^password_change/done/$', views.password_change_done, name='password_change_done'), url(r'^password_reset/$', views.password_reset, name='password_reset'), url(r'^password_reset/done/$', views.password_reset_done, name='password_resetdone'), url(r'^reset/(?P[0-9A-Za-z-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.password_reset_confirm, name='password_reset_confirm'), url(r'^reset/done/$', views.password_reset_complete, name='password_reset_complete'), ] 1 2 3 4 5 6 7 8 9 10 11 12 line10: 用户登录页面; line11: 用户登出页面; line13:密码更改页面; line14: 密码更改完成页面; line16: 申请密码重置页面; line17: 密码重置完成页面; line18:由验证邮箱进入密码重置页面; line20: 密码重置完成页面;

19.3 重置密码流程

步骤1: 进入重置密码页面; 步骤2: 输入用户id(邮箱),后台生成随机码并写入数据库,并将随机码合并至URL中; 步骤3: 将含有激活码的激活链接发到用户邮箱中。 步骤4:反馈发送成功的页面。 步骤5: 用户访问邮箱,点击密码修改邮件中的激活链接地址,后台首先根据随机码验证URL的有效性,如果有效则进入密码修改页面,后台通过分解URL中的随机码验证有效性。 步骤6: 将用户修改后的密码存入后台数据库,然后跳转到的修改成功页面。 19.4 密码重置过程要点 django提供URL及控制器,只需制作template。 在密码重置页面中,需注意name=“email”是固定的,不能改变。

          <div class="panel-body">
                <form action="" method="post">{% csrf_token %}
                    <div class="input-group">
                        <span>注册邮箱</span>
                        <input type="email" class="form-control" id="id_email" name="email" />
                    </div>
                    <br />
                    <input type="submit" class="btn-primary" value="发送">
                </form>
            </div>

1 2 3 4 5 6 7 8 9 10 在密码重置邮件中,激活链接可以参照如下格式:

{{ email }},您好,请点击下面的链接完成密码重置: {{ protocol }}://{{ domain }}/accounts/reset/{{ uid }}/{{ token }} 1 2 3 在用户输入新密码页面中,错误校验名称为validlink,重置密码name=new_password1,确认重置密码name=new_password2。

                   <form action="" method="post">{% csrf_token %}
                        <div class="input-group">
                            <span class="input-group-addon">新密码 </span>
                            <input type="password" class="form-control" id="id_new_password1" name="new_password1" />
                        </div>
                        <br />
                        <div class="input-group">
                            <span class="input-group-addon">确认密码 </span>
                            <input type="password" class="form-control" id="id_new_password2" name="new_password2" />
                        </div>
                        <br />
                        <input type="submit" class="btn btn-primary" value="确认" />
                    </form>

1 2 3 4 5 6 7 8 9 10 11 12 13 19.5 密码重置代码 19.5.1 重置密码页面(registration/password_reset_form.html)

{% block content %}

{% if form.errors %}
{{ form.errors }}
{% endif %}
<div class="row">
    <div class="col-md-3">

    </div>
    <div class="col-md-6">
        <div class="panel panel-info">
            <div class="panel-heading">
                <b>发送重置邮件</b>
            </div>
            <div class="panel-body">
                <form action="" method="post">{% csrf_token %}
                    <div class="input-group">
                        <span>注册邮箱</span>
                        <input type="email" class="form-control" id="id_email" name="email" />
                    </div>
                    <br />
                    <input type="submit" class="btn-primary" value="发送">
                </form>
            </div>
        </div>
    </div>
    <div class="col-md-3">

    </div>
</div>

{% endblock %} 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 19.5.2 给用户发送邮件的主题(registration/password_reset_subject.txt)

重置密码邮件 1 19.5.3 给用户发送邮件的内容(registration/password_reset_email.html)

{{ email }},您好,请点击下面的链接完成密码重置: {{ protocol }}://{{ domain }}/accounts/reset/{{ uid }}/{{ token }} 1 2 19.5.4 反馈发送成功的页面(registration/password_reset_done.html)

<title>密码重置申已发送</title>
<link rel="stylesheet" href="/static/bootstrap.min.css" />

密码重置邮件已发送,请前往邮箱查看密码重置邮件

1 2 3 4 5 19.5.5 密码修改页面(registration/password_reset_confirm.html) {% block content %} {% if validlink %}
{% if form.non_field_errors %} {% for error in form.non_field_errors %}
{{ error }}
{% endfor %} {% endif %} {% if form.new_password1.errors %} {% for error in form.new_password1.errors %}
密码:{{ error }}
{% endfor %} {% endif %} {% if form.new_password2.errors %} {% for error in form.new_password2.errors %}
确认密码:{{ error }}
{% endfor %} {% endif %}
重置密码
{% csrf_token %}
新密码

确认密码

{% else %}
该链接已失效
{% endif %} {% endblock %} 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 19.5.6 修改成功页面(registration/password_reset_complete.html) 密码重置成功

恭喜您,密码重置成功!

vieyahn2017 commented 4 years ago

20.用户头像上传功能

20.1 创建前的思考 去数据库里存什么:创建用户信息数据表并在表中创建用户头像字段; 定义一个什么样的页面:申请密码重置页面,提交完成页面,重置邮件主题,重置邮件内容,密码重置页面,密码重置完成页面; 页面切换:申请密码重置页面提交完成页面; 密码重置页面重置完成页面; 定义什么样的URL:django中已设定URL,需参照django格式; 时间管理:django中已设置时间管理。 汉化功能:不涉及汉化功能; 其他辅助功能:无;

20.2 URL配置 用户头像功能在usercenter这个app中,因此首先需要在主urls中进行挂载,然后在分urls进行URL配置。

forum/urls.py: urlpatterns = [

other urlpatterns

url(r'^usercenter/', include('usercenter.urls')),  # 用户中心子URL
# other urlpatterns
]

1 2 3 4 5 usercenter/urls.py urlpatterns = [ url(r'^uploadavatar/', upload_avatar), 1 2 20.3 上传头像流程

步骤1: 在index页面点击上传头像链接,跳转到用户头像上传页面。 步骤2: 控制器将用户上传的头像写入至硬盘中; 步骤3: 浏览器通过nginx访问用户上传的头像并显示在浏览器中; 20.3 用户头像上传要点 1)数据库中存储URL长度,一般不会超过300 2)linux下头像文件夹的权限管理 20.4 页面制作

<div class="col-md-6">
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <label>上传头像</label>
        <input type="file" name="avatar" />
        <br />
        <input type="submit" class="btb btn-primary" value="提交">
    </form>
</div>

1 2 3 4 5 6 7 8 9 20.5 控制器

@login_required def upload_avatar(request): if request.method == "GET": return render(request, "upload_avatar.html") else: profile = request.user.userprofile avatar_file = request.FILES.get("avatar", None) file_path = os.path.join("/usr/share/userres/avatars/", avatar_file.name) with open(file_path, 'wb+') as destination: for chunk in avatar_file.chunks(): destination.write(chunk) url = "http://%s/avatars/%s" % (request.get_host(), avatar_file.name) profile.avatar = url profile.save() return redirect("/") 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 20.6 头像展示

            {% if user.userprofile.avatar %}
                <img src="{{ user.userprofile.avatar }}" alt="头像文字" style="width: 100%; height: 150px;" />
            {% else %}
                <a href="/usercenter/uploadavatar/">
                您还没有头像,去上传
                </a>
            {% endif %}

1 2 3 4 5 6 7 20.7 models

class UserProfile(models.Model):

other field

avatar = models.CharField("头像", max_length=300, blank=True)
vieyahn2017 commented 4 years ago

21. 为用户增加额外的属性

21.1 创建前的思考 去数据库里存什么:创建用户相关属性,如生日,性别,喜好等资料; 定义一个什么样的页面:创建用户资料页面; 页面切换:进入用户资料完善页面,切换到首页; 定义什么样的URL:用户资料设定页面; 时间管理:django中已设置时间管理。 汉化功能:不涉及汉化功能; 其他辅助功能:无;

21.2 创建额外属性的数据表

21.3 维护一一对应的关系 在创建User表实例时需要同时在UserProfile表中增加相应实例 需补全在未添加UserProfile表前已存在的User实例的UserProfile数据模型。 21.4 manage.py的作用 manage.py定位settings.py的位置,在settings.py中设置有数据库配置信息,选择数据库运行django. 21.5 编写脚本

import os os.environ["DJANGO_SETTINGS_MODULE"] = "forum.settings"

import django django.setup()

from usercenter.models import UserProfile from django.contrib.auth.models import User

for u in User.objects.all(): p = UserProfile(user=u) p.save() 1 2 3 4 5 6 7 8 9 10 11 12 21.6 设置联锁删除

class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE)

vieyahn2017 commented 4 years ago

22. javascript介绍

22.1 js介绍 js可以让页面实现更丰富的效果,如内容切换,动画等 与linux,python类似,js也有自己的控制终端,也可以理解为运行在浏览器上的脚本:

22.2 js基础数据类型

  1. 数字型,不区分整数和浮点数,1.5和1都是数字型
  2. 字符串类型,与PYTHON一样;
  3. 列表类型,与PYTHON一样;
  4. 对象,对应PYTHON中就是字典;

22.3 js 操作符 基本与PYTHON一致,在js中==代表会先转换成值做比较,也就是2==‘2’返回True,而===与PYTHON中==相同,即判断类型和值; !:not &&:and ||:or

22.4 基础语句

  1. 赋值语句,无论什么赋值语句前面都是var,不管是浮点或是整型;
  2. 弹窗语句。alert(“content”)
  3. 终端输入语句;console.log(…)
  4. ;空语句,与PYTHON中PASS意义一致;
  5. 判断类型。typeof()

22.5 常用流程控制语句

  1. if语句逻辑与PYTHON一致,区别是语句后面均需要加;,且语句需用{}括起来;

22.6 函数定义 function foo(a,b){ function}; 参数(a,b)不传不会报错 传递(1,2),第二个参数类型为number 传递(1),第二个参数类型为undefined 22.7 js的使用 页面上一个窗口就是一个window,window有很多属性,window.location就是地址栏; window.location.href,当前访问路径; 通过控制浏览器的一个对象就可以控制浏览器; 无论在窗口定义什么样的变量,这个变量都会附着在window上面,比如定义var x=1,再输入window.x,值也是1; window.document代表当前页面,即实际显示部分;

22.8 如何把js写进页面里 CSS与JS引入方式基本相同,不同的是CSS在页面哪个位置引用都是一样的,但是JS是有顺序的,JS的代码是从页面从上至下依次执行的,同时JS与HTML之间也有执行顺序,而如果先执行JS的话网站加载速度会很慢,因此很多网站都是先加载HTML,然后再用JS渲染,一般情况下写在body闭标签前。

23. jQuery介绍

23.1 jQurey来源 由于源生的javascript很难使用,因此衍生出jQuery jQuery与JavaScript之间的关系就好像是CSS与BOOTSTRAP之间的关系。

23.2 jQuery简单操作 1) 选择节点

选择id=OK的节点; $(“#OK”) 1 2 选择class的类,类里面的input标签,input标签type为submit, $(“.class input[type=submit]”) 1 2 2) 读取属性,设置属性 自己定义的属性就是data-开头的属性,这个属性对浏览器来说是不做任何处理的,如果是获取的话就是$(“#ok”).attr(“data-x”),如果是设置属性的话就是$(“#ok”).attr(“data-x”.”cba”) 3) 获取、设置开闭标签之间的内容

$("#OK").html() $("#OK").html("一段html代码") 1 2 4) 获取输入框的内容

$(…).val() 1 深入的话就是可以对输入框中的值进行设置。

23.3 jQuery事件监听 事件监听就是:在什么元素上,发生了什么事儿,那么就干什么.如下所示:

$(#id).click("回调函数") 1 回调函数:

function "可以没有名字" (e){ //code } 1 2 3 取消监听:

$("#id").off(); 1 23.4 jQuery常用函数——网络请求 get请求:data是网络请求得到的数据

$.get("/article/list/3",function(data){ // code }); 1 2 3 post请求:请求参数以对象的形式,即var params,

var params = {"username":"","password":""}; $.post("/article/create/3",params,function(data){ // code }); 1 2 3 4

24. JSON介绍

24.1 JSON介绍

JSON字符串本质上还是字符串,类似与HTML本质上也是一个字符串,如果字符串符合了HTML格式上的要求,那么就可以称之为HTML代码,同样的,如果字符串符合JSON的要求,那么我们可以认为这是一个JSON字符串,但他们的本质上还是字符串。

24.2 JSON格式标准 JSON与PYTHON及JS非常类似, 数字:“1” 字符串:‘“1”’ 列表:‘[1]’ 字典:字典中的key必须是用双引号引起来的‘[“Hello”,{“1”:”world”}]’,JSON转换过程中会有少量的失真。 JSON只能转换简单数据类型,复杂数据类型是无法转换的,比如对象,下文时间对象是无法转化成JSON格式的,只能先通过PYTHON转换成字符串格式,然后再把字符串转化成JSON字符串。

24.3 Python,js通过JSON传递数据

因为PYTHON是在服务器端,JS在浏览器端,因此JSON是在浏览器端与服务器端非常重要的格式,可以轻易的与PYTHON及JS交互。 在Python端是基于Python的标准库json

import json
# python-->json
json.dumps(py_obj)
# json-->python
json.loads(json_str)

在js端是内置的JSON对象,

# json-->js
JSON.stringify(js_obj)
# js-->json
JSON.parse(json_str)

在服务器端,代码经过处理,先是做成py_obj的形式,然后通过json.dumps(py_obj)转换成json字符串,然后return给前端, 在前端,json字符串在jQuery回调函数中请求参数中,在回调函数内部通过JSON.parse(data)转换成js_obj。

vieyahn2017 commented 4 years ago

25. 文章评论功能

25.1 评论功能要点

1) 建立一张新表,包含owner, article, content, status, create_timestamp, last_update_timestamp字段;

2)URL:/comment/create/,创建评论是在文章详情页,因此无需url配置,且基于JS来实现,这个接口只处理JS的请求,不返回任何页面。

3) 控制器函数 创建create_comment,显示部分在文章详情页

4) HTML模板 在原有文章详情页进行补充完善

25.2 控制器展示部分

  1. 文章详情页的展示:文章,相关评论,分页信息。
  2. detailView是用来显示数据模型的对象的,

class ArticleDetailView(DetailView): model = Article template_name = 'article_detail.html' context_object_name = 'article'

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    page_no = int(self.request.GET.get('page_no', 1))
    all_comments = Comment.normal_objects.filter(article=context['article'])
    comments,pagination_data = paginate_queryset(all_comments, page_no, cnt_per_page=3)
    context['comments'] = comments
    context['pagination_data'] = pagination_data
    return context

1 2 3 4 5 6 7 8 9 10 11 12 13 25.3 页面展示部分 展示的部分就是一个表格,与之前类似。

   {% for comment in comments %}
        <tr>
            <td style="width: 200px">
                作者:{{ comment.owner.username }}
            </td>
            <td>
                <a href="#replyRegion" data-id="{{ comment.id }}" class="replyBtn btn btn-primary pull-right">
                    回复
                </a>
                {% if comment.to_comment %}
                    <blockquote>
                        <b>{{ comment.to_comment.owner.uername }}:</b>
                        <br />
                        {{ comment.to_comment.content|linebreaksbr }}
                    </blockquote>
                {% endif %}
                {{ comment.content|linebreaksbr }}
            </td>
        </tr>
    {% endfor %}
</table>
{% include 'component/paginator.html' %}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 25.4 创建评论功能 接口可以理解为:一个URL定义就是一个接口, 接收什么参数:评论文章的ID,评论的内容,评论人(后台可以得到) 返回什么结果:创建成功,创建失败,返回状态信息。

创建评论前端部分: 注意:如果创建的是一个多行的评论,那么保存到的就是“第一行\n第二行”,但是展示的时候是{comment.content},而前端的换行符是 ,两种符号不一致,django提供了一种过滤器,即可以写成{comment.content | linebreaksbr},则显示的页面的部分就可以正常换行了。

创建评论JS部分


作者:{{ user.username }}



<style type="text/css">
.mainTable td.author{
    width: 10%;
}
.mainTable td.content{
    width:87%;
}
blockquote{
    font-size: 13px;
}
img{
    max-width: 200px;
}
</style>
<script type="text/javascript" src="/static/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/static/js/jquery.csrf.js"></script>
<script type="text/javascript">
$(document).ready(function () {
  var article_id = {{ article.id }};
  var page_cnt = {{ pagination_data.page_cnt }};
  var to_comment_id = 0;

  $(".replyBtn").click(function (e) {
    to_comment_id = parseInt($(e.target).attr("data-id"));
    $("#commentContent").focus();
    return false;
  });

  $("#commentBtn").click(function () {
    var comment = $("#commentContent").val();
    var param = {"article_id": article_id, "content": comment, "to_comment_id": to_comment_id};
    $.post("/comment/create/", param, function (data) {
      var ret = JSON.parse(data);
      if (ret["status"] == "ok") {
          $("#commentContent").val("");
          window.location.href = "/article/articledetail/{{ article.id }}?page_no=" + (page_cnt + 1);
      } else {
          alert(ret["msg"]);
      }
    });
  })
});
</script>

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 第一行:引入jQuery 第二行:引入jQuery.csrf.js,这个文件是协助处理csrf验证的; 三行以下:document:说明是jQuery的文档对象,.ready代表等待页面加载完成后,调用回调函数function() var article = {{ article.id}},django渲染只要在前端就都能生效,不管是写在css,js,html中,如果article.id=12,那么js中的article_id就是12. commentBtn:评论按钮,.click当点击的时候调用回调函数,获取评论输入框的内容commentContent.val() 然后把评论文章的id及评论内容commentContent.val()打包成一个字典,用jQuery的post的形式返回,返回值是data(json字符串),通过json.parse的形式解析

vieyahn2017 commented 4 years ago

26. 回复评论功能

26.1 数据模型 在原来的评论数据表中增加一列to_comment,记录指向评论的id,如果为null,则说明为评论文章的,如果有id的话则是回复评论的,相当于这列也是外键,只不过这个外键指向了自己,因此在models中定义的时候需要将外键表写成“self”

to_comment = models.ForeignKey("self",null=True,blank=True,verbose_name='被回复评论者') 1 26.2 URL及控制器 URL还是创建评论的那个接口,但是控制器里面的接口接收的参数需要增加一个,需要增加一个to_comment_id

to_comment_id = int(request.POST.get('to_comment_id', 0))
// code
if to_comment_id != 0:
    to_comment = Comment.objects.get(id=to_comment_id)
    // code
else:
    to_comment = None

1 2 3 4 5 6 7 26.3 回复评论页面 首先在展示上,如果回复了另外一条评论,那么另外一条评论需要展示在这个评论显示的区域里面,而且以一种引用的形式,其实引用的形式就是HTML的标签(blockquote), if comment.to_comment表示是否是评论其他评论的,

{% if comment.to_comment %}

{{ comment.to_comment.owner.uername }}:
{{ comment.to_comment.content|linebreaksbr }}

{% endif %} {{ comment.content|linebreaksbr }} 1 2 3 4 5 6 7 8 26.4 锚点的概念 锚点是页面内跳转使用,当a标签引用的是页面内某个元素的id,则会跳向页面内元素。

26.5 回复评论页面 data-id是代表了回复评论的id,需要进行记录,在后续需要传递给后端,

回复

var to_comment_id = 0;

$(".replyBtn").click(function (e) { to_comment_id = parseInt($(e.target).attr("data-id")); $("#commentContent").focus(); return false; });

vieyahn2017 commented 4 years ago

27. 消息系统

27.1 消息系统要点 1) 当文章被评论,或评论被回复的时候,为用户存下一条消息。 2) 在首页右侧显示未读消息数量 3) 点击未读消息数量打开消息列表页 4) 点击某一条消息,跳转到事件发生的页面

27.2 数据模型 owner:消息来源归属人; content:消息内容; link:消息页面发生的连接; status:已读还是未读

class Usermessage(models.Model): owner = models.ForeignKey(User,verbose_name="owner") content = models.CharField("内容", max_length=400) link = models.CharField("链接", max_length=400) status = models.IntegerField('状态', choices=((0,'未读'),(1, '已读')),default=0)

create_timestamp = models.DateTimeField(auto_now_add=True)
last_update_timestamp = models.DateTimeField(auto_now=True)

def __str__(self):
    return self.content
class Meta:
    verbose_name = "用户消息"
    verbose_name_plural = "用户消息"

1 2 3 4 5 6 7 8 9 10 11 12 13 14 27.3 URL及控制器

urlpatterns = [ url(r'^list/$', message_list, name="message_list"), url(r'^read/(?P\d+)$', read_message, name='read_message'), ] 1 2 3 4 @login_required def message_list(request): read_messages = Usermessage.objects.filter(owner=request.user, status=1).order_by("-id") unread_messages = Usermessage.objects.filter(owner=request.user, status=0).order_by("-id") return render_to_response("message_list.html", {"read_messages": read_messages,"unread_messages": unread_messages})

@login_required def read_message(request,message_id): message = Usermessage.objects.get(id=int(message_id)) message.status = 1 message.save() return redirect(message.link) 1 2 3 4 5 6 7 8 9 10 11 12 13 27.4 HTML显示要点 1) 首页上显示消息条数 利用Bootstrap中的badge类实现

{% if msg_cnt %} {{ msg_cnt }} {% endif %} 1 2 3 2) 消息页面显示页面 利用bootstrap中的list-group-item来实现

{% block content %}

{% endblock %} 1 2 3 4 5 6 7 8 9 10 11 27.5 消息存入

if to_comment_id != 0: to_comment = Comment.objects.get(id=to_comment_id) new_msg = Usermessage(owner=to_comment.owner, content='有人回复了你的评论‘%s’'% to_comment.content[:30], link="http://%s/article/articledetail/%s"%(request.get_host(),article_id)) new_msg.save() else: to_comment = None new_msg = Usermessage(owner=article.owner, content="有人评论了你的文章《%s》"%article.title, link="http://%s/article/articledetail/%s"%(request.get_host(),article.id)) new_msg.save()

vieyahn2017 commented 4 years ago
  1. cookie与session 28.1 cookie的含义 当访问网站的时候,浏览器会存储用户相关信息,在该用户再次使用此浏览器时候,浏览器可以提取用户存储信息。

Cookie是浏览器中存储的,域名相关的一些数据,存储形式为键值对,如域名-数据。

28.2 cookie的弱点 1) 不方便同步,换设备信息就没了 2) 容量有限 3) 有被盗的风险

28.3 session:会话 在没有session之前,浏览器对每一个用户的请求都看作是全新的,每次请求都要重头开始,在设置session之后,每次请求都会带有cookie数据,cookie中带有标识,在访问对应网站时会携带标识,通过标识获取数据。 cookie存储介质可能指数据库,缓存,或者文件。 用户id会与session id绑定,因此用户在访问网站的时候会记录cookie,在这个用户再一次访问该网站时首先会根据用户id访问cookie,

vieyahn2017 commented 4 years ago

29. Admin的汉化

29.1 在settings里面更改语言

LANGUAGE_CODE = 'zh-hans' 1 29.2 在models中更改admin语言

def __str__(self):
    return self.content
class Meta:
    verbose_name = "用户消息"
    verbose_name_plural = "用户消息"

1 2 3 4 5 29.3 在app中admin.py设置语言

from django.contrib import admin from .models import Article

class Articleadmin(admin.ModelAdmin): list_display = ('block', 'title', 'content', 'status', 'create_timestamp', 'last_update_timestamp') actions = ['make_picked'] inlines = [CommentInline] fieldsets = ( ("基本", {"classes": ("",), "fields": ("title", "content")}), ("高级", {"classes": ("collapse",), "fields": ("status", )}) ) readonly_fields = ("owner", "content", "status", "create_timestamp")

def make_picked(self, request, queryset):
    for a in queryset:
        a.status = 10
        a.save()
make_picked.short_description = '设置精华'

admin.site.register(Article, Articleadmin) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 29.4 在app下创建apps.py并进行汉化

from django.apps import AppConfig

class ArticleConfig(AppConfig): name = 'article' verbose_name = '文章' 1 2 3 4 5 6 1) 在app article中新建apps.py文件; 2) 在apps.py中from django.apps import AppConfig; 3) 定义一个类(类名根据需要),类继承(AppConfig); 4) 写入name = app 名称,verbose_name = app中文名称; 5) 在settings.py中INSTALLED_APPS中原有的article更名为article.apps.ArtilceAPPConfic

vieyahn2017 commented 4 years ago

30.日志功能

30.1 日志功能概述 由简单的输出变成对象,对象名称为LogRecord,除了我们写的信息以外,还会有一个级别,一共分为5个级别,从小到大依次是

VERBOSE(冗杂的);

INFO(通知)

WARNING(警告)

ERROR(错误)

CRITICAL(严重的),

级别与信息组合之后输入到日志处理的组件中,日志处理的组件叫logger,logger也有级别,如果logger的级别是info,那么logger的级别就是过滤LogRecord,如果日志记录的级别没有logger高,那么logger会直接忽略掉报文信息,进入到Logger内部后,有多个并行的任务处理线,称之为handler,handler与logger一样有级别,它把低于它自己级别的日志都忽略掉,对于某一条任务处理线来说,当接收到一条日志记录后,首先会判断日志级别有没有自己高,如果高于自己的话则进行处理,然后进入到filter环节,再进入到实际的处理环节formater,包括写邮件,发邮件,或发送至网络中去。 logger包括多个handler,filter,formater也是配置在handler上,因此我们可以这样定义,logger的定义依赖于handler的定义,handler的定义依赖于filter的定义和formater的定义,这也是配置规则。

30.2 配置样例

LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(message)s' }, }, 'handlers': { 'info_record': { 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': os.path.join(os.path.dirname(BASE_DIR), 'log/info.log'), 'formatter': 'verbose' }, 'error_record': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': os.path.join(os.path.dirname(BASE_DIR), "log/error.log"), 'formatter': 'verbose' }, }, 'loggers': { 'forum': { 'handlers': ["info_record", "error_record"], 'level': "DEBUG", } } } 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 30.3 日志使用简单举例 获取logger对象import logging ,logging是标准库 通过LOGGER.info 及LOGGER.error写入日志信息,在日志文件中就可以进行查看。

vieyahn2017 commented 4 years ago

31. 利用中间件简化任务

31.1 中间件的需求原因 1) 对每一个请求都捕获异常 2)对每一个请求都记录请求参数 3) 对每一个请求都为request增加一个属性

因此我们需要一个全局的办法对请求/响应做改动,中间件可以满足这样的需求。 31.2 中间件流程

31.3 中间件的编写 中间件就是一个类, 类名可以按需求定义,

def init(self, get_response): self.get_response = get_response 1 2 上面两句话是固定格式,不用改

def call(self, request): 1 call这个函数每处理一次请求都会执行一次; 然后编写对请求处理的代码

response = self.get_response(request) 1 上面这句话是固定的

return response 1 上面这句话也是固定的 下段代码实现的功能是打印get 和post的所有参数。

class PrintParamsMiddleware(object): def init(self, get_response): self.get_response = get_response

def __call__(self, request):
    print(request.GET)
    print(request.POST)
    response = self.get_response(request)
    return response

1 2 3 4 5 6 7 8 9 31.4 中间件的配置 middleware按照从上到下的顺序处理请求,然后按照从下到上的顺序处理响应,

MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middlewares.PrintParamsMiddleware', ] 1 2 3 4 5 6 7 8 9 10 31.5 利用中间件记录所有异常

class PrintParamsMiddleware(object): def init(self, get_response): self.get_response = get_response

def __call__(self, request):
    try:
        response = self.get_response(request)
    except Exception as e:
        LOGGER.exception(e)
        return HttpResponse("wrong")
    return response
vieyahn2017 commented 4 years ago

32. nginx入门

32.1 域名的概念 请求先会去DNS(Domain Network Server)域名服务器查询名称对应的网站ip,计算机通过实际的Ip去访问服务器。 如果网站流量大需要多台服务器,域名服务器可以将同一个网站名称解析成多个ip。 服务器A将流量分给服务器B、C、D的过程称为反向代理。

32.2 域名的组成 域名分为一级域名,二级域名,域名后缀 32.3 计算机解析域名的方法 1) 查看hosts文件,看系统对访问的域名是否有指定的ip 2) 使用网络上的DNS服务查找ip 32.4 静态文件服务器与nginx

静态文件就是返回数据时,内容不会被改变的文件 静态文件的访问效率要比我们的动态网页高很多,速度很快 静态文件服务器提供了静态文件的下载 常用的静态文件服务器有apache,nginx 32.5 配置静态文件服务器 此部分还是以本机来进行配置,

listen 80:监听80端口 在http下,网站名称后面缺省端口号默认为80 在https下,网站名称后面缺省端口号默认为443

server_name 绑定域名 charset utf-8编码 location / url路径 这里放置实际的文件,实际上是一种映射关系,将url路径映射到实际的 /urs/share/userres/路径下。 nginx 配置文件位置:/etc/nginx/conf.d/userres.conf server{ listen 80;#http下缺省为80,https下缺省为443 server_name res.myforum.com;#域名 charset utf-8;#编码方式 location / { alias /usr/share/userres/; } 1 2 3 4 5 6 7 8 32.6 nginx常用命令

启动服务:sudo service nginx start 停止服务:sudo service nginx stop 重启服务:sudo service nginx reload 查看状态:sudo service nginx status 访问日志:/var/log/nginx/access.log 错误日志:/var/log/nginx/error.log 配置文件:/etc/nginx/conf.d/… 测试配置:sudo nginx -t

vieyahn2017 commented 4 years ago

33.网站部署

33.1 代码拷贝至云服务器 1)代码压缩 2)代码拷贝至线上:scp 代码压缩包 root@域名:拷贝路径 3)线上解压 33.2 33.2 上线后不显示图片的原因 原因是因为线下域名与线上域名不一致导致

解决方案: 我们可以设置http://公网ip/media/,在/media/之后的交由nginx进行处理,除了/media/以外均交由django进行处理,

33.3 线上安装nginx location / 默认所有请求都走127.0.0.1 location /media / 就作为静态文件处理路径 nginx 是最佳匹配,因此只要符合/media/,则优先执行。

server{ listen 80; server_name 公网ip; charset utf-8;

location / {
    proxy_pass http://127.0.0.1:8000/;
    }
location /media/ {
    alias /usr/share/userres/;
    autoindex on;#自动列出文件夹下内容
    }
}

1 2 3 4 5 6 7 8 9 10 11 12 13 33.4 在nginx重新配置后需同步在django中更改 在nginx重新配置静态文件位置后,需在django项目中同步替换路径

grep –R –exclude-dir = static res.myforum.com * 1 33.5 设置ini文件,方便线上线下更改 1)增加ini文件的原因 我们可以直接将res.myforum.com进行替换,但是不太好,因为线上线下都需要进行测试,如果改来改去太影响效率了,而且转移到线上后还会有更多参数修改,因此最好的办法是制作Ini文件,线上一个ini文件,线下一个ini文件。

2) ini文件配置方法 ini配置文件最好放在代码目录外面,跟项目分开,防止线上线下拷贝时混淆。 在django中读取ini文件,先导入configparser,实例化ini文件解析器,然后确定ini文件位置,然后读取ini文件,代入实际配置值。 下图中读取的是getboolen,还可以是getfloat,getint,根据实际情况进行配置。

// ini 文件配置 [default] debug = on media_base_url = http://res.myforum.com/

[other] key = value

//读取ini文件配置 import configparser config = configparser.ConfigParser() config_path = os.path.join(os.path.dirname(BASE_DIR),) config.read(config_path) DEBUG = config['default'].getbolean('debug') MEDIA_BASE_URL = config['default']['media_base_url'] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 33.6 线上部署后DEBUG=False对系统的影响 1) settings.py中需要设置ALLOWED_HOSTS

ALLOWED_HOSTS = ['127.0.0.1','线上域名'] 1 2)原有静态文件URL设置失效 出于效率考虑,强制使用静态文件服务器处理静态资源, 原有静态文件URL设置都会失效的话,我们就将所有的静态文件都放在一个名为dist_static的文件夹下,同时需注意设置文件夹权限。

location /static/{ alias /root/forum/dist_static/; } 1 2 3 33.7 收集静态资源 静态文件涉及3部分的内容,分别是项目静态文件,Ue静态文件,admin静态文件,利用django shell 执行收集静态文件并放至/dist_static/路径下。

项目静态文件目录:/root/forum/static/ UE静态文件目录:/root/forum/DjangoUeditor/static/ admin静态文件目录:django源代码中

在setting.py中配置静态文件新路径:

STATIC_ROOT = os.path.join(BASE_DIR,'dist_static') 1 // 执行搜索操作 python3.5 manage.py collectstatic 1 2 33.8 线上部署配置小结 1) 调整nginx配置:转发请求,动态请求由django执行,处理静态文件,静态文件由nginx执行,效率更高; 2) 修改代码:读取config.ini,替换一些变量,方便线上线下测试; 3) 调整ALLOWED_HOSTS,除本机外添加网站域名; 4) 按照1)中配置路径,搜集静态文件并放置相应路径中; 5)

vieyahn2017 commented 4 years ago

34. 高性能部署

34.1 修改默认的runserver

uwsgi处理跟网络处理相关,django跟业务相关, uwsgi服务器使用uwsgi协议,因此nginx不能使用proxy_pass原封不动的将请求转发,而是需要修改成uwsgi协议转发。

34.2 安装配置uwsgi

// 安装依赖 yum install -y zlib-dev sqlite-devel bzip2-devel pcre-devel python-devel // 安装uwsgi pip3.5 install uwsgi

//配置uwsgi [uwsgi] uid = root #以root:root运行 gid = root autoload = true #自动加载插件 socket = 0.0.0.0:8200 #runserver stats = 0.0.0.0:8201 chdir = /root/forum. logto = /root/log/uwsgi.log #日志文件 module = forum.wsgi:application process = 4 #4个工作进程 master = true #1个主进程 no-orphans = true #主进程死,全死 pidfile = /root/wsgi.pid #记录主进程id 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 这里也标明了常规项目的文件目录结构,即: /root/ …|–conf/ …|…|–config.ini …|…|–uwsgi.ini …|–forum/ …|…|–django代码 …|…|–forum/ …|…|–wsgi.py …|–log/ …|…|–uwsgi.log …|–wsgi.pid

34.3 uwsgi配置补充 nginx 将proxy_pass改为uwsgi协议后配置:

location / { uwsgi_read_timout 30; uwsgi_pass 127.0.0.1:8200; include uwsgi_params; } 1 2 3 4 5 chdir = /root/forum/ 和module = forum.wsgi:application,载入uwsgi工作目录到/root/forum下,forum里面的wsgi.py的application变量中

34.4 运行uwsgi 简单的启动方法,但是启动后无法做其他命令

--ini /root/conf/uwsgi.ini 1 有效的启动uwsgi方法,

nohup uwsgi --ini /root/conf/uwsgi.ini & 1 停止uwsgi的方法: 通过cat命令查看wsgi.pid文件中主进程编号,然后利用kill命令即可杀死进程,也可以利用命令kill -9 cat wsgi.pid 来执行,··(数字1左边)代表先执行里面的语句,输出结果后再执行kill命令。

34.5 错误排查 查看nginx的错误日志:

tail var/log/nginx/error.log 1 查看uwsgi错误日志位置:

tail /root/log/uwsgi.log