诉求
仿照admin组件,实现对表的URL分配管理。
实现思路
1.在settings.py文件中注册APP,注册示例为:
'app01.apps.App01Config',
'app02.apps.App02Config',
‘stark_demo.apps.StarkDemoConfig',
2. 在每一个APP中的apps.py 文件中添加
1 from django.apps import AppConfig
2 from django.utils.module_loading import autodiscover_modules
3
4 class App01Config(AppConfig):
5 name = 'app01'
6
7 def ready(self):
8 autodiscover_modules('stark',)
实现在Django项目启动时,扫描每个APP项目下的stark.py文件的文件,执行其中的代码,注册每个APP下的model,为每一个model生成增删改查四条URL。
来一张流程图进行说明。
Starksite类和Modelstark类的创建
1 from django.conf.urls import url
2 from django.shortcuts import HttpResponse, render, reverse,redirect
3 from django.utils.safestring import mark_safe
4 from django.forms import ModelForm
5 from app01.models import *
6 from app02.models import *
7
8 # 针对使用stark组件的默认样式类
9
10
11 class Modelstark(object):
12 list_display = ["__str__"]
13 list_display_link = ["__str__"]
14 model_form = None
15 # init 方法在实例化类的时候执行
16
17 def __init__(self, model, site):
18 self.model = model
19 self.site = site
20 self.label_model = (self.model._meta.app_label, self.model._meta.model_name)
21 '''
22 功能:
23 将显示、编辑、删除、增加页面的url 封装到函数中。
24 函数名:
25 def get_add_url(self)
26 def get_del_url(self,obj)
27 def get_edit_url(self,obj)
28 def get_list_url(self)
29 '''
30 def get_add_url(self):
31 add_url = reverse("%s_%s_add" % self.label_model, )
32 return add_url
33
34 def get_del_url(self,obj):
35 del_url = reverse("%s_%s_del" % self.label_model, args=(obj.pk,))
36 return del_url
37
38 def get_edit_url(self,obj):
39 edit_url = reverse("%s_%s_edit"% self.label_model,args=(obj.pk,))
40 return edit_url
41
42 def get_list_url(self):
43 list_url = reverse("%s_%s_show"%self.label_model,)
44 return list_url
45 '''
46 功能:
47 为显示页面右侧的添加按钮生成动态的URL
48 函数名:
49 def add_btn(self,obj=None,header=False)
50 '''
51 def add_btn(self,obj=None,header=False):
52 if header:
53 pass
54 return mark_safe("<a href='%s' type='button' class='btn btn-success btn-lg pull-right'>添加</a>"%self.get_add_url())
55 '''
56 功能:
57 为显示页面生成编辑、删除和复选框的标签
58 函数名:
59 def edit(self,obj=None,header=False)
60 def delete(self,obj=None,header=False)
61 def checkbox(self,obj=None,header=False)
62 '''
63 def edit(self,obj=None,header=False):
64 if header:
65 return "操作"
66 return mark_safe("<a href='%s''>编辑</a>"%self.get_edit_url(obj))
67
68 def delete(self,obj=None,header=False):
69 if header:
70 return "操作"
71 return mark_safe("<a href='%s''>删除</a>" %self.get_del_url(obj))
72
73 def checkbox(self,obj=None,header=False):
74 if header:
75 return mark_safe("<input type='checkbox' id='action-toggle'>")
76 pass
77
78 return mark_safe("<input type='checkbox' value='%s'>"%obj.pk)
79
80 pass
81 '''
82 功能:
83 为每一个类生成modelform,用于渲染标签
84 判断用户是否定义了model_form,如果定义了则用用户自己的类,没有则用默认的modelForm
85 备注:
86 显示中文错误提示信息的解决思想是:
87 在样式类中定义一个 model_form = None 的静态变量,在 def default_modelform(self) 函数中判断 model_form = None的bool值,若为False,
88 则表明用户没有自定义 model_form ,因此走默认的modelForm.
89 若model_form 为True,则表明用户对要注册的表定义了modelForm,因此用户会在自己的 XXXConfig类中声明 model_form = 自定义的modelForm,
90 此时用用户自定义的 modelForm
91 函数名:
92 def mdelform(self)
93 '''
94 def default_modelform(self):
95 class modelForm(ModelForm):
96 class Meta:
97 model = self.model
98 fields = "__all__"
99 if not self.model_form:
100 return modelForm
101 else:
102 return self.model_form
103 '''
104 功能:
105 显示、编辑、增加、删除4个视图函数
106 函数名:
107 def show_list(self, request)
108 def add_list(self, request)
109 def edit_list(self, request,id)
110 def del_list(self, request,id)
111 '''
112 def show_list(self, request):
113 data_list = self.model.objects.all()
114 # 显示表头名称
115 header_list = []
116 for i in self.real_list_display():
117 if callable(i):
118 # 获取函数名:函数名.__name__
119 # header_list.append(i.__name__) -----> low版
120 header_list.append(i(self, header=True))
121 else:
122 if i == "__str__":
123 header_list.append(self.model._meta.model_name.upper())
124 else:
125 header_list.append(self.model._meta.get_field(i).verbose_name)
126
127 # print(self.list_display)
128 # 首先循环每一条记录
129 """
130 需要构建的样式为:
131 [
132 ["id1","name1"],
133 ["id2","name2"],
134 ["id3","name3"],
135 ]
136 """
137 # 显示数据
138 ret_data_list = []
139 for obj in data_list:
140 tmp_list = []
141 # 遍历显示的字段,i 指的是字段名称
142 for i in self.real_list_display():
143 if callable(i):
144 val = i(self, obj)
145 else:
146 # val 为反射后的value
147 val = getattr(obj, i)
148 # 判断当前的 字段名称 是否在 list_display_link 中,在的话让该字段对应的值作为a标签
149 if i in self.list_display_link:
150 val = mark_safe("<a href='%s'>%s</a>"%(self.get_edit_url(obj),val))
151 tmp_list.append(val)
152 ret_data_list.append(tmp_list)
153 # 显示添加按钮
154 btn_list = []
155 # 使用类.函数 的方式需要传self,但是使用类的对象.方法的方式不需要传self
156 val = Modelstark.add_btn(self)
157 btn_list.append(val)
158
159 return render(request, "show_list.html", locals())
160
161 def add_list(self, request):
162 modelForm = self.default_modelform()
163 errors = {}
164 if request.method == "POST":
165 form = modelForm(request.POST)
166 if form.is_valid():
167 form.save()
168 return redirect(self.get_list_url())
169 else:
170 return render(request,"add.html",locals())
171 form = modelForm()
172 return render(request,"add.html",locals())
173
174 def edit_list(self, request,id):
175 obj = self.model.objects.filter(id=id).first()
176 modelForm = self.default_modelform()
177 if request.method == "POST":
178 form = modelForm(request.POST,instance=obj)
179 if form.is_valid():
180 form.save()
181 return redirect(self.get_list_url())
182 else:
183 pass
184 form = modelForm(instance=obj)
185 return render(request,"edit.html",locals())
186
187 def del_list(self, request,id):
188 if request.method == "POST":
189 self.model.objects.filter(id=id).delete()
190 return redirect(self.get_list_url())
191 list_url = self.get_list_url()
192 return render(request,"delete.html",locals())
193 '''
194 功能:
195 在显示页面要真正显示的字段,对list_display进行扩展
196 函数名:
197 def real_list_display(self)
198 '''
199 def real_list_display(self):
200 new_list_display = []
201 new_list_display.extend(self.list_display)
202 new_list_display.append(Modelstark.edit)
203 new_list_display.append(Modelstark.delete)
204 new_list_display.insert(0,Modelstark.checkbox)
205 return new_list_display
206 '''
207 功能:
208 为没一个注册的model生成增删改查四条URL
209 函数名:
210 def get_url_func(self)
211 '''
212 def get_url_func(self):
213 tmp = []
214 app_name = self.model._meta.app_label
215 model_name = self.model._meta.model_name
216 ret = (app_name, model_name)
217 tmp.append(url("^$", self.show_list, name="%s_%s_show" %ret))
218 tmp.append(url("^add/$", self.add_list, name="%s_%s_add" %ret))
219 tmp.append(url("^edit/(\d+)/$", self.edit_list, name="%s_%s_edit" %ret))
220 tmp.append(url("^del/(\d+)/$", self.del_list, name="%s_%s_del" %ret))
221 '''
222 在访问URL之前就已经分别为每张表创建了4条URL
223 '''
224 return tmp
225
226
227 # 创建stark类,实例化Starksite,用于注册model
228
229
230
231 class Starksite(object):
232
233 def __init__(self):
234 # 保存表的注册信息,保存的格式为:以model类为键,以该model实例化样式对象为值
235 self._registry = {}
236
237 # 类的注册函数
238 def register(self, model, modelconfig=None):
239 '''
240 实现model的注册
241 :param model: 要注册的类
242 :param modelconfig: 该注册类的样式对象
243 :return: None
244 '''
245
246 '''
247 判断用户是否自己定义了样式类,如果没有,此时的modelconfig为None,则用默认的ModelStark样式类进行实例化注册;
248 若用户自定义了注册类的样式对象,则使用用于自定的样式对象
249 '''
250 if not modelconfig:
251 modelconfig = Modelstark
252 # model是每次循环注册的表,self是实例化的Startksite类的实例化对象
253 '''
254 在对注册的model进行样式实例化的时候,modelconfig类实际上是Modelstark类的实例化,实例化要执行类的__init__函数,init函数的定义为:
255 def __init__(self, model, site):
256 self.model = model
257 self.site = site
258 self.label_model = (self.model._meta.app_label, self.model._meta.model_name)
259 此时实例化的方式为:类名.__init__() 方式,因此需要传self。
260 另外需要注意的是modelconfig(model,self)中的self是哪个类的实例化对象
261 当在注册类的时候没有传入制定的样式类,那么这里的self是Modelstark类的实例化,
262 但是,当用户传入定制的样式类时,此时的self是modelconfig类的实例化
263 '''
264 self._registry[model] = modelconfig(model, self)
265
266 def get_urls(self):
267 '''
268 生成表的一级和二级URL
269 :return: tmp
270 '''
271 tmp = []
272 '''
273 item():使字典中的键值对以元组的形式显示
274 model: 注册的model类
275 model_config:该类对应的样式类对象
276 app_label:以字符串形式获取类所在的APP
277 model_name:以字符串的获取类名
278 '''
279 for model, model_config in self._registry.items():
280 app_label = model._meta.app_label
281 model_name = model._meta.model_name
282 # 为什么不能去掉None
283 u = url("^%s/%s/" % (app_label, model_name), (model_config.get_url_func(), None, None))
284 tmp.append(u)
285 return tmp
286 '''
287 把urls函数做成静态方法,urls.py 中执行urls函数不需要加()
288 '''
289 @property
290 def urls(self):
291 return self.get_urls(), None, None
292
293
294 # 创建单例对象,在每一个app中的stark文件中调用
295 site = Starksite()
各功能的实现思路
一级与二级URL的设计思路
在注册完表后,执行urls.py文件,执行
1 urlpatterns = [
2 url(r'^admin/', admin.site.urls),
3 url(r'^stark/', site.urls),
4 ]
site为类Starksite的单例实例化对象,site.urls 调用类下的urls方法,而urls方法实际上是执行get_urls(self) 方法,为每一个model首先生成一级URL,其次调用类的样式对象下的get_url_func(self)
函数,生成二级URL,同时为每一个增删改查URL创建别名,用于反向解析。
显示页面的数据显示
1.数据显示的思路
首先类中定义一个 list_display = ["__str__"]的静态变量,此静态变量在用于没有重写该变量时,则默认显示该对象的 def __str__(self) 方法的返回值。
将显示的数据以 [ [1,"python",12,"<a>编辑</a>"],[2,"java",23,"<a>编辑</a>"],[3,"php",111,"<a>编辑</a>"],]的形式在后后台处理成功后,循环判断要显示的数据是一个字符串还是可执行函数,若是可执行函数则执行函数后追加保存到列表中,若是字符串则直接追加保存在列表中。
PS:
1.getattr(obj,str) 函数用于反射,实现通过对象的字段名名称找到该字段对应的值。
2.要循环的字段实际上是new_list_display, 即对list_display进行扩展,是默认显示的字段有复选框、对象名称、编辑、删除
def real_list_display(self):
new_list_display = []
new_list_display.extend(self.list_display)
new_list_display.append(Modelstark.edit)
new_list_display.append(Modelstark.delete)
new_list_display.insert(0,Modelstark.checkbox)
return new_list_display
2.编辑和删除a标签的实现
把编辑和删除的a标签分别作为一个函数,使该函数的返回值为 mark_safe("<a>编辑</a>"),在循环要显示的字段对象的数据时,执行该函数。
PS:
mark_safe()函数实现前端safe相同的功能,使django不转移标签对象。
3.表头的实现
根据list_display 循环显示表头,当遇到“__str__”时,找到该该对象的“__str__“”方法的返回值,当遇到制定的字段名称,如public时,则使用 self.model._meta.get_field(i).verbose_name获取该字段的verbose_name,追加到列表中。
PS:
get_field(arg) 可以获取到model表中该arg字段对象,该对象可以调用字段的属性,如verbose_name。
4.让字段作为a标签,实现跳转到编辑页面的实现
在循环显示数据的时候, 判断通过getattr()方法获取的字段值是否在list_display_link中,如果在,则使用mark_safe()使该value成为a标签。
5. ModelForm 组件实现页面的增删改查
只需要为要进行form渲染的类继承ModelForm,即可。
6.使用ModelForm实现显示中文错误信息
在样式类中创建一个model_form = None的静态变量,在创建 ModelForm 类的函数 def default_modelform(self)中判断model_form的值,值为None,则使用默认的modelform,如果为True,则使用用户stark.py中定义的样式类。
在每个APP中stark.py文件的创建
1 from .models import *
2 # 对单例对象的调用
3 from stark_demo.service.sites import site,Modelstark
4 from django.forms import ModelForm
5
6
7 class BookModelForm(ModelForm):
8 '''
9 重写model的modelform类
10 '''
11 class Meta:
12 model = Book
13 fields = "__all__"
14 error_messages = {
15 "title":{"required":"不能为空"},
16 "public":{"required":"不能为空"}
17 }
18
19
20 class BookConfig(Modelstark):
21
22 list_display = ["id","title","public"]
23 list_display_link = ["id","title",]
24 model_form = BookModelForm
25
26
27 site.register(Book,BookConfig)