摘要:学习 Django admin 组件,仿照源码的逻辑,自定义了一个简易的 stark 组件,实现类似 admin 的功能。
可自动生成 url 路由,对于model 有与之相应的配置类对象,可进行增、删、改、查的操作。
通过继承 ModelStark 配置类,重写类参数和方法,可自定义查找显示信息,模糊查询字段,批量操作方法等
那么让我们开始吧~
思路:
在启动时------> 1、遍历所有app的stark.py文件,注册model
2、自动生成,注册的model对应的url
执行时 -------> 1、url路由分发,调用配置类中相应的方法
2、在 ModelStark 配置类中处理数据、渲染页面
3、返回 Responce 对象
具体实现:
一、先创建一个组件 stark
二、并在组件的 app.py 文件中,写下遍历的其他APP的方法
from django.apps import AppConfig from django.utils.module_loading import autodiscover_modules class StarkConfig(AppConfig): name = 'stark' def ready(self): #这句表示 自动遍历所有app 的 stark.py 文件 autodiscover_modules('stark')
注意:在settings.py 文件中 ,注册 stark 组件时,一定要写上,只写 ‘stark’ 的话,不会执行ready的
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
"stark.apps.StarkConfig"
]
三、 在 组件的 stark.py文件中
定义一个 单例的 全局类 StrakSite ,该类控制以及路由的生成,并调用配置类,生成第二级的路由
class StrakSite: def __init__(self, name='stark'): self.name = name # 注册了的所有表与表的配置类对象 self._registry = {} def register(self, model, model_config=None): if not model_config: #如果没有自定义配置类,就用父类的配置类 model_config = ModelStark self._registry[model] = model_config(model) # URL分发 第一级 def get_urls(self): temp = [] for model, model_config in self._registry.items(): app_label = model._meta.app_label model_name = model._meta.model_name temp.append(url(r'^%s/%s/' % (app_label, model_name), model_config.urls)) return temp @property def urls(self): #路由分发 return self.get_urls(), None, None
# 单例模式 所有的model共享同一个StrakSite对象
site = StrakSite()
配置类,在python代码里生成前端HTML标签:
class ModelStark: list_display = ['__str__'] # 列表时,定制显示的列。 modelfoem_class = [] # 自定义配置类 list_display_links = [] # list页面点击可以跳转到编辑页面的字段 search_fields = [] # 搜索框,自定义的搜索字段 actions = [] # 自定义批处理函数列表 list_filter = [] # 自定义 右侧筛选字段 def __init__(self, model): self.model = model self.app_label = model._meta.app_label self.model_name = model._meta.model_name meta= model._meta print(meta) # 生成复选框 def checkbox_col(self, obj=None, is_header=False): ''' 生成复选框 :param is_hander: :return: ''' if is_header: return '选择' # _url= return mark_safe('<input type="checkbox" name="selected_action" value="%s">' % obj.pk) # 反向解析,获取URL def get_url(self, type, id=None): ''' 反向解析,获取URL :param type: :param id: :return: ''' name = '%s_%s_%s' % (self.app_label, self.model_name, type) if id: return reverse(name, args=(id,)) else: return reverse(name) # 生成删除按钮 def del_col(self, obj=None, is_header=False): ''' 生成删除按钮 :param is_hander: :return: ''' if is_header: return '删除' _url = self.get_url('delete', obj.pk) return mark_safe('<a id="%s_delete" href="%s">删除</a>' % (self.model_name, _url)) # 生成添加a标签 def add_col(self): ''' 生成添加按钮 :param is_hander: :return: ''' _url = self.get_url('add') return mark_safe('<a id="%s_add" href="%s"><span class="glyphicon glyphicon-plus pull-lift"></span></a>' % (self.model_name, _url)) # 生成编辑按钮 def edit_col(self, obj=None, is_header=False): ''' 生成编辑按钮 :param is_hander: :return: ''' if is_header: return '编辑' _url = self.get_url('edit', obj.pk) return mark_safe('<a id="%s_edit" href="%s">编辑</a>' % (self.model_name, _url)) # 按照表格显示需要的列格式,将每个字段按顺序放进列表里 def get_new_list_display(self): new_display = [] new_display.extend(self.list_display) # 这里一定要传 类名.方法名 如果用self.方法名的话 # 调用的时候 这里传入的方法会自动传self值,而我们自定义的display方法并不会,所以在通用方法处理时会报错 new_display.insert(0, ModelStark.checkbox_col) new_display.extend([ModelStark.edit_col, ModelStark.del_col]) return new_display # 查 def list(self, request): self.data_list = self.model.objects.all() # 列表展示页面类 show = ShowList(self, request, self.data_list) # 如果有自定义查询字段且输入了查询内容 该操作要写在分页之前,因为对data_list进行了修改 self.search_info = request.GET.get("search") if self.search_fields and self.search_info: show.get_search(self.search_info) # 批量操作 action_list = show.get_actions() if request.method == "POST": action_info = request.POST.get("action") if self.actions and action_info: id_list = request.POST.getlist("selected_action") data = self.model.objects.filter(pk__in=id_list) action = getattr(self, action_info) action(request,data) # 生成左侧筛选栏 该操作要写在分页之前,因为对data_list进行了修改 filter_html = None if self.list_filter: filter_html = show.get_filter() # 分页HTML page_html = show.get_page_html() # 表头 title_list = show.get_hander() # 表格内容 table_list = show.get_body() # 生成 添加a标签 add_opp = self.add_col() return render(request, 'stark/list.html', {'title_list': title_list, 'table_list': table_list, 'page_html': page_html, 'add_opp': add_opp, 'action_list': action_list, 'search_about': (self.search_fields, self.search_info), 'filter_html': filter_html, }) # 增 def add(self, request): if request.method == 'GET': # 生成modelform对象 form = self.modelform_class() from django.forms.boundfield import BoundField # bfield 是 BoundField类的子类 for bfield in form: if isinstance(bfield.field, ModelChoiceField): bfield.is_pop = True # 得到该字段的关联表 filed_rel_model = self.model._meta.get_field(bfield.name).rel.to app_label = filed_rel_model._meta.app_label model_name = filed_rel_model._meta.model_name name = '%s_%s_add' % (app_label, model_name) _url = reverse(name) bfield.url = _url + "?pop_back_id=" + bfield.auto_id return render(request, 'stark/add.html', {'form': form}) else: # 得到带参数的modelform对象 form = self.modelform_class(request.POST) from django.forms.boundfield import BoundField # bfield 是 BoundField类的子类 for bfield in form: if isinstance(bfield.field, ModelChoiceField): bfield.is_pop = True # 得到该字段的关联表 filed_rel_model = self.model._meta.get_field(bfield.name).rel.to app_label = filed_rel_model._meta.app_label model_name = filed_rel_model._meta.model_name name = '%s_%s_add' % (app_label, model_name) _url = reverse(name) bfield.url = _url + "?pop_back_id=" + bfield.auto_id # 校验页面填值 if form.is_valid(): # 保存添加的数据 obj = form.save() try: pop_back_id = request.GET.get('pop_back_id') if pop_back_id: return render(request, 'stark/close.html', {'pop_back_id': pop_back_id, 'text': str(obj), 'pk': obj.pk}) except Exception: pass # 跳转回list页面 _url = self.get_url('list') return redirect(_url) else: return render(request, 'stark/add.html', {'form': form}) # 删 def delete(self, request, id): self.model.objects.get(pk=id).delete() _url = self.get_url('list') return redirect(_url) # 改 def edit(self, request, id): model_obj = self.model.objects.get(pk=id) if request.method == 'GET': # 生成一个带有model对象内容的modelform对象 form = self.modelform_class(instance=model_obj) return render(request, 'stark/edit.html', {'form': form}) else: form = self.modelform_class(request.POST, instance=model_obj) if form.is_valid(): form.save() _url = self.get_url('list') return redirect(_url) else: return render(request, 'stark/edit.html', {'form': form}) def extra_urls(self): return [] # URL分发 第二级 def get_urls(self): temp = [] # 增 temp.append(url(r'add/$', self.add, name='%s_%s_add' % (self.app_label, self.model_name))) # 删 temp.append(url(r'(?P<id>\d+)/delete/$', self.delete, name='%s_%s_delete' % (self.app_label, self.model_name))) # 改 temp.append(url(r'(?P<id>\d+)/edit/$', self.edit, name='%s_%s_edit' % (self.app_label, self.model_name))) # 查 temp.append(url(r'$', self.list, name='%s_%s_list' % (self.app_label, self.model_name))) temp.extend(self.extra_urls()) print(temp) return temp @property def urls(self): return self.get_urls(), None, None # 获取modelform类 @property def modelform_class(self): if self.modelfoem_class: return self.modelfoem_class else: class ModelFormClass(forms.ModelForm): class Meta: model = self.model fields = '__all__' return ModelFormClass
因为查询页面太大,所以单独抽成一个ShowList类,其中封装了list页面的表头、表格内容、分页、搜索栏、批量操作栏、标签查找栏等
class ShowList(object): def __init__(self, conf_obj, request, data_list): self.conf_obj = conf_obj self.data_list = data_list self.new_list_display = self.conf_obj.get_new_list_display() self.request = request # 获取列表的表头 def get_hander(self): # 表头 title_list = [] for field in self.new_list_display: if isinstance(field, str): # 如果没有自己传list_display进来,默认的是'__str__',则打印大写的表名 if field == '__str__': field = self.conf_obj.model_name.upper() else: # 获取字段的对象 field_obj = self.conf_obj.model._meta.get_field(field) # 从字段对象中获取字段的verbose_name,在model模型类里写的 field = field_obj.verbose_name else: # 如果不是表字段的话,执行对应的方法 eg: edit delete field = field(self.conf_obj, is_header=True) title_list.append(field) return title_list # 获取表格内容 def get_body(self): table_list = [] for obj in self.data_list: list_info = [] for field in self.new_list_display: if isinstance(field, str): try: if self.conf_obj.model._meta.get_field(field).choices: field_val = getattr(obj, 'get_%s_display' %field ) else: field_val = getattr(obj, field) if field in self.conf_obj.list_display_links: _url = self.conf_obj.get_url('edit', obj.pk) field_val = mark_safe( '<a id="%s_edit" href="%s">%s</a>' % (self.conf_obj.model_name, _url, field_val)) except Exception as e: # __str__ 的字段 field_val = getattr(obj, field) else: field_val = field(self.conf_obj, obj=obj) list_info.append(field_val) table_list.append(list_info) return table_list # 获取自定义分页的THML def get_page_html(self): # 来源url url_prefix = self.request.get_full_path() # 数据总量 total_num = self.data_list.count() # 当前页 current_page = self.request.GET.get('page') # 生成页面对象 self.page_obj = Page(total_num, current_page, url_prefix, per_page=10, show_page_num=9) # 得到当前页展示的数据列表 self.data_list = self.data_list[self.page_obj.data_start:self.page_obj.data_end] # 得到对应的分页HTML page_html = self.page_obj.page_html() return page_html # 搜索栏操作 def get_search(self, search_info): search_condition = Q() search_condition.connector = "or" for field in self.conf_obj.search_fields: # 模糊查询 search_condition.children.append((field + "__icontains", search_info)) self.data_list = self.conf_obj.model.objects.filter(search_condition) # 自定义批量操作 def get_actions(self): temp = [] for action in self.conf_obj.actions: temp.append({ 'name': action.__name__, 'desc': action.desc }) return temp # 右侧筛选栏 def get_filter(self): self.params = copy.deepcopy(self.request.GET) self.params['page'] = None self.params.pop("page") filter_dic = {} filter_condition = Q() for k, v in self.params.items(): if k in self.conf_obj.list_filter and v != 'all': filter_dic[k] = v filter_condition.children.append((k, v)) self.data_list = self.data_list.filter(filter_condition) temp = {} for filter in self.conf_obj.list_filter: filter_field_obj = self.conf_obj.model._meta.get_field(filter) val_html = [] queryset = filter_field_obj.rel.to.objects.all() self.params[filter] = 'all' val_html.append('<a href="?%s">ALL</a>' % (self.params.urlencode())) for obj in queryset: self.params[filter] = obj.pk if (filter in filter_dic) and (filter_dic[filter] == str(obj.pk)): val_html.append('<a class="red href="?%s">%s</a>' % (self.params.urlencode(), str(obj))) else: val_html.append('<a href="?%s">%s</a>' % (self.params.urlencode(), str(obj))) temp[filter] = val_html return temp
四、HTML页面和,静态文件,以及工具文件(mypage分页)也都放在组件里,在查找时,最外层的找不到,会直接到组件里找对应的文件,所以可以放在组件里
Django 之 modelForm (edit.html页面的编写)
使用方法:
在app的 stark.py 文件里,
1、 注册 model,生成对应的url
2、 #自定义配置类
list_display = ["title", "price", "publish"] #配置该表显示的字段列
list_display_links = ["title"] #配置点击哪些字段可以跳转到编辑页面
search_fields = ["price"] #配置哪些字段可以作为可模糊的字段
list_filter = ["publish"] #右侧边分类栏,可分类现实数据
#自定义 批量操作方法
from django.shortcuts import HttpResponse, render, redirect from app01.models import * from django.http import JsonResponse #注册 model,生成对应的url site.register(User) site.register(Role) #自定义配置类 class BookConfig(ModelStark): list_display = ["title", "price", "publish"] #配置该表显示的字段列 list_display_links = ["title"] #配置点击哪些字段可以跳转到编辑页面 search_fields = ["price"] #配置哪些字段可以作为可模糊的字段 list_filter = ["publish"] #右侧边分类栏,可分类现实数据 #自定义 批量操作方法 def delete_action(self, queryset): queryset.delete() #批量操作方法的名称 delete_action.desc = "批量删除" def init_price_action(self, queryset): queryset.update(price=100.0) init_price_action.desc = "批量初始化" #批量操作方法列表 actions = [delete_action, init_price_action] #注册该类,将对应的配置类传入 site.register(models.Book, BookConfig)
#自定义列
#自定义视图
#自定义url
class StudentConfig(ModelStark): #自定义列 方法 def class_display(self, obj=None, is_header=False): if is_header: return "已报班级" temp = [] for i in obj.class_list.all(): temp.append(str(i)) return ",".join(temp) #自定义列 def score_display(self, obj=None, is_header=False): if is_header: return "学习详情" return mark_safe("<a href='/stark/app01/student/score/%s'>学习详情</a>" % obj.pk) #自定义列 方法列表 list_display = ["customer", class_display, score_display] #自定义视图 def score_view(self, request, sid): if request.is_ajax(): sid = request.GET.get("sid") cid = request.GET.get("cid") # 查询学生sid在班级cid在的所有成绩 ret = StudentStudyRecord.objects.filter(student=sid, classstudyrecord__class_obj=cid).values_list( "classstudyrecord__day_num", "score") print("ret", ret) data = [["day%s" % i[0], i[1]] for i in ret] return JsonResponse(data, safe=False) student_obj = Student.objects.filter(pk=sid).first() class_list = student_obj.class_list.all() return render(request, "score_view.html", locals()) #自定义url def extra_urls(self): temp = [] temp.append( url("score/(\d+)", self.score_view) ) return temp site.register(Student, StudentConfig)