实现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')