Django
0x01 入门
(1)简介
- 官方网站链接
- Django 是一个开放源代码的Web应用框架,由Python写成
- Django 采用 MTV 的框架模式,即模型 M,视图 V 和模版 T
(2)安装
a. 创建虚拟环境
- 安装 virtualenv:
pip install virtualenv virtualenvwrapper-win
- 查看虚拟环境:
workon
- 创建新的虚拟环境 env:
mkvirtualenv env
- 进入虚拟环境 env:
workon env
- 删除虚拟环境 env:
rmvirtualenv env
b. 安装 Django
- 安装 Django:
pip install django=4.2 -i
- 查看安装结果:
pip show django
也可以在 Pycharm 新建 Django 项目时自动安装 Django
(3)创建项目
- 方法一:在 cmd 中使用命令
django-admin startproject Projectname
- 方法二:在 Pycharm 中创建 Django 项目
(4)项目目录与配置文件
a. 项目目录
- manage.py:管理项目的命令行工具,进行站点运行以及数据库自动生成都通过本文件
- ProjectName
- __ init__.py:用于工具初始化
- asgi.py:Python 异步服务器网关接口
- settings.py:项目配置文件,定义了项目引用的组件、项目名、数据库、静态资源等
- urls.py:维护项目的 URL 路由映射
- wsgi.py:Python 服务器网关接口
b. 配置文件
settings.py(该文件新建项目时自动生成)
# 项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent
# 加密解密的密钥
SECRET_KEY = 'django-insecure-xxxxxx'
# 是否开启调试模式
DEBUG = True
# 被允许的域名、IP
ALLOWED_HOSTS = []
# 插入自定义引用名称
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
...
]
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
...
]
# 根路由文件
ROOT_URLCONF = 'djangoProject.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',
],
},
},
]
# WSGI 目录
WSGI_APPLICATION = 'djangoProject.wsgi.application'
# 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# 密码验证
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
...
]
# 国际化信息配置
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# 静态资源目录
STATIC_URL = 'static/'
# 默认主键字段类型
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
(5)运行项目
- 设置 settings.py 中
ALLOWED_HOSTS = ['*']
- 使用命令
python manage.py runserver [IP Address]:[Port]
(6)数据迁移
- 迁移就是将模型映射到数据库的过程
- 使用命令
python manage.py makemigrations
生成迁移文件 - 使用命令
python manage.py migrate
执行迁移
不需要初始化迁移文件夹,每个应用默认有迁移文件夹 migrations
(7)创建应用 App
- 使用命令
python manager.py startapp App
创建名为 App 的应用 - 使用应用前需要将应用配置到 settings.py 的
INSTALLED_APPS
中 - 应用目录:
- migrations 包:生成迁移文件
- __ init__.py:初始化包,暂空
- admin.py:管理站点模型的声明文件,默认空
- apps.py:应用信息定义文件
- models.py:添加模型层数据类文件
- tests.py:代码测试文件
- views.py:定义 URL 相应函数
(8)HTTP 前后端交互
(9)Django 框架流程
(10)视图函数基本使用
-
在 views.py 中建立一个路由响应函数
from django.http import HttpResponse def hello(request): return HttpResponse('Hello')
-
在 urls.py 中注册
from App.views import * urlpatterns = [ path('hello/', hello) ]
-
启动项目并访问 127.0.0.1:8000/hello
(11)路由基本使用
-
子路由 App/urls.py
from django.urls import path from App.views import * urlpatterns = [ path('/hello', hello) ]
-
根路由 urls.py
from django.urls import path, include urlpatterns = [ path('app/', include('App.urls')), ]
-
启动项目并访问 127.0.0.1:8000/app/hello
(12)模板基本使用
- 模板是 HTML 写好的页面
- 创建模板文件夹 templates,并在其中创建模板文件
- 在 views.py 中通过
return render(request, 'xxx.html')
来加载并渲染模板
(13)模型基本使用
-
在 models.py 中引入 models:
from django.db import models
-
创建继承于 models.Model 的自定义模型类:如用户类
class User(models.Model): name = models.CharField(max_length=30) # 对应 SQL 的 name varchar(30) age = models.IntegerField(20) # 对应 SQL 的 age int default 20
-
模型 models 表结构一旦改变就需要进行数据迁移
-
使用模型
-
views.py
from App.models import * def get_users(request): users = UserModel.objects.all() return render(request, 'users.html', {'users': users})
-
users.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for user in users %} <li>{{ user.name }}, {{ user.age }}</li> {% endfor %} </ul> </body> </html>
-
(14)后台管理基本使用
- 在 admin.py 中将 model 加入后台管理:
admin.site.register(Grade)
- 创建超级用户:
python manage.py createsuperuser
- 访问 admin 后台:127.0.0.1:8000/admin
0x02 路由
(1)简介
- 在项目开发过程中,通常在每个应用中创建各自的 urls.py 路由模块
- 路由的分发:从根路由出发,将每个应用所属的 url 请求转发到各自的路由模块中的过程
(2)创建用户模型
-
models.py
class UserModel(models.Model): name = models.CharField(max_length=30) age = models.PositiveIntegerField() # 非负数
-
数据迁移
python manage.py makemigrations python manage.py migrate
-
与数据库连接
- 新建 SQLite 数据源
- 常规 -> 文件:打开项目根目录下的 db.sqlite3
- 确认已配置相关数据库驱动即可完成连接
(3)添加子路由
-
添加模板:App/templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h2>首页</h2> <hr /> </body> </html>
-
添加视图:views.py
from django.shortcuts import render def index(request): return render(request, 'index.html')
-
根路由:urls.py
from django.urls import path, include urlpatterns = [ path('user/', include('App.urls')) ]
-
子路由:App/urls.py
from django.urls import path from App.views import * urlpatterns = [ path('index/', index) ]
(4)反向解析和命名空间
a. 基本概念
-
反向解析:使用路由别名替代 URL 路径,避免在修改 URL 时产生硬编码依赖
-
命名空间:一种将路由命名为层次结构的方式,使得查询路由时可以限定在该命名空间内,防止路由冲突
b. 项目应用-页面跳转
-
添加模板:App/templates/user_list.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户列表</title> </head> <body> <h2>用户列表</h2> <hr /> </body> </html>
-
修改视图:views.py
def user_list(request): return render(request, 'user_list.html')
-
修改子路由:App/urls.py
path('userList/', user_list, name='userListName')
-
修改模板:App/templates/index.html
<a href="/user/userList/">URL 路由方式</a> <hr /> <a href="{% url 'userListName' %}">反向解析方式</a> <hr /> <a href="{% url 'App:userListName' %}">使用命名空间的反向解析方式</a>
-
使用命名空间的反向解析方式前需要修改根路由:urls.py
path('user/', include(('App.urls', 'App'), namespace='App'))
-
“反向解析方式”和“使用命名空间的反向解析方式”不能同时作用于同一个页面
-
(5)路由传参
a. 单个参数
-
添加模板:App/templates/user_detail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户详情</title> </head> <body> <h2>用户详情</h2> <hr /> <h3>{{ user.name }} 的年龄是 {{ user.age }}</h3> </body> </html>
-
修改视图:views.py
from App.models import * def user_list(request): users = UserModel.objects.all() return render(request, 'user_list.html', {'users': users}) def user_detail(request, uid): user = UserModel.objects.get(pk=uid) return render(request, 'user_detail.html', {'user': user})
-
修改子路由:App/urls.py
path('userDetail/<int:uid>/', user_detail, name='userDetailName')
-
修改模板:App/templates/user_list.html
<ul> {% for user in users %} <li> <a href="{% url 'App:userDetailName' user.id %}"> {{ user.name }} </a> </li> {% endfor %} </ul>
b. 多个参数
-
修改视图:views.py
def user_ab(request, a, b): return HttpResponse(f"{a} - {b}")
-
修改子路由:App/urls.py
path('userAB/<int:a>/<int:b>', user_ab, name='userABName')
(6)重定向 Redirect
-
修改子路由:App/urls.py
path('redirect/', my_redirect)
-
修改视图:views.py
-
重定向到指定链接
def my_redirect(request): return redirect('https://www.baidu.com')
-
重定向并反向解析传值
-
依序传值
def my_redirect(request): return redirect(reverse('App:userDetailName', args=(2,)))
-
对象传值
def my_redirect(request): return redirect(reverse('App:userDetailName', kwargs={'uid': 2}))
-
-
0x03 模板
(1)简介
- 模板用于帮助开发者快速呈现给用户页面的工具
- 模板处理分为两个过程:加载 HTML、渲染数据
- 模板主要有两个部分:HTMl 静态代码、模板语言和动态代码段
- 动态代码段可以做基本的静态填充和运算、转换、逻辑
- 页面可以按页面数据来源进行分类
- 静态页面:数据来源于本地固定数据
- 动态页面:数据来源于后台服务器
(2)动态代码段
a. 变量
- 模板中的变量是视图传递给模板的,遵守标识符规则
- 一般变量的语法:
{{ var }}
- 如果变量不存在,则插入空字符串
- 当变量是方法时,不能有参数
- 当变量是列表时,使用正索引
- python 中:
items = ["a", "b", "c"]
- HTML 中:
{{ items.1 }}
- python 中:
b. 标签
- 模板中的标签语法:
{% tag %}
- 作用:
- 加载外部传入的变量
- 在输出中创建文本
- 控制循环或逻辑
c. 注释
-
单行注释
{# 注释 #}
-
多行注释
{% comment %} 注释1 注释2 {% endcomment %}
(3)if
语句
a. 格式
-
单分支
{% if 表达式 %} 语句 {% endif %}
-
双分支
{% if 表达式 %} 语句 {% else %} 语句 {% endif %}
-
多分支
{% if 表达式 %} 语句 {% else if %} 语句 {% else %} 语句 {% endif %}
b. 举例
-
无逻辑条件判断
{% if isDelete %} <h2>Boolean</h2> {% endif %}
-
与或非判断
{% if a and b %} <p>and</p> {% else if a or b %} <p>or</p> {% else if not a %} <p>not</p> {% endif %}
-
成员判断
{% if "a" in "bcd" %} <p>bcd</p> {% else if "a" not in "efg" %} <p>a</p> {% endif %}
(4)for
语句
-
一般格式:
{% for 变量 in 列表 %} 语句1 {% empty %} 语句2 {% endfor %}
- 当列表为空或不存在时,执行 empty 语句
-
forloop
{% for 变量 in 列表 %} <p>{{ forloop.counter }}</p> {% endfor %}
{{ forloop.counter }}
:表示当前是第几次循环,从 1 开始{{ forloop.counter0 }}
:表示当前是第几次循环,从 0 开始{{ forloop.revcounter }}
:表示当前是第几次循环,倒序数,至 1 停{{ forloop.revcounter0 }}
:表示当前是第几次循环,倒序数,至 0 停{{ forloop.first }}
:表示是否是第一个元素,返回布尔值{{ forloop.last }}
:表示是否是最后一个元素,返回布尔值
(5)过滤器
-
一般过滤器格式:
{{ var | 过滤器 }}
- 作用:在变量显示前修改
-
加法过滤器:`{{ value | add:2 }}
-
大写过滤器:
{{ name | upper }}
- 首字母大写:
{{ name | first | upper }}
- 尾字母大写:
{{ name | last | upper }}
- 首字母大写:
-
小写过滤器:
{{ name | lower }}
-
截断过滤器:
{{ name | truncatechars: 30 }}
-
连接过滤器:
{{ name | join: '=' }}
-
默认过滤器:
{{ var | default: value }}
-
日期过滤器:
{{ day | date: 'y-m-d' }}
-
转义过滤器:
{{ code | safe }}
-
打开/关闭自动转义
{% autoescape on/off %} 语句 {% endautoescape %}
-
(6)继承
-
block:
{% block XXX %} 语句 {% endblock %}
XXX
为该 block 的名称
-
extends:继承
{% extends '父模块路径' %}
-
include:加载模板进行渲染
{% include '模板文件' %}
-
{{ block.super }}
:获取父模版中 block 中的内容 -
举例:
-
父模板:block.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>父模版</h2> <hr /> {% block head %} <div> <button>Head</button> </div> {% endblock %} </body> </html>
-
其他模板:other.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>其他模板</h2> <hr /> </body> </html>
-
子模版:child.html
{# 继承父模版 #} {% extends 'block.html' %} {% block head %} <div> <button>head</button> </div> {# 保留父模版内容 #} {{ block.super }} {# 导入其他模板 #} {% include 'other.html' %} {% endblock %}
-
(7)Jinja2 模板引擎
a. 简介
- Jinja2 是一个模板引擎,在 Flask 中也有应用
- Jinja2 基于 Django 默认模板引擎开发,性能更好,功能更全
b. 安装与配置
-
使用命令
pip install jinja2
安装 -
在 settings.py 同目录下新建 jinja2_env.py 文件配置 Jinja2
from django.templates.static import static from django.urls import reverse from jinja2 import Environment def environment(**options): env = Environment(**options) env.globals.update({ 'static': static, 'url': reverse }) return env
-
在 settings.py 中配置 Jinja2 模板引擎
TEMPLATES = [ # 新增 Jinja2 模板引擎 { 'BACKEND': 'django.template.backends.jinja2.Jinja2', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'environment': 'DjangoPro2.jinja2_env.environment', 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, # 原 Django 引擎模板 { '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', ], }, }, ]
-
在设置中修改模板语言为 Jinja2
c. 使用
-
Jinja2 可以使用函数调用
{% for i in range(1, 4) %} <h3>{{ i }}</h3> {% endfor %}
-
循环下标读取方法改变
{% for i in range(1, 4) %} <h3>{{ loop.index }}</h3> {% endfor %}
-
……
0x04 模型基础
(1)简介
-
Django 根据属性的类型确认以下信息
- 当前选择的数据库支持字段的类型
- 渲染管理表单时使用的默认 HTML 控件
- 在管理站点最低限度的验证
-
Django 会为表增加自动增长的主键列,当设置过主键列后,默认主键列不会生成
-
属性名遵循标识符命名规则,但由于 Django 的查询方式,不允许使用连续的下划线
- 定义属性时需要设置字段类型
-
使用模型:
- 导入模型:
from django.db import models
- 通过
models.Field
创建字段类型的对象并赋值给属性
- 导入模型:
-
逻辑删除和物理删除
-
对于重要数据仅作逻辑删除
- 实现方法:设置
is_delete
属性为False
is_delete = models.BooleanField(default=False)
- 实现方法:设置
-
-
基本操作
-
Django 自动生成与模型相应的 SQL 语句并执行
-
Django 使用对象关系映射框架操控数据库
-
对象关系映射(Object Relational Mapping,简称 ORM):一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换
-
ORM 关系:
graph LR 模型-->表 -->模型 类对象-->表结构 对象-->表的一条数据 类属性-->表的字段
-
-
(2)字段类型
a. 常用字段类型
AutoField
:根据实际 ID 自动增长的 IntegerField,通常不指定CharField(max_length=字符长度)
:字符串,默认表单控件为 InputTextField
:文本字段,默认表单控件为 TextareaIntegerField
:整数DecimalField(max_digits=None, decimal_places=None)
:使用 Python 的 Decimal 实例表示十进制浮点数max_digits
:位数总数decimal_places
:小数点后的位数
FloatField
:使用 Python 的 float 实例表示浮点数BooleanField
:布尔值,默认表单控件为 CheckboxInputDateField([auto_now=None, auto_now_add=False])
:使用 Python 的 datetime.date 实例表示日期auto_now
:保存对象时设置为当前时间时间戳,一般用于表示最后一次修改的时间auto_now_add
:保存对象创建的时间戳
TimeField
:使用 Python 的 datetime.time 实例表示时间,参数同DateField
DateTimeField
:使用 Python 的 datetime.datetime 实例表示日期和时间,参数同DateField
FileField
:上传文件的字段ImageField
:继承自FileField
,但会对文件进行校验- 需要安装 Pillow:
pip install Pillow
- 需要安装 Pillow:
b. 常用字段参数
-
null=True
:数据库字段是否可以为空 -
blank=True
:Django 的 Admin 中添加数据时是否可以为空 -
primary_key=True
:设置主键 -
auto_now
和auto_now_add
:详见DateField
中的说明 -
choices
:设置 Admin 下拉菜单-
举例:设置用户类型下拉菜单
USER_TYPE_LIST = ( (1, '一般用户'), (2, 'Vip 客户'), (3, '超级用户') ) user_type = models.IntegerField(choices=USER_TYPE_LIST, default=1, verbose_name='用户类型')
-
-
max_length
:最大长度 -
default
:默认值 -
verbose_name
:Admin 中字段的显示名称 -
name|db_column
:数据库中字段的名称 -
unique=True
:是否不允许重复 -
db_index=True
:数据库索引 -
editable=True
:是否允许 Admin 编辑,否则不显示 -
设置表名
class Meta: db_table = 'person'
(3)单表操作
a. 增加数据
-
方法一:创建对象实例并调用
save
方法obj = Author() obj.first_name = 'zhang' obj.last_name = 'san' obj.save()
-
方法二:创建对象并初始化后调用
save
方法obj = Author(first_name='zhang', last_name='san') obj.save()
-
方法三:调用
create
方法Author.objects.create(first_name='zhang', last_name='san')
-
调用
get_or_create
方法防止重复创建Author.objects.get_or_create(first_name='zhang', last_name='san')
-
b. 删除数据
-
方法一:使用 Queryset 的
delete
方法-
删除指定条件的数据
Author.objects.filter(first_name='zhang').delete()
-
删除所有数据
Author.objects.all().delete()
-
-
方法二:使用模型对象的
delete
方法obj = Author.objects.get(id=1) obj.delete()
c. 修改数据
-
方法一:使用模型对象的
update
方法Author.objects.filter(last_name='san').update(last_name='si')
-
方法二:重新赋值后使用
save
方法保存obj = Author.objects.get(id=1) obj.first_name = 'li' obj.save()
-
如果只更新某个字段,则使用以下方法
obj = Author.objects.get(id=1) obj.first_name = 'li' obj.save(update_fields=['first_name'])
-
d. 查找数据
I. 基础查询
get()
:获取单条数据,如get(id=1)
- 如果没有符合的结果:
DoesNotExist
异常 - 如果有多个符合的结果:
MultipleObjectsReturned
异常
- 如果没有符合的结果:
first()
:返回查询集中的第一个对象last()
:返回查询集最后一个对象count()
:返回查询集中对象的个数exists()
:判断查询集中是否有数据all()
:获取全部数据values()
:获取指定列的值,可传多个参数,返回形式是字典values_list()
:获取指定列的值,可传多个参数,返回形式是元组
II. 条件过滤
filter(id__gt=2)
:获取 id 大于 2 的值filter(id__gte=2)
:获取 id 大于等于 2 的值filter(id__lt=2)
:获取 id 小于 2 的值filter(id__lte=2)
:获取 id 小于等于 2 的值filter(id__lt=4, id__gt=2)
:获取 id 大于 2 且 小于 4 的值filter(id__in=[1, 2, 4])
:获取 id 在[1, 2, 4]
之间的值exclude(id__in=[1, 2, 4])
:获取 id 不在[1, 2, 4]
之间的值filter(age__range=[10, 20])
:获取整数 age 在 10~20 之间的值filter(name__contains="van")
:获取 name 中含 van 的值filter(name__icontains="van")
:获取 name 中含 van 的值(大小写不敏感)filter(name__regex="van")
:正则匹配filter(name__iregex="van")
:正则匹配(大小写不敏感)
III. 排序
filter(name='zhangsan').order_by('id')
:升序排序filter(name='zhangsan').order_by('-id')
:降序排序- 按年龄排序,相同则按 id 排序:
filter(name='zhangsan').order_by('age', 'id')
- 按年龄排序,相同则按 id 排序:
IV. 聚合
aggregate(Max('age'))
:最大值aggregate(Min('age'))
:最小值aggregate(Sum('age'))
:求和aggregate(Avg('age'))
:平均值aggregate(Count('age'))
:计数
(4)手动分页
-
all()[10:20]
:切片,取所有数据的 10 条到 20 条,下标从 0 开始,不为负 -
在 views.py 中通过切片进行分页
def paginate(request, page=1): per_page = 10 items = Model.objects.all()[(page-1) * per_page : page * per_page] total = Model.objects.count() total_page = math.ceil(total / 10) pages = range(1, total_page+1) return render(request, 'paginate.html', {'items': items, 'pages': pages})
-
添加子路由
path('paginate/<int:page>', paginate)
-
网页中添加按钮访问指定页
<ul> {% for page in pages %} <li><a href="{% url 'paginate' page %}"><button>{{ page }}</button></a></li> {% endfor %} </ul>
(5)分页器
-
Django 中有自动分页器
-
views.py
from django.core.paginator import Paginator def paginate(request, page=1): per_page = 10 datas = Model.objects.all() # 分页器 paginator = Paginator(datas, per_page) # 获取第 page 页的数据 items = paginator.page(page) # 获取页码范围 pages = paginator.page_range return render(request, 'paginate.html', {'items': items, 'pages': pages})
0x05 模型进阶
(1)配置 MySQL 数据库
-
安装 MySQL
-
安装 MySQL 驱动:
pip install mysqlclient
-
在 Django 中配置和使用 MySQL
-
settings.py
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django', 'USER': 'root', 'PASSWORD': 'password', 'HOST': '127.0.0.1', 'PORT': '3306' } }
-
(2)多表关系
- OneToOneField:一对一(学号对学生),将字段定义在任意一端
- ForeignKey:一对多(班级对学生),将字段定义在“多”的一端
- ManyToManyField:多对多(老师对学生),将字段定义在任意一端
(3)一对多关系
-
举例:一个班级有多个学生,一个学生只属于一个班级
class Grade(models.Model): name = models.CharField(max_length=20) class Student(models.Model): name = models.CharField(max_length=20) grade = models.ForeignKey(Grade)
-
对象的使用
- 正向,Student 视角,包含外键
- 获取学生所在班级(对象):
student.grade
- 获取学生所在班级名称:
student.grade.name
- 获取学生所在班级(对象):
- 反向,Grade 视角,不包含外键
- 获取班级所有学生(Manager 对象):
grade.student_set
- 获取班级所有学生(QuerySet 查询集):
grade.student_set.all()
- 获取班级所有学生(Manager 对象):
- 正向,Student 视角,包含外键
a. 增加数据
I. 正向
-
方法一:创建对象实例并使用
save
方法obj = Student(name='zhangsan', grade_id=1) obj.save()
-
方法二:对象初始化后使用
create
方法Student.objects.create(name='zhangsan', grade_id=1)
-
使用
get_or_create
方法防止重复创建Student.objects.get_or_create(name='zhangsan', grade_id=1)
-
使用字典
dic = {'name': 'zhangsan', 'grade_id': 1} Student.objects.create(**dic)
-
使用对象
grade = Grade.objects.get(id=1) Student.objects.get_or_create(name='zhangsan', grade=grade)
-
II. 反向
- 同正向增加数据操作
b. 删除数据
I. 正向
- 一般操作:
Student.objects.filter(id=1).delete()
II. 反向
- 删除操作可以在定义外键时,通过
on_delete
参数来配置models.CASCADE
:默认值,级联删除models.PROJECT
:保护模式,组织级联删除models.SET_NULL
:置空模式,null=True
参数必备models.SET_DEFAULT
:置默认值,default
参数必备models.SET()
:删除时重新动态指定一个实体访问元素models.DO_NOTHING
:无操作
c. 修改数据
I. 正向
- 一般操作:
Student.objects.filter(id=1).delete(grade_id=2)
II. 反向
- 同正向修改数据操作
d. 查询数据
I. 正向
-
一般操作
students = Student.objects.filter(name='zhangsan') grade = students[0].grade_id.name
II. 反向
-
同正向查询数据操作
-
可以在定义外键时,通过指定
related_name
参数来配置grade = models.ForeignKey('Grade', related_name="info") obj,info.all()
-
可以通过 Student 提供的信息查看
obj = Student.objects.get(grade__name)
(4)多对多关系
-
对于多对多关系,Django 会自动创建第三张表,也可以通过
through
参数指定 -
举例:用户与小组
class Group(models.Model): name = models.CharField(max_length=20) def __str__(self): return self.name class User(models.Model): name = models.CharField(max_length=20) groups = models.ManyToManyField(Group) def __str__(self): return self.name
a. 增加数据
-
分别创建 user 和 group
user = User(name='zhangsan') user.save() group = Group(name="zuiyi") g.save()
-
通过 Manager 对象使用
add()
方法user.groups.add(group) # 或者 group,user_set.add(user)
b. 删除数据与修改数据
- 同一对多类似
c. 查询数据
I. 正向
-
查询 id 为 1 的用户所在的所有组
user = User.objects.get(id=1) user.groups.all()
II. 反向
-
查询 id 为 1 的组中包含的用户
group = Group.objects.get(id=1) group.user_set.all()
(5)一对一关系
-
一对一关系是 Django 独有的一个连表操作,是特殊的一对多关系,相当于加了
unique=True
-
举例:身份证号和人
class IdCard(models.Model): num = models.IntegerField() def __str__(self): return str(self.num) class Person(models.Model): idcard = models.OneToOneField(IdCard) name = models.CharField(max_length=20) def __str__(self): return self.name
0x06 视图
(1)简介
-
Django 中的视图主要用来接受 Web 的请求并作出响应
-
视图的本质是 Python 中的函数
-
视图的响应分为两大类:
- 以 JSON 数据形式返回(JsonResponse)
- 以网页的形式返回
- 重定向到另一个网页(HttpResponseRedirect)
- 错误视图(HttpResponseNotFound/HttpResponseForbidden/HttpResponseNotAllowed)
-
视图响应过程:
graph LR 浏览器输入-->A(URLs 路由匹配) -->视图响应 -->回馈到浏览器 -
视图参数
- HttpRequest 的实例
- 通过 URL 正则表达式传参
-
位置:通常在应用下的 views.py 中定义
(2)HttpRequest 请求对象
-
Django 框架接收到 http 请求后会将 http 请求包装为 HttpRequest 对象并传递给视图
-
常用属性与方法
- 属性
path
:请求的完整路径method
:请求的方法,如 GET、POST 等GET/POST
:类似字典,包含了 GET/POST 的所有参数FILES
:类似字典,包含了上传的文件COOKIES
:字典,包含了所有 COOKIEsession
:类似字典,表示会话
- 方法
is_ajax()
:判断是否是 Ajaxget_full_path()
:返回包含参数字符串的请求路径
- 属性
-
QueryDict:类字典对象,与字典区别在于可以有相同的键
-
获取数据
dict['name']
或dict.get('name')
-
获取指定键对应的所有值
dict.getlist('name')
-
(3)HttpResponse 响应对象
-
服务器返回给客户端的数据,HttpResponse 由开发者自行创建
- 方法一:不使用模板,直接调用
HttpResponse()
,返回 HttpResponse 对象 - 方法二:使用模板进行渲染
- 使用 render:
render(request, template_name[, context])
request
:请求体对象template_name
:模板路径context
:字典参数
- 使用 render:
- 方法一:不使用模板,直接调用
-
常用属性与方法
- 属性
content
:返回的内容charset
:编码格式status_code
:响应状态码
- 方法
write(xxx)
:写出文本flush()
:冲刷缓冲区set_cookie(key, vale='xxx', max_age=None)
:设置 Cookiedelete_cookie(key)
:删除 Cookie
- 属性
-
子类
-
HttpResponseRedirect
- 响应重定向,可以实现服务器内部跳转
- 使用时一般需要反向解析
return HttpResponseRedircet('/page/1')
-
JsonResponse
- 返回 JSON 数据的请求,一般用于异步请求上
- 返回 JSON 数据时
Content-type
是application/json
JsonResponse(dict)
-
0x07 会话技术
(1)Cookie 简介
-
Cookie实际上是一小段的文本信息
- 客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie
- 客户端浏览器会把Cookie保存起来,当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器
- 服务器检查该Cookie,以此来辨认用户状态
- 服务器还可以根据需要修改Cookie的内容
-
Cookie 本身由服务器生成,通过 Response 将 Cookie 写到浏览器上,当再次访问时浏览器会根据不同的规则携带 Cookie
- Cookie 既不能跨浏览器,也不能跨域
-
通过 response 设置 Cookie:
response.set_cookie(key, value[, max_age=None, expires=None])
-
max_age
:整数,单位为秒,指定 Cookie 过期时间,默认值为None
,表示关闭 -
expires
:指定过期时间,支持 datetime 或 timedelta,可以指定一个具体日期和时间expires=datetime.datetime(2000, 1, 1, 0, 0, 0) # or expires=datetime.datetime.now()+datetime.timedelta(days=10)
-
-
通过 request 获取 Cookie:
request.COOKIES.get('username')
-
通过 response 删除 Cookie:
response.delete_cookie('username')
-
Cookie 存储于客户端
- 优点:减轻服务器压力,提高网站性能
- 缺点:安全性不高
(2)创建模型与模板
-
models.py
class UserModel(models.Model): username = models.CharField(max_length=30, unique=True) password = models.CharField(max_length=225) age = models.IntegerField(default=20)
-
templates
-
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <a href="{% url 'login' %}">登录</a> <a href="{% url 'register' %}">注册</a> </body> </html>
-
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册</title> </head> <body> </body> </html>
-
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> </body> </html>
-
-
views.py
from django.shortcuts import render def index(request): return render(request, 'index.html') def register(request): return render(request, 'register.html') def login(request): return render(request, 'login.html')
-
urls.py
from django.urls import path, include urlpatterns = [ path('user/', include('App.urls')) ]
-
App/urls.py
from django.urls import path from App.views import * urlpatterns = [ path('index/', index, name='index'), path('register/', register, name='register'), path('login/', login, name='login'), ]
(3)实现用户注册
-
修改 register.html
<body> <form method="post" action=""> <p>Username: <input type="text" name="un" /></p> <p>Password: <input type="password" name="pw" /></p> <p>Age: <input type="number", name="age" /></p> <p><button>Submit</button></p> </form> </body>
-
修改 views.py
def register(request): if request.method == 'GET': return render(request, 'register.html') elif request.method == 'POST': un = request.POST.get('un') pw = request.POST.get('pw') age = request.POST.get('age') return render(request, 'register.html')
-
启动项目并测试,出现 CSRF 相关错误时使用以下方法解决:
-
方法一:修改 settings.py(不推荐)
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', ]
-
方法二:修改 register.html
<form method="post" action=""> {% csrf_token %} <p>Username: <input type="text" name="un" /></p> <p>Password: <input type="password" name="pw" /></p> <p>Age: <input type="number", name="age" /></p> <p><button>Submit</button></p> </form>
-
-
实现注册功能:修改 views.py
def register(request): if request.method == 'GET': return render(request, 'register.html') elif request.method == 'POST': un = request.POST.get('un') pw = request.POST.get('pw') age = request.POST.get('age') users = UserModel.objects.filter(username=un) if users.exists(): return HttpResponse('该用户已存在') try: user = UserModel() user.username = un user.password = pw user.age = age user.save() except: return HttpResponse('注册失败') return redirect(reverse('login'))
(4)CSRF
- Cross Site Request Forgery,跨站请求伪造,指盗用身份后以该身份发送恶意请求
- 防止 CSRF
- Django 下的 CSRF 预防机制
- 在 POST 请求时,在表单中添加
{% csrf_token %}
(5)Cookie 实现登录功能
-
修改 login.html
<body> <form method="post" action=""> {% csrf_token %} <p>Username: <input type="text" name="un" /></p> <p>Password: <input type="password" name="pw" /></p> <p><button>Submit</button></p> </form> </body>
-
修改 views.py
def index(request): userid = request.COOKIES.get('userid', 0) user = UserModel.objects.filter(id=userid).first() return render(request, 'index.html', {'user': user}) def login(request): if request.method == 'GET': return render(request, 'login.html') elif request.method == 'POST': un = request.POST.get('un') pw = request.POST.get('pw') # 登录验证 users = UserModel.objects.filter(username=un, password=pw) if users.exists(): user = users.first() # 设置 Cookie response = redirect(reverse('index')) response.set_cookie('userid', user.id) return response def logout(request): # 删除 Cookie response = redirect(reverse('index')) response.delete_cookie('userid') return response
-
修改 index.html
<body> {% if user %} User: {{ user.username }} - {{ user.age }} <a href="{% url 'logout' %}">注销</a> {% else %} <a href="{% url 'login' %}">登录</a> <a href="{% url 'register' %}">注册</a> {% endif %} </body>
-
修改子路由
path('logout/', logout, name='logout')
(6)Session 实现登录功能
a. 启用 Session
-
服务器端会话技术 Session 依赖于 Cookie
-
Session 的数据存储到数据库中会进行 Base64 编码
-
每个 HttpRequest 对象都有一个类字典的 session属性
-
修改 settings.py 以启用 Session
INSTALLED_APPS = [ # ... 'django.contrib.sessions' ] MIDDLEWARE = [ # ... 'django.contrib.sessions.middleware.SessionMiddleware' ]
b. 基本操作
-
通过 request 设置 Sessions 值
request.session['user_id'] = user.id request.session.set_expiry(86400) # 设置过期时间
-
获取 Sessions 值
username = request.session.get("user_id") # or session_name = request.session["session_name"]
-
删除 Sessions 值
session_key = request.session.session_key del request.session[session_key] # or request.session.delete(session_key)
flush()
:删除当前的会话数据并删除会话的 Cookieclear()
:清除所有会话
c. 实现登录
-
修改 views.py
def index(request): userid = request.session.get('userid', 0) user = UserModel.objects.filter(id=userid).first() return render(request, 'index.html', {'user': user}) def login(request): if request.method == 'GET': return render(request, 'login.html') elif request.method == 'POST': un = request.POST.get('un') pw = request.POST.get('pw') # 登录验证 users = UserModel.objects.filter(username=un, password=pw) if users.exists(): user = users.first() # 设置 Session response = redirect(reverse('index')) request.session['userid'] = user.id return response def logout(request): session_key = request.session.session_key request.session.delete(session_key) return redirect(reverse('index'))
0x08 静态文件和媒体文件
静态文件:存放在服务器的 css、js、image等,一般称为 static
媒体文件:用户上传的文件,一般称为 media
(1)文件使用
a. 静态文件
-
修改 settings.py
INSTALLED_APPS = [ # ... 'django.contrib.staticfiles' ] # ... STATIC_URL = '/static/'
-
在 App/static 目录下存放静态文件
-
可以通过
STATICFILES_DIRS
来指定额外的静态文件搜索目录STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ]
-
-
在模板中使用 load 标签加载静态文件
{% load static %} <img src="{% static 'App/example.jpg' %}" />
b. 媒体文件
-
修改 settings.py
MEDIA_ROOT = BASE_DIR / 'static/uploads'
(2)文件上传
a. 单文件
-
修改 upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>上传文件</title> </head> <body> <form method="post" action="" enctype="multipart/form-data"> {% csrf_token %} <input type="file" name="myfile" /> <br /> <input type="submit" value="upload"> </form> </body> </html>
-
修改 views.py
def upload(request): if request.method == 'POST': myFile = request.FILES.get('myfile', None) file_name = '1.jpg' if not myFile: return HttpResponse("无文件上传") file_path = os.path.join(settings.MEDIA_ROOT, file_name) with open(file_path, 'ab') as fp: for part in myFile.chunks(): fp.write(part) fp.flush() # 将路径保存到数据库 user = UserModel() user.icon = 'uploads/' + file_name user.save() return render(request, 'upload.html')
b. 多文件
-
修改 upload.html
<input type="file" name="myfile" multiple />
-End-