供应链开发第七天
# 导言
接下来我们要做什么?! 做一个承上启下的工作.
整个供应链项目大致分为三部分进行开发:
1 part - 面向供应链的web端 --> 用户端(供应商)
2 part - 后台管理web端 --> 管理员使用
3 part - 小程序 or App --> 用户端(司机)
(´・Д・)」 现目前,基本完成了第一趴的工作, 接着开始后台管理的开发工作!!
关于后台管理的开发,会按照一下的思路进行学习:
step1: 学习Django本身权限相关的知识
- 说到Django权限,很容易想到Django已经自带权限校验功能了,为何我们通常在开发中不会使用它呢? 因为不是很适配.
- 武sir开源的组件: <自行找到以往的视频进行学习,暂略 ★学习后可"快速"的开发前后端不分离的CRM项目!!>
权限组件“比django内置的权限组件更通用些”、Starck组件“可快速的进行CURD”(Starck组件借鉴了Django admin的源码)
step2: drf + vue来实现权限管理
尽管,通常情况下,面向用户端的开发会选择用前后端分离,后台管理的开发往往会使用前后端不分离.
但我们还是得学习权限管理如何集成到 drf + vue 中!!
step3: 后端管理业务
eg:管理员的审核工作等
2
3
4
5
6
7
8
9
10
面试聊什么?
面试时的阐述,不能只是业务层面的CURD,更应该聊一些更深层次的东西.
你跟面试官说,你做了个短信登陆、做了个签合同,在面试官看来,没太大的意义/吸引力.
就已经完成的第一趴 供应商web端的开发而言,我们可以聊 drf源码的流程、你分析的过程以及对其进行的扩展(视图、返回值、异常等).
那么在即将学习的第二趴 后台管理. 我们也要有可以聊的东西!!
程序员阶段
- 学习
- 工作搬砖,进行CURD
- 自定义组件,用三五行代码来实现通用的功能,来尽可能的减少CURD这一类浪费时间对自己没有成长的工作
开发的项目越多,遇到的项目场景越多,组件不断的开发迭代就会越来越通用,这一过程就在培养我们 抽象+扩展 的能力!!
2
3
4
5
6
7
8
9
10
# 信号
本小节讲阐述 如何自定义信息, 以及 Django 内部已经给我们内置了哪些信息!!
插入一条数据,如何进行日志记录? -- 考察的就是你是否懂信号
# 自定义信号
后续的开发过程中, 使用自定义信号是比较少的! 但依旧需要进行了解.
学会自定义信号的意义何在? 自定义信号的应用场景不是没有,只是比较少.
比如:
消息通知的功能,函数1发短息、函数2发微信 、函数3发邮件
当有了信号后, 往后需要发送报警之类的信息时,无需自己去调用函数1、函数2、函数3,只需触发下信号,自动去调用
若不想发送邮件,直接注释掉 对函数2的注册回调 即可, 业务代码不会有任何的影响. 这样的设计,就是 "解藕" !!
# 内置信号
Django的开发者在Django源码里内置了很多的信号!
Django的开发者只是自定义很多信号, 并规定了哪些地方触发这些信号, 但并没有给这些信号注册回调函数.
注册回调的工作是留给我们做的!! 就像是给我们预留了 钩子 !!
# Django内置的信号
来看看Django内置了哪些信号!!
Model signals 执行数据库相关操作的信号
信号 | 何时触发 |
---|---|
pre_init | django的model执行其构造方法前, 自动触发 |
post_init | django的model执行其构造方法后, 自动触发 |
pre_save | django的model对象保存前, 自动触发 |
post_save | django的model对象保存后, 自动触发 |
pre_delete | django的model对象删除前, 自动触发 |
post_delete | django的model对象删除后, 自动触发 |
m2m_changed | django的model中使用m2m字段操作第三张表. (add,remove,clear) 前后, 自动触发 |
class_prepared | 程序启动时, 检测已注册的app中model类, 对于每一个类, 自动触发 |
Management signals 执行数据进行迁移时相关操作的信号
信号 | 何时触发 |
---|---|
pre_migrate | 执行migrate命令前, 自动触发 |
post_migrate | 执行migrate命令后, 自动触发 |
Request/response signals 请求响应时相关操作的信号.
PS: 在中间件里也可以操作, 与使用信号的方案解决不冲突, 只是方式方法不一样罢了!
信号 | 何时触发 |
---|---|
request_started | 请求到来前, 自动触发 |
request_finished | 请求结束后, 自动触发 |
got_request_exception | 请求异常后, 自动触发 |
Test signals 测试用例时相关操作的信号
信号 | 何时触发 |
---|---|
setting_changed | 使用test测试修改配置文件时, 自动触发 |
template_rendered | 使用test测试渲染模板时, 自动触发 |
Database Wrappers 数据库连接时相关操作的信号
信号 | 何时触发 |
---|---|
connection_created | 创建数据库连接时, 自动触发 |
from django.core.signals import request_finished
from django.core.signals import request_started
from django.core.signals import got_request_exception
from django.db.models.signals import class_prepared
from django.db.models.signals import pre_init, post_init
from django.db.models.signals import pre_save, post_save
from django.db.models.signals import pre_delete, post_delete
from django.db.models.signals import m2m_changed
from django.db.models.signals import pre_migrate, post_migrate
from django.test.signals import setting_changed
from django.test.signals import template_rendered
from django.db.backends.signals import connection_created
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 案例
举个例子: 在Django ORM创建数据时, 触发post_save信号!
# contenttypes
它是Django配置文件里已经注册好的app组件!!
'django.contrib.contenttypes',
别头铁,别上头, 不是说学习了contenttypes就说一定得用它, 它是存在效率问题的, 毕竟它要绕一下, 数据量大的话不建议!
可参考这两篇博客辅助理解 (很有用哦!!):
https://www.liujiangblog.com/course/django/281
,
https://www.liwenzhou.com/posts/Django/about_django_contenttypes/
看到没, 在进行数据库迁移后, contenttypes表中保存了 Django中注册的所有app以及每个app下有哪些表!!
# 表结构分析
现有一个需求, 课程分为学位课和普通课程, 每个课程根据服务周期不同收费标准也是不同的, 请设计出合适的表结构!
1:1 两表之间 1行对1行
-- A表的一行数据对应B表的一行数据
1:n 两表之间 1行对多行
-- A表的一行数据对应B表的多行数据
n:n 两表之间 多行对多行/相互的1行对多行
-- A表的一行数据对应B表的多行数据,B表的一行数据对应A表的多行数据 (比如书和作者)
-- 是会生成第三表的!!
ContentTypes
django_content_type表中的第1行的数据,对应C表的多行数据
django_content_type表中的第2行的数据,对应D表的多行数据
也就是一行表中存在 A:C=1:n A:D=1:n A:E=1:n 等多个1对多个关系!!
■ 方案一:
就是 一对多的关系, 多增添几张表!!
弊端 - 假设再来个 "其他课程表", 就需要再增加一个对应的价格策略表..
■ 方案二:
就是 将多张表的一对多的关系 写在一张表里
弊端 - 三个外键字段,它们必须允许为空.
并且在实际ORM操作中,必须注意,不能同时对这三个外键字段赋值,最多只能赋值一个!该表的数据很多的话,null值空数据也会很多!!
★ 这些外键字段通常只有一个有值,其他都是空的
■ 方案三:
就是 使用ContentTypes!!
ψ(`∇´)ψ 你思考下,方案二,我后续再添加其他类型的课程表,价格策略表里就需要同步添加外键字段.
而我使用方案三,添加了其他类型的课程表后,价格策略表我是不用改的!!
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
# 方案一
方案一, 就是 一对多的关系, 多增添几张表!!
class DegreeCourse(models.Model):
"""学位课表"""
name = models.CharField(max_length=128, unique=True)
class Course(models.Model):
"""普通课程表"""
name = models.CharField(max_length=128, unique=True)
class DegreeCoursePricePolicy(models.Model):
"""学位课的价格策略表"""
expiration_date = models.CharField(verbose_name="有效期", max_length=32)
price = models.IntegerField(verbose_name="价格")
degreecourse = models.ForeignKey(DegreeCourse, on_delete=models.CASCADE)
class DegreeCoursePricePolicy(models.Model):
"""普通课的价格策略表"""
expiration_date = models.CharField(verbose_name="有效期", max_length=32)
price = models.IntegerField(verbose_name="价格")
course = models.ForeignKey(Course, on_delete=models.CASCADE)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方案二
方案二, 将多张表的一对多的关系写在一张表里
class DegreeCourse(models.Model):
"""学位课表"""
name = models.CharField(max_length=128, unique=True)
class Course(models.Model):
"""普通课程表"""
name = models.CharField(max_length=128, unique=True)
class Other(models.Model):
"""其他课程表"""
name = models.CharField(max_length=128, unique=True)
class DegreeCoursePricePolicy(models.Model):
"""价格策略表"""
expiration_date = models.CharField(verbose_name="有效期", max_length=32)
price = models.IntegerField(verbose_name="价格")
degreecourse = models.ForeignKey(DegreeCourse, blank=True,null=True, on_delete=models.CASCADE)
course = models.ForeignKey(Course, blank=True,null=True, on_delete=models.CASCADE)
other = models.ForeignKey(Other, blank=True,null=True, on_delete=models.CASCADE)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方案三
方案三: 使用ContentTypes!! 核心: 哪张表的哪一行数据!/多张表的一对多关系写在了同一张表里!!
若我们不用django_content_type表.
那么在周期价格策略表的content_type_id这一列就会用设计表名列代替,就会有很多重复的表名值!!是不妥的!!
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
class DegreeCourse(models.Model):
"""学位课表"""
name = models.CharField(max_length=128, unique=True)
# 用于GenericForeignKey反向查询,不会生成表字段
degree_price_policy = GenericRelation("PricePolicy") # ★
class Course(models.Model):
"""普通课程表"""
name = models.CharField(max_length=128, unique=True)
# 用于GenericForeignKey反向查询,不会生成表字段
general_price_policy = GenericRelation("PricePolicy") # ★
class PricePolicy(models.Model):
"""每个课程不同有效期对应不同价格 表"""
expiration_date = models.CharField(verbose_name="有效期", max_length=32)
price = models.IntegerField(verbose_name="价格")
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # ★
object_id = models.PositiveIntegerField(verbose_name="content_type关联的那张表的某一行记录") # ★
# 不会生成表字段content_object.
# 写它是为了方便新增数据(直接设值为object_id所对应的记录/对象,会自动生成content_type和object_id)
content_object = GenericForeignKey('content_type', 'object_id') # ★
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
# 如何新增查询
实践是检验真理的唯一标准, 我们采用方案三进行实验!!
# 新增
迁移数据库后, 可使用脚本添加实验数据!!
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')
import django
django.setup()
from app01 import models
# -- 单表新增数据
models.DegreeCourse.objects.create(name="Python全栈")
models.DegreeCourse.objects.create(name="爬虫")
models.DegreeCourse.objects.create(name="Linux")
models.Course.objects.create(name="App逆向")
models.Course.objects.create(name="Django项目")
models.Course.objects.create(name="小程序")
# -- 给价格策略表添加数据 (手动自己去找content_type_id、object_id对应的值)
from django.contrib.contenttypes.models import ContentType
object_id = models.DegreeCourse.objects.get(name="Python全栈").pk
content_type_id = ContentType.objects.get(app_label="app01", model="degreecourse").pk
models.PricePolicy.objects.create(
content_type_id=content_type_id, # 7
object_id=object_id, # 1
expiration_date="3个月",
price=100
)
# -- 给价格策略表添加数据 (自动生成content_type_id、object_id对应的值)
models.PricePolicy.objects.create(
content_object=models.DegreeCourse.objects.get(name="Python全栈"),
expiration_date="6个月",
price=150
)
obj = models.DegreeCourse.objects.get(name="爬虫")
models.PricePolicy.objects.create(
content_object=obj,
expiration_date="3个月",
price=80
)
models.PricePolicy.objects.create(
content_object=models.DegreeCourse.objects.get(name="爬虫"),
expiration_date="6个月",
price=120
)
models.PricePolicy.objects.create(
content_object=models.Course.objects.get(name="App逆向"),
expiration_date="3个月",
price=10
)
models.PricePolicy.objects.create(
content_object=models.Course.objects.get(name="App逆向"),
expiration_date="6个月",
price=15
)
models.PricePolicy.objects.create(
content_object=models.Course.objects.get(name="Django项目"),
expiration_date="3个月",
price=8
)
models.PricePolicy.objects.create(
content_object=models.Course.objects.get(name="Django项目"),
expiration_date="6个月",
price=12
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 查询
正向 GenericForeignKey ; 反向 GenericRelation
问题1: 查询所有的价格策略 (正向 GenericForeignKey)
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')
import django
django.setup()
from app01 import models
# 查询所有的价格策略 (正向 GenericForeignKey)
queryset = models.PricePolicy.objects.all()
for obj in queryset:
print(
obj.id,
obj.expiration_date,
obj.price,
"||",
obj.content_object, "/",
obj.content_type.get_object_for_this_type(pk=obj.object_id),
"||",
obj.content_object.name, "/",
obj.content_type.get_object_for_this_type(pk=obj.object_id).name,
"||",
obj.content_type.model_class(),
obj.content_type.model_class().objects.get(pk=obj.object_id).name,
)
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
问题2: 想获取某个课程的所有价格策略 (反向 GenericRelation)
如果我们不用GenericRelation, 解决这个问题就很复杂.
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')
import django
django.setup()
from app01 import models
from django.contrib.contenttypes.models import ContentType
obj_id = models.DegreeCourse.objects.filter(name="Python全栈").first().pk
content_type_id = ContentType.objects.get(app_label="app01", model="degreecourse").pk
queryset = models.PricePolicy.objects.filter(object_id=obj_id, content_type=content_type_id)
print(queryset)
for obj in queryset:
print(obj.id, obj.expiration_date, obj.price)
"""
<QuerySet [<PricePolicy: PricePolicy object (1)>, <PricePolicy: PricePolicy object (2)>]>
1 3个月 100
2 6个月 150
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当我们用了GenericRelation, 就很方便快速的查询了!
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')
import django
django.setup()
obj = models.DegreeCourse.objects.filter(name="Python全栈").first()
queryset = obj.degree_price_policy.all()
print(queryset)
for obj in queryset:
print(obj.id, obj.expiration_date, obj.price)
"""
<QuerySet [<PricePolicy: PricePolicy object (1)>, <PricePolicy: PricePolicy object (2)>]>
1 3个月 100
2 6个月 150
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 举一反三
应用场景的举一反三
应用场景举一反三:
现在有一个论坛项目,用户可以在论坛里发布帖子或图片,用户当然还可以对帖子发表评论或者对图片发表评论!
=> User用户表 id name
Post帖子表 id title 外键user_id“表明哪个用户发布的帖子 不为空”
Picture图片表 id picture 外键user_id“表明哪个用户发布的图片 不为空”
Comment评论表
- 不妥的设计
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(User)
post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
picture = models.ForeignKey(Picture, null=True, blank=True, on_delete=models.CASCADE)
- 应该采用的设计
class Comment(models.Model):
content = models.TextField()
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType) # - 外键关联django_content_type表
object_id = models.PositiveIntegerField() # - 关联数据的主键
content_object = GenericForeignKey('content_type', 'object_id')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# admin
利用Django Admin提供的后台管理可以对Django中的ORM表快速进行增删改查!!
Django admin 通用性不够, 其使用的逻辑思维是面向程序员的, 自己用用还行, 不可能将其交付给用户..
所以Django admin的基本使用, 我们就不学了!! 哈哈哈哈. 看看它的源码是可以的.
前几年很火的xadmin现在也没什么人用了, 它停止了维护, 最新版本的Django好像也不兼容了!!
前后端不分离的后台, 权限部分我们使用rbac组件, 业务部分我们使用stark组件!! (*≧ω≦)
stark组件也是借鉴了 Django admin的源码, 学习下来受益匪浅!!
stark组件中像什么组合搜索、action批量操作等都更贴近用户, 比原来的django admin好太多啦!!
前后端不分离的stark组件其实对标的就是前后端分离的drf组件!! 都是为了快速开发业务..
前后端不分离的权限组件是rbac, 那么前后端分离的权限相关组件是怎样的呢?? 往后, 我们会开发出来的!!
# 快速使用
简单的康康admin的使用!!
◎ 创建ORM表并进行数据库迁移.
◎ 在admin.py中进行注册
from django.contrib import admin
from app01 import models
class DegreeCourseAdmin(admin.ModelAdmin):
list_display = ["id", "name"]
admin.site.register(models.DegreeCourse, DegreeCourseAdmin)
admin.site.register(models.Course)
admin.site.register(models.PricePolicy)
2
3
4
5
6
7
8
9
10
11
12
◎ 创建超级用户 python manage.py createsuperuser
dengchuan admin123!
◎ 开启admin相关的路由 path('admin/', admin.site.urls),
◎ 启动项目,登陆后台
# 源码分析
呀!!
# 基础URL和视图
基础URL的创建在admin的源码中是写死了的!!
分析源码得到的知识点: 文件的单例模式 + 延迟或懒加载 + include路由分发的本质(分析include源码和path源码可得出)
基础URL,例如,admin后台登陆和登出的URL:
http://127.0.0.1:8000/admin/login/
http://127.0.0.1:8000/admin/logout/
2
3
★ 基础URL的创建在admin的源码中是写死了的!!它是如何体现的呢?
PS: drf的request对象也应用了 __getattr__
的知识点!!
根据上面的截图分析, 我们可以得知 admin.site.urls
--> getattr(AdminSite(),"urls")
接下来,我们就去 AdminSite类中分析它的 urls 方法!! 看该方法到底返回的是什么!!
根据上面的截图分析, 我们可以得知 admin.site.urls
--> [一堆url],namespace,name
urlpatterns = [
path('admin/', admin.site.urls),
]
# -- 相当于 ---
urlpatterns = [
path('admin/', ([
path("", wrap(self.index), name="index"),
path("login/", self.login, name="login"),
path("logout/", wrap(self.logout), name="logout"),
], "admin", "admin"),
)
]
2
3
4
5
6
7
8
9
10
11
12
13
14
# 动态url和视图
我们知道admin组件会给注册admin的ORM表自动实现CURD的功能.. 背后怎么做的?!
先来看现象:
以Course表为例,admin组件为其生成了4个url,分别实现 列表页面、新增、修改、删除 的功能!
http://127.0.0.1:8000/admin/app01/course/
http://127.0.0.1:8000/admin/app01/course/add/
http://127.0.0.1:8000/admin/app01/course/3/change/
http://127.0.0.1:8000/admin/app01/course/3/delete/
- 根据上述url的表象分析其生成的规则:
/admin/ORM表所在的app的名字/ORM表的类名小写/
/admin/ORM表所在的app的名字/ORM表的类名小写/add/
/admin/ORM表所在的app的名字/ORM表的类名小写/\d+/change/
/admin/ORM表所在的app的名字/ORM表的类名小写/\d+/delete/
2
3
4
5
6
7
8
9
10
11
只要我们配置了ORM表并进行了admin注册,admin组件就会为该ORM表自动生成四个url和对应的视图函数, 是如何办到的呢?
接下来,我们分析admin的源码来分析它的实现原理!!
■ Step1: 项目启动,默认加载每个app中的admin.py文件!!
■ Step2: 下面就是项目中admin.py文件里的内容, 重点分析 register 方法!!
在前面我们已经分析出 admin.site.xxx 因为懒加载/延迟加载的缘故, xxx这个属性要去 AdminSite这个类的实例 中去找!!
from django.contrib import admin
from app01 import models
class DegreeCourseAdmin(admin.ModelAdmin):
list_display = ["id", "name"]
admin.site.register(models.DegreeCourse, DegreeCourseAdmin)
admin.site.register(models.Course)
admin.site.register(models.PricePolicy)
2
3
4
5
6
7
8
9
10
11
12
admin.site.xxx --> 去AdminSite()中找xxx..
执行上面admin.py的代码后 AdminSite()的_registry
的值是这样的:
AdminSite()._registry --> {
models.DegreeCourse : DegreeCourseAdmin(), # - 注意,DegreeCourseAdmin是继承ModelAdmin的!!
models.Course : admin.ModelAdmin(),
models.PricePolicy :admin.ModelAdmin(),
}
2
3
4
5
■ Step3: 再回到 路由匹配时的 admin.site.urls , 回顾下, 其本质就是 执行 AdminSite这个类 中的 get_urls 方法!!
在前面我们探究了该方法中 基本url的实现, 现探究该方法里 动态url的实现!!
# 伪代码总结
借此伪代码的总结, 狠狠的复习了下 武sir写的Stark组件 自动生成url的 代码!!
# -- 启动项目加载每个app目录下的admin.py
from django.contrib import admin
admin.site.register(models.Depart, admin.ModelAdmin)
admin.site.register(models.Info)
# -- django.contrib.admin文件的内容 (相较与源码,该处的伪代码去除了懒加载的实现)
class AdminSite: # ◆ 对标我们写的stark组件里的StarkSite类
def __init__(self):
self._registry = {}
def register(self, model, config_class=ModelAdmin): # 1.执行每个app目录下的admin.py内容时会调用它!!
self._registry[model] = config_class()
def get_urls(self):
# 基本路由
urlpatterns = [
path("login/", self.login, name="login"),
]
# 动态路由
for model, model_admin in self._register.items():
app_name = model._meta.app_label
model_name = model._meta.model_name
urlpatterns += [
path(f"{app_name}/{model_name}/",(model_admin.get_urls(),None,None)) # ★做了一层路由分发!!
]
return urlpatterns
@property
def urls(self): # 2.path('admin/', admin.site.urls) 路由匹配时会调用它!!
return self.get_urls(), None, None
def login(self):
pass
site = AdminSite()
class ModelAdmin: # ◆ 对标我们写的stark组件里的StarkHandler类
def get_urls(self):
return [
path("list",self.changelist_view()),
path("add",self.add_view()),
]
def changelist_view(self):
pass
def add_view(self):
pass
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
实现下懒加载Hhh 很简单嘛~
from django.utils.functional import LazyObject
class Person:
def do_something(self):
print("哈哈哈")
class DefaultAdminSite(LazyObject):
def _setup(self):
self._wrapped = Person()
obj = DefaultAdminSite()
obj.do_something()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# auth
来康康 Django 的auth组件!!
提供了两个功能:
1> 登陆时保存用户;
2> 对登陆Django Admin的用户的权限分配.
前面我们通过 python manage.py createsuperuser 创建了个超级管理员用户
超级管理员可以在后台创建普通用户,接着在后台手动给各个普通用户赋予不同的赋予权限!!
Ps: 超级管理员用户默认是拥有所有权限的,但可以在后台取消其超级管理员的资格: auth_user表记录的is_superuser字段的值从1变为0.
2
3
# Manage是什么
(´・Д・)」
简单来说, ORM表对应类中设置 objects = 某个类的实例
(eg: objects = UserManager(), 注,UserManager类肯定会继承 models.Manager),
那么 orm表.objects
时, 不仅有原本的all、filter等方法, 还可调用 UserManager 中的方法!!
不设置的话, 默认 ORM表对应类中的 objects 的值是 models.Manager !!
不难分析出,models.User.objects 里不仅有原本的all、filter等方法.
还有_create_user、create_user、create_superuser、with_perm、get_by_natural_key等方法!!
2
以往我们创建超级用户都是通过 python manage.py createsuperuser
命令来实现的, 我们学习了Manage后的原理后..
(´・Д・)」 可以还通过以下方式来创建用户:
# 权限处理机制
auth组件里对用户的权限是怎么玩的呢?
或者说 登陆Django admin后台, 访问当前登陆用户不具备权限的url是访问不了的, 是如何实现的呢?
不必深究, 了解其中大致的实现处理机制就行!! 我们写的rbac组件更好用,更通用,也可以移植到其它项目中!!
# 权限表内容
Django的权限体现在 auth_permission 这张表里!
Q: 该权限表里的数据从哪里来的? 里面的数据是什么?
■ 权限表里的是什么?
Django Admin帮助我们对一张表生成CURD的url
auth_permission表中codename字段值(内部根据url的namen转换了下)就是代指这些url!!
■ 权限表里的内容如何来的?
如何来的?涉及到信号!! auth组件的apps.py文件里 AuthConfig类里有个ready方法
-- 该ready方法里注册了信号 post_migrate, 执行migrate命令时自动触发该信号的执行!!
-- 该信号会执行create_permission函数!该函数干了啥?
- create_contenttypes(...)
"☆" 作用: 往django_content_type里写入数据.
- Permission.objects.using(using).bulk_create(perms)
"☆" 作用: Django会自动根据orm表对应的类生成CURD的别名,并写入auth_permission表中!!无需自己手动录入了.
2
3
4
5
6
7
8
9
10
11
12
# 分配权限
admin后台中: 可以给用户赋予权限,有哪些权限可供选择,就读取的是auth_permission表.
admin后台给用户分配权限又是如何办到的呢?
它将auth_permission表和auth_user表关联到了 auth_user_user_permission 表中!!
# 权限校验
在admin后台给用户分配权限后,admin组件的源码如何在访问url时判断当前登陆用户是否具有访问该url的权限的呀?!
Admin组件强耦合Auth组件了,它两通常是搭配着一起使用的.
由于admin为每个表生成的增删改查的方法分别是:
changelist_view
、add_view
、delete_view
、change_view
, 所以每个权限的判断都定义在了相应的视图函数中!
关键在于 has_perm 方法: 读取当前用户的所有权限,然后判断当前用户访问的url是否在当前用户所拥有的权限中!!
PS: 在admin后台还有组的概念, 给组分配权限, 再给用户分配组..
# 一些思考
(¯﹃¯)
Q: Django里已经有admin后台了,我们还有必要自己写登陆页面,登陆认证之类的吗?
A: 有必要!!
因为admin里,无论是权限还是登陆都针对的是后台里admin里的用户进行的处理!!
并且绑定的比较紧,auth权限只能应用到admin里!! 在admin里进行了校验判断,就那些curd函数, 提示词: has_perm.
你说我给用户分配好权限后,将该用户登陆我们自己的系统,那么我们得在每一个函数里加上是否有权限的校验,太麻烦了!!
So,我们不用admin组件!
Q: 能将auth组件的权限校验移植到我们的项目中吗?
A: 能!但太费劲!还不如自己写一个.因为auth组件跟admin组件的耦合绑定性太强了!
在app注册里,留了admin必须留auth!!
就auth组件生成的权限表的数据都是依据django admin根据orm表生成的curd来创建的!! 想要格外的,改生成的规则,这些实现起来都很费劲.
Q: 公司里使用admin组件多吗?
A: 不多!!简单的自己用的后台管理,自己用没问题; 开发给用户用的,都不行!!
Q: 能都将Django Admin的页面和vue结合?
A: 不行!! Django Admin的页面有大量的模版语法和模版引擎渲染的处理,是直接渲染的; vue是发生ajax请求后渲染的.
Q: 即然admin、auth都不咋用,那么今天的学习有何意义?
A: 1> 在之前我们创建纯净的Django项目,为何要注释掉那些代码就可以解释了!也知道了每个app都是干嘛的.
2> 知识点: 信号(面试时考察的还蛮多的)、manage(考察的较少)、contenttypes(增加了设计表结构的思路)
3> 了解了admin源码的流程,理解stark组件就会更得心应手.
4> 权限的设计思路,无需看源码,了解了大致的实现思路.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23