数据增删改查组件
stark.py
from functools import wraps from django.urls import re_path from django.shortcuts import HttpResponse, render, reverse from django.utils.safestring import mark_safe from django.forms import ModelForm from django.shortcuts import redirect from types import FunctionType class StarkConfig(object): order_by = [] # 子类中重写该静态属性即可改变排序方法,默认按pk升序排列 list_display = [] # 类的静态字段提供需要展示的列 model_form_class = None def __init__(self, model_class): self.model_class = model_class self.site = site # 显示行头的checkbox def display_checkbox(self, row=None, header=False): if header: # 表头行 return mark_safe("<input type='checkbox' name='pk'") else: # 数据行 return mark_safe("<input type='checkbox' name='pk' value='%s'>" % row.pk) # 显示行末的编辑按钮 def display_edit(self, row=None, header=False): if header: return "编辑" else: return mark_safe( "<a href='%s'><i class='fa fa-edit' aria-hidden='true'></i></a>" % self.reverse_change_url(row)) # 显示行末删除按钮 def display_del(self, row=None, header=False): if header: return "删除" else: return mark_safe( "<a href='%s'><i class='fa fa-trash-o' aria-hidden='true'></i></a>" % self.reverse_del_url(row)) # 行末显示删除和编辑按钮 def display_edit_del(self, row=None, header=False): if header: return "操作" else: tpl = "<a href='%s'><i class='fa fa-edit' aria-hidden='true'></i></a> | <a href='%s'><i class='fa fa-trash-o' aria-hidden='true'></i></a>" % ( self.reverse_change_url(row), self.reverse_del_url(row)) return mark_safe(tpl) # 获取添加按钮,可定制 def get_add_btn(self): # 有添加权限,返回按钮代码 if True: return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url()) else: return None # 获取排序列表,可定制 def get_order_by(self): # 用该方法来预留钩子 return self.order_by # 获取需要展示的字段列表,可定制 def get_list_display(self): # 用该方法来预留钩子,后期可添加功能,如根据角色来定义展示的内容 return self.list_display # 获取ModelFormClass,可定制 def get_model_form_class(self): if self.model_form_class: return self.model_form_class class AddModelForm(ModelForm): class Meta: model = self.model_class fields = "__all__" return AddModelForm # 生成删除链接 def reverse_del_url(self, row): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) return reverse('%s:%s_%s_del' % info, args=(row.pk,)) # 生成添加连接 def reverse_add_url(self): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) return reverse("%s:%s_%s_add" % info) # 生成列表展示连接 def reverse_changelist_url(self): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) return reverse("%s:%s_%s_changelist" % info) # 生成修改连接 def reverse_change_url(self, row): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) return reverse('%s:%s_%s_change' % info, args=(row.pk,)) # 数据展示视图 def changelist_view(self, request): """ 所有URL的查看页面 :param request: :return: """ list_display = self.get_list_display() header_list = [] if list_display: for name_or_func in list_display: if isinstance(name_or_func, FunctionType): verbose_name = name_or_func(self, header=True) else: verbose_name = self.model_class._meta.get_field(name_or_func).verbose_name header_list.append(verbose_name) else: header_list.append(self.model_class._meta.model_name) query_set = self.model_class.objects.all().order_by(*self.get_order_by()) ####这里表格数据处理应该用yield和inclusion_tag重写### data_list = [] for row in query_set: row_list = [] if not list_display: # 用户没有指定需要展示的字段 row_list.append(row) data_list.append(row_list) continue for name_or_func in list_display: if isinstance(name_or_func, FunctionType): # 行头展示 col = name_or_func(self, row=row) else: col = getattr(row, name_or_func) row_list.append(col) data_list.append(row_list) self.get_add_btn() return render(request, 'stark/changelist.html', {'header_list': header_list, 'data_list': data_list, 'addbtn': self.get_add_btn()}, ) # 数据添加视图 def add_view(self, request): """ 所有添加页面都在此处理 :param request: :return: """ if request.method == 'GET': form = self.get_model_form_class()() return render(request, 'stark/add.html', {'form': form}) form = self.get_model_form_class()(request.POST) if form.is_valid(): form.save() return redirect(self.reverse_changelist_url()) # 数据修改视图 def change_view(self, request, pk): """ 所有页面编辑函数 :param request: :param pk: :return: """ obj = self.model_class.objects.filter(pk=pk).first() if not obj: return HttpResponse("数据不存在") ModelFormClass = self.get_model_form_class() if request.method == 'GET': form = ModelFormClass(instance=obj) return render(request, 'stark/change.html', {'form': form}) form = ModelFormClass(data=request.POST, instance=obj) if form.is_valid(): form.save() return redirect(self.reverse_changelist_url()) return render(request, 'stark/change.html', {'form': form}) # 数据删除视图 def delete_view(self, request, pk): """ 所有删除页面 :param request: :param pk: :return: """ if request.method == 'GET': return render(request, 'stark/delete.html', {'cancel_url': self.reverse_changelist_url()}) if request.method == 'POST': obj = self.model_class.objects.filter(pk=pk).delete() return redirect(self.reverse_changelist_url()) # 自定义内部钩子装饰器,让所有url进来都走这个函数,就可以批量添加功能 def wrapper(self, func): @wraps(func) # 保留原函数信息 def inner(*args, **kwargs): return func(*args, **kwargs) return inner # 创建类的方法,获取该类相关urls,这里不同的app进来对应的self就不一样 def get_urls(self): info = self.model_class._meta.app_label, self.model_class._meta.model_name urlpatterns = [ re_path(r'^list/$', self.wrapper(self.changelist_view), name='%s_%s_changelist' % info), re_path(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info), re_path(r'^(?P<pk>\d+)/change/$', self.wrapper(self.change_view), name='%s_%s_change' % info), re_path(r'^(?P<pk>\d+)/del/$', self.wrapper(self.delete_view), name='%s_%s_del' % info), ] extra = self.extra_url() if extra: # 扩展urls urlpatterns.append(extra) return urlpatterns # 钩子函数,可以在子类中自定义该方法,对urls进行扩展 def extra_url(self): pass @property # 该装饰器使调用该方法时直接用函数名即可,无需加括号 def urls(self): return self.get_urls() # 自定义注册类 class AdminSite(object): def __init__(self): self._registry = {} # 用于存储注册进来的类,key是类,值是StarkConfig或自定义的StarkConfig的子类 self.app_name = 'stark' self.namespace = 'stark' # 函数注册方法 def register(self, models_class, stark_config=None): if not stark_config: stark_config = StarkConfig self._registry[models_class] = stark_config(models_class) # 动态创建urls def get_urls(self): urlpatterns = [] for k, v in self._registry.items(): # 对注册的类进行循环,创建urls app_label = k._meta.app_label model_name = k._meta.model_name # v.urls是StarkConfig或自定义的StarkConfig的子类的urls,对路由进行再分发 urlpatterns.append(re_path(r"^%s/%s/" % (app_label, model_name,), (v.urls, None, None))) return urlpatterns @property def urls(self): # 返回一个元祖,这里返回内容就和include()方法返回的内容一样 return self.get_urls(), self.app_name, self.namespace # 实例化注册类,需要注册的时候,导入该实例对象即可,大家操作的都是同一个实例对象 site = AdminSite()
在app01app中注册stark组件,并且自定义展示排序,展示的字段,以及扩展url
from app01 import models from django.urls import re_path from django.shortcuts import HttpResponse,render from stark.service.stark import site,StarkConfig def test(request): return HttpResponse('扩展URL') class UserinfoConf(StarkConfig): order_by = ['-id']#按ID倒叙排列 list_display = [StarkConfig.display_checkbox,'id','name','password','email',StarkConfig.display_edit_del] def extra_url(self): return re_path(r'test/',test) class DepartmentConfig(StarkConfig): list_display = [StarkConfig.display_checkbox,'id','name','tel','user',StarkConfig.display_edit] site.register(models.UserInfo,UserinfoConf) site.register(models.Department,DepartmentConfig)
在app02app中注册stark组件,自定义展示的数据列,自定义model_form_class
from app02 import models from stark.service.stark import site from stark.service.stark import StarkConfig from django.forms import ModelForm class PermissionModelForm(ModelForm): """ 自定义model form """ class Meta: model=models.Permission fields=['title','url'] def clean_title(self): return self.cleaned_data['title'] class PermissionConfig(StarkConfig): list_display = [StarkConfig.display_checkbox,'id','title','url',StarkConfig.display_del] model_form_class = PermissionModelForm site.register(models.Permission,stark_config=PermissionConfig)
在stark组件中,单独创建template模板
基板layout.html
{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>路飞学城</title> <link rel="shortcut icon" href="{% static 'stark/imgs/luffy-study-logo.png' %} "> <link rel="stylesheet" href="{% static 'stark/plugins/bootstrap/css/bootstrap.css' %} "/> <link rel="stylesheet" href="{% static 'stark/plugins/font-awesome/css/font-awesome.css' %} "/> <link rel="stylesheet" href="{% static 'stark/css/commons.css' %} "/> <link rel="stylesheet" href="{% static 'stark/css/nav.css' %} "/> <style> body { margin: 0; } .no-radius { border-radius: 0; } .no-margin { margin: 0; } .pg-body > .left-menu { background-color: #EAEDF1; position: absolute; left: 0; top: 48px; bottom: 0; width: 220px; border: 1px solid #EAEDF1; overflow: auto; } .pg-body > .right-body { position: absolute; left: 225px; right: 0; top: 48px; bottom: 0; overflow: scroll; border: 1px solid #ddd; border-top: 0; font-size: 13px; min-width: 755px; } .navbar-right { float: right !important; margin-right: -15px; } .luffy-container { padding: 15px; } </style> {% block css %}{% endblock %} </head> <body> <div class="pg-header"> <div class="nav"> <div class="logo-area left"> <a href="#"> <img class="logo" src="{% static 'stark/imgs/logo.svg' %}"> <span style="font-size: 18px;">路飞学城 </span> </a> </div> <div class="left-menu left"> <a class="menu-item">资产管理</a> <a class="menu-item">用户信息</a> <a class="menu-item">路飞管理</a> <div class="menu-item"> <span>使用说明</span> <i class="fa fa-caret-down" aria-hidden="true"></i> <div class="more-info"> <a href="#" class="more-item">管他什么菜单</a> <a href="#" class="more-item">实在是编不了</a> </div> </div> </div> <div class="right-menu right clearfix"> <div class="user-info right"> <a href="#" class="avatar"> <img class="img-circle" src="{% static 'stark/imgs/default.png' %}"> </a> <div class="more-info"> <a href="#" class="more-item">个人信息</a> <a href="/logout/" class="more-item">注销</a> </div> </div> <a class="user-menu right"> 消息 <i class="fa fa-commenting-o" aria-hidden="true"></i> <span class="badge bg-success">2</span> </a> <a class="user-menu right"> 通知 <i class="fa fa-envelope-o" aria-hidden="true"></i> <span class="badge bg-success">2</span> </a> <a class="user-menu right"> 任务 <i class="fa fa-bell-o" aria-hidden="true"></i> <span class="badge bg-danger">4</span> </a> </div> </div> </div> <div class="pg-body"> <div class="left-menu"> <div class="menu-body"> </div> </div> <div class="right-body"> <div> </div> <div style="padding: 10px"> {% block content %} {% endblock %} </div> </div> </div> <script src="{% static 'stark/js/jquery-3.3.1.min.js' %} "></script> <script src="{% static 'stark/plugins/bootstrap/js/bootstrap.js' %} "></script> {% block js %} {% endblock %} </body> </html>
数据展示模板changelist.html
{% extends 'stark/layout.html' %} {% block content %} <div style="margin: 8px 0"> {% if addbtn %} {{ addbtn }} {% endif %} </div> <table class="table table-bordered"> <thead> <tr> {% for header in header_list %} <th>{{ header }}</th> {% endfor %} </tr> </thead> <tbody> {% for row in data_list %} <tr> {% for col in row %} <td>{{ col }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> {% endblock %}
数据修改模板change.html
{% extends 'stark/layout.html' %} {% block css %} <style> .add input,select { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; } </style> {% endblock %} {% block content %} <div style="width: 500px;margin: 0 auto;"> <form class="add" method="post"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.id }}">{{ field.label }}:</label> {{ field }}{{ field.errors.0 }} </div> {% endfor %} <button type="submit" class="btn btn-default">Submit</button> </form> </div> {% endblock %}
数据添加模板add.html
{% extends 'stark/layout.html' %} {% block css %} <style> .add input,select { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; } </style> {% endblock %} {% block content %} <div style="width: 500px;margin: 0 auto;"> <form class="add" method="post"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.id }}">{{ field.label }}:</label> {{ field }}{{ field.errors.0 }} </div> {% endfor %} <button type="submit" class="btn btn-default">Submit</button> </form> </div> {% endblock %}
数据删除模板delete.html
{% extends 'stark/layout.html' %} {% block css %} {% endblock %} {% block content %} <div style="width: 500px;margin: 0 auto;"> <form class="add" method="post"> {% csrf_token %} <p class="bg-info">确认删除!</p> <button type="submit" class="btn btn-danger">确认</button> <a href="{{ cancel_url }}" class="btn btn-success">取消</a> </form> </div> {% endblock %}