Sansa组件
诉求
仿照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)