实现CRM五大参数功能
1. 模型表字段的数据
1.1
from app01 import models models.Book._meta.app_label # 获取模型表所对应的的应用名 models.Book._meta.model_name # 获取模型表对应的字符串名 models.Book._meta.get_field(\'title\') # 获取字段对象 # <django.db.models.fields.CharField: title>
# verbose_name 可以给字段添加名称 title = models.CharField(max_length=32, verbose_name=\'书名\') price = models.DecimalField(max_digits=5, decimal_places=2) # 获取字段对应的verbose_name, models.Book._meta.get_field(\'title\').verbose_name # \'书名\' # 如果字段没有指定verbose_name属性时, 则使用字段的字符串名 models.Book._meta.get_field(\'title\').verbose_name # price
2. 新建一个stark组件
2.1
INSTALLED_APPS = [ \'stark.apps.StarkConfig\', ]
StarkConfig位于stark文件夹下的apps模块内
from django.apps import AppConfig from django.utils.module_loading import autodiscover_modules class StarkConfig(AppConfig): name = \'stark\' def ready(self): # 项目一启动,就会自动查找每一个应用下的stark.py文件 return autodiscover_modules(\'stark\')
2.2 模型表的注册
# django admin中模型表注册位置为 应用下的 admin.py文件中 # 自定义的stark,需要将注册的地址改为应用下的 stark.py文件中
3. 路由反向解析
3.1 实现一级路由
# 实现一级路由,之所以不在这里实现二级路由分发,是因为这里的self是 site对象, # 当book,publish ... 传过来的时候,self知道依旧是site,无法对表的种类进行区分 def get_urls(self): tmp = [] for model_class, config_obj in self._registry.items(): # config_obj为配置类对象,跟模型表一一对应,因此,我们在配置类中进行二级路由分发 app_label = model_class._meta.app_label model_name = model_class._meta.model_name # tmp.append(url(r\'^%s/%s\' %(app_label, model_name), self.test)) # 实现一级路由 tmp.append(url(r\'^%s/%s/\' % (app_label, model_name), config_obj.urls)) # 实现一级路由 return tmp
3.2 实现二级路由
class StarkSite(object): @property def urls(self): tmp = [ url(r\'^$\', self.list_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'list\')), url(r\'^add/\', self.add_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'add\')), url(r\'^edit/(\d+)/\', self.edit_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'edit\')), url(r\'^delete/(\d+)/\', self.delete_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'delete\')), ] return tmp, None, None
3.3 二级路由与方向解析
class ModelStark(object): def __init__(self, model): self.model = model self.app_label = self.model._meta.app_label # 模型表所在的应用名 self.model_name = self.model._meta.model_name # 模型表对应的字符串名 def check_col(self, is_header=False, obj=None): if is_header: return \'选择\' return mark_safe(\'<input type="checkbox"/>\') def get_reverse_url(self, type, obj=None): if obj: _url = reverse(\'%s_%s_%s\' % (self.app_label, self.model_name, type), args=(obj.pk,)) else: _url = reverse(\'%s_%s_%s\' % (self.app_label, self.model_name, type)) return _url def edit_col(self, is_header=False, obj=None): if is_header: return \'编辑\' _url = self.get_reverse_url(\'edit\', obj) return mark_safe(\'<a href="%s">编辑</a>\' % _url) def delete_col(self, is_header=False, obj=None): if is_header: return \'删除\' _url = _url = self.get_reverse_url(\'delete\', obj) return mark_safe(\'<a href="%s">删除</a>\' % _url)
4. 核心代码(核心笔记都在代码注释,其他的可以不用看)
4.1 目录结构
4.2 app01包下的核心代码
from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32, verbose_name=\'姓名\') age = models.IntegerField(verbose_name=\'年龄\') # 与AuthorDetail建立一对一关系 authorDetail = models.OneToOneField(to=\'AuthorDetail\', on_delete=models.CASCADE) def __str__(self): return self.name class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday = models.DateField(verbose_name=\'出生日期\') telephone = models.BigIntegerField(verbose_name=\'手机号\') addr = models.CharField(max_length=64, verbose_name=\'地址\') def __str__(self): return self.addr class Publish(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32, verbose_name=\'出版社名称\') city = models.CharField(max_length=32, verbose_name=\'城市\') email = models.EmailField(verbose_name=\'邮箱\') def __str__(self): return self.name class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32, verbose_name=\'书名\') publishDate = models.DateField(verbose_name=\'出版日期\') price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name=\'价格\') # 与Publish建立一对多的关系,外键字段建立在多的一方 publish = models.ForeignKey(to=\'Publish\', to_field=\'nid\', on_delete=models.CASCADE, verbose_name=\'出版社\') # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors = models.ManyToManyField(to=\'Author\', ) def __str__(self): return self.title
from app01 import models from django.utils.safestring import mark_safe from stark.service.stark import site, ModelStark class BookConfig(ModelStark): list_display = [\'title\', \'price\', \'publishDate\', \'publish\'] list_display_links = [\'title\', \'price\'] from django.forms import ModelForm class BookModelForm(ModelForm): class Meta: model = models.Book fields = \'__all__\' from django.forms import widgets as wid widgets = { \'title\':wid.TextInput(attrs={\'class\': \'form-control\'}), \'price\':wid.TextInput(attrs={\'class\': \'form-control\'}), \'publishDate\':wid.DateInput(attrs={\'class\': \'form-control\'}), \'publish\':wid.Select(attrs={\'class\': \'form-control\'}), \'authors\':wid.SelectMultiple(attrs={\'class\': \'form-control\'}) } model_form_class = BookModelForm search_fields = [\'title\', \'price\'] def patch_init(self, request, queryset): queryset.update(price=666) patch_init.desc = \'价格批量处理\' def path_delete(self, request, queryset): queryset.delete() path_delete.desc = \'批量删除\' actions = [patch_init, path_delete] list_filter = [\'publish\', \'authors\'] site.register(models.Book, BookConfig) site.register(models.Publish) site.register(models.Author) site.register(models.AuthorDetail)
4.3 stark包下的核心代码
from django.shortcuts import HttpResponse, render, reverse, redirect from django.conf.urls import url from django.utils.safestring import mark_safe from stark.utils.my_page import Pagination from django.db.models import Q class ShowList(object): def __init__(self, config_obj, queryset, request): self.config_obj = config_obj self.queryset = queryset self.request = request current_page = self.request.GET.get(\'page\', 1) self.page_obj = Pagination(current_page=current_page, all_count=self.queryset.count(), request=self.request) self.page_queryset = self.queryset[self.page_obj.start:self.page_obj.end] def get_header(self): # 表头展示 header_list = [] for field_or_func in self.config_obj.get_new_list_display(): if isinstance(field_or_func, str): # 当用户没有指定list_display 默认展示当前表的大写字符串表名 if field_or_func == \'__str__\': val = self.config_obj.model._meta.model_name.upper() else: val = self.config_obj.model._meta.get_field(field_or_func).verbose_name else: val = field_or_func(self.config_obj, is_header=True) header_list.append(val) return header_list def get_body(self): body_list = [] # [[obj1.title, obj1.price], [obj2.title, obj2.price],] for obj in self.page_queryset: tmp = [] for field_or_func in self.config_obj.get_new_list_display(): if isinstance(field_or_func, str): val = getattr(obj, field_or_func) # 从对象中找到某一个字段对应的值 if field_or_func in self.config_obj.list_display_links: _url = self.config_obj.get_reverse_url(\'edit\', obj) val = mark_safe(\'<a href="%s">%s</a>\' % (_url, val)) else: val = field_or_func(self.config_obj, obj=obj) tmp.append(val) body_list.append(tmp) return body_list def get_actions(self): tmp_list = [] # actions = [patch_init] for action in self.config_obj.actions: tmp_list.append({ \'name\': action.__name__, \'desc\': action.desc }) return tmp_list # [\'name\':\'\', \'desc\':\'\'] def get_filter(self): tmp_dict = {} for field in self.config_obj.list_filter: # [\'publish\', \'authors\'] tmp_list = [] rel_model = self.config_obj.model._meta.get_field(field).rel.to rel_queryset = rel_model.objects.all() filter_value = self.request.GET.get(field) # 获得用户点了什么 import copy params1 = copy.deepcopy(self.request.GET) if field in params1: params1.pop(field) s = mark_safe(\'<a href="?%s">ALL</a>\'% params1.urlencode()) else: s = mark_safe(\'<a href="">ALL</a>\') tmp_list.append(s) params = copy.deepcopy(self.request.GET) for obj in rel_queryset: params[field] = obj.pk # url = \'%s=%s\' % (field, obj.pk) if filter_value == str(obj.pk): s = mark_safe(\'<a href="?%s" class="active">%s</a>\' % (params.urlencode(), str(obj))) else: s = mark_safe(\'<a href="?%s">%s</a>\' % (params.urlencode(), str(obj))) tmp_list.append(s) # tmp_list.append(rel_queryset) tmp_dict[field] = tmp_list # {[\'publish\':[obj1, obj2, obj3]],\'authors\': [obj1, obj2, obj3]} return tmp_dict class ModelStark(object): list_display = [\'__str__\', ] list_display_links = [] model_form_class = None search_fields = [] actions = [] list_filter = [] def __init__(self, model): self.model = model self.app_label = self.model._meta.app_label # _meta.app_label 模型表所在的应用名 self.model_name = self.model._meta.model_name # _meta.model_name 模型表对应的字符串名 self.key_word = \'\' # 表格最左侧的选择框, is_header 代表该字段是否是表头信息 def check_col(self, is_header=False, obj=None): # 选择框的特点: 第一行为 选择两个字, 第二行为选择框的 勾选栏 状态 if is_header: return \'选择\' return mark_safe(\'<input type="checkbox" value="%s" name="selected_action"/>\' %obj.pk) def get_reverse_url(self, type, obj=None): # type: 代表种类,如list,add,edit,delete,obj为对象,如果obj存在,则为编辑或者删除 if obj: _url = reverse(\'%s_%s_%s\' % (self.app_label, self.model_name, type), args=(obj.pk,)) else: _url = reverse(\'%s_%s_%s\' % (self.app_label, self.model_name, type)) return _url def edit_col(self, is_header=False, obj=None): if is_header: return \'编辑\' _url = self.get_reverse_url(\'edit\', obj) return mark_safe(\'<a href="%s">编辑</a>\' % _url) def delete_col(self, is_header=False, obj=None): if is_header: return \'删除\' _url = self.get_reverse_url(\'delete\', obj) return mark_safe(\'<a href="%s">删除</a>\' % _url) # 让每个表中都含有选择、编辑、删除的列 def get_new_list_display(self): tmp = [] tmp.append(ModelStark.check_col) tmp.extend(self.list_display) # 根据查找顺序,此时查找的list_display不是上面定义的list_display, # 而是传过来的self表自定义的list_display,如若没有自定义的,则使用上面定义好的 if not self.list_display_links: tmp.append(ModelStark.edit_col) tmp.append(ModelStark.delete_col) return tmp def search(self, request, queryset): key_word = request.GET.get(\'q\') # 获取用户输入的值 self.key_word = \'\' if key_word: self.key_word = key_word # search_field = [\'title\', \'price\'] # queryset = queryset.filter(title__contains=key_word) # title__contains是变量名,因此不能循环search_field进行字符串拼接,获得的是字符串而不是变量 """ 查询条件变量名 查询条件 and ---> or """ q = Q() # Q查询允许通过字符串进行查找 q.connector = \'or\' # Q默认为and查询, 查询时需要改为or # 不停地往children中添加查询条件 for search_field in self.search_fields: q.children.append((\'%s__icontains\' % search_field, key_word)) # 拼接查询条件 queryset = queryset.filter(q) return queryset def filter_data(self, request, queryset): q = Q() for field in self.list_filter: if field in request.GET: field_value = request.GET.get(field) q.children.append((field, field_value)) queryset = queryset.filter(q) return queryset def list_view(self, request): print(self.model) # self为模型表对象,谁传过来就是谁,book传过来,self.model就是book queryset = self.model.objects.all() print(queryset) if request.method == \'POST\': # 获取用户选中的主键字段 action = request.POST.get(\'action\') # 函数名字符串形式 pk_list = request.POST.getlist(\'selected_action\') # 根据主键字段查询苏欧欧的数据 queryset_list = self.model.objects.filter(pk__in=pk_list) # 将queryset对象传给函数处理 real_action = getattr(self, action) # 拿真正的函数名 real_action(request, queryset_list) # search功能 queryset = self.search(request, queryset) # filter功能 list_filter = [\'publish\', \'authors\'] queryset = self.filter_data(request, queryset) show_obj = ShowList(self, queryset, request) url = self.get_reverse_url(\'add\') return render(request, \'stark/list_view.html\', locals()) def get_model_form(self): if self.model_form_class: return self.model_form_class from django.forms import ModelForm class ModelFormClass(ModelForm): class Meta: model = self.model fields = \'__all__\' return ModelFormClass def add_view(self, request): model_form_class= self.get_model_form() model_form_obj = model_form_class() if request.method == \'POST\': model_form_obj = model_form_class(request.POST) pop_back_id = request.GET.get(\'pop_back_id\') if model_form_obj.is_valid(): obj = model_form_obj.save() if pop_back_id: # 如果pop_back_id有值 说明是子页面提交过来的post请求 pk = obj.pk text = str(obj) return render(request, \'stark/pop.html\', locals()) return redirect(self.get_reverse_url(\'list\')) from django.forms.models import ModelChoiceField for form_obj in model_form_obj: # print(form_obj.field) # form_obj.field获得的是该字段的类型 if isinstance(form_obj.field, ModelChoiceField): form_obj.is_pop = True rel_model = self.model._meta.get_field(form_obj.name).rel.to # 获取外键字段对应的表的名字 rel_app_label = rel_model._meta.app_label rel_model_name = rel_model._meta.model_name url = reverse(\'%s_%s_%s\' % (rel_app_label, rel_model_name, \'add\')) print(form_obj.auto_id) # 当前input框对应的id值 url = url + \'?pop_back_id=%s\' % form_obj.auto_id form_obj.url = url return render(request, \'stark/add_view.html\', locals()) def edit_view(self, request, id): edit_obj = self.model.objects.filter(pk=id).first() model_form_class = self.get_model_form() model_form_obj = model_form_class(instance=edit_obj) if request.method == \'POST\': model_form_obj = model_form_class(request.POST, instance=edit_obj) if model_form_obj.is_valid(): model_form_obj.save() return redirect(self.get_reverse_url(\'list\')) return render(request, \'stark/edit_view.html\', locals()) def delete_view(self, request, id): self.model.objects.filter(pk=id).delete() return redirect(self.get_reverse_url(\'list\')) # 实现二级路由分发 起别名(反向解析)) @property def urls(self): tmp = [ url(r\'^$\', self.list_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'list\')), url(r\'^add/\', self.add_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'add\')), url(r\'^edit/(\d+)/\', self.edit_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'edit\')), url(r\'^delete/(\d+)/\', self.delete_view, name=\'%s_%s_%s\' % (self.app_label, self.model_name, \'delete\')), ] return tmp, None, None class StarkSite(object): def __init__(self, name=\'admin\'): self._registry = {} # model_class class -> admin_class instance def register(self, model, admin_class=None, **options): if not admin_class: admin_class = ModelStark # Instantiate the admin class to save in the registry self._registry[model] = admin_class(model) # admin_class(model)为配置类对象 # 实现一级路由,之所以不在这里实现二级路由分发,是因为这里的self是 site对象, # 当book,publish ... 传过来的时候,self知道依旧是site,无法对表的种类进行区分 def get_urls(self): tmp = [] for model_class, config_obj in self._registry.items(): # config_obj为配置类对象,跟模型表一一对应,因此,我们在配置类中进行二级路由分发 app_label = model_class._meta.app_label model_name = model_class._meta.model_name # tmp.append(url(r\'^%s/%s\' %(app_label, model_name), self.test)) # 实现一级路由 tmp.append(url(r\'^%s/%s/\' % (app_label, model_name), config_obj.urls)) # 实现一级路由 return tmp @property def urls(self): return self.get_urls(), None, None site = StarkSite()
from django.apps import AppConfig from django.utils.module_loading import autodiscover_modules class StarkConfig(AppConfig): name = \'stark\' def ready(self): # 项目一启动,就会自动查找每一个应用下的stark.py文件 return autodiscover_modules(\'stark\')
4.4 HTML页面
{% extends \'stark/base.html\' %} {% block css %} <link rel="stylesheet" href="/static/css/mycss.css"> {% endblock %} {% block content %} <h2>添加数据</h2> <div class="col-md-6 col-md-offset-3"> <form action="" method="post"> {% csrf_token %} {% for form_obj in model_form_obj %} <div class="plus-father"> <p>{{ form_obj.label }}{{ form_obj }} <span>{{ form_obj.errors.0 }}</span> </p> {% if form_obj.is_pop %} <span class="plus" onclick="WindowOpen(\'{{ form_obj.url }}\')">+</span> {% endif %} </div> {% endfor %} <input type="submit" class="btn btn-primary pull-right" > </form> </div> <script> function WindowOpen(url) { window.open(url, \'\', \'width=800px, height=400px\') } function addOptions(pop_back_id, pk, text) { // 动态创建option标签 var opEle = document.createElement(\'option\'); //给标签赋值 opEle.innerText = text; opEle.value = pk; opEle.selected = \'selected\'; // 查找option所在的父标签 var seEle = document.getElementById(pop_back_id); seEle.appendChild(opEle) } </script> {% endblock %}
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> <script src="/static/jquery-3.4.1.js"></script> <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"> <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script> {% block css %} {% endblock %} <style> .active { color: red; } .plus { position: absolute; font-size: 24px; color: #369; top: 19px; right: -27px; } .plus-father { position: relative; } </style> </head> <body> <div class="container"> <div class="row"> {% block content %} {% endblock %} </div> </div> </body> > </html>
{% extends \'stark/base.html\' %} {% block css %} <link rel="stylesheet" href="/static/css/mycss.css"> {% endblock %} {% block content %} <h2>编辑数据</h2> <div class="col-md-6 col-md-offset-3"> <form action="" method="post"> {% csrf_token %} {% for form_obj in model_form_obj %} <p>{{ form_obj.label }}{{ form_obj }}</p> <span>{{ form_obj.errors.0 }}</span> {% endfor %} <input type="submit" class="btn btn-primary pull-right"> </form> </div> {% endblock %}
{% extends \'stark/base.html\' %} {% block content %} <h2>数据展示</h2> <div class="col-md-9"> <a href="{{ url }}" class="btn btn-primary">添加数据</a> {# search功能开始 #} {% if show_obj.config_obj.search_fields %} <form class="form-inline pull-right"> <div class="form-group"> <div class="input-group"> <input type="text" class="form-control" id="exampleInputAmount" placeholder="关键字" name="q" value="{{ show_obj.config_obj.key_word }}"> </div> </div> <button type="submit" class="btn btn-primary">Search</button> </form> {% endif %} {# search功能结束 #} {# action样式开始 #} <form action="" method="post" class="form-inline"> {% csrf_token %} <select name="action" id="" class="form-control"> <option value="">-----------------------</option> {% for foo in show_obj.get_actions %} <option value="{{ foo.name }}">{{ foo.desc }}</option> {% endfor %} </select> <input type="submit" value="GO" class="btn btn-primary"> <table class="table table-bordered table-striped"> <thead> {% for head in show_obj.get_header %} <th>{{ head }}</th> {% endfor %} </thead> <tbody> {% for body in show_obj.get_body %} <tr> {% for foo in body %} <td>{{ foo }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form> {# action样式结束 #} {{ show_obj.page_obj.page_html|safe }} </div> <div class="col-md-3"> {% if show_obj.config_obj.list_filter %} <div class="alert-info text-center">FILTER</div> {% for k,v in show_obj.get_filter.items %} <div class="panel panel-default"> <div class="panel-heading">By {{ k }}</div> <div class="panel-body"> {% for foo in v %} <p>{{ foo }}</p> {% endfor %} </div> </div> {% endfor %} {% endif %} </div> {% endblock %}
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <script> window.opener.addOptions(\'{{ pop_back_id }}\', \'{{ pk }}\', \'{{ text }}\'); window.close() </script> </body> </html>
5 pop实现添加数据(需配合视频)
pop window.open(url,\'\',\'width=800px\') # 打开一个url页面, 中间为\'\',不用管, 第三为弹框的大小 子页面可以调用父页面中的方法 window.opener.fatherFunc(...) window.close() 1.哪些标签需要加 加号 form_obj.field form_obj.is_pop = True 2.给加号绑定点击事件 url是外键字段所对应的模型表的添加url app_label = models.Book._meta.app_label model_name = models.Book._meta.model_name url = reverse(\'%s_%s_add\'%(app_label,model_name)) function WindowOpen(url){ window.open(url,\'\',\'width=800px,height=400px\') } 2.如何在后端添加逻辑中区分是主页面还是子页面发送的post请求 在打开子页面的url后面加get请求参数 获取form_obj渲染的标签id值 form_obj.auto_id 3.父页面新增添加数据的方法 function addOption = document.createElement(\'option\')