自动生成
Bingo!! Stark组件, 是一个帮助开发者快速实现数据库表增删改查的组件!! (*≧ω≦)
# 储备知识
先来学习三个知识点!
1> Django项目启动时, 路由加载之前自定义执行某个py文件
2> 单例模式.
3> Django include路由分发的本质
# 执行文件
目标: Django [项目启动时], 在 [路由加载之前] 自定义执行某个py文件
在任意app的apps.py中的Config类中定义ready方法, 并在该方法中调用 autodiscover_modules !!
Django在启动时, 就会去 已注册 的所有app的目录下 找 xxx.py 文件并导入!! (发生在路由加载之前.
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
class App01Config(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app01'
def ready(self):
autodiscover_modules('xxx')
2
3
4
5
6
7
8
9
10
进行验证:
Ps: 截图中使用的是 命令行启动项目的, 并加上了--noreload
. 不加它, 结果就会打印两次.
这是因为, Django默认在启动时 启动了两个线程,一个在运行项目,一个在检测项目是否发生变化!!
思考下,为啥这么配置就可以了安? 这出自Django的源码!简单说一哈.
从manage.py开始,一直command+B跳转
- execute_from_command_line(sys.argv) -- To execute_from_command_line
- utility.execute() -- To execute
- django.setup() -- To setup
- apps.populate(settings.INSTALLED_APPS) -- To populate
- 关键代码找到啦!!
# Phase 3: run ready() methods of app configs.
"""
代入截图的示例中,self.get_app_configs()就是 app01.App01Config、app02.App02Config
然后执行它们下面的ready方法!!
"""
for app_config in self.get_app_configs():
app_config.ready()
# 再来看看我们在ready方法里调用的autodiscover_modules的源码!!
def autodiscover_modules(*args, **kwargs):
from django.apps import apps
register_to = kwargs.get('register_to')
for app_config in apps.get_app_configs():
for module_to_search in args:
try:
if register_to:
before_import_registry = copy.copy(register_to._registry)
# ★★ 关键在这!!
# "app01.xxx" "app02.xxx" 然后import_module以字符串的形式导入该模块!
# 相当于 from app01 import xxx 、from app02 import xxx
import_module('%s.%s' % (app_config.name, module_to_search))
except Exception:
if register_to:
register_to._registry = before_import_registry
if module_has_submodule(app_config.module, module_to_search):
raise
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
提示: 有何用呢?
如果xxx.py执行的代码往 "某个神奇的地方" 放入了一些值, 之后路由加载时, 可以去 "某个神奇的地方" 获取那些值!!
# 单例模式
最最常见的设计模式!!
★ Python模块导入的特性:
在Python中,如果已经导入过的文件再次被重新导入时候,python不会再重新解释一遍,而是选择从內存中直接将原来导入的值拿来用!!
★ 应用:
如果以后存在一个单例模式的对象,可以先在此对象中放入一个值,然后再在其他的文件中导入该对象,通过对象再次将值获取到!!
Ps: 在以前的博客笔记中, python元类知识那还讲解了 单例模式的其他几种实现和应用!! 需要则自取.
# include本质
经过源码的分析, 可得知 有三种书写的方式!! 这三种方式都是等效的!!
注意区分 截图中 文件对象/模块获取分发的路由关系列表; URLResolver对象获取分发的路由关系列表 !!
方式一:
from django.urls import path, include
urlpatterns = [
path('web/', include("app01.urls")),
]
2
3
4
5
方式二:
from django.urls import path
from app01 import urls
urlpatterns = [
path('web/', (urls, None, None)),
]
2
3
4
5
6
7
方式三:
from django.urls import path
from app01 import views
urlpatterns = [
path('web/', ([
path('index/', views.index),
path('home/', views.home),
], None, None)),
]
2
3
4
5
6
7
8
9
# 小示例
将上述三个知识点 应用到一个小示例中, 加深理解!!
代码截图如下:
其他:
eg: model_class = models.UserInfo
model_class._meta.app_label 获取当前model类所在的app名称
model_class._meta.model_name 获取当前model类的表名称
model_class._meta.get_field("name") 获取当前model类中的某个字段对象!!
model_class._meta.get_field("name").verbose_name 获取当前model类中的某个字段对象的verbose_name属性!!
2
3
4
5
# 准备阶段
创建项目 + 创建基础的业务表
■ 新建一个python项目dc_stark + 利用pycharm构建虚拟环境
pip install Django==3.2 -i https://pypi.tuna.tsinghua.edu.cn/simple
django-admin startproject dc_stark .
python manage.py startapp app01
python manage.py startapp app02
python manage.py startapp stark
- 项目根目录下创建apps文件,将app01放到里面
- 记得将 apps/app01/apps.py 里的 name = 'app01' 改为 name = 'apps.app01'
- 注册app 'apps.app01.apps.App01Config',
<app02、stark同理!!>
■ 创建基础的业务表
apps/app01/models.py
- Depart部门表
- title
- UserInfo用户表
- name、age、email、外键depart
apps/app02/models.py
- Host主机表
- host、ip
记得进行数据库的迁移!!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 自动生成
目标: 为app中的每个model类自动创建URL以及相关视图函数!!
需求: 对"准备阶段"的三张表做增删改查..
传统的解决方式是, 为每张表创建4个url + 为每张表创建4个视图函数!! 随着表的增多, 这种重复性的工作也会增多!!
我们需要利用 "储备知识" 小节 所涉及的 知识点 , 解决这些重复性的工作, 帮我们自动生成url和视图函数!!
apps/app01/models.py
- Depart部门表
/app01/depart/list/
/app01/depart/add/
/app01/depart/edit/(\d+)/
/app01/depart/del/(\d+)/
- UserInfo用户表
/app01/userinfo/list/
/app01/userinfo/add/
/app01/userinfo/edit/(\d+)/
/app01/userinfo/del/(\d+)/
apps/app02/models.py
- Host主机表
/app02/host/list/
/app02/host/add/
/app02/host/edit/(\d+)/
/app02/host/del/(\d+)/
■ 我们约定俗成:
- 默认 app名称/表名称/CURD/
- 前缀 app名称/表名称/前缀/CURD/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 自动生成URL
(*≧ω≦)
关键代码如下:
model_class._meta.app_label
获取当前model类所在的app名称;
model_class._meta.model_name
获取当前model类的表名称.
验证下结果:
# 视图函数提取到基类
在上面自动生成的url代码中, 你会发现 每张表4个url 对应的那 4个视图函数都是 重复编写的!!
我们要解决这个问题!!
# 回顾OOP小知识
简单复习下 OOP 中的 封装和继承!!
面向对象的封装
class Foo(object):
def __init__(self, name):
self.name = name # 给对象封装一些属性
def show_detail(self): # ★调用该方法时,self是不一样的!!
msg = "我叫%s,来自于地球." % self.name
print(msg)
obj1 = Foo('dc')
obj2 = Foo('武沛齐')
obj3 = Foo('珊珊')
obj1.show_detail() # 我叫dc,来自地球.
obj2.show_detail() # 我叫武沛齐,来自地球.
obj3.show_detail() # 我叫珊珊,来自地球.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
面向对象的继承
class Base: # 该类是父类/基类,放一些公共的方法!!
def __init__(self, name):
self.name = name
def show_detail(self):
msg = "我叫%s,来自于地球." % self.name
print(msg)
class Foo(Base):
def show_detail(self): # 重写!
msg = "我叫%s,来自于月球." % self.name
print(msg)
class Bar(Base):
pass
obj1 = Foo('dc')
obj1.show_detail() # 我叫dc,来自月球.
obj2 = Bar('武沛齐') # 我叫武沛齐,来自地球.
obj2.show_detail()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 开始封装和继承
利用封装和继承来解决 重复编写的视图代码!!
关键代码如下:
# 细节优化
共3点优化
# 使用默认的handler
你可以发现,上面的代码,自己写一个视图函数相关的handler类,继承后,类体代码就是pass.
从代码执行逻辑以及最终效果上思考, 跟执行使用 父类/基类 StarkHandler,没什么不同!!
So, 当类体代码是pass时, 我们应该支持省略该handler类, 直接使用默认的handler, 即StarkHandler!!
技术要点: 使用默认参数!
# url支持前缀
生成的url支持加前缀
# ★ url的分发扩展
按照已有的代码, 每个model类默认生成了4个url 写死了, 这样不好! 我们应该支持 url的增加和减少!!
▲ 示例中实现了 "额外的增加URL" 、 "在原默认的4个url上只使用其中某几个URL"
PS: 其实还可以在同一个Handler实现 额外的+4个默认中的其中几个. Hhh. 分析下代码就明白啦!!
技术要点: 多层的路由分发!!
我认为最为精髓的一点的是, 在原本的路由分发的基础上,又做了一层路由分发, 最后, url的掌控权就 集中在了 handler里啦!!
还有一点!! 务必理清楚 截图中 红色线的标注 关于self是谁, 这很重要!这很重要!!这很重要!!!
# url别名
为url设置别名后, 方便以后反向生成!! 这非常重要!!
# 普通版
有无前缀的name设置 的代码 看起来有点冗余, 需要进行处理下!!
# ★ 优化版
ψ(`∇´)ψ 以后反向生成的name都不用我们自己写了, 直接调用对应的方法就能获取到name!!
在普通版的基础上修改下, StarkHandler 类 即可, 其余的不用变!!
# 附录
代码如下:
apps/stark/apps.py
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
class StarkConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.stark'
def ready(self):
autodiscover_modules('stark')
2
3
4
5
6
7
8
9
10
apps/app01/stark.py
from django.shortcuts import HttpResponse
from django.urls import path
from apps.app01 import models
from apps.stark.service.v1 import site, StarkHandler
class DepartHandler(StarkHandler):
def extra_urls(self):
"""额外的增加URL"""
return [
path('detail/<int:pk>/', self.detail_view)
]
def detail_view(self, request, pk):
return HttpResponse('详细页面')
class UserInfoHandler(StarkHandler):
def get_urls(self):
"""在原默认的4个url上只使用其中某几个URL"""
patterns = [
path("edit/<int:pk>/", self.change_view),
path("del/<int:pk>/", self.delete_view),
]
return patterns
site.register(models.Depart, DepartHandler)
site.register(models.UserInfo, UserInfoHandler)
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
apps/app02/stark.py
from apps.app02 import models
from apps.stark.service.v1 import site
site.register(models.Host)
site.register(models.Host, prev="yy")
2
3
4
5
apps/stark/service/v1.py
from django.http import HttpResponse
from django.urls import path
class StarkHandler:
def __init__(self, model_class, prev):
self.model_class = model_class
self.prev = prev
def changelist_view(self, request):
"""列表页面"""
data_list = self.model_class.objects.all()
return HttpResponse('列表页面')
def add_view(self, request):
"""添加页面"""
return HttpResponse('添加页面')
def change_view(self, request, pk):
"""编辑页面"""
return HttpResponse('编辑页面')
def delete_view(self, request, pk):
"""删除页面"""
return HttpResponse('删除页面')
def get_url_name(self, param):
app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
if self.prev:
return f"{app_label}_{model_name}_{self.prev}_{param}"
else:
return f"{app_label}_{model_name}_{param}"
@property
def get_list_url_name(self):
"""获取列表页面URL的name"""
return self.get_url_name("list")
@property
def get_add_url_name(self):
"""获取新增页面URL的name"""
return self.get_url_name("add")
@property
def get_change_url_name(self):
"""获取修改页面URL的name"""
return self.get_url_name("change")
@property
def get_delete_url_name(self):
"""获取删除页面URL的name"""
return self.get_url_name("delete")
def get_urls(self):
patterns = [
path("list/", self.changelist_view, name=self.get_list_url_name),
path("add/", self.add_view, name=self.get_add_url_name),
path("edit/<int:pk>/", self.change_view, name=self.get_change_url_name),
path("del/<int:pk>/", self.delete_view, name=self.get_delete_url_name),
]
patterns.extend(self.extra_urls())
return patterns
def extra_urls(self):
return []
class StarkSite:
def __init__(self):
self._registry = []
self.app_name = "stark"
self.namespace = "stark"
def register(self, model_class, handler_class=None, prev=None):
"""
:param model_class: models中数据库中对应的类!
:param handler_class: 处理请求的视图函数所在的类!
:param prev: 生成的url的前缀
:return:
"""
if not handler_class:
handler_class = StarkHandler
self._registry.append({
"model_class": model_class,
"handler": handler_class(model_class, prev),
"prev": prev,
})
def get_urls(self):
patterns = []
for item in self._registry:
model_class = item['model_class']
handler = item["handler"]
prev = item["prev"]
app_label, model_name = model_class._meta.app_label, model_class._meta.model_name
if prev:
patterns.append(path(f"{app_label}/{model_name}/{prev}/", (handler.get_urls(), None, None)))
else:
patterns.append(path(f"{app_label}/{model_name}/", (handler.get_urls(), None, None)))
return patterns
@property
def urls(self):
return self.get_urls(), self.app_name, self.namespace
site = StarkSite()
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
dc_stark/urls.py
# from django.contrib import admin
from django.urls import path
from apps.stark.service.v1 import site
urlpatterns = [
# path('admin/', admin.site.urls),
path('stark/', site.urls),
]
2
3
4
5
6
7
8