stark组件简单来说是帮助开发者快速实现数据库表的增删查改。
前期知识储备
django项目启动时,自定义执行某个py文件
定制一个功能,在django启动项目时,自动触发某个py文件的执行。django启动时,且在读取项目中路由之前执行某个py文件。
在django的每个应用下都有一个apps.py这个文件,在这个文件中的Config类中定义ready方法,并在ready方法中调用autodiscover_modules(‘文件名’)。
from django.utils.module_loading import autodiscover_modules
class RbacConfig(AppConfig):
name = 'rbac'
def ready(self):
autodiscover_modules('xxx')
django在启动时,就会去已注册的所有app的目录下找xxx.py文件,并自动导入(先在自己目录下查找,如果没有再去其他已注册app下查找)。因为django启动项目会执行已注册的apps.py的Config类的ready方法。
xxx.py文件
print('rbac 的xxx文件')
启动项目时输出的log
细心的已经发现了相同的log打印了两次,是因为django项目启动了两次。这是django的机制,有两个线程一个是启动项目,一个是检测代码是否有改变,当检测到代码改变时会自动重启项目。如果不想要这个功能在启动时执行python manage.py runserver 127.0.0.1:8081 --noreload
即可。
python模块导入特性
在python中如果已经导入过的文件再次被重新导入时,python不会重新解释一遍,而是选择从内存中直接将原来导入的值拿来使用。即模块导入多次,只解释执行一次。
可以通过这种方式来实现最简单的单例模式。
django路由分发的本质(include)
通过阅读include源码可以发现,include函数返回的时一个有三个元素的元组(urlconf_module, app_name, namespace)
,元组的第一个元素是从字符串变成app.urls的对象(即urls.py文件导入的对象),通过此对象可以获取urls.urlpatterns
获取分发的路由。
由此get到了路由分发的第二种编写方式:
from app01 import urls
urlpatterns = [
url(r'^web/' (urls, app_name, namespace))
]
在源码内部读取路由时,如果第一个参数有urls.urlpatterns
,那么子路由就从该属性中获取;如果第一个参数无urls.urlpatterns
属性,那么子路由就是第一个参数。
由此get到了路由分发的第三种编写方式:
from app01 import urls
urlpatterns = [
url(r'^web/' ([
url(r'^index/', veiws.index),
url(r'^home/', veiws.home),
], app_name, namespace))
]
知识点结合的小例子
新建一个django项目,其目录结构如下:
settings.py中注册两个app
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'app02.apps.App02Config',
]
利用模块实现的单例模式,在一个人文件中导入并写入内容,另一个文件中导入读取内容。
my_site.py
from django.conf.urls import url
from app01 import views
class StarkSite():
def __init__(self):
self._register = []
def get_urls(self):
patterns = []
for app in self._register:
patterns.append(url(r'{}/'.format(app), views.index))
return patterns
@property
def urls(self):
return (self.get_urls(), None, None)
site = StarkSite()
app01/xxx.py
from my_site import site
site._register.append('app1')
app02/xxx.py
from my_site import site
site._register.append('app2')
app01/apps.py
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
class App01Config(AppConfig):
name = 'app01'
def ready(self):
autodiscover_modules('xxx')
app02/apps.py
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
class App02Config(AppConfig):
name = 'app02'
def ready(self):
autodiscover_modules('xxx')
项目下的urls.py
from django.conf.urls import url
from django.contrib import admin
from my_site import site
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^web/', site.urls)
]
实现的功能时给web下的路由分发添加了app1,app2。
代码执行流程:
- 项目启动,顺序执行已注册app的AppxxConfig类的ready方法
- ready方法中的autodiscover_modules(‘xxx’)执行,从当前app目录下查找xxx.py文件,如果没有去其他已注册的app下查找,查找到这个文件进行导入
- 在app01导入xxx.py模块时,会解释执行xxx.py中的代码,在xxx.py中导入了
from my_site import site
,找到my_site.py这个文件,解释执行my_site.py文件,会产生一个StarkSite类的实例化对象并放在内存中,site指向这个内存地址。 - 向site对象中的__register添加分发的路由字符串值
- 在app02导入xxx.py模块时,解释执行xxx.py文件,在xxx.py中导入了`from my_site import site,在内存中发下这个文件已经导入过了,直接拿到对于内存地址,向site对象中的__register添加分发的路由字符串值
- 在app01下的xxx.py和app02下的xxx.py文件拿到的是同一个实例化对象,基于模块实现的单例模式,导入多次解释执行一次,在app01和app02均可以想这个对象中增加内容。
- 在项目下的url.py中调用site.urls就可以获取到一个url对象列表来进行路由分发
- 执行路由分发
自动生成url
对于app下的每个表都自动生成四个url,对应增删改查,有连个app,三个表其内容如下
app01/models.py
class Depart(models.Model):
title = models.CharField(verbose_name="部门名称", max_length=32)
def __str__(self):
return self.title
class UserInfo(models.Model):
name = models.CharField(verbose_name="用户名", max_length=32)
age = models.CharField(verbose_name="年龄", max_length=32)
mail = models.CharField(verbose_name="邮箱", max_length=32)
depart = models.ForeignKey(verbose_name="部门", to='Depart')
def __str__(self):
return self.name
app02/models.py
class Host(models.Model):
host = models.CharField(verbose_name="主机名", max_length=32)
ip = models.GenericIPAddressField(verbose_name='IP', protocol='both')
期望生成的url格式为:app名/类名小写/list/
/app01/depart/list/
/app01/depart/add/
/app01/depart/change/(?P<pk>\d+)/
/app01/depart/delete/(?P<pk>\d+)/
...
/app02/host/delete/(?P<pk>\d+)/
单例模式的py文件内容:stark/service/v1.py
from django.conf.urls import url
from django.shortcuts import HttpResponse
# 视图函数的基类,传递要操作的模型类
class StarkHandle(object):
def __init__(self, model_class):
self.model_class = model_class
print(self.model_class)
def change_list_view(self, request):
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('删除页面')
class StarkSite(object):
def __init__(self):
self._registry = []
self.name = 'stark'
self.namespace = 'stark'
def get_urls(self):
# 自动扫描self._registry中的内容,生成url对象列表
urls = []
for item in self._registry:
model_class = item.get('model_class')
handle = item.get('handle')
urls.append(url(r'^{}/{}/list/$'.format(model_class._meta.app_label, model_class._meta.model_name),
handle.change_list_view))
urls.append(url(r'^{}/{}/add/$'.format(model_class._meta.app_label, model_class._meta.model_name),
handle.add_view))
urls.append(url(r'^{}/{}/change/(?P<pk>\d+)/$'.format(model_class._meta.app_label, model_class._meta.model_name),
handle.change_view))
urls.append(url(r'^{}/{}/delete/(?P<pk>\d+)/$'.format(model_class._meta.app_label, model_class._meta.model_name),
handle.delete_view))
return urls
@property
def urls(self):
# 返回的是元组 ([url对象列表], name, namespace), 路由分发的第三种编写格式
return self.get_urls(), self.name, self.namespace
def register(self, model_class, handle_class=StarkHandle):
# 注册每个app下的模型类,和其操作模型类的handle类的实例化对象
self._registry.append({'model_class': model_class, 'handle': handle_class(model_class)})
site = StarkSite()
在app目录下创建stark.py,在app下的appConfig类中ready方法下autodiscover_modules(‘stark’),stark.py文件内容,向site中注册模型类和操作模型类的类
from stark.service.v1 import site, StarkHandle
# 用户可以继承StarkHandle类,重写其中的视图函数实现不同的独有的功能
class HostHandle(StarkHandle):
pass
site.register(Host, HostHandle)
from app01.models import Depart, UserInfo
from stark.service.v1 import site, StarkHandle
class DepartHandle(StarkHandle):
pass
site.register(UserInfo) # 使用默认的StarkHandle类,做处理url对应的视图函数类
site.register(Depart, DepartHandle)
在项目的urls.py中使用,自动生成的url
from django.conf.urls import url
from django.contrib import admin
from stark.service.v1 import site
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'stark/', site.urls)
]
效果:
为每个app下的每个表生成个增删查改四个url和其对于的视图函数
自动生成url的改进
增加用户自定义url前缀
1.改进分析:
在某些场景下用户需要对url增加一些前缀,如给list增加private前缀和publick前缀,生成两套url
stark/ ^app01/userinfo/private/list/$
stark/ ^app01/userinfo/publicl/ist/$
针对于不同人群展示不同的url信息的场景,要在注册时将用户自定制的前缀信息传入。
2.代码实现:
class StarkSite(object):
def get_urls(self):
urls = []
for item in self._registry:
model_class = item.get('model_class')
handle = item.get('handle')
prev = item.get('prev')
app_label, model_name = model_class._meta.app_label, model_class._meta.model_name
# 根据用户是否传入prev前缀,生成不同的url
if prev:
urls.append(url(r'^{}/{}/{}/list/$'.format(app_label, model_name, prev),
handle.change_list_view))
urls.append(url(r'^{}/{}/{}/add/$'.format(app_label, model_name, prev),
handle.add_view))
urls.append(url(r'^{}/{}/{}/change/(?P<pk>\d+)/$'.format(app_label, model_name, prev),
handle.change_view))
urls.append(url(r'^{}/{}/{}/delete/(?P<pk>\d+)/$'.format(app_label, model_name, prev),
handle.delete_view))
else:
urls.append(url(r'^{}/{}/list/$'.format(app_label, model_name),
handle.change_list_view))
urls.append(url(r'^{}/{}/add/$'.format(app_label, model_name),
handle.add_view))
urls.append(url(r'^{}/{}/change/(?P<pk>\d+)/$'.format(app_label, model_name),
handle.change_view))
urls.append(url(r'^{}/{}/delete/(?P<pk>\d+)/$'.format(app_label, model_name),
handle.delete_view))
return urls
def register(self, model_class, handle_class=StarkHandle, prev=None):
"""
:param model_class: models中数据库表对应的类
:param handle_class: 处理请求的视图函数所在的类
:param prev: 生成url的前缀
:return:
"""
self._registry.append({'model_class': model_class, 'handle': handle_class(model_class), 'prev': prev})
site = StarkSite()
注册代码:
site.register(UserInfo, prev='private') # 使用默认的StarkHandle类,做url对应的视图函数类
site.register(UserInfo, prev='public') # 为url添加不同的前缀,一个表可以生成多种url
3.效果展示:
对于userinfo生成了两套url
用户自定义url内容
改进分析:
在上述代码中,我们添加增删查改功能都是直接写死的
urls.append(url(r'^{}/{}/list/$'.format(app_label, model_name),
handle.change_list_view))
urls.append(url(r'^{}/{}/add/$'.format(app_label, model_name),
handle.add_view))
urls.append(url(r'^{}/{}/change/(?P<pk>\d+)/$'.format(app_label, model_name),
handle.change_view))
urls.append(url(r'^{}/{}/delete/(?P<pk>\d+)/$'.format(app_label, model_name),
handle.delete_view))
这样自由度不够,要支持用户可以自定制增(add)删(delete)查(list)改(change)的后缀,如将删除的url修改为/app01/depart/del/(?P<pk>\d+)/
。
代码实现:
在做一层路由分发,urls.append(url(r'^{}/{}/list/$'.format(app_label, model_name),handle.change_list_view))
修改为urls.append(url(r'^{}/{}/list/$'.format(app_label, model_name), ([], )))
,url对象列表可以由用户来定制。
from django.conf.urls import url
from django.shortcuts import HttpResponse
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
def __init__(self, model_class):
self.model_class = model_class
print(self.model_class)
def change_list_view(self, request):
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_urls(self):
# 可以使用默认的,用户也可以重写这个方法来自定制生成的url对象列表
patterns = [
url(r'^list/$', self.change_view),
url(r'^add/$', self.change_view),
url(r'^change/(?P<pk>\d+)/$', self.change_view),
url(r'^delete/(?P<pk>\d+)/$', self.delete_view),
]
return patterns
class StarkSite(object):
def get_urls(self):
urls = []
for item in self._registry:
model_class = item.get('model_class')
handle = item.get('handle')
prev = item.get('prev')
app_label, model_name = model_class._meta.app_label, model_class._meta.model_name
if prev:
# 在做一层路由分发, 路由分发函数由handle类中实现
urls.append(url(r'^{}/{}/{}/'.format(app_label, model_name, prev), (handle.get_urls(), None, None)))
else:
urls.append(url(r'^{}/{}/'.format(app_label, model_name), (handle.get_urls(), None, None)))
return urls
使用,app01/stark.py
from django.shortcuts import HttpResponse
from django.conf.urls import url
from app01.models import Depart, UserInfo
from stark.service.v1 import site, StarkHandle
class DepartHandle(StarkHandle):
def get_urls(self):
# 自定义这个参数,动态控制url的生成,在此函数中为Depart表生成了三个url,去掉了修改的功能
patterns = [
url(r'^list/$', self.change_view),
url(r'^add/$', self.change_view),
url(r'^del/(?P<pk>\d+)/$', self.delete_view),
]
return patterns
site.register(UserInfo, prev='private') # 使用默认的StarkHandle类,做url对应的视图函数类
site.register(UserInfo, prev='public') # 为url添加不同的前缀,一个表可以生成多种url
site.register(Depart, DepartHandle)
可以看到depart下的路由分发只有三个url,很方便的由用户来定制url
当用户要给表增加一个url时,我们需要将在原有的url的基础上再进行添加url,要添加一个用户对url进行添加的功能,增加一个extra_urls的方法,用户可以通过重写这个方法来新增url。
class StarkHandle(object):
def get_urls(self):
# 默认的提供给用户的url,增删查改
patterns = [
url(r'^list/$', self.change_view),
url(r'^add/$', self.change_view),
url(r'^change/(?P<pk>\d+)/$', self.change_view),
url(r'^delete/(?P<pk>\d+)/$', self.delete_view),
]
# 将扩展的url添加到url对象列表中
patterns.extend(self.extra_urls())
return patterns
def extra_urls(self):
# 扩展url
return []
使用
class DepartHandle(StarkHandle):
def extra_urls(self):
# 用户添加url, 并在类中实现对应视图函数
return [url(r'detail/(\d+)$', self.detail_view)]
def detail_view(self, request, pk):
return HttpResponse('详细页面')
site.register(Depart, DepartHandle)
为url设置别名
为了我们方便反向解析url,我们要为每个url设置别名,设置规格是每条url的别名都是不能重复的,并且每个url可以设置前缀,结合这两点我们的名字规则可以设置为:
- 有前缀的列表页url
app名字_模型类名小写_前缀名_list
- 无前缀的列表页url
app名字_模型类名小写_list
修改StarkHandle类,在初始化时接收prev前缀参数,定义property修饰的方法来获取url的别名。
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
def __init__(self, model_class, prev):
self.model_class = model_class
self.prev = prev
def change_list_view(self, request):
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 "{}_{}_{}_{}".format(app_label, model_name, self.prev, param)
return "{}_{}_{}".format(app_label, model_name, param)
@property
def get_list_url_name(self):
"""
获取列表页面url的name
:return:
"""
return self.get_url_name('list')
@property
def get_add_url_name(self):
"""
获取新增页面url的name
:return:
"""
return self.get_url_name('add')
@property
def get_change_url_name(self):
"""
获取修改页面url的name
:return:
"""
return self.get_url_name('change')
@property
def get_delete_url_name(self):
"""
获取删除页面url的name
:return:
"""
return self.get_url_name('delete')
def get_urls(self):
patterns = [
url(r'^list/$', self.change_view, name=self.get_list_url_name),
url(r'^add/$', self.change_view, name=self.get_add_url_name),
url(r'^change/(?P<pk>\d+)/$', self.change_view, name=self.get_change_url_name),
url(r'^delete/(?P<pk>\d+)/$', self.delete_view, name=self.get_delete_url_name),
]
patterns.extend(self.extra_urls())
return patterns
def extra_urls(self):
return []
修改注册的功能代码,在实例化StarkHandle类时传递prev前缀
class StarkSite(object):
def register(self, model_class, handle_class=StarkHandle, prev=None):
"""
:param model_class: models中数据库表对应的类
:param handle_class: 处理请求的视图函数所在的类
:param prev: 生成url的前缀
:return:
"""
self._registry.append({'model_class': model_class, 'handle': handle_class(model_class, prev), 'prev': prev})
使用:自动生成了带特定规则的别名
site.register(UserInfo, prev='private')
site.register(UserInfo)