DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • BBS

  • 订单平台

  • CRM

  • flask+layui

  • django+layui

  • 供应链

    • 供应链开发第一天
    • 供应链开发第二天
    • 供应链开发第三天
    • 供应链开发第四天
    • 供应链开发第五天
    • 供应链开发第六天
    • 供应链开发第七天
      • 导言
      • 信号
        • 自定义信号
        • 内置信号
        • Django内置的信号
        • 案例
      • contenttypes
        • 表结构分析
        • 方案一
        • 方案二
        • 方案三
        • 如何新增查询
        • 新增
        • 查询
        • 举一反三
      • admin
        • 快速使用
        • 源码分析
        • 基础URL和视图
        • 动态url和视图
        • 伪代码总结
      • auth
        • Manage是什么
        • 权限处理机制
        • 权限表内容
        • 分配权限
        • 权限校验
        • 一些思考
  • 实战
  • 供应链
DC
2024-01-01
目录

供应链开发第七天

# 导言

接下来我们要做什么?! 做一个承上启下的工作.

整个供应链项目大致分为三部分进行开发:
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:管理员的审核工作等
1
2
3
4
5
6
7
8
9
10

面试聊什么?

面试时的阐述,不能只是业务层面的CURD,更应该聊一些更深层次的东西.
你跟面试官说,你做了个短信登陆、做了个签合同,在面试官看来,没太大的意义/吸引力.
就已经完成的第一趴 供应商web端的开发而言,我们可以聊 drf源码的流程、你分析的过程以及对其进行的扩展(视图、返回值、异常等).
那么在即将学习的第二趴 后台管理. 我们也要有可以聊的东西!!

程序员阶段
- 学习
- 工作搬砖,进行CURD
- 自定义组件,用三五行代码来实现通用的功能,来尽可能的减少CURD这一类浪费时间对自己没有成长的工作
  开发的项目越多,遇到的项目场景越多,组件不断的开发迭代就会越来越通用,这一过程就在培养我们 抽象+扩展 的能力!!
1
2
3
4
5
6
7
8
9
10

# 信号

本小节讲阐述 如何自定义信息, 以及 Django 内部已经给我们内置了哪些信息!!

插入一条数据,如何进行日志记录? -- 考察的就是你是否懂信号

# 自定义信号

后续的开发过程中, 使用自定义信号是比较少的! 但依旧需要进行了解.

学会自定义信号的意义何在? 自定义信号的应用场景不是没有,只是比较少.
比如:
消息通知的功能,函数1发短息、函数2发微信 、函数3发邮件
当有了信号后, 往后需要发送报警之类的信息时,无需自己去调用函数1、函数2、函数3,只需触发下信号,自动去调用
若不想发送邮件,直接注释掉 对函数2的注册回调 即可, 业务代码不会有任何的影响. 这样的设计,就是 "解藕" !!

image-20240220185110975

# 内置信号

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 案例

举个例子: 在Django ORM创建数据时, 触发post_save信号!

image-20240220195607310


# 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下有哪些表!!

image-20240221120155022

# 表结构分析

现有一个需求, 课程分为学位课和普通课程, 每个课程根据服务周期不同收费标准也是不同的, 请设计出合适的表结构!

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!!

ψ(`∇´)ψ 你思考下,方案二,我后续再添加其他类型的课程表,价格策略表里就需要同步添加外键字段.
而我使用方案三,添加了其他类型的课程表后,价格策略表我是不用改的!!
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
# 方案一

方案一, 就是 一对多的关系, 多增添几张表!!

image-20240221190746193

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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方案二

方案二, 将多张表的一对多的关系写在一张表里

image-20240221191042021

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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方案三

方案三: 使用ContentTypes!! 核心: 哪张表的哪一行数据!/多张表的一对多关系写在了同一张表里!!

image-20240221193149125

若我们不用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')  # ★
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

# 如何新增查询

实践是检验真理的唯一标准, 我们采用方案三进行实验!!

# 新增

迁移数据库后, 可使用脚本添加实验数据!!

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
    )
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
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

image-20240221223626863

# 查询

正向 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,
        )
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

image-20240221224152793

问题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
"""
1
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
"""
1
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')
1
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)
1
2
3
4
5
6
7
8
9
10
11
12

◎ 创建超级用户 python manage.py createsuperuser dengchuan admin123!

image-20240510102748141

◎ 开启admin相关的路由 path('admin/', admin.site.urls),
◎ 启动项目,登陆后台

image-20240510102817062

# 源码分析

呀!!

# 基础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/
1
2
3

★ 基础URL的创建在admin的源码中是写死了的!!它是如何体现的呢?

image-20240510123442046

PS: drf的request对象也应用了 __getattr__ 的知识点!!

根据上面的截图分析, 我们可以得知 admin.site.urls --> getattr(AdminSite(),"urls")
接下来,我们就去 AdminSite类中分析它的 urls 方法!! 看该方法到底返回的是什么!!

image-20240510131035445

根据上面的截图分析, 我们可以得知 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"),
    )
]
1
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/
1
2
3
4
5
6
7
8
9
10
11

只要我们配置了ORM表并进行了admin注册,admin组件就会为该ORM表自动生成四个url和对应的视图函数, 是如何办到的呢?
接下来,我们分析admin的源码来分析它的实现原理!!

■ Step1: 项目启动,默认加载每个app中的admin.py文件!!

image-20240510163133670

■ 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)
1
2
3
4
5
6
7
8
9
10
11
12

image-20240510170140169

admin.site.xxx --> 去AdminSite()中找xxx..
执行上面admin.py的代码后 AdminSite()的_registry 的值是这样的:

AdminSite()._registry --> {
    models.DegreeCourse : DegreeCourseAdmin(),  # - 注意,DegreeCourseAdmin是继承ModelAdmin的!!
    models.Course : admin.ModelAdmin(),
    models.PricePolicy  :admin.ModelAdmin(),
}
1
2
3
4
5

■ Step3: 再回到 路由匹配时的 admin.site.urls , 回顾下, 其本质就是 执行 AdminSite这个类 中的 get_urls 方法!!  
在前面我们探究了该方法中 基本url的实现, 现探究该方法里 动态url的实现!!

image-20240510165825949

# 伪代码总结

借此伪代码的总结, 狠狠的复习了下 武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
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
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()
1
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.
1
2
3

# Manage是什么

(´・Д・)」

简单来说, ORM表对应类中设置 objects = 某个类的实例
(eg: objects = UserManager(), 注,UserManager类肯定会继承 models.Manager),
那么 orm表.objects 时, 不仅有原本的all、filter等方法, 还可调用 UserManager 中的方法!!
不设置的话, 默认 ORM表对应类中的 objects 的值是 models.Manager !!

image-20240510192640402

不难分析出,models.User.objects 里不仅有原本的all、filter等方法.
还有_create_user、create_user、create_superuser、with_perm、get_by_natural_key等方法!!
1
2

以往我们创建超级用户都是通过 python manage.py createsuperuser命令来实现的, 我们学习了Manage后的原理后..
(´・Д・)」 可以还通过以下方式来创建用户:

image-20240510193813962

# 权限处理机制

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表中!!无需自己手动录入了.
1
2
3
4
5
6
7
8
9
10
11
12

image-20240511164150213

# 分配权限

admin后台中: 可以给用户赋予权限,有哪些权限可供选择,就读取的是auth_permission表.

admin后台给用户分配权限又是如何办到的呢?
它将auth_permission表和auth_user表关联到了 auth_user_user_permission 表中!!

image-20240511164707064

# 权限校验

在admin后台给用户分配权限后,admin组件的源码如何在访问url时判断当前登陆用户是否具有访问该url的权限的呀?!

Admin组件强耦合Auth组件了,它两通常是搭配着一起使用的.

由于admin为每个表生成的增删改查的方法分别是: changelist_view、add_view、delete_view、change_view, 所以每个权限的判断都定义在了相应的视图函数中!

关键在于 has_perm 方法: 读取当前用户的所有权限,然后判断当前用户访问的url是否在当前用户所拥有的权限中!!

image-20240511171420852

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> 权限的设计思路,无需看源码,了解了大致的实现思路.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

供应链开发第六天

← 供应链开发第六天

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式