Django
新建
支持Python的版本–3.5,3.6,3.7,3.8·在线安装
虚拟机执行sudo pip3 install django==3.2.12 检查是否成功sudo pip3 freezelgrep -i ‘Django’
1 | tarena@tedu :~$ sudo pip3 freeze|grep -i 'Django ' |
有该输出代表成功
·离线安装–官网下载离线安装包
·将安装包复制到虚拟机
·解压缩tar -xvf Django-2.2.12.tar.gz·进入目录cd Django-2.2.12
·执行安装sudo python3 setup.py install
·检查是否成功sudo pip3 freeze|grep -i ‘Django’
启动服务
启动[测试开发阶段]
- 1.终端cd进入到项目文件夹,例如cd mysite1
- 2.进入到项目文件夹后,执行python3 manage.py runserver 端口启动django服务【注:该启动方式下,Django在前台启动服务,默认监听8000端口】
- 3.浏览器访问http://127.0.0.1:8000可看到django的启动页面注:【如果想更换端口,则可以用python.3 manage.py runserver端口号】
·关闭服务
方式1:在runserver启动终端下执行Ctrl +c可关闭Django服务·
方式2:在其他终端下执行sudo lsof -i:8000查询出Django的进程id执行kill -9对应Django进程id
使用python manage.py查看可以启动的服务
settings.py包含了Django项目启动的所有配置项·配置项分为公有配置和自定义配置
·配置项格式例:(BASE_DIR’xxx‘)变量名一定要大写
公有配置- jango官方提供的基础配置
文件配置
settings文件
1 | BASE_DIR#项目的路径 |
BASE_DIR
用于绑定当前项目的绝对路径(动态计算出来的)这个路径是…/项目文件名,所有文件
夹都可以依懒此路径
DEBUG
用于配置Django项目的启动模式,取值:
True表示开发环境中使用开发调试模式(用于开发中). False表示当前项目运行在生产环境中
- INSTALLED_APPS -指定当前项目中安装的应用列表
- MIDDLEWARE -用于注册中间件
- TEMPLATES -用于指定模板的配置信息
- DATABASES -用于指定数据库的配置信息
- LANGUAGE_CODE-用于指定语言配置
ALLOWED HOSTS
示例:如果要在局域网其它主机也能访问此主机的Django服务,启动方式如下:
python3 manage.py runserver
0.0.0.0:5000
指定网络设备如果内网环境下其他主机想正常访问该站点,
需加ALLOWED_HOSTS =['内网ip]
ROOT_URLCONF -用于配置主url配置’mysite1.urls’.
设置中文(在settings文件中)
1 | from django.utils.translation import gettext_lazy as _ |
LANGUAGE_CODE = “zh-Hans”
URL文件(Uniform Resource Locator统一资源定位符)
1 | from django.contrib import admin |
url分配地址
path()函数
- 导入- from django.urls.import path
- 语法- path(route, views, name=None)
参数:
- route:字符串类型,匹配的请求路径
- views:指定路径所对应的视图处理函数的名称
- name:为地址起别名,在模板地址反向解析时使用
1 | from django.http import HttpResponse |
path转换器
语法:<转换器类型:自定义名>
作用:若转换器类型匹配到对应类型的数据,则将数据按照关键字传参的方式传递给视图函数
例子:path(‘page/<int:page>’, views.xxx)
转化器类型 | 作用 | 案例 | 可匹配 |
---|---|---|---|
str | 匹配除了’/'之外的非空字符串 | page/<str:user> | page/Bloss |
int | 匹配0或任何整数,返回int型 | page/<int:num> | page/10 |
slug | 匹配任意由ASCII字母或数字以及连字符和下划线组成的短标签 | page/<slug:sl> | page/im-am |
path | 匹配任意由ASCII字母或数字以及连字符和下划线组成的短标签 | page/<path:ph> | page/1/v/ |
在url文件中:
1 | path('page/<int:num1>/<str:way>/<int:num2>', views.page_num), |
在views文件中
1 | def page_num(request, num1, way, num2): |
三个参数传入view文件中的page_num函数
re_path()函数
在url的匹配过程中可以使用正则表达式进行精确匹配
语法:
re_path(reg, view, name=xxx)
正则表达式为命名分组模式(?P
1 | from django.urls import path, re_path |
请求及相应
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
2 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
Django中的请求
path_info: URL字符串
method:字符串,表示HTTP请求方法,常用值:‘GET’、“POST”
GET: QueryDict查询字典的对象,包含get请求方式的所有数据
POST: QueryDict查询字典的对象,包含post请求方式的所有数据
FILES:类似于字典的对象,包含所有的上传文件信息
在views文件中
1 | def methond(request): |
例如GET方法,当浏览器中传入参数时www.***.com/methond?a=8
1 | >>> |
COOKIES: Python字典,包含所有的cookie,键和值都为字符串
session:似于字典的对象,表示当前的会话
body:字符串,请求体的内容(POST或PUT)
scheme:请求协议(‘http’/‘https’)
request.get_full_path():请求的完整路径
request.META:请求中的元数据(消息头)
-
request.META[‘REMOTE ADDR’]:客户端IP地址
-
200请求成功
-
301永久重定向-资源(网页等)被永久转移到其它URL-
-
302 临时重定向
-
404-请求的资源(网页等)不存在-
-
500内部服务器错误
构造函数格式:
HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)
作用:
向客户端浏览器返回响应,同时携带响应体内容
-
'text/html’(默认的,html文件)
-
'text/plain’(纯文本)
-
‘text/css’ (css文件)
-
‘text/javascript’ (js文件)
-
'multipart/form-data’(文件提交)
-
‘application/json’ (json传输)- ‘application/xml’ (xml文件)数据类型, status=状态码)
-
类型 作用 状态码 HttpResponseRedirect 重定向 302 HttpResponseNotModified 未修改 304 HttpResponseBadRequest 错误请求 400 HttpResponseNotFound 无对应资源 404 HttpResponseForbidden 请求被禁止 403 HttpResponseServerError 服务器错误 500
1 | from django.http import HttpResponse,HttpResponseRedirect#使用的上面的表格内容 |
GET&POST
通常在views中先进行判断使用的哪个方法
1 | def re_get(request): |
在浏览器中输入www.***.com/methond?a=8,则在终端中会打印出1,如果没有参数传入则会报错
使用request.GET.get方法可以解决无参数传入不报错的情况,输出no
1 | def re_get(request): |
在又多个相同参数名传入不同参数时,使用GET.get.getlist()。应用场景时同一个键值传入多个数据
1 | def re_get(request): |
在浏览器中输入www.***.com/methond?a=8&a=50s
1 | >>['1', '50'] |
使用post接收传入的数据
1 | POST_FORM = """ |
CSRF请求终端,防止CSRF网络攻击。在setting中注释掉csrf
1 | -禁止掉 settings.py |
传统MVC代表 Model-View-Controller(模型-视图-控制器)模式。
√M模型层(Model),主要用于对数据库层的封装
√V视图层(View),用于向用户展示结果(WHAT+ HOW )
√C控制(Controller ,用于处理请求、获取数据、返回结果(重要)
作用:降低模块耦合度
django的MTV代表 Model-Template-View(模型-模板-视图)模式。
√ M模型层(Model)负责与数据库交互
√T模板层(Template)负责呈现内容到浏览器(HOW)
√V视图层(View)是核心,负责接收请求、获取数据、返回结果(WHAT)作用:降低模块间的耦合度(解耦)
模板
创建模板文件夹<项目名>/templates在settings.py 中TEMPLATES配置项
1.BACKEND:指定模板的引擎
- DIRS:模板的搜索目录(可以是一个或多个)
- APP_DIRS∶是否要在应用中的templates 文件夹中搜索模板文件
- OPTIONS:有关模板的选项
·配置项中需要修改部分
设置DIRS - 'DIRS": [os.path.join(BASE_DIR, ‘templates’)],
1 | 'DIRS": [os.path.join(BASE_DIR, 'templates')], |
templates创建在根目录下
方案1-通过 loader 获取模板,通过HttpResponse进行响应在视图函数中
1 | from django.template import loader |
方案2-使用render()直接加载并响应模板
在视图函数中:
1 | from django.shortcuts import render |
视图函数中可以将Python变量封装到字典中传递到模板
模板中,我们可以用的语法调用视图传进来的变量
1 | def get_template(request): |
在html中
1 | <div>{{uname}}</div> |
能传入的种类
- str -字符串
- int - 整型
- list -数组
- tuple -元组
- dict - 字典
- func -方法
- obj -类实例化的对象
在模板中使用变量语法
1 | {{变量名}} |
在views中
1 |
|
在对应html中
1 |
|
面向对象
1 | class FangJan: |
实例数据初始化
1 | class FangJan: |
传入参数
1 | class human: |
模板标签
if标签
1 | {%if 条件表达式1 %} |
if 标签注意:
- if 条件表达式里可以用的运算符==,!=,<, >,<=,>=,in, not
in, is,is not, not、and、or - 在if标记中使用实际括号是无效的语法。如果您需要它们指示
优先级,则应使用嵌套的if标记。
for标签
1 | {% for 变量 in 可迭代对象 %} |
https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#for
变量 | 描述 |
---|---|
forloop.counter | 循环的当前迭代(从1开始索引) |
forloop.counter0 | 循环的当前迭代(从0开始索引) |
forloop.revcounter | counter值的倒序 |
forloop.revcounter0 | revcounter值的倒序 |
forloop.first | 如果这是第一次通过循环,则为真 |
forloop.last | 如果这是最后一次循环,则为真 |
forloop.parentloop | 当嵌套循环,parentloop表示外层循环 |
1 | def for_test(request): |
在html中
1 |
|
在使用for循环,对字典中的数据进行操作时候,需要dic.键名
定义:在变量输出时对变量的值进行处理
作用:可以通过使用过滤器来改变变量的输出显示
1 | 语法:{{变量|过滤器1:参数值1'|过滤器2:参数值2'…..}}. |
过滤器 | 说明 |
---|---|
lower | 将字符串转换为全部小写 |
upper | 将字符串转换为大写形式 |
safe | 默认不对变量内的字符串进行html转义 |
add:“n” | 将value的值增加n |
truncatechars:“n” | 如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列("…")结尾。 |
在html代码中
1 |
|
在python中传入和前端有关的代码会自动过滤掉敏感字符例如:
1 | dic['script'] = "<script>alert('warning')</script>" |
这个正常传入就不会被html执行,如果加上过滤器|safe表示这段代码是安全的,可以被执行。
模板的继承
模板继承可以使父模板的内容重用,子模板直接继承父模板的全部内容并可以覆盖父模板中相应的块
语法–父模板中:
-
定义父模板中的块block标签
-
标识出哪些在子模块中是允许被修改的
-
block标签:在父模板中定义,可以在子模板中覆盖
-
语法–子模板中:
继承模板extends标签(写在模板文件的第一行)1
例如{% extends 'base.html' %}
-
子模板重写父模板中的内容块
1 | {% block block_name %} |
子模板块用来覆盖父模板中 block_name块的内容
1 | {% endblock block_name %} |
在视图文件中
1 | def main_page(request): |
上面定义了三个文件的视图返回模板
在html文件中
1 |
|
son1
1 | {% extends "main.html" %} <--开头继承主视图文件--> |
son2
1 | {% extends "main.html" %} <--开头继承主视图文件--> |
重写的覆盖规则
- 不重写,将按照父模板的效果显示
- 重写,则按照重写效果显示
注意
- 模板继承时,服务器端的动态内容无法继承
静态资源标签
模板中访问静态文件– img标签为例方案2
1 | 通过{% static %}标签访问静态文件 |
1 | 1.加载static - {% load static %} |
3.样例
1 | {% load static %} |
URL反向解析
1,绝对地址
整个路径(协议+IP:port+路径)
2,相对地址
/path/1,带/开头的为(协议+IP:port+/path/1)
path/1,没有/开头的(当前最后一个/前的URL+path/1)
url反向解析是指在视图或模板中,用path定义的名称来动态查找或计算出相应的路由
path函数的语法
- path(route, views,name=“别名”)
- path(‘page’, views.page_view, name=“page_url”)
根据path中的name=
关键字传参给url确定了个唯一确定的名字,在模板或视图中,可以通过这个名字反向推断出此url信息
1 | 在模板中使用{% url "反向的name" "path转换器的名字"%} |
在url文件中
1 | path('son2', views.son2,name='url2'), |
定义的这两个name表示在全部文件中都可以使用
在html文件中使用
1 |
|
在视图文件中使用
1 | from django.urls import reverse |
静态文件配置
用于存放css,js文件和font文件,image图像
静态文件配置- settings.py中
1,配置静态文件的访问路径【该配置默认存在】
- 通过哪个url地址找静态文件
- STATIC_URL = ‘/static/’
- 说明:
- 指定访问静态文件时是需要通过/static/xxx或http://127.0.0.1:8000/static/xxx
[xxx表示具体的静态资源位置]
再setting文件中
1 | STATIC_URL = '/static/' #静态资源请求的url |
还可以添加其他路径
应用
应用的概念再django中,是为了分开为多个MTV进行管理每个板块应用中有单独的路由,视图,模板,模型。应用之间的代码相互隔离。
创建应用
1 | python3 manage.py startapp 应用名 |
注册应用在setting中
1 | INSTALLED_APPS = [ |
删除应用,先删除应用中的models.py再删除整个目录树,取消setting中的注册
分布式路由
Django中,由路由配置文件(url.py)不进行处理用户具体路由的请求,路由配置文件的可以做请求的分发(分布式请求处理)。具体的请求可以由各自的应用的url.py来进行处理
步骤1–主路由中调用include函数
语法:include(‘app名字.url模块名’)
作用:用于将当前路由转到各个应用的路由配置文件的urlpatterns进行分布式处理
步骤2–应用下配置urls.py应用下手动创建urls.py文件内容结构向全路由完全一样
在主路由url中
1 | from django.urls import path, include |
1 | from django.urls import path |
应用内部可以配置模板目录
1.应用下手动创建templates文件夹
2.settings.py中开启应用模板功能
TEMPLATE配置项中的’APP_DIRS’值为True即可
应用下templates和外层templates都存在时,django得查找模板规则
- 1.优先查找外层templates目录下的模板
- 2.按INSTALLED_APPS配置下的应用顺序逐层查找
命名模板最好app_***.html或者在app目录的templates下面再添加一个和app同名的文件夹然后把html移入。
在views中
1 | def ***(request): |
ORM
windows系统数据库部署
https://www.cnblogs.com/zhangkanghui/p/9613844.html
在windows系统下安装数据库http://mirrors.sohu.com/mysql/MySQL-8.0/
下载好解压后放入python目录
配置环境变量
进入变量配置界面在path中加入路径
1 | E:\Python\mysql-8.0.26-winx64\bin |
使用管理员打开cmd
1 | mysqld --initialize-insecure --user=mysql |
在E:\python\mysql\mysql-8.0.12-winx64目录下生成data目录
安装mysql
1 | mysqld -install |
启动mysql
1 | net start MySQL |
登录mysql
登录MySQL
登录mysql:(因为之前没设置密码,所以密码为空,不用输入密码,直接回车即可)
1 | mysql -u root -p |
查询密码
1 | select host,user,authentication_string from mysql.user; |
Linux系统
安装mysql
1 | apt install mysql-server |
具体mysql查询命令查看https://www.runoob.com/mysql/mysql-create-database.html
配置mysql
1 | pip install mysqlclient |
在ubuntu中需要libmysqlclient-dev和python3-dev
1 | apt list --installed|grep -E 'libmysqlclient-dev|python3-dev' #判断是否有依赖包 |
创建数据库
ORM:
- 类=数据表
- 对象=数据行
- 属性=字段
进入mysql数据库执行
1 | create database 数据库名 default charset utf8 |
通常数据库名跟项目名保持一致
settings.py里进行数据库的配置
修改DATABASES配置项的内容,由sqlite3变为mysql
1 | DATABASES = { |
定义: ORM (Object Relational Mapping)即对象关系映射,是一种程序技术,它允许你使用类和对象对数据库进行操作,人避免通过SQL语句操作数据库
作用:
- 1.建立模型类和表之间的对应关系,允许我们通过面向对象的方式来操作数据库。
- 2.根据设计的模型类生成数据库中的表格。
- 3.通过简单的配置就可以进行数据库的切换。
优点:
-
只需要面向对象编程,不需要面向数据库编写代码。
-
对数据库的操作都转化成对类属性和方法的操作。
-
不用编写各种数据库的sql语句
-
实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异。
-
不在关注用的是mysql、oracle…等数据库的内部细节。
-
通过简单的配置就可以轻松更换数据库,而不需要修改代码
编写model
数据库迁移
迁移是Diango同步您对模型所做更改(添加字段,删除模型等)到您的数据库模式的方式
生成迁移文件–执行python3 manage.py makemigrations将应用下的models.py文件生成一个中间文件,并保存在migrations文件夹中
执行迁移脚本程序–执行python3 manage.py migrate执行迁移程序实现迁移。将每个应用下的migrations目录中的中间文件同步回数据库
1 | python manage.py makemigrations |
在迁移数据库的时候,如果出现问题删除,migrations中除了__init__.py的内容全部删除,再删除models文件中对应的拆创建的表,再删除数据库中django_session对应的记录数据
1 | drop databases * |
在app文件的models文件中
1 | from django.db import models |
defualt表示默认值,字段存入的时候不知道则走默认值
字段类型 | 数据库对应名字 | 注意 |
---|---|---|
models.BooleanField() | tinyint(1) | 编程中使用的是True或False数据库中对应的01 |
models.CharField() | varchar | 必须指定max_length参数 |
models.DateField() | date | auto_now:每次保存对象时,自动设置该字段为当前时间(取值:True/False)。auto_now_add:当对象第一次被创建时自动设置当前时间(取值:True/False)。default:设置当前时间(取值:字符串格式时间如: ‘2019-6-1’)。以上三个参数只能多选一 |
models.DateTimeField() | datetime(6) | 表示日期和时间,参数同DateField |
models.FloatField() | double | 都是用小数表示 |
models.DecimalField() | decimal(x,y) | max_digits,decimal_places |
models.EmailField() | varcahr | 都是字符串 |
models.intergerField() | int | 都是使用整数 |
models.imageField() | varchar(100) | 都使用字符串 |
models.TextField() | longtext | 标识不定长的字符数据 |
字段选项
字段选项,指定创建的列的额外的信息
允许出现多个字段选项,多个选项之间使用,隔开
- primary_key
如果设置为True,表示该列为主键,如果指定一个字段为主键,则此数库表不会创建id字段 - blank
设置为True时,字段可以为空。设置为False时,字段是必须填写 - null
如果设置为True,表示该列值允许为空。
默认为False,如果此选项为False建议加入default选项来设置默认值 - default
设置所在列的默认值,如果字段选项null=False建议添加此项 - unique=Ture唯一
null和default二选一
- db_index
如果设置为True,表示为该列增加索引unique
如果设置为True,表示该字段在数据库中的值必须是唯一(不能重复出现的) - db_column
指定列的名称,如果不指定的话则采用属性名作为列名 - verbose_name
设置此字段在admin界面上的显示名
1 | title = models.CharField('书名', max_length=100, default='',unique=True,null=True)#default默认值意思是如果不输入默认为多少,unique表示唯一不能重复,null可以允许为空。 |
整形的字段限制
validators=[最小,最大]
1 | from django.db import models |
Meta类
使用内部 Meta类来给模型赋予属性,Meta类下有很多内建的类属性,可对模型类做一些控制
1 | class book(models.Model): |
文件记录
文件记录在了mysql中的django_migrations字段
管理器对象
每个继承自models.Model的模型类,都会有一个objects对象被同样继承下来。这个对象叫管理器对象
创建数据方法
方法一:
1 | b1 = book.objects.create(title='python',price=50) |
方法二:
1 | b1 = Book(title="python",price=50) |
Django Shell
在Django提供了一个交互式的操作项目叫
Django Shell它能够在
交互模式用项目工程的代码执行相应的操作
利用Django Shell可以代替编写view的代码来进行直接操作注意:项目代码发生变化时,重新进入Django shell
在交互模式中传入数据需要导入
1 | from app.models improt book |
ORM查询
数据库的查询需要使用管理器对象进行
通过MyModel.objects管理器方法调用查询方法
all()方法
- 用法: MyModel.objects.all()
- 作用:查询MyModel实体中所有的数据等同于select * from tabel
- 返回值: QuerySet容器对象,内部存放MyModel 实例
values(“列1”,“列2”)
- 用法:MyModel.objects.values(…)
- 查询部分列返回元组数据,等同于select 列1,列2 from ***
values_list(“列1”,“列2”)
- 用法:MyModel.objects.values_list(…)
- 返回元组形式的查询结果,等同于select 列1,列2 from ***
order_by()
- 用法:MyModel.objects.order_by("-列",“列”)
- 作用:与all()方法不同,它会用SQL语句的ORDER BY子句对查询结果进行根据某个字段选择性的进行排序,-表示降序
1 | queryset对象的取出方法 |
使用django shell
1 | from book.models import book #导入book表单 |
如果不想循环遍历打印,在models文件中
1 | from django.db import models |
组合查询方法:
1 | b1 = Book.bojects.all().order_by() |
对应sql语句查询
1 | print(b1.query) |
在视图中接入数据库通过导入,在视图文件中
1 | from .models import 类名 |
在模板中
1 |
|
条件查询
filter()
- 语法: MyModel.objects.filter(属性1=值1,属性2=值2)
- 作用:返回包含此条件的全部的数据集
- 返回值:QuerySet容器对象,内部存放MyModel实例
- 说明:当多个属性在一起时为"与"关系
在视图文件下
1 | from .models import book |
exclude()
- 语法:MyModel.objects.exclude(属性1=值1,属性2=值2)
- 作用:返回不包含此条件的全部数据
get()
-
语法:MyModel.objects.get(属性1=值1,属性2=值2)
-
作用:返回满足条件的唯——条数据
-
说明:该方法只能返回一条数据
-
查询结果多余一条数据则抛出出,Model.MultipleObjectsReturned异常
-
查询结果如果没有数据则抛出Model.DoesNotExist异常
注意get和filter获取的数据类型不一样
count()
数据的数量
查询谓语词
- 定义:做更灵活的条件查询时需要使用查询谓词
- 说明:每一个查询谓词是一个独立的查询功能
__exact
等值匹配
用法:字段__exact,常用于查询为NULL的数据
1 | book.create.filter(id__exact=1) |
__contains
包含指定值
用法:字段__contains
1 | book.create.filter(id__contains="1")#查询字段包含1的数据select * from book name like "%w%" |
__startswith
以什么为开头
__endswith
以什么为结束
__gt
大于多少的
1 | book.objects.filter(book__gt=50) |
__gte
大于等于
__lt
小于
__lte
小于等于
__in
查找数据是否在指定范围内
1 | book.objects.filter(book__in=['python','C']) |
__range
查找数据是否在指定区间范围内
1 | book.objects.filter(book__page=(300,400)) |
增删改操作
获取数据,修改保存
1 | b = book.objects.get(id = 1) |
获取数据,删除数据
1 | b = book.objects.get(id = 1) |
伪删除
通常不会轻易在业务里把数据真正删掉,取而代之的是做伪删除,即在表中添加一个布尔型字段(is_active),默认是True;执行删除时,将欲删除数据的is_active字段置为False
注意:用伪删除时,确保显示数据的地方,均加了is_active=True的过滤查询。
F和Q对象
F对象
用于同时并发处理相同的一个数值,解决资源竞争
在视图文件中
1 | from django.db.models import F |
具体用法
1 | book.objects.filter(sale_price__gt=F('get_price'))#表示过滤当前售卖价格大于自身获取价格的书 |
Q对象
当在获取查询结果集使用复杂的逻辑或|逻辑非~等操作时可以借助于Q对象进行操作
在视图文件中
1 | from django.db.modles import Q |
聚合查询
聚合查询是指对一个数据表中的一个字段的数据进行部分或全部进行统计查询,查bookstore_book数据表中的全部书的平均价格,查询所有书的总个数等,都要使用聚合查询 。
在视图文件中
聚合函数:Sum求和,Avg求平均值,Count统计数量,Max最大值,Min最小值
整表聚合
1 | from django.db.models import * |
分组聚合
分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
语法:
- QuerySet.annotate(结果变量名=聚合函数(列’))返回值:
- QuerySet
先分组,再聚合
1 | b = Book.objects.values("price").annotate(num=Count('id'))#以price为分组,对id进行统计,意思是price一样的为一个字典 |
原生数据库操作
Django也可以支持直接用sql语句的方式通信数据库
查询:使用MyModel.objects.raw()进行数据库查询操作查询
语法: MyModel.objects.raw(sql语句,拼接参数)
返回值:RawQuerySet集合对象
1 | b = book.objects.raw("select * from book where id=%s,['1 or 1=1']") |
使用拼接,可以解决注入问题
cursor
完全跨过模型类操作数据库–查询/更新/删除
1.导入cursor所在的包
from django.db import connection
⒉.用创建cursor类的构造函数创建cursor对象,再使用cursor对象,为保证在出现异常时能释放cursor资源,通常使用with语句进行创建操作
1 | from django.db import connection |
内容修改
音乐名管理系统
路由,使用的app子url
在主路由中配置如下
1 | path('music/', include('music.urls')), |
在子路由urls文件配置如下:
1 | urlpatterns = [ |
在模型文件中:
1 | from django.db import models |
定义四个数据类型,id设置为了主键
在视图文件中
1 | def index(request,): #展示页面 |
在模板文件中:
展示页面
1 |
|
admin
django提供了比较完善的后台管理数据库的接口,可供开发过程中调用和测试使用
django会搜集所有已注册的模型类,为这些模型类提拱数据管理界面,供开发者使用
创建最高权限的账号
1 | python manage.py createsuperuser |
自定义模型类
若要自己定义的模型类也能在/admin后台管理界中显示和管理,需要将自己的类注册到后台管理界面
注册步骤:
1,在应用app中的admin.py中导入注册要管理的模型models类,如:from .models import Book
2,调用admin.site.register方法进行注册,如:admin.site.register(自定义模型类)
模型管理器类
作用:
为后台管理界面添加便于操作的新功能,显示上面的栏目。
说明:
后台管理器类须继承自django.contrib.admin里的ModelAdmin类
再对应的app/admin.py
1 | from django.contrib import admin |
控制后台修改链接
1 | list_display_links = ['title']#默认这个字段为可以修改的选项 |
过滤器
1 | list_filter = ['defaulname'] #以defaulname为过滤条件 |
搜索框
1 | search_fields = ['title']#以title为查询索引 |
添加在列表页编辑字段
1 | list_editable = ['name'] |
https://docs.djangoproject.com/en/2.2/ref/contrib/admin/
在admin文件中
1 | from django.contrib import admin |
在模型文件中
1 | from django.db import models |
simple ui
1 | pip install django-simpleui |
在setting文件中
1 | INSTALLED_APPS = [ |
克隆静态文件
1 | python3 manage.py collectstatic |
如果克隆出错,在setting文件加入以下代码
1 | STATIC_ROOT = os.path.join(BASE_DIR, "static") |
修改基础信息,在任意一个app下面的admin.py
1 | admin.site.site_title = "同剂商城管理系统" #页面上的标题 |
在setting中定义其他内容
1 | SIMPLEUI_CONFIG = { |
内部约束
1 | gender = ( |
在外部获取内部约束使用get_字段名_display()
1 | models.表名.get_字段名_display() |
Form
用来将model中的文件直接转换成为页面的input框。
1 | from django import forms |
在html文件中使用
1 | {% for i in form %} |
编辑时候,原数据填入框内
1 | def userview_edit(reqeust,id): |
删除
1 | def userview_edit(reqeust,id): |
关系映射
在关系型数据库中,通常不会把所有数
据都放在同一张表中,不易
于扩展,常见关系映射有:
1.—对一映射
如:一个身份证对应一个人
2.一对多映射
如:一个班级可以有多个学生
3.多对多映射
如:一个学生可以报多个课程,—个课程可以有多个学生学习
一对一映射
—对一是表示现实事物间存在的一对一的对应关系。
语法:OneToOneField(类名,on_delete=)
在模型文件需要绑定的类中
1 | class book(models.Model) |
- 特殊字段选项【必须】
- on_delete - 级联删除(1,2用的多)
- models.CASCADE级联删除。Django模拟SQL约束ON DELETE CASCADE的行为,并删除包含ForeignKey的对象。
- SET_NULL关联变为空设置ForeignKey null;需要指定null=True,black=True
- models.PROTECT 抛出ProtectedError 以阻止被引用对象的删除;[等同于mysql默认的RESTRICT]
- SET_DEFAULT 将ForeignKey设置为其默认值;必须设置ForeignKey的默认值。
- 无外键的模型类[Author]:
author1 = Author.objects.create(naem=“Blosslom”) - 有外键的模型类[book]
book = book.objects.create(name=“python”, author=author1)#关联Blosslom的类 - book = Wife.objects.create(name=‘python’,id=1)#关联Blosslom对应主键值
创建
先创建无外键的
1 | author1 = Author.objects.create(naem="Blosslom")#在模型层里的属于class author类 |
查询
1,正向查询:直接通关外键属性查询,则称为正向查询,有外键的字段
1 | from .models import book # |
2,反向查询-没有外键属性的一方,可以调用反向属性查询到关联的另一方
反向关联实例化对象名.关联类名小写.属性
在模型文件中
1 | class Author(models.Model) |
在视图文件中
1 | class Author(models.Model) |
一对多映射
一对多是表示现实事物间存在的一对多的对应关系。
如:—个学校有多个班级,—个班级有多个学生,一本图书只能属于一个出版社,一个出版社允许出版多本图书
—对多需要明确出具体角色,在多表上设置外键
在需要绑定的多键值中
1 | from django.db import models |
创建
先创建一,再创建多
1 | user1 = models.objects.create(name='') |
查询
1,正向查询[通过address查询user
1 | from .models import * |
反向查询[通过Publisher查询对应的所有的Book]
1 | user1 = User.objects.get(name='')#获取一个对象 |
多对多映射
多对多表达对象之间多对多复杂关系,如:每个人都有不同的学校(小学,初中,高中…),每个学校都有不同的学生…
mysql中创建多对多需要依赖第三张表来实现
Django中无需手动创建第三张表, Django自动完成
语法:在关联的两个类中的任意一个类中,增加:属性= models.ManyToManyField(MyModel)
1 | from django.db import models |
创建
多对多先创建谁都无所谓
1 | from .models import * |
所有都是没有外键操作的类都使用反向的类名_set
1 | from .models import * #先创建有外键的 |
正向查询
1 | good = goods.objects.get(name='') |
反向
1 | maker = manufactor.objects.get(maker='') |
cookies&session
cookies是存储在客户端浏览器上的
Chrome浏览器可能通过开发者工具的Application >>Storage >> Cookies
查看和操作浏览器端所有的Cookies值
- cookies在浏览器上是以键-值对的形式进行存储的,键和值都是以ASCII字符串的形存储(不能是中文字符串)
- 存储的数据带有生命周期
- cookies中的数据是按域存储隔离的,不同的域之间无法访问
- cookies的内部的数据会在每次访问此网址时都会携带到服务器端,如果cookies过大会降低响应速度
HttpResponse.set_cookie(key, value=’’, max_age=None,expires=None)
- key:cookie的名字
- value:cookie的值
- max_age:cookie存活时间,秒为单位
- expires:具体过期时间
- 当不指定max_age和expires时,关闭浏览器时此数据失效
1 | def cookies(request): |
在视图函数中写cookies
- 删除Cookies
- HttpResponse.delete_cookie(key)-删除指定的key 的Cookie。
- 如果key不存在则什么也不发生
获取Cookies - 通过request.COOKIES 绑定的字典(dict)获取客户端的COOKIES数据
- value = request.COOKIES.get(‘cookies名’,‘默认值’)
1 | def get_cookie(request): |
session是在服务器上开辟一段空间用于保留浏览器和服务器交互时的重要数据
实现方式
使用session需要在浏览器客户端启动cookie,且在cookie中存储sessionid
每个客户端都可以在服务器端有一个独立的Session-注意:不同的请求者之间不会共享这个
数据,与请求者一一对应
在setting中
1 | INSTALLED_APPS = [ |
session对于象是一个类似于字典的SessionStore类型的对象,可以用类拟于字典的方式进行操作
session 能够存储如字符串,整型,字典,
列表等。
- 1,保存session的值到服务器
- request.session[‘KEY’] = VALUE
- 2,获取session的值
- value = request.session[‘KEY’]
value = request.session.get(‘KEY’,默认值) - 3,删除session
- del request.session[‘KEY’]
1 | def set_session(request): |
settings.py中相关配置项
- SESSION_COOKIE_AGE
作用:指定sessionid在cookies中的保存时长(默认是2周),如下:例如:SESSION_COOKIE_AGE= 60*60*24*7*2 - SESSION_EXPIRE_AT_BROWSER_CLOSE= True设置只要浏览器关闭时,session就失效(默认为False)
- 注意:Django中的session数据存储在数据库中,所以使用session前需要确保已经执行过migrate
1, django_session表是单表设计;且该表数据量持续增持【浏览器故意删掉sessionid&过期数据未删除】
2、可以每晚执行python3 manage.py clearsessions【该命令可删除已过期的session数据】
哈希算法
md5:32位16进制
1 | import hashlib |
缓存
在setting文件中
1 | CACHES = { |
在python中创建一个表
1 | python manage.py createcachetable |
全局缓存
在路由里面写缓存
1 | from django.views.decorators.cache import cache_page |
在视图函数中写缓存
1 | from django.views.decorators.cache import cache_page |
局部缓存
1 | from django.core.cache import cache |
1, cache.set(key, value, timeout)-存储缓存
key:缓存的key,字符串类型
value:Python对象
timeout:缓存存储时间(s),默认为CACHES中的TIMEOUT值
返回值: None
2, cache.get(key)- 获取缓存
key:缓存的key
返回值:为key的具体值,如果没有数据
则返回None
3, cache.add(key, value)-存储缓存,只在key不存在时生效返回值: True[存储成功]or False[存储失败]
4, cache.get_or_set(key, value, timeout)-如果未获取到数据则执行set操作返回值: value
5, cache.set_many(dict,timeout)-批量存储缓存dict: key和value的字典
timeout:存储时间(s)
返回值:插入不成功的key的数组
6, cache.get_many(key_list)-批量获取缓存数据
心
key_list:包含key的数组
返回值:取到的key和value的字典
7, cache.delete(key)-删除key的缓存数据返回值:None
8, cache.delete_many(key_list)- 批量删除返回值: None
浏览器缓存
不会向服务器发送请求,直接从缓存中读取资源1,响应头–Expires
定义:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点
样例: Expires:Thu, 02 Apr 2030 05:14:08 GMT
2,响应头-Cache-Control
在HTTP/1.1中,Cache-Control主要用于控制网页缓存。比如当`Cache-Control:max-age=120代表请求创建时间后的120秒,缓存失效
说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用Cache-Control
3,Last-Modified响应头和If-Modified-Since请求头说明:
Last-Modified为文件的最近修改时间,浏览器第一次请求静态文件时,服务器如果返回Last-Modified响应头,则代表该资源为需协商的缓存
当缓存到期后,浏览器将获取到的Last-Modified值做为请求
头lf-Modified-Since的值,与服务器发请求协商,服务端返回304响应码[响应体为空].代表缓存继续使用,200响应码代表缓存不可用[响应体为最新资源]
4,Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化, Etag就会重新生成。
中间件
中间件是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件"系统,用于全局改变 Django的输入或输出。
中间件以类的形式体现
每个中间件组件负责做一些特定的功能。例如, Django 包含一个中间件组件AuthenticationMiddleware,它使用会话将用户与请求关联起来。
中间件类须继承自django.utils.deprecation.MiddlewareMixin类
中间件类须实现下列五个方法中的一个或多个:
process_request(self, request)
执行路由之前被调用,在每个请求上调用,返回None可以进入下一个环节或HttpResponse对象
process_view(self, request, callback, callback_args,callback_kwargs)
调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象
process_response(self, request, response)
所有响应返回浏览器被调用,在每个请求上调用,response是视图函数return的内容,返回HttpResponse对象
process_exception(self, request, excepption)
当处理过程中抛出异常时调用,返回一个HttpResponse对象
process_template response(self, request, response)
在视图函数执行完毕且试图返回的对象中包含render方法时被调用;该方法需要返回实现了render方法的响应对象
注:中间件中的大多数方法在返回None时表示忽略当前操作进入下一项事件,当返回HttpResponese对象时表示此请求结束,直接返回给客户端
再settings.py中需要注册一下自定义的中间件
1 | MIDDLEWARE = [ |
再项目文件夹下创建middleware的软件包,再创建py文件
注意:配置为数组,以视图为分界,中间件被调用时以’先上到下’再‘由下到上的顺序调用
1 | class Request(MiddlewareMixin): |
request.META['REMOTE_ADDR"]可以得到远程客户端的IP地址
reqeust.path_info获取用户当前访问的路径 返回形式不带ip和端口是 /路径/
request.path_ info 可以得到客户端访问的请求路由信息
1 | class LimitVisit(MiddlewareMixin): |
CSRF跨站伪造请求攻击
某些恶意网站上包含链接、表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息试图在
你的网站上完成某些操作,这就是跨站请求伪造(CSRF,即Cross-Site Request Forgey)。
1 | django.middleware.csrf.CsrfViewMiddleware |
特殊说明:
如果某个视图不需要django进行csrf保护,可以用装饰器关闭对此视图的检查
样例:
1 | from django.views.decorators.csrf import csrf_exempt |
分页
方式一
设置页码
1 | from django.utils.safestring import mark_safe |
方式二
分页是指在web页面有大量数据需要显示,为了阅读方便在每页页中只显示部分数据。
优点:
1.方便阅读
2.减少数据提取量,减轻服务器压力。
-
Django提供了Paginator类可以方便的实现分页功能
-
Paginator类位于
django.core.paginator
模块中。 -
负责分页数据整体的管理
-
对象的构造方法
paginator = Paginator(object list, per_page)
-参数-
object_list 需要分货数据的对象列表- per_page每页数据个数
-返回值:Paginator的对象
- count:需要分页数据的对象总数
- num_pages:分页后的页面总数
- page_range:从1开始的range对象,用于记录当前面码数
- per_page 每页数据的个数
paginator对象
.page(number)
-参数number为页码信息(从1开始)返回当前number页对应的页信息
如果提供的页码不存在,抛出InvalidPage异常lnvalidPage:总的异常基类,包含以下两个异常子类
- PageNotAnInteger:当向page()作传入一个不是整数的值时抛出
- EmptyPage:当尚page()提供一个有效值,但是那个页面上没有任何对象时抛出
负责具体某一页的数据的管理创建对象
Paginator对象的page()方法返回Page对象
page = paginator.page(页码)
Page对象属性- object_list:当前页上所有数据对象的列表-
- number:当前页的序号,从1开始
- gaginator:当前page对象相关的Paginator对象
- has_next():如果有下一页返回True
- has_previous():如果有上一页返回True
- has_other_pages():如果有上一页或下页返回True
- next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
- previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
-
在视图文件中
1 | from django.core.paginator import Paginator |
在html文件中
1 | <!DOCTYPE html> |
CSV文件
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)
说明:可被常见制表工具,如excel等直接进行读取Python提供了内建库csv;可直接通过该库操作csv文件案例如下:
import csv
with open(‘eggs.csv’, ‘w’, newline=") as csvfile:
writer = csv.writer(csvfile)
writer.writerow([‘a’ , ‘b’, ‘c’])
在网站中,实现下载CSV,注意如下:
响应Content-Tyoe类型需修改为text/csv。这告诉浏览器该文档是CSV文件,而不是HTML文件
响应会获得一个额外的Content-Disposition标头,其中包含CSV文件的名称。它将被浏览器用于开启“另存为…"对话框
1 | import csv |
上传
在前端
文件上传必须为POST提交方式
表单<form>
中文件上传时必须有带有enctype= "multipart/form-data"时才会包含文件内容数据。
表单中用
标签上传文件
1 | <form action='' methon='' enctype= "multipart/form-data"> |
在后端
视图函数中,用request.FILES取文件框的内容
file=request.FILES[‘xxx’]
说明: 1,FILES的key对应页面中file框的name值
2, file 绑定文件流对象,
3, file.name文件名
4, file.file文件的字节流数据
1 | up_file = request.FILES['file'] #file是form中的name |
在setting中
配置文件的访问路径和存储路径
在setting.py中设置MEDIA相关配置;Django把用户上传的文件,统称为media资源
Django把用户上传的文件,统称为media资源
1 | MEDIA_URL = '/media/' |
MEDIA_URL和MEDIA_ROOT需要手动绑定
步骤:主路由中添加路由
1 | from django. conf import settings |
说明:等价于做了MEDIA_URL开头的路由,Django接到该特征请求后去MEDIA_ROOT路径查找资源
借助ORM
FileField(upload_to=‘子目录名’)
1 | up_file = request.FILES['file'] |
邮件发送
SMTP的全称是"Simple Mail Transfer Protocol",即简单邮件传输协议(25号端口)。
它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转
属于“推送”协议
IMAP全称是lnternet Mail Access Protocol,
即交互式邮件访问协议,是一个应用层协议(端口是143)。
用来从本地邮件客户端(Outlook Express.Foxmail、Mozilla Thunderbird等)访问远程服务器上的邮件属于“拉取”协议
两者均为“拉取"型协议,负责从邮件服务器中下载邮件
- IMAP具备摘要浏览功能,可预览部分摘要,再下载整个邮件
IMAP为双向协议,客户端操作可反馈给服务器 - POP3必须下载全部邮件,无摘要功能
POP3为单向协议,客户端操作无法同步服务器
Django中配置邮件功能,主要为SMTP协议,负责发邮件
原理:
给Django授权一个邮箱
Django用该邮箱给对应收件人发送邮件
django.core.mail封装了电子邮件的自动发送SMTP协议
在setting文件中
1 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' |
在视图文件中
1 | from django.core import mail |
发送html邮件
1 | from django.core.mail import EmailMessage |
前后端分离
数据获取/发送模式
1 | request.GET.get('')#获取数据 |
json
1 | import json |
环境部署
项目部署是指在软件开发完毕后,将开发机器上运行的软件实际安装到服务器上进行长期运行
1.在安装机器上安装和配置同版本的环境[py,数据库等
2.django 项目迁移(没用)
1 | sudo scp /home/tarena/django/mysite1 |
3.用uwSGI替代python3 manage.py runserver 方法启动服务器
4.配置nginx反向代理服务器
5.用nginx配置静态文件路径,解决静态路径问题
wSGl (Web Server Gateway Interface)Web服务器网关接口,是
Python应用程序或框架和Web服务器之间的一种接口,被广泛使用
使用python manage.py runserver通常只在开发和测试环境中使用
当开发结束后,完善的项目代码需要在一个高效稳定的环境中运行,这时可以使用WSGl
uwSGI是wSGl的一种,它实现了http协议wSGI协议以及uwsgi协议
uwSGI功能完善,支持协议众多,在python web圈热度极高
uwSGI主要以学习配置为主
Ubuntu执行sudo pip3 install uwsgi==2.0.18 -i
https://pypi.tuna.tsinghua.edu.cn/simple/
检查是否安装成功
sudo pip3 freezelgrep -i ‘uwsgi’
如果成功安装,则会输出uwSGI==2.0.1
添加配置文件项目同名文件夹/uwsgi.ini
如: mysite1/mysite1/uwsgi.ini
文件以[uwsgi]开头,有如下配置项:套接字方式的IP地址:端口号【此模式需要有nginx】
socket=127.0.0.1:8000
Http通信方式的IP地址:端口号
http=127.0.0.1:8000
项目当前工作目录(绝对路径)
chdir=/home/tarenal…/my_project
项目中wsgi.py文件的目录,相对于当前工作目录
wsgi-file=my_project/wsgi.py
进程个数
process=4
每个进程的线程个数
threads=2
服务的pid记录文件
pidfile=uwsgi.pid
服务的目志文件位置(后台启动加日志)
daemonize=uwsgi.log
开启主进程管理模式
master=true
1 | [uwsgi] |
启动uwsgi
cd 到 uwSGI配置文件所在目录
uwsgi --ini uwsgi.ini
停止uwsgi
cd 到uwSGI配置文件所在目录
uwsgi --stop uwsgi.pid
1,无论是启动还是关闭,都需要执行ps aux|grep 'uwsgi’确认是否符合预期
2,启动成功后,进程在后台执行,所有日志均输出在配置文件所在目录的uwsgi.log中
3,Django中代码有任何修改,需要重新启动uwsgi
redis
Redis是非关系型的kv型数据库
·特点:
1、开源的,使用C编写,基于内存且支持持久化
2、支持数据类型丰富,字符串strings,散列hashes,列表lists,集合sets,有序集合sorted sets等等
3、支持多种编程语言(CC++ Python Java PHP …)
4、单进程单线程
1、持久化
将内存中数据保存到磁盘中,保证数据安全,方便进行数据备份和恢复
2、过期键功能
为键设置一个过期时间,让它在指定时间内自动删除<节省内存空间>
#音乐播放器,日播放排名,过期自动删除
3、事务功能
弱事务型的数据库,只是具备简单的事务功能
4、主从复制
如何让redis保持高可用状态,官方提供主从搭建方案
5、Sentinel哨兵
在搭配了基础的主从结构后,哨兵可做到自动故障转移
1 | redis-benchmark -q -n 10000 |
Ubuntu
安装: sudo apt-get install redis-server
安装后, Ubuntu会将redis列为开机自动启动项
服务端启动/停止/重启:
sudo /etc/init.d/redis-server status | start | stop | restart
客户端连接
redis-cli -h IP地址 -p 6379 -a 密码
1 | redis-cli #进入服务 |
·配置文件所在路径letc/redis/redis.conf
mysql的配置文件在哪里?
/etc/mysql/mysql.conf.d/mysqld.cnf
1 | sudo cp /etc/redis/redis.conf /etc/redis/redis_bak.conf #备份 |
配置文件
1 | requirepass 密码 |
websocket
安装组件
1 | pip install channels |
setting
1 | #注册 |
asgi文件
1 | import os |
创建routing
1 | from django.urls import re_path |
在app中创建会话文件comsumer
1 | from channels.generic.websocket import WebsocketConsumer |
在html界面中
1 | <script> |
连接方式
页面由正常路由发送进去(urls+views),和websocket相关的连接在html页面内置了WebSocket发送连接由routing+comsumer文件处理.
客户端推送消息
例如
1 | <body> |
在comsumer中receive函数接收
1 | def websocket_receive(self, message): |
服务端推送消息
comsumer中connect函数发送
1 | def websocket_connect(self, message): |
在html中
1 | socket.onmessage = function (event){ |
群聊实现
前端未变化,在后端增加一个全局变量,使用列表存储
1 | All_list = [] |
使用内存存储,在setting中配置
1 | CHANNEL_LAYERS = { |
使用redis存储
1 | pip install channels-redis |
在setting中配置
1 | CHANNEL_LAYERS = { |
consumer文件中
1 | class Chatsumer(WebsocketConsumer): |
项目
笔记项目
- 产品/运营经理:负责产品功能细节的把控
- 开发
前端–负责显示部分内容的开发【多】
后端–负责服务器部分的功能开发【少】
运维–管理linux服务器,组件化配置 - 安全问题
- 测试–负责找出产品功能的问题(BUG)·美术-负责产品素材方面得绘制
登录界面视图
1 | # 接收到get返回页面 |
装饰器校验账户是否登录
1 | # 定义装饰器 |
退出登录
1 | def login_out(request): |