博客项目

无论进行什么项目,首先进行需求分析。

需求分析

要建立最初始需求文档,每个功能要一个一个梳理,建立一个个功能点,清晰,然后需要客户过目,事后要放入项目归档。项目管理很关键。但是客户往往是跳跃性的想法,我们需要不断的沟通交流才能设计好的服务。
需求理清后第一件事就是建表,改setting配置(因为这个在启动时就加载,如果启动后修改可能不会刷新。)

我们可以通过快速迭代,不断的更新版本以寻求怎样的功能能提高市场占有率。

千万不要过度设计。

要设计窗口吗?
不需要,只要借助浏览器。
选型,调查一下。基本上都是用BS,虽然也有一些问题,兼容性问题,就是不能用IE。

最重要的是博文和用户,即用户管理,博文管理。

数据库选择
CMDB配置管理数据库( Configuration Management Database,CMDB)是一个逻辑数据库,包含了配置项全生命周期的信息以及配置项之间的关系(包括物理关系、实时通信关系、非实时通信关系和依赖关系)。

MIS管理信息系统(管理信息系统–Management Information System),主要指的是进行日常事务操作的系统。这种系统主要用于管理需要的记录,并对记录数据进行相关处理,将处理的信息及时反映给管理者的一套网络管理系统。

使用Mysql 8.0 InnoDB引擎(增删改查功能)。

流程化,流程系统更加麻烦。

先确定数据的表格,设立外键时类型和大小要一致。
先建立user表,sql语句如下

CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(48) NOT NULL,`email` varchar(64) NOT NULL,`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'MD5加密\r\n',PRIMARY KEY (`id`),UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

含有id 唯一标识 主键
,name 用户名,描述性字段
,email 用户电子邮箱
,password 用户密码,不能明文储存,采用单向加密,如MD5(缺点,可以反查,计算速度过快,穷举太快)

建立一个文章的post表

CREATE TABLE `post` (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(128) NOT NULL,`pubdata` datetime(6) NOT NULL,`author_id` int(11) NOT NULL,`content` text NOT NULL,PRIMARY KEY (`id`),KEY `author_id` (`author_id`),CONSTRAINT `post_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

含有id 唯一标识
,title 标题,描述性字段
,pubdata 发布日期,日期类型
,author_id,博文作者id,外键,必须是table user的id(外键可以不写,在逻辑层面做到也可以)

作者和用户id需要有一个关系,在创建文章时,auther_id必须存在于user(id)中,如果不存在则会抛异常。如果要删除post表中的内容,则可以直接删除,如果要删除user表中的内容,则需要设定级联删除ON DELETE CASCADE 级联删除 ON UPDATE CASCADE 级联更新,但一般不用这个。我们更希望在逻辑层面上删除,通过事务控制更好。
当然post表最重要的,content,类型是选择text(大约有6万多个字符),或者longtext(考虑使用,有必要吗?)。这是一个大字段,在应用时一般会再建立一张表单独放开,一般查询的时候不会用到这个大字段,频繁查询的字段放在post表中,这两张表也有联系,一一对应关系,post生成一个id后,content表才可以使用这个id,使用事务解决(但是如果被打断了怎么办,这是风险。)。同一个用户的附加信息要放在外面,即必填表和非必填项,事务控制在创建用户时一起添加这些信息,insert into,也可以用外键约束。

博客的数据库系统显然是一对多的关系,一个作者有多个博文。
content的选取?选取大小?图片如何处理?单独放一张表。
博文一般在几万字内,因此需要很大,选择text很合适,甚至longtext。
图片储存,博文就像HTML一样,图片通过路径信息将图片嵌入在内容中的,所以保存的内容还是字符串。
来源有两种:外链,通过URL链接访问,本站不用存储图片,但是容易引起盗链问题。
本站存储,需要提供博文的文本编辑器,提供图片上传到网站存储,并生成图片URL,这个URL嵌入到博客正文中。这个要解决图片存储的问题,水印问题,临时图片清理,在线压缩问题等等。使用磁盘阵列,云,分布式文件系统等,运维相关,术语要懂。

content不应该和频繁查询的字段放在同一张表中,应该单独放一张表。

CREATE TABLE `content` (`id` int(11) NOT NULL AUTO_INCREMENT,`content` text NOT NULL,`post_id` int(11) NOT NULL,PRIMARY KEY (`id`),KEY `post_id` (`post_id`),CONSTRAINT `content_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这里生产的SQL脚本不用来生成表,使用ORM工具来创建表,用来检查实体类构建是否正确。

在pycharm中新建一个项目,下面开始django之路。

使用pycharm创建一个项目后,在文件路径下的view,terminal输入pip list 里面两个包就是虚拟环境必须的包。pip和setuptools。
然后在settings中Project:name中的project interpreter,python解释器中,可以更改你用的python版本,安装的包等。
项目要用的包一定要有,不用的包绝对不要出现,

先完成核心业务
满足用户,注册登录登出,user表
博客的发文文章列表,文章详情,post和content表

多人博客项目
BS开发的Django;
Django采用MVC架构设计的开源的WEB快速开发框架。
优点:
快速开发,(Auth,Cache,模板,并不推荐使用)。
MVC设计模式
实用的管理后台(实际的后台管理是管理业务问题,Flask框架简单轻巧)
自带ORM,Template,Form,Auth核心(管理模块,有些能去除)
组件
简介的URL设计(不支持post,get)
插件丰富(大多数不是原生的)

缺点:
太大,东西全而大
同步阻塞!

这就是为什么很多人用tomado(基于协程的异步模型,效率高),大多数情况下用Django就够了,但是做网站光用django肯定是不行的,Django是动态网页服务,一定要做动静分离,做优化,动态用动态服务器,静态用静态服务器,效率更高(企业级应用)。
模板就是替换字符串,模板Jinja做不到前后端分离。后端只负责Json数据,传到前端后,靠JS静态加载数据,之间靠定义接口耦合,对http开放的API接口,restful风格。

Django版本使用1.11,可以支持2.7,-3.6的python版本。2.0以上的不在支持python2.7了。python2.7可以提供很多插件,也可以自己写,熟系框架后。

作为一个Python web框架,django需要安装python。

Django版本 Python版本
1.11 2.7,3.4,3.5,3.6,3.7(在1.11.17中添加)
2.0 3.4,3.5,3.6,3.7
2.1,2.2 3.5,3.6,3.7

windows安装Django很简单:直接pip install Django即可安装最新的正式版Django。在创建的新项目中的terminal下输入即可。
安装完后,先简单了解一下Django,在如图所示的库文件里可以看到:

django-admin。我们看看都有哪些命令:

Type 'django-admin help <subcommand>' for help on a specific subcommand.
Available subcommands:
[django]checkcompilemessagescreatecachetabledbshelldiffsettingsdumpdataflushinspectdbloaddatamakemessagesmakemigrationsmigraterunserversendtestemailshellshowmigrationssqlflushsqlmigratesqlsequenceresetsquashmigrationsstartappstartprojecttesttestserver
Note that only Django core commands are listed as settings are not properly configured (error: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SET
TINGS_MODULE or call settings.configure() before accessing settings.).

注意:这些命令都是在开发环境下才可以用即:

最常用的就是startproject,查看一下help:django-admin help startproject

usage: django-admin startproject [-h] [--template TEMPLATE][--extension EXTENSIONS] [--name FILES][--version] [-v {0,1,2,3}][--settings SETTINGS][--pythonpath PYTHONPATH] [--traceback][--no-color] [--force-color]name [directory]Creates a Django project directory structure for the given project name in the
current directory or optionally in the given directory.positional arguments:name                  Name of the application or project.directory             Optional destination directoryoptional arguments:-h, --help            show this help message and exit--template TEMPLATE   The path or URL to load the template from.--extension EXTENSIONS, -e EXTENSIONSThe file extension(s) to render (default: "py").Separate multiple extensions with commas, or use -emultiple times.--name FILES, -n FILESThe file name(s) to render. Separate multiple filenames with commas, or use -n multiple times.--version             show program's version number and exit-v {0,1,2,3}, --verbosity {0,1,2,3}Verbosity level; 0=minimal output, 1=normal output,2=verbose output, 3=very verbose output--settings SETTINGS   The Python path to a settings module, e.g."myproject.settings.main". If this isn't provided, theDJANGO_SETTINGS_MODULE environment variable will beused.--pythonpath PYTHONPATHA directory to add to the Python path, e.g."/home/djangoprojects/myproject".--traceback           Raise on CommandError exceptions--no-color            Don't colorize the command output.--force-color         Force colorization of the command output.

django-admin startproject blog . 在项目里创建Django项目,"."表示在本根目录下,不会另外创建目录,直接将当前目录作为项目目录。结构如下:

manage.py在这里我们创建的所有子命令都归这里管,项目管理的命令行工具。应用创建、数据库迁移等都用它完成。
blog中init目前没什么用;settings,全局配置的,文件不能少;URL做路由用,路径映射配置,默认情况下只配置/admin的路由;WSGI不用多说,就是WSGI协议。Django就是更加丰富的基于WSGI的框架。

先来看看settings:全局配置,数据库参数。(!!!注意配置文件千万别删除,即使修改也要注释掉原有的配置)

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

__file__指的是当前的.py文件路径是什么,abspath,它的绝对路径是什么,dirname(os.path.abspath(file))绝对路径所对应的目录是什么,之后再一个dirname,指的是再上一级目录,全局量,项目根目录。作为一个基路径,供其他对象使用。例如(该文件下的):

DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3','NAME': os.path.join(BASE_DIR, 'db.sqlite3'),}
}

将数据库文件sqlite加入进项目根目录。但我们这里用自己的数据库。

ALLOWED_HOSTS = [] # 允许的主机INSTALLED_APPS = [  # 安装的app,我们自己app也可以加进来'django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',
]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',
]ROOT_URLCONF = 'blog.urls'  # 所有的URL配置从哪里开始,就是简单的多级路由,在blog包下的urls开始,最后靠动态加载。TEMPLATES = [  # 模板配置{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [],  # 模板在哪里'APP_DIRS': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},
]AUTH_PASSWORD_VALIDATORS = [  # 用户验证相关,我们一般用自己的验证方式。{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',},{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},
]# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/  # 国际化,这些需要本地化,一会儿再说。LANGUAGE_CODE = 'en-us'TIME_ZONE = 'UTC'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/STATIC_URL = '/static/'  # 静态路由,我们一般用nginx实现,不会走这里。

看看urls:

from django.contrib import admin
from django.urls import pathurlpatterns = [  # 可以访问到某个包下面,启用一个后台管理界面。path('admin/', admin.site.urls),
]

这里的urls取得是一个函数,

数据库的配置
我们将默认参数注释,将下面的代码替换即可:

DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'blog', # 库名'USER': 'mydatabaseuser', # 按连接数据库的用户名写'PASSWORD': 'mypassword',  # 密码'HOST': '127.0.0.1', # 数据库主机ip'PORT': '3306',# 数据库端口 mysql一般是3306}
}

这样就配好了一个数据库连接。OPTIONS选项要参考Mysql文档。最好的学习还是去看官方django文档。

mysql数据库引擎
内建的引擎有:postgresql,mysql,sqlite3,oracle四种(前面加django.db.backends.即可),当然我们还需要数据库驱动。官方建议安装 mysqlclient,用pip install mysqlclient即可安装, 最新版的是mysqlclient-1.4.4(请按博客日期比较)。如果安装的是最新版的django和python一般不会出问题。出问题会出error:C++ 14.0 is required ,原因是编译环境,即使安装了C++14也没用,解决办法是直接安装编译好的文件。直接百度搜索即可找到解决办法,(一般是直接下载安装编译好的文件即可)不赘述。

创建应用

创建用户应用:python manage.py startapp user,这是对user的应用,还需要post,content。输入之后会在项目根目录下创建一个user目录。

init 空
admin 后台管理
apps app的一些配置
models 一些模块创建在这里
tests 测试用
views 视图函数放在这里,
migrations 迁移,用来生成数据库的表,增量型。里面的init是空的。

创建好后要在settings中的apps要加入user。进行注册应用。

模型models

模型方法:

字段类 说明
AutoField 自增的整数字段。如果不指定,django会为模型自动增加主键字段
BooleanField 布尔值字段,对应表单控件CheckboxInput
NullBooleanField 比BooleanField多一个null值
CharField 字符串,max_length设定字符串长度,对应表单控件TextInput
TextField 大文本字段,一般超过4K个字符使用,对应表单控件TextInput
IntegerField 整数字段
BigIntegerField 更大整数字段,8字节
DateField 使用python的datetime.date实例表示的日期,auto_now=False每次修改对象自动设置为当前时间。auto_now_add=False对象第一个创建时自动设置为当前时间。auto_now,auto_now_add,default互斥。对应控件为TextInput,关联了一个JS编写的日历控件
TimeField 使用python的Datetime.time实例表示时间,参数同上
DateTimeField 使用python的datetime.datetime实例表示的日期,同上
FileFiled 一个上传文件的字段
ImageField 继承了FileFiled的所有属性和方法,但对上传的文件进行校验,确保是一个有效的图片

字段选项

说明
db_column 表中字段的名称。如果未指定,则使用属性名
primary_key 是否主键
unique 是否唯一键
default 缺省值,不是数据库字段的缺省值,而是新对象产生时被填入的缺省值
null 表的字段是否可以为null,默认False
blank django表单验证中,是否可以不填写,默认为False
db_index 字段是否有索引

关系型字段类

说明
ForeignKey 外键,表示一对多
MangToManyField 表示多对多
OneToOneField 一对一

一对多时,自动创建会增加_id后缀。从一访问多,使用对象.小写模型类_set;从一访问一,使用对象.小写模型类。
访问id 对象.属性_id

创建user的model类

基类models.Model
表名不指定则默认使用_<model_name>。使用Meta类修改表名。
在modles中写下如下代码:

class User(models.Model):class Meta:db_table = 'user' # 数据库表名id = models.AutoField(primary_key=True)name = models.CharField(max_length=48,null=False)email = models.CharField(max_length=64,null=False,unique=True)password = models.CharField(max_length=128,null=False)def __repr__(self):return "{} {} {}".format(self.id, self.name, self.email)__str__ = __repr__

迁移Migration
迁移:从模型定义生成数据库的表
生成迁移文件
在写好model时在,根目录下的terminal输入python manage.py makemigrations,生成迁移文件。
此时在user包下的migration包中会出现第一个迁移文件。如下:

from django.db import migrations, modelsclass Migration(migrations.Migration):initial = Truedependencies = []operations = [migrations.CreateModel(name='User',fields=[('id', models.AutoField(primary_key=True, serialize=False)),('name', models.CharField(max_length=48)),('email', models.CharField(max_length=64, unique=True)),('password', models.CharField(max_length=128)),],options={'db_table': 'user',},),]

这是一个脚本文件,一般都会用sql脚本来做,不会采用这种方式。如果修改了model,需要调用makemigrations后,序号会增加,当前是0001_initial。

执行迁移,生成数据库的表输入:
python manage.py migrate即可
可以在数据库中看到文件生成了。

可以看到user表建立了,并且所有设置均正确。sql语句如下:

CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(48) NOT NULL,`email` varchar(64) NOT NULL,`password` varchar(128) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

如果数据库连接不成功,请注意密码,host,库名是否输入正确。

django后台管理

1、创建管理员
管理员用户名
密码
命令python manage.py createsuperuser,创建超级用户即可。输入用户名密码即可。
2、本地化
setting.py 中设置语言,时区,名称可以查看django\contrib\admin\locale目录

3、启动WEB server看看是否成功:python manage.py runserver

4、登录后台管理
http://127.0.0.1:8000/admin可以看到后台管理界面
5、注册应用模块
在user应用的admin.py添加

from django.contrib import admin
from .models import Useradmin.site.register(User)

在后台管理界面就可以对user进行增删改了

路由

由python的WSGI库编写的WEB框架中,路由功能就是实现URL模式匹配和处理函数之间的映射。对于django也是如此。路由配置要在项目的urls.py中配置,也可以实现多级匹配,在每一个应用中,建立一个urls.py文件配置路由映射。
url函数
url(regex,view,kwargs=None,name=Name),进行模式匹配
regex:正则表达式,与之匹配的URL会执行对应的第二个参数view
view:用于执行与正则表达式匹配的URL请求
kwargs:视图使用字典类型的参数
name:用来反向获取URL

先做一个示例:

from django.http import HttpRequest,HttpResponsedef index(request):return HttpResponse(b'good work') # 可以是Json,不过参数必须是字典# return JsonResponse({'user':'good work'}) # 这个就是异步调用常用的。urlpatterns = [path('admin/', admin.site.urls),path('index', index),
]

在网页中能访问http://127.0.0.1:8000/index。但是不能访问http://127.0.0.1:8000/index/(这里是404报错)
也就是说在1.1版本的浏览器发起两次请求一次是index,一次是index/,服务器会根据你写的路径自动添加/。
在项目中首页多数使用HTML显示,为了加载速度快,一般多使用静态页面,如果首页内容过多,还有部分数据要变化,则将变化部分使用AJAX技术从后台获取数据,在前台动态的修改DOM树,然后将数据渲染出来即可,要用到异步技术,用JS将数据处理,这样后端就是单纯的数据服务。

本次我们只是实验,将使用模板技术实现。

模板

如果使用react实现前端页面,其实Django就没有必须使用模板,它其实就是一个后台服务程序,接受请求,响应数据。接口设计可以是纯粹的Restful风格,这次我们还是用模板技术实现html。
前端发起HTTP请求,后端发送数据结果,数据是由Json传送,或者restful风格的API,就是HTTP连接,配合上get post方法,数据提交靠Json(在request对象里,不同的框架在不同的地方)。

模板的目的就是为了可视化,将数据按照一定布局格式输出,而不是为了数据处理,所以一般不会有复杂的处理逻辑。模板的引入实现了业务逻辑和显示格式的分离,这样,在开发中,就可以分工协作,页面开发完成页面布局设计,后台开发完成数据处理逻辑的实现。就是一个填空题,填数据就完事了。

python的模板引擎默认使用Django template language(DTL)构建。

模板配置
在settings.py中设置模板项目的路径:

TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [os.path.join(BASE_DIR, 'Templates')],'APP_DIRS': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},
]

先分析一下源码是啥;
DIRS列表,定义模板文件的搜索路径顺序,这里路径咋database下有示例,我们照着写,即os.path.join(BASE_DIR, ‘Templates’),即项目根目录(这里是blog-test)下的Templates文件(自己设定),这里写模板的话属于全局模板,我们也可以在user,app下建立单独的模板。
APP_DIRS 是否在每个已经安装的应用中查找模板。应用自己目录下有templates目录,例如django/contrib/admin/templates。如果应用需要可分离、可重用,建议把模板放在应用目录下。

下载是将文件打开,然后分解成数据流一个一个传给你。
渲染是将文件打开,将数据传输给浏览器,解析成DOM树。

模板渲染

模板处理的两个步骤,加载模板,渲染。
在这里我们可以使用render函数。看栗子:

from django.http import HttpRequest,HttpResponse
from django.template import loader,RequestContextfrom django.shortcuts import render
def index(request):# tp = loader.get_template('index.html')# context = RequestContext(request,{'abc':123})# return HttpResponse(tp.render(context))return render(request, 'index.html',context={'abc':123})  # (request, template_name, context=None, content_type=None, status=None, using=None):# return HttpResponse(b'good work')

从源码可知render函数会返回HttpResponse对象,参数含义见字面意思,请求,模板名,数据字典,数据类型,状态码,。

模板页如下

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>hello</title>
</head>
<body>here is the content <h1>{{abc}}</h1>
</body>
</html>

将模板页放入Templates目录下,名称是index.和html。使用浏览器访问即可。

下面介绍DTL的一些常用语法

DTL常用语法

变量

语法{{ variable }}
变量名有字母数字下划线点号组成。
点号使用时,遵循以下原则:
1、字典查找,例如foo[“bar”],把foo当做字典,bar当做key;
2、属性或方法的查找,例如foo.bar,把foo当做对象,bar当做属性或方法;
3、数字索引查找,例如foo[bar],把foo当做列表,使用索引访问。
如果变量未能找到,则缺省插入空白字符串;在模板中调用方法,不能加小括号,自然也不能传递数据;{{ my_dict.keys }}这样是可以的。

模板标签

if/else 标签
基本语法格式:
{% if condition %}
… display
{% endif %}
或者
{% if condition %}
… display
{% elif condition2 %}
… display
{% else %}
… display
{% endif %}
条件支持and,or,not
注意,这是标签断开的,所以不能用缩进表示,必须有结束标签

for标签
{% for athlete in athlete_list %}
{{athlete.name}}
{% endfor %}
常用变量:

变量 说明
forloop.counter 当前循环冲1开始的计数
forloop.counter0 当前循环从0开始的计数
forloop.revcounter 当前循环的末尾开始倒计数至1
forloop.revcounter0 当前循环的末尾开始倒计数至0
forloop.first 是否是第一次进入循环
forloop.last 是否是最后一次进入循环
forloop.parentloop 循环嵌套时,当前循环的外层循环

给标签增加一个reversed使得该列表被反向迭代:
{% for athlete in athlete_list reverse %}
{{athlete.name}}
{% empty %}
…如果被迭代列表是空的或者不存在,则执行empty
{% endfor %}

可以嵌套使用for标签。
testfor.html模板,看栗子:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>test for</title>
</head>
<body>
<ul>{% for k,v in dic.items %}<li> {{forloop.counter}} {{k}},{{v}}</li>{% endfor %}
</ul>
<hr>
<ul>{% for k,v in dic.items %}<li> {{forloop.counter0}} {{k}},{{v}}</li>{% endfor %}
</ul>
<hr>
<ul>{% for k,v in dic.items %}{{forloop.first}}{{forloop.last}}<li> {{forloop.revcounter0}} {{k}},{{v}}</li>{% endfor %}
</ul>
<hr>
<ul>{% for k,v in dic.items %}<li> {{forloop.revcounter}} {{k}},{{v}}</li>{% endfor %}
</ul>
<hr>
<hr>
<ul>{% for k in lis reversed%}<li> {{forloop.counter}} {{k}}</li>{% endfor %}
</ul>
</body>
</html>

比较简单,不多说,需要注意的是在render里的参数要和for里的列表或者字典名一致,并且是个字典;如return render(request, 'testfor.html',context={'dic':d,'lis':lisx})

ifequal/ifnotequal标签
比较两值,当他们相等时,显示在{% ifequal/ifnotequal %}之中所有的值。和if标签类似,支持else标签。例如:

{% ifequal/ifnotequal a b %}<h1>hello<h1>
{%endifequal%}

其他标签
csrf_tiken用于跨站请求伪造保护,防止跨站攻击。

注释标签{# 注释内容 #}

过滤器

类似于带有通过条件的管道,在变量被显示前修改它。
语法很简单{{ 变量 | 过滤器 }},例如{{name|lower}} 变量name被过滤器处理后大写变为小写。可以被套接,一个接一个。
过滤器传参
有些过滤器可以传递参数,过滤器的参数在冒号之后,并且一定是双引号包裹。例如{{bio|truncatewords:“30”}},截取bio的前30个词。
{{list|join:","}}将list的元素用逗号连接起来。
其他过滤器:

过滤器 说明 举例
first 取列表第一个元素
last 去列表最后一个元素
yesno 变量可以是Ture、False、None,其参数给定逗号分隔的三个值,返回三个的其中一个,顺序就是true对应第一…如果参数只有两个,None等效false处理 {{value|yesno:“yeah,no,maby”}}
add 加法,参数为负就是减法 数字加{{value|add:“122”}},列表合并{{list|add:“newlist”}},
divisibleby 能否被整除 {{value|divisibleby:“2”}},能否被2整除
addslashes 在反斜杠、单引号或者双引号前加上反斜杠 {{value|addslashes}},
length 返回变量的长度 {% if list|length >1 %}
default 变量等价False则使用缺省值 {{value|default:“dddd”}},
default_if_none 变量为none使用缺省值 {{value|default_if_none:“122”}},
date 按照指定格式的字符串参数格式化date或者datetime对象 {{pub_date|date:“n j Y”}},n表示1-12月,j表示1-31日,Y表示年,可以查看https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#date

示例输出奇偶行不同颜色:

<ul>{% for k,v in dic.items %}<li style="color:{{forloop.counter| divisibleby:"2"|yesno:"red,blue"}}"> {{forloop.counter}} {{k}},{{v}}</li>{% endfor %}
</ul>

一般不会用style,多用class,以便随时修改。

用户功能设计与实现

我们只实现基本功能,接受用于通过post方法提交的注册信息,提交的数据是JSON格式。检查email是否已经存在于数据库表中,如果存在则返回错误状态码,如果不存在则将用户提交的数据存入表中,整个过程都采用AJAX异步过程,用户提交JSON数据,服务器端获取数据后处理,返回JSON。

URL:/user/reg
METHOD:POST(django做不到将方法一起映射。)

路由配置
有多种方式,可以直接放在blog下的urls,也可以放在user下的urls里,建议放在user下,这里在user包下创建一个urls。目录结构如下:

这里要用到postman或者一些测试工具,大家自己安装啦,postman是免费的,不过要注册。使用在这里不多说,看示例:
因为现在先做注册提交数据的处理函数,先解决连接问题,先将settings中的MIDDLEWARE(中间件)中的’django.middleware.csrf.CsrfViewMiddleware’,注释掉,因为在使用post提交数据时如果用的是一些测试工具,会出现Forbidden (CSRF cookie not set.): /user/reg,说你的cookie没有设定,因此测试时注释掉会更好。
之后:
在views中写入以下代码:(处理提交过来的数据)

from django.http import JsonResponse,HttpRequest,HttpResponse,HttpResponseBadRequestimport json
import loggingformt = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=formt,level=logging.INFO)
def reg(request:HttpRequest):try:playload = json.loads(request.body.decode())  # 请求有问题时email = playload['email']name = playload['name']password = playload['password']print(email,name,password)return HttpResponse("hey welcome")except Exception as e:logging.info(e)return HttpResponseBadRequest()  # HttpResponseBadRequest不是except子类,不能用raise无参构造

在user中的urls中写入以下代码:

from django.urls import path
from user.views import regurlpatterns = [path('reg', reg),
]

与blog下的urls一样,只提供路由匹配,因此在blog下的urls要修改一下,因为django将请求先按照blog下的urls中的 urlpatterns进行路由匹配,在决定放在哪个APP中,因此修改如下: path('user/', include('user.urls')),将path转到user下的urls,include就是将这种字符串转化为路径,并按照路径加载模块,在这里可以实现二级匹配。此时通过post提交数据就可以看到输入和输出如下:

其中要注意的是使用postman提交数据时json字符串要用双引号

此时我们能拿到用户注册的数据了

Json处理

我们用simplejson,比标准库好用一些,而且功能较强。安装用pip install simplejson;浏览器端提交的数据在请求的body中,要用到json解析,因此用更方便的simplejson,将上面代码的json替换为simplejson就可以了。

因为HttpResponse的错误类型都不是exception类,因此都不能使用raise无参构建,应该使用return,这些类都继承自HttpResponse,只有一个Http404是继承自Exception。前端可以通过状态码来判断是否成功

将上面的代码增加邮箱检查,用户信息保存,就要用到Django的模型操作。只需要将之前在user下的models中写的代码导入即可,如from .models import User,这样就可以进行注册进数据库了:代码如下

from django.http import JsonResponse,HttpRequest,HttpResponse,HttpResponseBadRequest
from .models import User
import simplejson
import loggingformt = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=formt,level=logging.INFO)def reg(request:HttpRequest):try:playload = simplejson.loads(request.body)  # 请求有问题时email = playload['email']name = playload['name']password = playload['password']query = User.objects.filter(email=email)print(query)if query:return HttpResponseBadRequest()name = playload['name']password = playload['password']user = User()user.email = emailuser.name = nameuser.password = passwordtry:user.save()  # 将注册数据存进数据库return JsonResponse({'user':user.id})except:raise# return HttpResponse("hey welcome")except Exception as e:logging.info(e)return HttpResponseBadRequest()  # HttpResponseBadRequest不是except子类,不能用raise无参构造

先介绍一下模型操作;
管理器对象
Django会为模型类提供一个objects对象,他是django.db.models.manager.Manager类型,用于数据库交互,当定义模型类的时候没有指定管理器,则Django会为模型类提供一个object的管理器。如果在模型类中指定管理器后,Django不再会提供默认的objects管理器了。这是Django的模型进行数据库查询查询操作的接口,Django应用的每个模型都至少有一个管理器。

Django ORM
数据库的校验validation是在对象的Save和Update方法上。
对模型对象的CRUD,被DjangoORM转换成相应的SQL语句操作不同的数据源。

查询

查询集:
查询会返回结果的集,他是django.db.models.query.QuerySet类型。
惰性求值,即创建查询集不会带来任何数据库的访问,直到调用数据时才会访问数据库,在迭代、序列化、if语句中都会立即求值,和sqlalchemy一样。返回的是可迭代对象。
缓存,每个查询集都包含一个缓存来最小化对数据库的访问。新建查询集缓存为空,首次对查询集求值时,会发生数据库查询,Django会把查询的结果存在这个缓存中,并返回请求的结构,接下来对查询集求值将使用缓存结构。

看栗子:

[user.name for user in User.objects.all()]
[user.name for user in User.objects.all()]  # 这两个没有使用缓存,查询两次库
\~~~~~~~~~~~~~~~~~~~~~~~~~~
qs = User.objects.all()
[user.name for user in qs]  # 使用了缓存,因为使用了同一个查询集

限制查询集(类似于python的切片)
查询集可以直接使用索引下标的方式,相当于SQL语句中的elimit和offset子句。
例如:

qs = User.objects.all()[20:40]  # limit 20 offset 20
qs = User.objects.all()[20:30]  # limit 10 offset 20

过滤器

名称 说明
all() 全选
filter() 过滤,返回满足条件的数据
exclude() 排除,排除满足条件的数据
order_by() 排序
values() 返回一个对象字典的列表,像json

filter(k1=v1).filter(k2=v2)等价于filter(k1=v1,k2=v2)
filter(pk=10)pk就是主键,不用关系主键字段名,也可以使用主键名来做条件。

返回单值得方法

名称 说明
get() 仅返回单个满足条件的对象,如果未能返回对象则抛出DoserNotExist异常;如果能返回多条,抛出MultipleObjectsReturned异常
count() 返回当前查询的总条数
first() 返回第一个对象,limit 1
last() 返回最后一个对象
exist() 判断查询集中是否有数据,如果有则返回True

字段查询表达式(Field lookup)
字段查询表达式可以作为filter(),exclude(),get()的参数,实现where子句。
语法:属性(字段名)名称__比较运算符=值,注意这里是双下划綫(一般来说数据库不存在连续的两个下划线命名的字段。)

名称 栗子 说明
exact filter(isdeleted=false), filter(isdeleted__exact=false) 严格等于,可省略不写
contains exclude(title__contains=‘地’) 是否包含,大小写敏感,等价于like “%地%”
startswith,endswith 以什么开头或结尾
isnull,isnotnull 是否为null
iexact, icontains, istartswith, iendswith i的意思是忽略大小写
in filter(pk__in=[1,2,3,4]) 是否在指定范围中
gt,gte,lt,lte 大于、大于等于,小于,小于等于
year、month、day、week_day、hour、minute、second filter(pub_date__year=200) 对日期类型属性属性处理

Q对象
虽然Django提供传入条件的方式,但是不方便,因此提供了Q对象来解决。
Q对象是django.db.models.Q,可以使用&(and)、|(or)操作符来组成逻辑表达式,~表示not。
可使用&|和Q对象来构造复杂的逻辑表达式
过滤器函数可以使用一个或多个Q对象,如果混用关键字参数和Q对象,那么Q对象必修位于关键字参数的前面,所有参数都将and在一起。

看栗子:

User.objects.filter(pk__gt=6).filter(pk__lt=10) 与

其他的一样,过于简单不多说。

注册接口完善

认证

HTTP协议是无状态协议,为了解决无状态产生的问题,出现了cookie和session技术。

传统的session-cookie机制
浏览器发起第一次请求到服务器,服务器发现浏览器没有提供session id,就认为是第一次请求,会返回一个新的session id给浏览器端。浏览器只要不关闭,这个session id就会随着每一次请求重新发给服务器端,服务器端查找这个session id,如果查到,就认为是同一个会话。如果没有查到,就认为是新的请求。
session是会话级别的,可以在这个会话session中创建很多数据,连接断开,session清除,包括session id。这个session id还需要有过期机制,一段时间如果没有发起请求,认为用户已经断开,就清除session。浏览器端也会清除响应的cookie信息。
服务器端保存着大量的session信息,很消耗服务器内存,而且如果多服务器部署,还要考虑session共享的问题,比如redis,memcached等方案。

无session方案
既然服务器只是需要一个ID来表示身份,那么不使用session也可以创建一个ID返回给客户端。但是,要保证客户端无法篡改(耗时长,无法实现算法等)。
服务器生成一个标识,并使用某种算法对标识签名。服务器端收到客户端发来的标识,需要检查签名。缺点就是,加密解密都需要cpu计算,无法让浏览器自己主动检查过期的数据以清除。可以添加时间戳。
这种技术简称JWT(Json WEB Token)

JWT
是一种采用Json方式安装传输信息的方式。我们使用PyJWT。安装pip install pyjwt,地址https://pypi.org/project/PyJWT/1.5.3/,在单点登录应用广泛。

jwt原理
先看一段代码,以及输出结果,就能了解JWT原理,当然你也可以通过看源码分析原理。

import jwt
from blog.settings import SECRET_KEY
password = SECRET_KEY
# encode(self,
#                payload,  # type: Union[Dict, bytes]
#                key,  # type: str
#                algorithm='HS256',  # type: str
#                headers=None,  # type: Optional[Dict]
#                json_encoder=None  # type: Optional[Callable]
#                ):
payload = {'name':'1234', 'test':'456789'}
pwd = jwt.encode(payload, password)
print(1,pwd)
header, pld, siq = pwd.split(b'.')
import base64
def fix(src):rem = len(src) % 4return src + b'=' * rem
print(2,base64.urlsafe_b64decode(fix(header)))
print(3,base64.urlsafe_b64decode(fix(pld)))
print(4,base64.urlsafe_b64decode(fix(siq)))from jwt.algorithms import get_default_algorithms
al_obj = get_default_algorithms()['HS256']
print(5,al_obj)
newkey = al_obj.prepare_key(password)
print(6,newkey)sig_input,_,_ = pwd.rpartition(b'.')
print(7,sig_input)
cryp = al_obj.sign(sig_input, newkey)
print(8,cryp)
print(9,siq)

输出

1 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiMTIzNCIsInRlc3QiOiI0NTY3ODkifQ.SHXiW2ZimmVwdtbiHtu-dEeELQ8ZQb_YrLE7GAQ5cGE'
2 b'{"typ":"JWT","alg":"HS256"}'
3 b'{"name":"1234","test":"456789"}'
4 b'Hu\xe2[fb\x9aepv\xd6\xe2\x1e\xdb\xbetG\x84-\x0f\x19A\xbf\xd8\xac\xb1;\x18\x049pa'
5 <jwt.algorithms.HMACAlgorithm object at 0x00000187A3DAE588>
6 b'hr18c2t(0@=f!a3jr=!2a6!=mk7o9o3f9a^d#l^4o#)25&(wlp'
7 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiMTIzNCIsInRlc3QiOiI0NTY3ODkifQ'
8 b'Hu\xe2[fb\x9aepv\xd6\xe2\x1e\xdb\xbetG\x84-\x0f\x19A\xbf\xd8\xac\xb1;\x18\x049pa'
9 b'SHXiW2ZimmVwdtbiHtu-dEeELQ8ZQb_YrLE7GAQ5cGE'

由上可知jwt生成的token分成三部分
1、header,由数据类型、加密算法构成
2、payload,需要传输的数据,一般来说放入python对象即可,会被序列化
3、signature,签名部分。是前面2部分数据分别base64编码后使用点号连接后,加密算法使用key计算好一个结果,再被base64编码,得到签名。
由此,所有的数据都是明文传输,只是做了base64,如果是敏感信息,就不要使用jwt。数据签名的目的不是为了隐藏数据,而是保证数据不被篡改,如果数据篡改了,发回到服务器,服务器用key再计算一遍,肯定无法匹配。

密码

使用邮箱加密码登录。
邮箱要求唯一是可以的,密码如何储存?

早期是明文存储,之后使用MD5,但是由于现在计算机计算能力强,容易被暴力破解,使用反查的方式找到密码。在之后就是加盐,使用hash(password+salt)的结果存入到数据库中,就算拿到数据库的密码反查,也没用。而且要随机加盐,增加破解难度。

密码等容易遭受暴力破解如穷举,因此我们用慢hash算法,如bcrypt,会让每一次计算都很慢,达到秒级,这样穷举时间长,以当前的计算机运行能力都需要很久。

bcrypt

安装 pip install bcrypt
注意:如果在python的terminal里一直安装不上,显示超时time out,或者无法连接,则先runserver一次即可安装了,应该是在项目根目录下某些东西尚未加载导致的。
看示例代码:

import bcrypt
import datetime
from blog.settings import SECRET_KEYpassword = SECRET_KEY
print(1,bcrypt.gensalt())
print(2,bcrypt.gensalt())
salt = bcrypt.gensalt()
tx = bcrypt.hashpw(password.encode(),salt)
print(3,tx)
start = datetime.datetime.now()
tx = bcrypt.hashpw(password.encode(),salt)
end = datetime.datetime.now()
print(4,tx)
print((end - start).total_seconds())

输出

1 b'$2b$12$2ia5yzV1/1DhI3CNRMxlj.'
2 b'$2b$12$X/vOVCGNh5YmB6Au6Z7ATu'
3 b'$2b$12$3wbS/EwNBXhBJyQObBQYBuIe0XnbXhOBX3KhQvo0PtYWliqapZuU.'
4 b'$2b$12$3wbS/EwNBXhBJyQObBQYBuIe0XnbXhOBX3KhQvo0PtYWliqapZuU.'
0.238363

由此可以看出,相同的盐,前后加密结果一样,不同的盐前后不同,时间在0.238363秒左右,因此穷举很费时间。我们先采用这种简单的方式进行密码锁。

因此修改之前的注册代码

from django.http import JsonResponse,HttpRequest,HttpResponse,HttpResponseBadRequest
from .models import User
import bcrypt
import jwt
import simplejson
import logging
import datetime
from django.conf import settingsformt = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=formt,level=logging.INFO)def gen_token(user_id):return jwt.encode({'user_id':user_id,  # 增加时间戳 判断是否重发'timestamp': int(datetime.datetime.now().timestamp())  # 时间取整}, settings.SECRET_KEY, 'HS256').decode()  # 转换为字符串def userindex(request):return HttpResponse('hey good job')def checkmail():return HttpResponse()def reg(request:HttpRequest):# print(1,request.GET)# print(2,request.method)# print(3,request.POST)# print(4,request.path)# print(5,request.headers)try:playload = simplejson.loads(request.body)  # 请求有问题时email = playload['email']name = playload['name']password = playload['password']query = User.objects.filter(email=email)print(query)if query:return HttpResponseBadRequest()name = playload['name']password = bcrypt.hashpw(playload['password'].encode(), bcrypt.gensalt())user = User()user.email = emailuser.name = nameuser.password = passwordtry:user.save()return JsonResponse({'token':gen_token(user.id)})except:raise# return HttpResponse("hey welcome")except Exception as e:logging.info(e)return HttpResponseBadRequest()  # HttpResponseBadRequest不是except子类,不能用raise无参构造

使用postman收到了发来的token

{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ0aW1lc3RhbXAiOjE1NjkyNDIzMjN9.c7EGVl4QgAbESXNZkGc-P1KWf1Mj7RrUc1uq998Ub0Y"
}

接下来解决用户登录接口的设计
接受用户通过post方法提交的登录信息,提交的数据是JSON格式。
从user表中的email找出匹配的一条记录,验证密码是否正确。
URL:/user/login
METHOD:POST

首先配置路由:
在user的urls的urlpatterns下写入path(‘login’,userlog),并从users.views import userlog
在views中写入以下代码。

def userlog(request):try:playload = simplejson.loads(request.body)email = playload['email']password = playload['password']user = User.objects.filter(email=email)user = user.first()  # 取单值if not user:return HttpResponseBadRequest()if not bcrypt.checkpw(password.encode(),user.password.encode()):  # 取出的为str类型return HttpResponseBadRequest()return JsonResponse({'user':{'user_id':user.id,'name':user.name,'password':user.password},'token':gen_token(user.id)})except Exception as e:logging.info(e)print('~~~~~~~~~')return HttpResponseBadRequest()  # HttpResponseBadRequest不是except子类,不能用raise无参构造

这里要注意,如果你往mysql数据库中存的是bytes,取出的则是str(bytes),encode一下就多了b’content’字符,会出现 Thread-1 8740 Invalid salt这种报错。因此存入数据库一定要转为str即decode()。登录成功返回json

{"user": {"user_id": 7,"name": "sesin","password": "$2b$12$vV/b1tFhCyZLRRc.YuuOfO8/a3oR38BFCWlf5DA04MfFb/FLg2XMK"},"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo3LCJ0aW1lc3RhbXAiOjE1NjkyOTIzNjF9.iVd194IsxFxYYz5iXiZ75s9ZTgRzywOZAzxjg55z9kk"
}

这里可以设置cookie,只需要jsonobj.set_cookie(‘jwt’,token)即可。

认证接口

接下来考虑如何获取浏览器提交的token信息。有以下两种
1、使用Header中的Authorization
通过这个给header增加token信息,通过header发送数据,方法可以是post,get。
2、自定义header,使用JWT来发送token,我们选择第二种方式

认证

基本上所有业务都需要认证用户的信息。
这里比较时间戳,如果过期就直接抛未认证401,客户端收到后就跳转到登录页。如果没有提交user id,就直接重新登录。如果用户查到了,填充user对象。

request-》时间戳比较-》user id 比较-》向后执行

在django.contrib.auth中也提供了许多方法,这里主要介绍其中的三个:
1、authenticate(**credentials)
提供了用户认证,即验证用户名以及密码是否正确,使用user=authenticate(username=’’,password=’’)
2、login(HttpRequest,user,backend=None)
该函数接受一个HttpRequest对象,以及一个认证了的User对象
此函数使用django的session框架给某个已认证的用户附加上session等信息。
3、logout(request)
注销用户,该函数接受一个httpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除,该用户即使没有登录,使用该函数也不会报错。
还提供了一个装饰器来判断是否登录django.contrib.auth.decorators.login_required本项目使用了无session版本,且用户信息自己建表管理,所以我们自己实现认证代码。

可以采用中间件技术自定义认证中间件,官方定义在Django的request和response处理过程中,由框架提供的hook钩子。但是这种方法会拦截所有的请求和响应,我需要判断是不是需要拦截的view函数,所以,还有一个方法,装饰器。

采用装饰器

在需要认证的view函数上增强认证功能,写一个装饰器函数。谁需要认证,就在这个view函数上应用这个装饰器。这样可以实现认证分离,并且简单。

看这一部分的代码:

AUTH_EXPIRE = 8*60*60def authenticate(view):def wrapper(request:HttpRequest):payload = request.META.get('HTTP_JWT')  # 通过 Meta 中含有所有post过来的参数,如果在header中含有JWT,则名字为HTTP_JWT,if not payload:return HttpResponse(status=401)try:payload = jwt.decode(payload,settings.SECRET_KEY, algorithms=['HS256'])print(payload)except:return HttpResponse(status=401)# 验证过期current = datetime.datetime.now().timestamp()if (current - payload.get('timestamp', 0))>AUTH_EXPIRE:return HttpResponse(status=401)try:user_id = payload.get('user_id',-1)user = User.objects.filter(pk=user_id).get()request.user = userexcept Exception as e:logging.info(e)return HttpResponse(status=401)ret = view(request)return retreturn wrapper@authenticate
def test(request):return HttpResponse('JWT test')

当然别忘了在user/urls/下加入path路径,并导入test,这样我们就实现了认证的接口,其中包括时间过期等,验证等。通过postman中的header写入token完成测试
收到一个JWT test,
如果在token中加入了exp,则jwt.decode(payload,settings.SECRET_KEY, algorithms=['HS256'])会自动验证时间,不需要后面的代码了。exp写的是时间过期点,必须取整。
因此修改的gen_token如下:

def gen_token(user_id):return jwt.encode({'user_id':user_id,  # 增加时间戳 判断是否重发'exp': int(datetime.datetime.now().timestamp() + AUTH_EXPIRE)  # 时间取整,加入}, settings.SECRET_KEY, 'HS256').decode()  # 转换为字符串

之后我们来做博文接口:

博文接口

博文功能如下:

功能 函数 request方法 路径
发布(增) pub post /pub
看文章(查) get get /(\d+)
列表(分页) getall get /

首先创建博文应用python manage.py startapp post,然后将post注册进settings.py中,然后才可以迁移。
先做一些准备工作,将上面的路径配置好,在post文件夹中添加urls.py文件,在~\blog\urls.py中添加 path(‘post/’, include(‘post.urls’)),在post/views中添加

from django.urls import path
from .views import pub,get,getallurlpatterns = [path('', getall),path('pub', pub),path('<int:id>',get),
]

定义views的函数:

from django.http import HttpResponse,HttpRequest,HttpResponseBadRequest,HttpResponseNotFounddef pub(request:HttpRequest):return HttpResponse('pub')def get(request:HttpRequest, id=0):return HttpResponse('get')def getall(request:HttpRequest):return HttpResponse('getall')

即可。这里注意(2.0版本的django语法变化,1,11直接使用正则表达式即可):要从URL捕获值,需要使用尖括号。
捕获的值可以选择包括转换器类型。例如,用于 <int:name>捕获整数参数。如果不包括转换器/,则匹配除字符以外的任何字符串。无需添加斜杠。嫌麻烦则可以使用re_path支持正则表达式即可。

模型:

先将模型写好。很简单就是将之前的数据库模型写好即可。看代码:

from user.models import Userclass Post(models.Model):class Meta:db_table = 'post'id = models.AutoField(primary_key=True)title = models.CharField(max_length=200, null=False)pubdate = models.DateTimeField(null=False)# author_id = models.IntegerField(null=False)  # sqlalchemy这样写但是这里不行author = models.ForeignKey(User, on_delete=None)  # 指定外键,migrate会生成author_id字段# self.content可以访问Content实例,其内容是self.content.contentdef __repr__(self):return "post {} {} {}".format(self.id, self.title, self.author)class Content(models.Model):class Meta:db_table = 'content'post = models.OneToOneField(Post, None, to_field='id')  # 由下面可以看到这里字典创建了一个id主键。一对一,外键引用post_idcontent = models.TextField(null=False)def __repr__(self):return "post {} {}".format(self.id, self.content[:20])

使用migrate创建完成后查看数据库信息。

CREATE TABLE `post` (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(200) NOT NULL,`pubdate` datetime(6) NOT NULL,`author_id` int(11) NOT NULL,PRIMARY KEY (`id`),KEY `post_author_id_2343ddae_fk_user_id` (`author_id`),CONSTRAINT `post_author_id_2343ddae_fk_user_id` FOREIGN KEY (`author_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `content` (`id` int(11) NOT NULL AUTO_INCREMENT,`content` longtext NOT NULL,`post_id` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `post_id` (`post_id`),CONSTRAINT `content_post_id_c6de7cfd_fk_post_id` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

可以看到没有啥问题,这里一定要检查一下,不然万一和你预想的不同,就需要及时更改。

pub接口实现

用户从浏览器提交Json数据,包含title,content。提交博文需要认证用户,从请求的header中验证jwt。
即post->@authenticate pub ->return post_id
看代码:

from django.http import HttpResponse,HttpRequest,HttpResponseBadRequest,HttpResponseNotFound,JsonResponse
from user.views import authenticate
from .models import Post,Content
from user.models import User
import datetime
import simplejson@authenticate
def pub(request:HttpRequest):post = Post()content = Content()try:payload = simplejson.loads(request.body)post.title = payload['title']post.author = User(id=request.user.id)  # 从request中取得user_id 并注入到post.author中,后面有两个空字符串,即User类的name和email。但我们只需要id就可以了post.pubdate = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))  # 这里必须包含时区post.save()content.content = payload['content']content.post = postcontent.save()return JsonResponse({'post_id':post.id})except Exception as e:print(e)return HttpResponseBadRequest()

通过postman使用post登录后,收取到的token放在post/pub下的header里,并且在body中写入title和content。点击发送即可收到,post_id了,注意:在pubdate中必须包含时区不然会出现G:\blog-test\venv\lib\site-packages\django\db\models\fields\__init__.py:1423: RuntimeWarning: DateTimeField Post.pubdate received a naive datet错误。但是文章依旧可以存储成功。

get接口实现

我们根据post_id查询博文并返回,查看博文并不需要认证。但是修改博文需要认证。同样list接口也不需要认证。

request get -> get Post by id ->return post+contentdef get(request:HttpRequest, id):  # 接收url中发来的idtry:id = int(id)post = Post.objects.get(pk=id)  # 取得单值if post:return JsonResponse({'post':{'post_id':post.id,'title':post.title,'author':post.author.name,'author_id':post.author.id,'postdate':post.pubdate.timestamp(),'content':post.content.content,}})except Exception as e:print(e)return HttpResponseBadRequest()

在url中输入http://127.0.0.1:8000/post/2,使用get方法即可收到return的带有文章详细信息的Json了。

getall接口实现

简单的说一下这个接口,发起get请求,通过查询字符串查询第几页数据。
即request: get -> get all (page=1) -> return post list
当然这里要注意分页信息。一般有,当前页page,行数限制size,总页数pages,记录总数count,因为page 和size的处理基本一致,所以为了更加方便的使用分页信息,这里写一个页码处理函数,方便以后修改。

def validate(d:dict, name:str, type_func, default, validate_func):try:result = type_func(d.get(name,default))result = validate_func(result, default)except:result = defaultreturn resultdef getall(request:HttpRequest):page = validate(request.GET,'page',int,1,lambda x,y: x if x>0 else y)size = validate(request.GET,'size',int,20,lambda x,y: x if x>0 and x < 101 else y)try:  # 按id倒排start = (page -1)*sizeposts = Post.objects.order_by('-id')count = posts.count()posts = posts[start:start+size]return JsonResponse({'posts':[{'post_id':post.id,'title':post.title} for post in posts],'pagination':{'page':page,'size':size,'count':count,'pages':math.ceil(count/size)}})except Exception as e:print(e)return HttpResponseBadRequest()

以上是博客后端的内容。

django2.2 简单博客 一相关推荐

  1. Python Flask框架-开发简单博客-认证蓝图

    作者:Eason_LYC 悲观者预言失败,十言九中. 乐观者创造奇迹,一次即可. 一个人的价值,在于他所拥有的.可以不学无术,但不能一无所有! 技术领域:WEB安全.网络攻防 关注WEB安全.网络攻防 ...

  2. Django实现简单博客系统

    Django实现简单博客系统 第一节 - 基础 1. 简单的导览图,学会不迷路 2. 基本操作介绍 3. 命令简单介绍 4. mysite:所建项目的管理功能目录 5. blog:我们创建的项目之一 ...

  3. php开发博客系统源码,php简单博客系统

    [实例简介] php简单博客系统,是基于php+mysql组合的简单系统,下来看看吧 [实例截图] [核心代码] webstar ├── web_star │   ├── code.php │   ├ ...

  4. 【Rust日报】2022-09-14 使用 Rust 构建简单博客 华为实习生招募

    使用 Rust 构建简单博客 作者以写 Go 为主,他认为学习任何语言都可以从一个 web 程序入手,那么事情就会变得明朗,本文展示的是作者在学习了一段时间 Rust 后开始搭建简单博客系统的过程,很 ...

  5. Python Flask框架-开发简单博客-项目布局、应用设置

    作者:Eason_LYC 悲观者预言失败,十言九中. 乐观者创造奇迹,一次即可. 一个人的价值,只在于他所拥有的.所以可以不学无术,但不能一无所有! 技术领域:WEB安全.网络攻防 关注WEB安全.网 ...

  6. express+node+mysql简单博客系统(一):登录接口

    今年一直想学一下node,现在马上就到年底了,赶紧安排! 准备 使用node.express和mysql开发简单的博客系统: 1.先安装node.express和mysql: 2.创建node项目,也 ...

  7. Node.js 博客实例(一)简单博客

    原教程 https://github.com/nswbmw/N-blog/wiki/_pages的第一章.因为版本号等的原因,在原教程基础上稍加修改就可以实现. 环境: win7旗舰版64位 Node ...

  8. Java项目:Springboot实现的一个简单博客管理系统

    作者主页:夜未央5788 简介:Java领域优质创作者.Java项目.学习资料.技术互助 文末获取源码 项目介绍 本项目为前后台管理系统,包括博主与游客两种角色: 博主角色包含以下功能: 博主登录,发 ...

  9. Django简单博客实战(六)---搜索功能

    Django-haystack插件实现 项目地址:https://github.com/ylpxzx/lifeblog 步骤 安装依赖包 pip install whoosh,jieba,django ...

最新文章

  1. mac os x 添加 用户 所属 组
  2. linux-linux top 命令各参数详解
  3. ecplise tomcat启动报错
  4. Boost:自定义小矢量的测试程序
  5. 【STM32】程序下载(ST-LINK V2)
  6. hbase shell远程连接_hbase与phoenix集成
  7. Github|类别不平衡学习资源(上)
  8. Linux-Ubuntu部署Springboot项目应用到生产环境(jar方式)
  9. AMD: Developer Guides, Manuals ISA Documents
  10. 轻薄于型 强悍于内 拯救者9000X 2021硬核发布
  11. k8s-kubectl进程源码分析
  12. 中科大和东北大学计算机考研,我国39所985高校,一共被分为五个档次,复旦大学处于第二档...
  13. K3 官改新手小白配置阿里DDNS 超级详细
  14. Windows环境下32位汇编语言程序设计(典藏版)(含CD光盘1张)
  15. MVC、MVP、MVI、MVVM 和 VIPER 设计模式
  16. javascript运算符_双重否定运算符是什么! 用JavaScript做?
  17. 三角形的内切圆和外接圆--【英雄会】
  18. Jackson之JSON序列化和多态反序列化
  19. 打开微信另存的 jpg 图片时,提示“Windows 照片查看器无法显示此图片,因为计算机上的可用内存可能不足”
  20. 淘宝店铺上新图片上传获取请求方法

热门文章

  1. android https cer证书转换BKS
  2. python中读取和查看图片的6种方法
  3. 【红外遥控器】基于FPGA的学习型红外遥控器verilog开发
  4. Dell PowerEdge R740xd解析:服务器只看参数那就错了
  5. office表格标题和表格距离过大怎么解决
  6. 2018年的第一篇文章(福利篇)
  7. 利用ps制作油画风格的照片
  8. css3缓慢出现,让CSS3旋转开始缓慢然后结束缓慢?
  9. 聊聊前端框架——尤雨溪
  10. ElementUI之el-pagination样式修改(小三角、“前往”、页码数)