简介
manage命令
1 | # python manage.py <xxx> |
hello world
1 | ### urls.py 项目入口urls |
Model
数据库
- 数据库配置
1 | DATABASES = { |
- 生成/更新表结构
python manage.py makemigrations
生成迁移文件python manage.py migrate
执行迁移文件(数据库表结构会同步更新)- 不建议手动到数据库删除表
- 创建管理用户(保存在auth_user表中)
python manage.py createsuperuser --email admin@aezo.cn --username admin
(输入密码admin888)
- 创建权限组数据并添加用户
insert into "auth_group" ("id", "name") values (1, '管理员')
insert into "auth_user_groups" ("id", "user_id", "group_id") values (1, 1, 1)
表结构定义
- 字段通用属性
verbose_name
定义字段说明,admin模块界面显示的字段名。中文定义如:verbose_name=u'字段说明'
primary_key=True
为主键blank=True
admin会根据在创建数据库时的非空与否属性来确定字段是否必填(所有类型字段默认都是不为空的)。对于时间和数字,允许为空的条件需要blank=True,null=True
否则会引发错误null=True
可为空。字符串可以不初始化,但是在修改时不能为空字符串。如果需要为空字符串可加blank=Truechoices
可选值元组default
默认值
DateTimeField
、DateField
、TimeField
^1- 上述类型,其值分别对应着Python里的datetime.datetime、datetime.date和datetime.time三个实例
auto_now=True
这个参数的默认值为false,设置为true时,能够在保存该字段时,将其值设置为当前时间,并且每次修改model,都会自动更新。因此这个参数在需要存储最后修改时间的场景下,十分方便。需要注意的是,设置该参数为true时,并不简单地意味着字段的默认值为当前时间,而是指字段会被”强制”更新到当前时间(只有每次调用Model.save时才会自动更新,其他的如QuerySet.update是无法更新的),你无法程序中手动为字段赋值;如果使用django再带的admin管理器,那么该字段在admin中是只读的auto_now_add
设置为True时,会在model对象第一次被创建时,将字段的值设置为创建时的时间,以后修改对象时,字段的值不会再更新。该属性通常被用在存储创建时间的场景下。与auto_now类似,auto_now_add也具有强制性,一旦被设置为True,就无法在程序中手动为字段赋值,在admin中字段也会成为只读的default=django.utils.timezone.now
默认值为当前时间且再admin模块可修改- 上述3个默认时间属性都不会再数据库级别创建默认值
- 程序中手动修改字段值
party.update_time = django.utils.timezone.now()
,此时保存实体后,存储的时间类型为UTC时间(在数据库中直接查看是会比Asia/Shanghai晚8个小时,但是setting.py中设置了时区为Asia/Shanghai,则admin页面显示是正常的Asia/Shanghai时间) 时间处理
1
2
3
4
5
6
7
8
9
10
11
12from django.utils import timezone
import datetime
party.update_time # 2020-01-06 07:39:53.063158+00:00
party.update_time.now() # 2020-01-06 16:16:26.077810
party.update_time.now(timezone.get_current_timezone()) # 2020-01-06 16:16:26.077810+08:00
datetime.datetime.now() # 2020-01-06 16:16:26.077810
datetime.datetime.now(timezone.utc) # 2020-01-06 08:16:26.077810+00:00
if party.update_time and (datetime.datetime.now(timezone.utc) - party.update_time).total_seconds() < 600:
print('party最近10分钟更新过')
ForeignKey
、ManyToManyField
、OneToOneField
分别在Model中定义多对一(使用ForeignKey的表为子表),多对多,一对一关系。相关参数related_name
- father.child_set.all()。获取子表(子表Child中有一个外键,此时父表中默认会存储
子表_set
来获取子表;也可在子表中定义 related_name;如果子表中有某一个表的两个外键,则必须要定义related_name) - father.child_related_name.all()。child_related_name为子表中定义的 related_name 名称
- book = models.ForeignKey(Author, related_name=’child_related_name’, on_delete=models.CASCADE)
- father.child_set.all()。获取子表(子表Child中有一个外键,此时父表中默认会存储
db_column
定义外键生成的字段名on_delete
删除主表时,对子表的行为。ForeignKey必须CASCADE
删除作者信息一并删除作者名下的所有书的信息PROTECT
删除作者的信息时,采取保护机制,抛出错误:即不删除Books的内容SET_NULL
只有当null=True才将关联的内容置空SET_DEFAULT
设置为默认值SET()
括号里可以是函数,设置为自己定义的东西DO_NOTHING
字面的意思,啥也不干
db_constraint=False
数据库中不创建外键。但是django仍然可以通过外键属性获取关联对象
Meta
为每个mode的元数据定义类verbose_name_plural
可在admin模块显示此字段定义表别名
主键
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33# 自定义主键
class Party(models.Model):
# 手动定义主键名称
# party_id = models.AutoField(primary_key=True) # mysql长度为10位int类型,且为自增主键
# party_id = models.BigAutoField(primary_key=True) # mysql长度为20位bigint类型,且为自增主键
# party_id = models.BigIntegerField(primary_key=True) # mysql长度为20位bigint类型,无法自增需要手动赋值主键
# 未定义主键则默认创建主键名为id,类型11位的int,且为自增
name = models.CharField(max_length=20)
'''在types.py中定义:
party_source = (
('A', '来源一'),
('B', '来源二'),
)
'''
party_source = models.CharField(verbose_name=u'Party来源', choices=types.party_source, max_length=60)
is_gov = models.BooleanField(verbose_name=u'是否为政府机构', default=True) # mysql字段类型为tinyint
update_time = models.DateTimeField(verbose_name=u'最近更新时间', null=True, blank=True)
# 关于联合主键
class PartyRole(models.Model):
party = models.ForeignKey('Party', on_delete=models.CASCADE) # 此处同时使用了ForeignKey外键,见下文
role_type = models.ForeignKey('RoleType', on_delete=models.CASCADE)
class Meta:
db_table = 'biz_party_role' # 定义实际表名
verbose_name_plural = u'Party角色' # admin模块界面显示的字段名,默认显示表名
unique_together = ("party", "role_type") # django未实现联合主键, 只能通过组合唯一建校验
# 自定义权限
permissions = (
("publish_goods", "Can publish goods"),
("comment_goods", "Can comment goods"),
)外键/关联(使用物理外键,配合admin模块可更快实现增删改查功能)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34## 案例一:models.py
# Author必须定义在Books上面
class Author(models.Model):
author = models.CharField(max_length=250)
class Books(models.Model):
# book = models.ForeignKey('Author', on_delete=models.CASCADE) # 此时Author可以定义在Books下面。并且Author可以通过配置文件导入,否则需要在Books上面定义
# 默认外键名为`属性名_id`
book = models.ForeignKey(Author, on_delete=models.CASCADE)
# book = models.ForeignKey(Author, on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True) # 定义逻辑外键/软外键
# 自关联
parent_book = models.ForeignKey(to='self', on_delete=models.CASCADE)
## 案例二:models.py
class PartyRelationship(BaseModel):
"""当事人(个人/组织)角色关系,如雇佣关系/部门关系等(职位属于人事模块)"""
party_relationship_type = models.ForeignKey('PartyRelationshipType', on_delete=models.PROTECT)
party_id_from = models.ForeignKey('Party', db_column='party_id_from', related_name='party_relationship_from',
on_delete=models.CASCADE)
party_id_to = models.ForeignKey('Party', db_column='party_id_to', related_name='party_relationship_to',
on_delete=models.CASCADE)
role_type_id_from = models.ForeignKey('RoleType', db_column='role_type_id_from', related_name='party_relationship_from',
on_delete=models.PROTECT)
role_type_id_to = models.ForeignKey('RoleType', db_column='role_type_id_to', related_name='party_relationship_to',
on_delete=models.PROTECT)
from_date = models.DateTimeField(default=timezone.now)
thru_date = models.DateTimeField(verbose_name=u'过期时间', null=True, blank=True)
class Meta:
db_table = 'biz_party_relationship'
# django未实现联合主键, 只能通过组合唯一建校验
unique_together = ("party_id_from", "party_id_to", "role_type_id_from", "role_type_id_to", "from_date")
verbose_name_plural = u'Party角色关系'
CRUD
基本增删改
1 | from api.test import models |
进阶查询
1 | from api.test import models |
联表查询
1 | from api.test import models |
QuerySet
- 从数据库中查询(all/filter)出来的结果一般是一个集合,这个集合叫做
QuerySet
^3 - 如果只是检查 MyUser 中是否有对象,应该用
models.MyUser.objects.all().exists()
- 推荐用
models.MyUser.objects.count()
来查询数量,而不是用len(users)
,前者用的是SQL为SELECT COUNT(*)
更高效 list(users)
可以强行将 QuerySet 变成列表- 案例
1 | # 参考:https://code.ziqiangxuetang.com/django/django-queryset-api.html |
- 进阶案例 ^4
1 | # 参考:https://code.ziqiangxuetang.com/django/django-queryset-advance.html |
自带App
admin
admin.py 快速生成管理界面
- 应用注册:在
setting.py
的INSTALLED_APPS
中加入此应用apps.monitor
admin.py
必须写在每个app对应根目录下 ^2
1 | ## party/models.py(模型) |
- 自定义筛选类
1 | ## api/admin.py |
- 一对多字段编辑(主子表一起编辑)
1 | from django.contrib import admin |
- ManyToMany多对多字段编辑
- 可以用filter_horizontal或filter_vertical定义字段
添加动作(操作函数)到actions/列表行增加操作按钮
1 | class ProjectAdmin(admin.ModelAdmin): |
自定义用户登录操作
1 | ## setting.py |
自定义Django后台名称和favicon图标
- 修改默认的Django标题 ^5
1 | from django.views.generic import RedirectView |
扩展主题
django.contrib.contenttypes
源码参考【A02_DjangoTest】
- 主要用于一张表A和多张表关联,表A中一个字段用于存其他的表名,另外一个字段用于存储其他表的主键
- 示例
1 | ## models.py |
django中间件(MIDDLEWARE)
- 中间件类型:
process_request
、process_view
、process_response
、process_exception
、process_render_template
- 中间件执行顺序:用户请求 -> 经过所有process_request -> 视图函数 -> 经过所有process_view
- CSRF(Cross-site request forgery)跨站请求伪造
- 全站开启或关闭,看中间件配置中是否有:’django.middleware.csrf.CsrfViewMiddleware’
- 在process_view中检查视图是否有装饰器
@crsf_exempt
(全局有csrf时,此函数不考虑csrf)、@crsf_protect
(全局无时,此函数考虑csrf) - 需要csrf检查时,从前请求体或cookies中获取token
- 在csrf存在时,且未CBV模式下,去掉某个url的csrf功能,有两种方式
- View子类上加
@method_decorator(csrf_exempt, name='dispatch')
- View子类重写dispatch方法,并在dispatch方法上加
@method_decorator(csrf_exempt)
(加在其他方法上无效)
- View子类上加
- 使用时在html的表单中加入 {%csrf_token%}
异常处理
- 覆盖500/404等异常展示:直接在templates目录中加404.html即可覆盖默认页面显示
- 自定义异常执行方法
1 | # django自定义错误返回处理方法 |
配置
1 | ### setting.py |
杂项
静态资源
django在配置文件中设置
DEBUG = False
后静态资源404问题。解决方式如以下几种:
python manage.py runserver --insecure
启动加参数--insecure
(正式环境中不建议)- 解决方法
1 | ## 在项目目录创建static文件 |
请求对象
request.POST['username']
或者request.POST.get('username')
获取普通input的值。参考python字典取值- 如果传递过来的数值不为空,那么这两种方法都没有错误,可以得到相同的结果。但是如果传递过来的数值为空,那么
request.POST['username']
则会提示Keyerror错误,而request.POST.get('username')
则不会报错,而是返回一个None - request.POST.get(‘username’, ‘default_value’)
request.GET
同理
- 如果传递过来的数值不为空,那么这两种方法都没有错误,可以得到相同的结果。但是如果传递过来的数值为空,那么
request.POST.getlist('hobby')
获取checkbox的值username = self.request.query_params.get('username', None)
获取url参数
请求生命周期
发送邮件
1 | ## settings.py |
记录日志
- 脚本中使用
1 | import logging # python内置库 |
- settings.py
1 | import time |
模块打包成python包
pip install setuptools
安装打包工具python setup.py sdist
执行打包pip install --user django-polls/dist/django-polls-0.1.tar.gz
基于用户安装django-polls
, 如果基于virtualenv
安装允许同时运行多个相互独立的Python环境,每个环境都有各自库和应用包命名空间的拷贝pip list
查看包列表pip uninstall django-polls
卸载包
FBV/CBV模式
- 函数作为视图或类作为视图,源码参考【A02_DjangoTest】
1 | # FBV: function base view |
docker发布
project-root/Dockerfile
举例
1 | FROM python:3.6 |
- setting.py
1 | DEBUG = bool(os.getenv('DJANGO_DEBUG', True)) |
- 编译
docker build --rm -t pybiz:1.0.0 -f ./Dockerfile .
- k8s-helm相关配置,其他参考Chart说明
1 | ## values.yaml |
- 继承
1 | # 多继承(优先级从左到右),寻找自身属性或方法 -> 寻找最左边父类的属性或方法 -> 寻找第二个父类的属性或方法 -> ... |
- 反射(
getattr
)
1 | class StudentsView(object): |
参考文章