stark组件之创建
stark组件之需求
仿照Django中的admin , 开发了自己的stark组件,实现类似数据库客户端的功能,对数据进行增删改查 .
stark之创建
1.在项目中 创建stark应用,app01,app01应用(app01,app02是我们用来演示的两个应用)
2.把创建的应用在settings.py进行注册子到django中
3.在app01.和app02中创建模型
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'app02.apps.App02Config', 'stark.apps.StarkConfig', ]
from django.db import models # Create your models here. from django.db import models # Create your models here. class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书籍名称") pub_date = models.DateField(verbose_name="出版日期") price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="价格") publish = models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE, null=True) authors = models.ManyToManyField("Author", db_table="book2authors") # 创建关系表 def __str__(self): return self.title class Meta: verbose_name = '书籍' class Publish(models.Model): name = models.CharField(max_length=32, verbose_name="名字") city = models.CharField(max_length=32) email = models.CharField(max_length=32) def __str__(self): return self.name class Meta: verbose_name = '出版社' class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() gender = models.IntegerField(choices=((1, '男'), (2, '女')),default=1) # books=models.ManyToManyField("Book") ad = models.OneToOneField("AuthorDetail", null=True, on_delete=models.CASCADE) def __str__(self): return self.name class Meta: verbose_name = "作者" class AuthorDetail(models.Model): birthday = models.DateField() telephone = models.BigIntegerField() addr = models.CharField(max_length=64) # author=models.OneToOneField("Author",on_delete=models.CASCADE) def __str__(self): return str(self.telephone) class Meta: verbose_name = '作者详情'
from django.db import models # Create your models here. class Article(models.Model): title=models.CharField(max_length=32) def __str__(self): return self.title
1. 创建应用
2.. 注册(用命令行创建的应用并不会帮你注册,所以在自己进来注册)
stark之启动
问题来了,谁来加载我们自己创建的目录呢,我们可以在stark应用下的apps.py中
我们首先要明确的是,django加载的时候,会先执行每一个应用下的apps.py文件的函数,这让我们stark在加载的时候启动形成了可能性.
1. 所以我们先在stark应用中的apps.py创建启动所有应用的函数
2. 然后在app01,app01创建相应的stark.py文件
(这样设置后,当启动项目的时候,执行stark应用中的apps.py中的函数时候,也就能启动app01,和app02中的stark.py文件)
stark之注册(注册到stark组件,使用stark功能)
我们在使用Django内置的admin组件的时候首先就是要对我们创建的模型表进行注册,只有注册过后我们才可以对该表的数据进行增删改查。
首先,创建一个Python page包,新建一个stark文件包,次级目录下建立一个service的包,在包下面创建一个site文件
我们在使用admin组件的时候,是在我们注册的时候调用一个单例对象site进行注册
所以我们在新建的stark文件中也生成一个site对象
class StarkSite():
"""
StarkSite:基本类
model:注册模型类
admin_class:注册模型类的配置类
"""
def __init__(self):
self._registry = {}
# 任何一个应用导入这个对象的时候都是同一个(单例模式)
site = StarkSite()
然后在app01,app02中导入(他们导入的是同一个对象,同一个字典)
from stark.service.sites import site
site对象是StarkSite类创建出来的,我们使用site调用rigister方法的,所以StarkSite应该有register方法(sites.py)
class StarkSite(): """ StarkSite:基本类 model:注册模型类 stark_class:注册模型类的配置类 """ def __init__(self): #初始化方法,创建对象是直接生成一个空字典,如果其他文件从这个文件直接调用实例对象,那么一直都是同一个对象 self._registry = {} def register(self, model, stark_class=None): #用这个类的对象可以直接调用这个方法, stark_class = stark_class or ModelStark #这个方法就是把一个模型类和模型类的配置对象(把模型类对象作为配置类的参数)作为一个键值对传入字典 self._registry[model] = stark_class(model) #如果没有自定义配置类(),那么就使用默认的配置类ModelStark # 当其他文件导入这个对象时候,一致都是同一个对象,单例模式 site = StarkSite()
注册需要用到一个配置类ModelStark,所以我们也要创建这么一个类(sites.py)
# 配置类,对于基础类的配置 class ModelStark(): # 在注册类模型时候会议模型类和这个类或者这个的子类的实例化对象形成键值对,并把模型类的对象作为参数传给此类作为实例化对象的参数 def __init__(self, model): self.model = model self.model_name = self.model._meta.model_name self.app_label = self.model._meta.app_label self.app_model = (self.app_label, self.model_name)
在app01用导入的site对象调用register方法()
site.register(Book, BookConfig)
site.register(Publish, PublishConfig) #当有自定义配置类就用自定义,没有就用默认的配置类
site.register(AuthorDetail)
site.register(Author,AuthorConfig)
stark之url设计
django项目建起来之后就会自动创建一个url文件,其中admin的url就已经配置好了
urlpatterns = [ url(r'^admin/', admin.site.urls), ]
进入源码看一眼,会发现url方法有property装饰器,会将函数装饰成一个属性
它会在项目的启动的时候就执行,将内部的url进行分发,生成url的方法就是get_urls()
自定义url(url二级分发)
在StarkSite类中(基类)
class StarkSite():
@property def urls(self): return self.get_urls(), None, None #执行获取url函数
#获取url路径(一级分发)
def get_urls(self):
temp = []
for model, config_obj in self._registry.items(): #用注册的字典循环出来注册进来的模型类对象和配置类对象
app_label = model._meta.app_label #应用名称
model_name = model._meta.model_name #模型类对象名称
temp.append(
path('%s/%s/' % (app_label, model_name), config_obj.urls), #把应用名称,模型名称拼起来作为路径,再进行二级分发
)
return temp
在ModelStark类中
# 配置类,对于基础类的配置
class ModelStark():
@property def urls(self): return self.get_urls(), None, None # 二级分发四个路径(增删该查) def get_urls(self): temp = [ path('', self.list_view, name="%s_%s" % self.app_model), #什么都不写代表查看视图 path('add/', self.add_view, name="%s_%s_add" % self.app_model), re_path('(\d+)/del/', self.del_view, name="%s_%s_del" % self.app_model), #删除和编辑都是带参数的需要用re_path re_path('(\d+)/change/', self.change_view, name="%s_%s_change" % self.app_model), ] return temp
url配置好之后嗨哟做一个反向解析,后面会用到
# 反向解析url(不反向解析到时候不同模型的编辑,删除等会跳转到同一个url) def get_list_url(self): _url = reverse('%s_%s' % self.app_model) return _url def get_del_url(self, obj): _url = reverse('%s_%s_del' % self.app_model, args=(obj.pk,)) return _url def get_change_url(self, obj): _url = reverse('%s_%s_change' % self.app_model, args=(obj.pk,)) return _url def get_add_url(self): _url = reverse('%s_%s_add' % self.app_model) return _url
stark之增删改查功能
查
在页面上查看后台数据库存储的数据最好的方法就是在页面上呈现一个列表,将字段设置为表头,
将数据存放在表单,这样我们查看数据也更加的清晰明了。在我们使用admin组件中有很多定制
的方法,其中有一个list_play 就是在列表中定制显示的字段我们这边模仿他,然后list_play_links是
可以直接点击指定字段跳转到编辑页面的
class ModelStark(): # 类变量 list_play = ['__str__'] #指定展示类 list_play_links = [] #指定展示类中哪些字段可以被点击跳转到编辑页的 class_model_form = [] search_fields = [] #指定搜索字段 list_filter = [] #指定过滤字段 # 在注册类模型时候会议模型类和这个类或者这个的子类的实例化对象形成键值对,并把模型类的对象作为参数传给此类作为实例化对象的参数 def __init__(self, model): self.model = model self.model_name = self.model._meta.model_name #模型名 self.app_label = self.model._meta.app_label #应用名 self.app_model = (self.app_label, self.model_name)
因为我们主要想实现的功能就是增删改,所以我们还要在页面上加上相应的编辑和删除字段,
还可以配置一个checkbox字段以便进行批量数据处理,因为这三个字段是所有的数据都需要的,
所以就将其定义在stark组件中公共的配置类中。
class ModelStark():
# 复选框,删除,编辑按钮 ,如果是表头返回字段名,不是表头返回a标签 def checkbox(self, obj=None, is_head=False): if is_head: return '选择' return mark_safe('<input type="checkbox" name="choose_pk" value="%s">' % obj.pk) def edit(self, obj=None, is_head=False): if is_head: return '编辑' return mark_safe('<a href="%s">编辑</a>' % self.get_change_url(obj)) #加入方向解析 def _delete(self, obj=None, is_head=False): if is_head: return '删除' return mark_safe('<a href="%s">删除</a>' % self.get_del_url(obj)) #加入到反向解析
经过这样两次配置就可以知道页面上到底要展示那些字段了,
就可以将用户独有的配置与公共的配置结合在一起展示在页面上
class ModelStark():
# 获取表头字段(默认第一列是复选框,最后两列为编辑和删除按钮) # 对表头字段处理(默认的,函数,或者verbox)返回给前端 def get_new_display(self): new_list_display = [] new_list_display.extend(self.list_play) #先把定义的字段加入一个新的列表 new_list_display.insert(0, ModelStark.checkbox) #在新的列表第一个位置植入复选框 new_list_display.append(ModelStark._delete) #在最后一个字段加入删除 if not self.list_play_links: #判断是否有字段是a标签(可以跳转到编辑页面的),没有就加入编辑按钮 new_list_display.append(ModelStark.edit) return new_list_display
因为展示界面需要编写的逻辑代码会比较多,所以我们可以将展示相关的功能拿出来独立的封装成一个展示类,
将一个个需要展示的功能封装成一个个方法,这个更利于整体代码的解耦合。
先看一下关于查函数
def list_view(self, request): # self 模型类对象对应的配置类对象,可能是默认配置类ModelStark对象,也可能是自定义配置类对象 # self.model 当前访问表的模型类 # post请求为批量操作请求,先拿到函数名,和要操作的值,再进行操作jiu可以 if request.method == "POST": patch_func_str = request.POST.get('patch_func') choose_pk = request.POST.getlist('choose_pk') # getlist获取列表pahc queryset = self.model.objects.filter(pk__in=choose_pk) patch_func = getattr(self, patch_func_str) res = patch_func(request, queryset) if res: return res # 当函数有返回值的时候,返回,没有的时候,就就继续往下走,展现最先的数据
queryset = self.model.objects.all() #当请求是走查这个视图函数的时候走这里拿到所有的对象 showList = Show_list(self, request, queryset) #创建展示类对象showList并把相应的参数传进去 # 动态获取表名的当前模型类的添加url table_name = self.model._meta.verbose_name add_url = self.get_add_url() return render(request, 'stark/list_view.html', locals())
#展示类
# 展示类 class Show_list(): def __init__(self, config_obj, request, queryset): #展示类,需要的参数,配置类对象,request,queryset,原生的所有查询数据 self.config_obj = config_obj self.request = request self.queryset = queryset self.getPagination() # 展示表头 def show_head(self): # 展示表头 head_list = [] for field in self.config_obj.get_new_display(): #从整合出来的列表当中获取每一个字段 try: field_obj = self.config_obj.model._meta.get_field(field) #拿到模型类的字段对象 head_list.append(field_obj.verbose_name) #拿到字段的verbose_name(自定义名称),这个只有是字符串的时候才会走这边 except FieldDoesNotExist as e: #拿不到verbose_name走这里,有两种情况 if field == '__str__': #默认的配置类,或者说自定义类,但是没有指定展示字段,还是用的默认配置类的展示列表self.list_play=['__str__'] val = self.config_obj.model._meta.model_name.upper() #返回表名大写 else: val = field(self.config_obj, is_head=True) #是函数就直接执行,拿到返回值 head_list.append(val) return head_list # 展示表体 def show_body(self): # 展示体数据到前端 data = [] for obj in self.page_queryset: tem = [] for field in self.config_obj.get_new_display(): # field是字符串不能直接点 if callable(field): val = field(self.config_obj, obj) else: try: field_obj = self.config_obj.model._meta.get_field(field) if field_obj.choices: val = getattr(obj, 'get_' + field + '_display') else: val = getattr(obj, field) except FieldDoesNotExist as e: val = getattr(obj, field)() # 最好加括号 if field in self.config_obj.list_play_links: val = '<a href="%s">%s</a>' % (self.config_obj.get_change_url(obj), val) tem.append(mark_safe(val)) data.append(tem) return data
stark之前端页面展示
建立如图所示的文件目录结构 ,用来存放网页文件(不一定要这样的结构,这样的结构是为了避免重名)
#list_view.html
{# 表格 #} <table class="table table-striped table-hover"> <thead> <tr> {% for item in showList.show_head %} #用实例化出来的展示对象showList去调用show_body方法拿到head_list <td>{{ item }}</td> #循环出来的字段放进td标签 {% endfor %} </tr> </thead> <tbody> {% for obj in showList.show_body %} <tr> {% for val in obj %} <td>{{ val }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table>
增
sites.py文件中
# 配置类,对于基础类的配置
class ModelStark():
# 添加视图函数 def add_view(self, request): # self 模型类对象对应的配置类对象,可能是默认配置类ModelStark对象,也可能是自定义配置类对象 # self.model 当前访问表的模型类 userModelForm = self.get_model_form() #先去执行get_model_form函数拿到空表单 if request.method == 'POST': #当是post方法就就用户传过来的数据填入表单,构造对象,然后对对象进行过绿.保存,返回查看页面 form = userModelForm(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'stark/add_view.html', {'form': form}) #验证不通过,返回他填入数据的表单 else: #get请求拿到静的表单 form = userModelForm() return render(request, 'stark/add_view.html', {'form': form}) # 二级分发四个路径(增删该查)
上面会设涉及到get_model_form()这个函数
# 配置类,对于基础类的配置 class ModelStark(): # 构建获取modelform表单函数(用来渲染页面和设置参数)用于编辑和添加视图函数 def get_model_form(self): class BaseModelForm(forms.ModelForm): #这是Modelform,并不是普通的form class Meta: model = self.model #对哪一张模型表生成form fields = '__all__' #对模型表中的那些字段进行生成form return self.class_model_form or BaseModelForm
add_view.html
<h3>添加</h3> {% include "stark/formbase.html" %} #这是因为添加和编辑的页面是一样的,所以提取到一个公共的form办单中 </body> </html>
formbase.html
<div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="" method="post" novalidate> #表单 {% csrf_token %} #验证钥匙 {% for field in form %} <div class="form-group"> #一个字段一个块{{field}}直接渲染出来一个input标签 <label for="">{{field.label}}</label> {{ field }} <span class="error pull-right">{{ field.errors.0 }}</span> </div> {% endfor %} <input type="submit" value="提交" class="btn btn-default"> </form> </div> </div> </div>
改
视图函数
# 编辑视图函数 def change_view(self, request, id): # self 模型类对象对应的配置类对象,可能是默认配置类ModelStark对象,也可能是自定义配置类对象 # self.model 当前访问表的模型类 edit_obj = self.model.objects.filter(pk=id).first() #先获取要修改的对象, userModelForm = self.get_model_form() #构造一张空表单 if request.method == "POST": form = userModelForm(request.POST, instance=edit_obj) #用传进来对象和空表单构建新的对象,然后筛选功率instance=obj参数一定要带,否则就是添加了 if form.is_valid(): form.save() #验证成功保存,重定向到查看页面 return redirect(self.get_list_url()) else: return render(request, 'stark/edit_view.html', {'form': form}) #不成功直接返回编辑页面 else: form = userModelForm(instance=edit_obj) return render(request, 'stark/edit_view.html', {'form': form})
edit_view.html
<h3>编辑</h3> {% include 'stark/formbase.html' %}
删
# 删除视图函数 def del_view(self, request, id): # self 模型类对象对应的配置类对象,可能是默认配置类ModelStark对象,也可能是自定义配置类对象 # self.model 当前访问表的模型类 # 获取查视图,当取消的时候,跳转到查看页面 list_url = self.get_list_url() #解析到查看页面的url if request.method == 'POST': #是post就筛选出阿拉那个对象从数据库中删除 self.model.objects.filter(pk=id).first().delete() return redirect(list_url) #删除成功重定向到查看页面 return render(request, 'stark/del_view.html', {'list_url': list_url}) #get请求拿到一个页面,form表单中有删除和取消两个按钮
del_view.html
<form action="" method="post"> {% csrf_token %} <a class="btn btn-warning" href="{{ list_url }}" >取消</a> <input type="submit" class="btn btn-danger" value="确认"> </form>
stark之查询功能
当页面上的记录过多,分为好几页甚至好几十页的时候我们自己就没有办法快速找到我们想要的记录了,
这个时候就需要用到一个search功能,对我们想要查找的数据进行一个模糊查询,快速定位到相关的记录。
这就需要在前端写一个用于接收用户数据的输入框,将用户输入的数据接收到后端进行处理
页面结构
{# 搜索框先判断永不有没有定制搜索字段 #} {% if showList.config_obj.search_fields %} <form class="form-inline pull-right"> <div class="input-group"> <input type="text" class="form-control" id="Q" name="Q" placeholder="请输入关键词" value="{{ showList.search_default_val }}"> </div> <button type="submit" class="btn btn-default">搜索</button> </form> {% endif %}
前面也有提到,当我们有一些独有的配置的时候就需要在每个模型表注册的同时注册一个配置类。
将需要自定制的配置写在配置类里面。这个时候我们应该自定义一个查询配置
search_fields = []
函数
def get_search_queryset(self):
#获取前端传来的值 val=self.request.GET.get("Q") if val: self.search_default_val = val from django.db.models import Q search_condition=Q() search_condition.connector="or" #Q默认是and,或者or连接 for search_field in self.config_obj.search_fields: #循环出来要匹配的字段 search_condition.children.append((search_field+"__icontains",val)) self.search_queryset=self.queryset.filter(search_condition) #用搜索条件进行过滤 return self.search_queryset return self.queryset
然后到查询界面的视图函数中将这个函数执行,将执行后返回的数据,返还给前端页面进行渲染
stark之action(批量处理)
当大量的数据需要进行处理的时候,仅凭一个人一个一个操作肯定会大大加大工作压力,
这就需要用到批量处理操作
1、自定义配置信息
在配置类中自定义批量处理的函数,将函数名当做变量放到列表以便处理
# 定义批量删除函数 def patch_delete(self, request, queryset): queryset.delete() patch_delete.short_desc = '批量删除' #一个简单的描述 # 空列表,为了让自定义配置模型有这个列表 actions = []
视图函数
def list_view(self, request): # self 模型类对象对应的配置类对象,可能是默认配置类ModelStark对象,也可能是自定义配置类对象 # self.model 当前访问表的模型类 # post请求为批量操作请求,先拿到函数名,和要操作的值,再进行操作jiu可以 #处理批量函数 if request.method == "POST": patch_func_str = request.POST.get('patch_func') choose_pk = request.POST.getlist('choose_pk') # getlist获取列表(要删除的数九列表) queryset = self.model.objects.filter(pk__in=choose_pk) patch_func = getattr(self, patch_func_str) res = patch_func(request, queryset) if res: return res # 当函数有返回值的时候,返回,没有的时候,就就继续往下走,展现最先的数据 queryset = self.model.objects.all() # 所有的对象 showList = Show_list(self, request, queryset) # 动态获取表名的当前模型类的添加url table_name = self.model._meta.verbose_name add_url = self.get_add_url() return render(request, 'stark/list_view.html', locals())
前端
{# 批量操作 #} <select name="patch_func" class="form-control " style="display: inline-block;width: 150px"> {% for func in showList.show_actions %} <option value="{{ func.name }}">{{ func.desc }}</option> {% endfor %} </select>
stark之filter(多级过滤)
类中应该有在过滤字段
视图函数
# 展示多级过滤函数 特别时候一对多,多对多字段 def show_list_filter(self): link_dict = {} import copy for field in self.config_obj.list_filter: # 为了拿到请求字段关联表对象的集合 params = copy.deepcopy(self.request.GET) # 拿到请求的键值对 field_obj = self.config_obj.model._meta.get_field(field) # 拿到字段对象,直接循环出来的是字符串 from django.db.models.fields.related import ForeignKey, ManyToManyField if isinstance(field_obj, ForeignKey) or isinstance(field_obj, ManyToManyField): rel_model = field_obj.remote_field.model # 这个方法只有一对多和多对多能使用,choice字段和普通字段都不能使用 data_list = rel_model.objects.all() elif field_obj.choices: data_list = field_obj.choices else: # data_list=self.config_obj.model.objects.all() data_list = [] # 添加all过滤标签 links = [] if params.get(field): params.pop(field) link_all = '<a href="?%s" class="btn btn-default">全部</a>' % params.urlencode() links.append(link_all) for data in data_list: if type(data) == tuple: pk, text = data else: pk, text = data.pk, str(data) # 把对象集合循环出来,做成a标签 current_request_field_val = self.request.GET.get(field) # 拿到当前请求字段对应的值 params[field] = pk if str(pk) == current_request_field_val: link = '<a href="?%s" class="active btn btn-default">%s</a>' % (params.urlencode(), text) else: link = '<a href="?%s" class="btn btn-default">%s</a>' % (params.urlencode(), text) links.append(link) link_dict[field] = links return link_dict
前端
{# 循环展示多级过滤标签 #把show_list_filter传过来的字段进项循环#} {% if showList.config_obj.list_filter%} <div class="panel panel-default filter_body"> {% for field,links in showList.show_list_filter.items %} <div class="filter_body_item"> <span class="btn btn-default disabled">{{ field }}</span> {% for link in links %} {{ link|safe }} {% endfor %} </div> {% endfor %} </div> {% endif %}