目录
一、定制一个启动文件
二、创建一个类,用以封装model class
三、动态生成URL(一)
四、动态生成URL(二)
五、定制特殊的视图函数
六、预留URL的钩子函数
七、定制列表页面
八、定制添加页面
CURD也就是增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写。也就是自定制一个具有增删改查功能的后台管理组件。
一、定制一个启动文件
先创建一个app,名为stark,然后修改stark/apps.py
如下:
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
class StarkConfig(AppConfig):
name = 'stark'
def ready(self):
# 去在settings.py的INSTALLED_APPS中已经注册的APP目录下寻找stark.py文件并执行
autodiscover_modules('stark')
注意:在StarkConfig类中定义一个方法,名称必须为ready,这个ready()方法会覆盖掉父类AppConfig中的ready方法(),AppConfig类中的ready方法如下,是一个空的。
提示:在子类中覆盖此方法以在Django启动时运行代码。
然后再创建一个app,名为app01,再在app01目录下创建一个stark.py文件,在文件中随便写入一句代码
print('django启动自动执行stark.py')
注意:需要确保settings.py的INSTALLED_APPS中已经注册app01和stark这两个新建的app
INSTALLED_APPS = [
...
'stark.apps.StarkConfig',
'app01.apps.App01Config',
]
启动项目,就可以看到app01/stark.py被立即执行。
二、创建一个类,用以封装model class
在stark应用目录下新建一个目录service (目录名称可自定义),然后新建文件service/selfadmin.py
,内容如下:
from django.shortcuts import HttpResponse
from django.conf.urls import url
class StarkConfig(object):
'''用于被继承时,自定义一些方法''
def __init__(self,mcls):
'''mcls应该是一个model class'''
self.mcls = mcls
class StarkSite(object):
def __init__(self):
self._registry = {}
def register(self,model_class):
self._registry[model_class] = StarkConfig(model_class)
site = StarkSite()
register()
方法,将传入的model_class封装成字典,key就是model_class本身,value是StarkConfig
类的对象。这个对象在动态生成URL时会用到。
在app01/stark.py
中注册model class
from stark.service import selfadmin
from app01 import models
selfadmin.site.register(models.UserInfo)
selfadmin.site.register(models.Role)
而此时service/selfadmin.py
中的self._registry
就等于
{models.UserInfo: StarkConfig(models.UserInfo), models.Role: StarkConfig(models.Role)}
三、动态生成URL(一)
动态生成URL,是根据注册的model class不同生成不同的URL.
在service/selfadmin.py
的StarkSite
类中增加一个urls()方法:
from django.shortcuts import HttpResponse
from django.conf.urls import url
class StarkConfig(object):
def __init__(self,mcls):
'''mcls应该是一个model class'''
self.mcls = mcls
class StarkSite(object):
def __init__(self):
self._registry = {}
def register(self,model_class):
self._registry[model_class] = StarkConfig(model_class)
'''
此时self._registry = {models.UserInfo: StarkConfig(models.UserInfo)}
'''
@property
def urls(self):
patterns = []
for model_class,config_obj in self._registry.items():
# 假设第一个循环{models.UserInfo: StarkConfig(models.UserInfo)}进来
app_name = model_class._meta.app_label # 获取models.UserInfo所在的app名称
model_name = model_class._meta.model_name # 获取models.UserInfo的类名小写
temp = url(r'^%s/%s/' %(app_name,model_name), self.login)
patterns.append(temp)
return patterns,None,'stark'
def login(self,request):
return HttpResponse('登录页面')
site = StarkSite()
注意:
urls()
方法返回三个值用逗号隔开,就相当于返回一个元组,而路由分发时用到的include()
方法实际上也是返回一个元组,所以这里就用自定义的urls()
方法代替include()
方法。详见Include()的本质
urls.py中:
# 先导入自定义的selfadmin模块
from stark.service import selfadmin
urlpatterns = [
...
url(r'^stark/', selfadmin.site.urls),
]
由于步骤二中已经注册了models.UserInfo
和models.Role
两个model_class类,所以现在可以生成两个URL,分别是 /stark/app01/userinfo/和/stark/app01/role/
四、动态生成URL(二)
步骤三,只为每个model class生成了一个URL,要想做增删改查,就必须至少生成四个URL。那么就需要在步骤三中动态生成的 URL中再做一次子路由分发。
以models.Role类为例,需要生成如下4个URL:
- /stark/app01/role/
- /stark/app01/role/add/
- /stark/app01/role/1/change/
- /stark/app01/role/1/delete/
修改service/selfadmin.py文件中的StarkConfig
类如下:
from django.shortcuts import HttpResponse
from django.conf.urls import url
class StarkConfig(object):
def __init__(self,mcls):
'''mcls应该是一个model class'''
self.mcls = mcls
self.app_name = self.mcls._meta.app_label # 获取model class的app名称
self.model_name = self.mcls._meta.model_name # 获取model class的类名小写
# app名称和类名生成每个URL唯一的name,如下urls()方法中应用
@property
def urls(self):
patterns = [
url(r'^$', self.list_view, name='%s_%s_list' %(self.app_name,self.model_name,)),
url(r'^add/', self.add_view, name='%s_%s_add' %(self.app_name,self.model_name,)),
url(r'^(\d+)/change/', self.change_view, name='%s_%s_change' %(self.app_name,self.model_name,)),
url(r'^(\d+)/delete/', self.delete_view, name='%s_%s_delete' %(self.app_name,self.model_name,)),
]
return patterns, None, None
def list_view(self,request):
return HttpResponse('列表页面')
def add_view(self,request):
return HttpResponse('增加页面')
def change_view(self,request,id):
return HttpResponse('修改页面')
def delete_view(self,request,id):
return HttpResponse('删除页面')
为每个URL定义一个name,用以在视图函数中反向生成URL。
修改service/selfadmin.py文件中的StarkSite
类的urls()
方法,将
temp = url(r'^%s/%s/' %(app_name,model_name), self.login)
改为:
temp = url(r'^%s/%s/' %(app_name,model_name), config_obj.urls)
# 此处的config_obj就是StarkConfig类的对象,此对象具有urls方法。
这样就可以为每个注册的model class生成4个URL了。
五、定制特殊的视图函数
如果你在以后使用CRUD组件时,觉得已经写好的视图函数不能满足你的需求,这时,应该可以重写视图函数,并覆盖之前写好的视图函数。就需要这样来做:
将service/selfadmin.py
模块中的StarkSite
类的register
方法修改为下面这样:
class StarkSite(object):
def __init__(self):
self._registry = {}
def register(self,model_class,self_class_config=None):
if not self_class_config:
self_class_config = StarkConfig
self._registry[model_class] = self_class_config(model_class)
'''
当self_class_config没有传值时,self_class_config就等于StarkConfig
当self_class_config有值,就使用self_class_config自己,self_class_config应该是用户自定义的一个类,\
它继承了StarkConfig类。
'''
@property
def urls(self):
patterns = []
for model_class,config_obj in self._registry.items():
# 假设第一个循环{models.UserInfo: StarkConfig(models.UserInfo)}进来
app_name = model_class._meta.app_label # 获取models.UserInfo所在的app名称
model_name = model_class._meta.model_name # 获取models.UserInfo的类名小写
temp = url(r'^%s/%s/' %(app_name,model_name), config_obj.urls)
patterns.append(temp)
return patterns,None,'stark'
site = StarkSite()
注意:重新定义了
register()方法
:
- 当self_class_config没有传值时,self_class_config就等于StarkConfig;
- 当self_class_config有值,就使用self_class_config自己,self_class_config应该是用户自定义的一个类,它继承了StarkConfig类。
这时,如果你在app01/stark.py
中注册models.UserInfo
时,自己定义一个UserInfoConfig
类,并将其作为第2个参数传给register
方法,那UserInfoConfig
类中定义的属性和方法,只要与StarkConfig
类中定义的属性和方法名称一致,就会将StarkConfig
类中定义的属性和方法覆盖,这样就可以达到你自定制的效果。
这里只是用
models.UserInfo
来举例而已。
app01/stark.py
如下:
from stark.service import selfadmin
from app01 import models
from django.shortcuts import HttpResponse
class UserInfoConfig(selfadmin.StarkConfig):
def list_view(self,request):
return HttpResponse('自定制的用户列表页面')
selfadmin.site.register(models.UserInfo,UserInfoConfig)
注意:自定义的
UserInfoConfig
类必须要继承StarkConfig
类
六、预留URL的钩子函数
前面的代码,我们只是固定为每个注册的model class生成4个URL。如果,你有额外增加子URL的需求,就需要修改一下service/selfadmin.py
中的StarkConfig
类,如下:
from django.shortcuts import HttpResponse
from django.conf.urls import url
class StarkConfig(object):
def __init__(self,mcls):
'''mcls应该是一个model class'''
self.mcls = mcls
self.app_name = self.mcls._meta.app_label
@property
def urls(self):
patterns = [
url(r'^$', self.list_view, name='%s_%s_list' %(self.app_name,self.model_name,)),
url(r'^add/', self.add_view, name='%s_%s_add' %(self.app_name,self.model_name,)),
url(r'^(\d+)/change/', self.change_view, name='%s_%s_change' %(self.app_name,self.model_name,)),
url(r'^(\d+)/delete/', self.delete_view, name='%s_%s_delete' %(self.app_name,self.model_name,)),
]
patterns.extend(self.extra_urls())
return patterns, None, None
def extra_urls(self):
'''
自定制额外url的钩子函数
:return:
'''
return []
# ...其他方法不变,省略
注意:以上修改的代码中,新定义了一个extra_urls()方法,此方法返回一个空列表,并且在
urls()
中通过patterns.extend(self.extra_urls())
扩展新增的url。
然后在app01/stark.py
的自定制类UserInfoConfig中定义一个extra_urls()
方法去覆盖stark.service.selfadmin.StarkConfig.extra_urls
方法,如下:
from stark.service import selfadmin
from app01 import models
from django.shortcuts import HttpResponse
from django.conf.urls import url
class UserInfoConfig(selfadmin.StarkConfig):
def list_view(self,request):
return HttpResponse('自定制的用户列表页面')
def extra_urls(self):
patterns = [
url(r'^test/', self.test),
]
return patterns
def test(self,request):
return HttpResponse('test页面')
selfadmin.site.register(models.UserInfo,UserInfoConfig)
这样的话,注册的models.UserInfo
类,除了会生成增、删、改、查4个URL,还会额外生成一个test的URL:/stark/app01/userinfo/test/
七、定制列表页面
列表页面也就是将数据库里的数据读出来,以表格的形式展示到页面上。
修改stark/service/selfadmin.py的StarkConfig类的list_view()视图函数如下:
from django.shortcuts import HttpResponse,render
from django.conf.urls import url
from django.urls import reverse
class StarkConfig(object):
list_display = []
def __init__(self,mcls):
'''mcls应该是一个model class'''
self.mcls = mcls
self.app_name = self.mcls._meta.app_label # 获取model class的app名称
self.model_name = self.mcls._meta.model_name # 获取model class的类名小写
# app名称和类名生成每个URL唯一的name,如下urls()方法中应用
@property
def urls(self):
patterns = [
url(r'^$', self.list_view, name='%s_%s_list' %(self.app_name,self.model_name,)),
url(r'^add/', self.add_view, name='%s_%s_add' %(self.app_name,self.model_name,)),
url(r'^(\d+)/change/', self.change_view, name='%s_%s_change' %(self.app_name,self.model_name,)),
url(r'^(\d+)/delete/', self.delete_view, name='%s_%s_delete' %(self.app_name,self.model_name,)),
]
patterns.extend(self.extra_urls())
return patterns, None, None
def extra_urls(self):
'''
自定制额外url的钩子函数
:return:
'''
return []
def list_view(self,request):
obj_list = self.mcls.objects.all()
header_list = []
if self.list_display:
for item in self.list_display:
title = self.mcls._meta.get_field(item).verbose_name
header_list.append(title)
body_dict = {}
if self.list_display:
for obj in obj_list:
val_list = []
for item in self.list_display:
val = getattr(obj,item)
val_list.append(val)
body_dict[obj.id] = val_list
print(body_dict)
'''
需要将body_dict传给模板文件进行渲染,之所以要将每个表的id做为Key,是因为在修改和删除数据时,
前端页面需要回传id。没有id就不知道要修改或删除表中的哪条数据
'''
else:
for obj in obj_list:
val_list = [obj,]
body_dict[obj.id] = val_list
print(body_dict)
app_name = self.app_name
model_name = self.model_name
'''
app_name和model_name传给模板,用以拼接删除和修改按钮的URL
'''
add_url = reverse('stark:%s_%s_add' %(app_name,model_name,))
return render(request, 'stark/list.html', locals())
解析:
- StarkConfig类的list_display 默认是一个空列表,可以在继承StarkConfig类的子类中根据需求重新定义,列表中应该包含你想展示在页面上的字段;
- list_view()是列表页面的视图函数;
- self.mcls,如果注册的是models.UserInfo类,那么此时,
self.mcls = models.UserInfo
add_url = reverse('stark:%s_%s_add' %(app_name,model_name,))
此代码是反向生成URL,以注册models.UserInfo为例,生成的URL是/stark/app01/userinfo/add
app01/stark.py注册UserInfo类
from stark.service import selfadmin
from app01 import models
class UserInfoConfig(selfadmin.StarkConfig):
list_display = ['id','username','email']
selfadmin.site.register(models.UserInfo,UserInfoConfig)
列表页面的模板文件stark/list.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
</head>
<body>
<div class="container" style="margin-top: 30px;">
<div class="table-responsive">
<a href="{{ add_url }}" class="btn btn-primary">添加</a>
<table class="table table-bordered table-striped table-hover">
{% if header_list %}
<!-- 展示表格title -->
<thead>
<tr>
{% for header in header_list %}
<th>{{ header }}</th>
{% endfor %}
<th>操作</th>
</tr>
</thead>
{% endif %}
<tbody>
{% for id,body_list in body_dict.items %}
<!-- 循环出每行数据 -->
<tr>
{% for body in body_list %}
<!-- 循环出每行数据中的每个表格数据 -->
<td>{{ body }}</td>
{% endfor %}
<td>
<a href='/stark/{{ app_name }}/{{ model_name }}/{{ id }}/change/' class="btn btn-warning">修改</a>
<a href='/stark/{{ app_name }}/{{ model_name }}/{{ id }}/delete/' class="btn btn-danger">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
web页面列表展示:
八、定制添加页面
添加、修改页面是利用Django的ModelForm来生成form表单和表单数据验证。
编辑service/selfadmin.py
from django.shortcuts import HttpResponse,render,redirect
from django.conf.urls import url
from django.urls import reverse
from django.forms import ModelForm
class StarkConfig(object):
list_display = []
ModelFormCls= None
def __init__(self,mcls):
'''mcls应该是一个model class'''
self.mcls = mcls
self.app_name = self.mcls._meta.app_label # 获取model class的app名称
self.model_name = self.mcls._meta.model_name # 获取model class的类名小写
# app名称和类名生成每个URL唯一的name,如下urls()方法中应用
def get_modelform_cls(self):
if self.ModelFormCls:
return self.ModelFormCls
else:
class TempModelForm(ModelForm):
class Meta:
model = self.mcls
fields = "__all__"
return TempModelForm
def add_view(self,request):
ModelFormCls= self.get_modelform_cls()
forms = ModelFormCls()
if request.method == 'POST':
forms = ModelFormCls(request.POST)
if forms.is_valid():
forms.save()
name = 'stark:%s_%s_list' %(self.app_name,self.model_name,)
return redirect(reverse(name))
return render(request,'stark/add.html',locals())
解析:
modelform_cls = None
可以在继承StarkConfig类的子类中根据需求重新定义名为modelform_cls的类,用以覆盖默认值;get_model_form_cls()
方法判断用户是否自定制了modelform_cls类;有,就用用户自定制的modelform_cls类;没有,就用默认的TempModelForm类。
app01/stark.py注册UserInfo类:
from stark.service import selfadmin
from app01 import models
from django.shortcuts import HttpResponse
from django.conf.urls import url
from django.forms import ModelForm
class UserInfoConfig(selfadmin.StarkConfig):
list_display = ['id','username','email']
class ModelFormCls(ModelForm):
class Meta:
model = models.UserInfo
fields = ['id','username','password','email']
labels = {
'ip':'IP',
'username': '用户名',
'password': '密码',
'email': '邮箱',
}
# 注意model class
selfadmin.site.register(models.UserInfo,UserInfoConfig)
添加页面的模板文件stark/add.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
{% csrf_token %}
{% for form in forms %}
<p>{{ form.label }}: {{ form }} {{ form.errors.0 }}</p>
{% endfor %}
<p><input type="submit"></p>
</form>
</body>
</html>
添加页面展示效果: