10.18正式开发stark组件*(三)
2018-10-18 19:15:54
等这个stark组件做完了再上传到github上面,然后再整理博客!这就到周末啦!
因为models导入的时候出现bug,所以只有源码没有测试数据!
源码都有注释,已经很详细啦! 一步一步能看懂!
里面重要的思想就是 用类封装,组件用模块封装! 然后解耦!把重复的东西封装成类!面向对象编程!
看源码也许是一种享受!
越努力,与幸运!永远不要高估自己!
新增了 搜索框!和批量操作!
批量操作没有默认的删除可以迭代自行完成!批量操作需要用户自己定制函数
先放上用到的新的知识点!
# Q 查询的两种方式 # Book.objects.filter(Q(title="yuan")|Q(price=123)) # # 第二种方式可以传入字符串 # q=Q() # q.connection="or" # q.children.append(("title","yuan")) # q.children.append(("price",123)) # 模糊查找 # ret=self.model.objects.filter(title__startswith="py") # ret=self.model.objects.filter(price__in=[12,34,56,78,222]) # ret=self.model.objects.filter(price__range=[10,100]) # ret=self.model.objects.filter(title__contains="o") # ret=self.model.objects.filter(title__icontains="o") # print(ret)
分页的组件(只要传好参数就可以直接用)
utils/page.py
import copy # 自定义分页组件 注意参数 class Pagination(object): def __init__(self, current_page, all_count, base_url, params, per_page_num=8, pager_count=11, ): """ 封装分页相关数据 :param params: 接收url里面的参数也就是键值对?xx=2&xx=3&xx=4 类似于这样的 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param per_page_num: 每页显示的数据条数 :param base_url: 分页中显示的URL前缀 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num self.base_url = base_url # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count # 最多显示页码数 self.pager_count_half = int((pager_count - 1) / 2) params = copy.deepcopy(params) params._mutable = True self.params = params # self.params : {"page":77,"title":"python","nid":1} @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示(11-1)/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_start = self.all_pager - self.pager_count + 1 pager_end = self.all_pager + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] self.params["page"] = 1 first_page = '<li><a href="%s?%s">首页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一页</a></li>' else: self.params["page"] = self.current_page - 1 prev_page = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(prev_page) for i in range(pager_start, pager_end): # self.params : {"page":77,"title":"python","nid":1} # 既保留每次循环的page 有保留了后面参数的值 # urlencode()是内置方法 把键值拼成url ?page=1&xx=22 类似于这样的 # 这样做法仅仅变页码page 然后后面参数也就是条件不变 self.params["page"] = i # {"page":72,"title":"python","nid":1} if i == self.current_page: temp = '<li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,) else: temp = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一页</a></li>' else: self.params["page"] = self.current_page + 1 next_page = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(next_page) self.params["page"] = self.all_pager last_page = '<li><a href="%s?%s">尾页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(last_page) return ''.join(page_html_list) # class Pagination(object): # # def __init__(self, data_num, current_page, url_prefix,params, per_page=10, max_show=4): # """ # 进行初始化. # :param data_num: 数据总数 # :param current_page: 当前页 # :param url_prefix: 生成的页码的链接前缀 # :param per_page: 每页显示多少条数据 # :param max_show: 页面最多显示多少个页码 # """ # self.data_num = data_num # self.per_page = per_page # self.max_show = max_show # self.url_prefix = url_prefix # # # 把页码数算出来 # self.page_num, more = divmod(data_num, per_page) # if more: # self.page_num += 1 # # try: # self.current_page = int(current_page) # except Exception as e: # self.current_page = 1 # # 如果URL传过来的页码数是负数 # if self.current_page <= 0: # self.current_page = 1 # # 如果URL传过来的页码数超过了最大页码数 # elif self.current_page > self.page_num: # self.current_page = self.page_num # 默认展示最后一页 # # # 页码数的一半 算出来 # self.half_show = max_show // 2 # # # 页码最左边显示多少 # if self.current_page - self.half_show <= 1: # self.page_start = 1 # self.page_end = self.max_show # elif self.current_page + self.half_show >= self.page_num: # 如果右边越界 # self.page_end = self.page_num # self.page_start = self.page_num - self.max_show # else: # self.page_start = self.current_page - self.half_show # # 页码最右边显示 # self.page_end = self.current_page + self.half_show # # # import copy # self.params=copy.deepcopy(params) # {"page":"12","title_startwith":"py","id__gt":"5"} # # # # @property # def start(self): # # 数据从哪儿开始切 # return (self.current_page - 1) * self.per_page # # @property # def end(self): # # 数据切片切到哪儿 # return self.current_page * self.per_page # # def page_html(self): # # 生成页码 # l = [] # # 加一个首页 # l.append('<li><a href="{}?page=1">首页</a></li>'.format(self.url_prefix)) # # 加一个上一页 # if self.current_page == 1: # l.append('<li class="disabled" ><a href="#">«</a></li>'.format(self.current_page)) # else: # l.append('<li><a href="{}?page={}">«</a></li>'.format(self.url_prefix, self.current_page - 1)) # # # # # {"page":"12","title_startwith":"py","id__gt":"5"} # "page=12&title_startwith=py&id__gt=5" # # # print(self.params.urlencode()) # for i in range(self.page_start, self.page_end + 1): # self.params["page"]=i # # {"page":"7","title_startwith":"py","id__gt":"5"} # "page=7&title_startwith=py&id__gt=5" # if i == self.current_page: # tmp = '<li class="active"><a href="{0}?page={1}">{1}</a></li>'.format(self.url_prefix, i) # else: # tmp = '<li><a href="{0}?{1}">{2}</a></li>'.format(self.url_prefix, self.params.urlencode(),i) # l.append(tmp) # # # # # # # # # 加一个下一页 # if self.current_page == self.page_num: # l.append('<li class="disabled"><a href="#">»</a></li>'.format(self.current_page)) # else: # l.append('<li><a href="{}?page={}">»</a></li>'.format(self.url_prefix, self.current_page + 1)) # # 加一个尾页 # l.append('<li><a href="{}?page={}">尾页</a></li>'.format(self.url_prefix, self.page_num)) # return "".join(l)
app01/models.py
from django.db import models # Create your models here. from django.db import models # Create your models here. 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) age=models.IntegerField() # 与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() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) def __str__(self): return self.telephone class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField() def __str__(self): return self.name class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField( max_length=32) publishDate=models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2) # 与Publish建立一对多的关系,外键字段建立在多的一方 publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors=models.ManyToManyField(to='Author',) def __str__(self): return self.title
server/server.py
from django.conf.urls import url from django.shortcuts import render, redirect from django.urls import reverse from django.utils.safestring import mark_safe from django.forms import ModelForm from stark.utils.page import Pagination from django.db.models import Q class ShowList(object): # 这是一个配置类的对象初始化 def __init__(self, config, data_list, request): self.config = config self.data_list = data_list self.request = request # 分页 data_count = self.data_list.count() current_page = int(self.request.GET.get("page", 1)) base_path = self.request.path self.pagination = Pagination(current_page, data_count, base_path, self.request.GET, per_page_num=3, pager_count=11,) self.page_data = self.data_list[self.pagination.start:self.pagination.end] # actions 获取actions这个配置类的列表 self.actions = self.config.actions # [patch_init,] # 获取下拉框 用户配置的action_list def get_action_list(self): temp = [] for action in self.actions: # [{"name":""patch_init,"desc":"批量初始化"}] temp.append({ "name": action.__name__, "desc": action.short_description }) return temp # 构建表头 def get_header(self): header_list = [] print("header", self.config.new_list_play()) # [checkbox,"pk","name","age",edit ,deletes] 【checkbox ,"__str__", edit ,deletes】 for field in self.config.new_list_play(): if callable(field): # header_list.append(field.__name__) val = field(self.config, header=True) header_list.append(val) else: if field == "__str__": header_list.append(self.config.model._meta.model_name.upper()) else: # header_list.append(field) val = self.config.model._meta.get_field(field).verbose_name header_list.append(val) return header_list # 构建表单数据 def get_body(self): new_data_list = [] for obj in self.page_data: temp = [] for filed in self.config.new_list_play(): # ["__str__",] ["pk","name","age",edit] if callable(filed): val = filed(self.config, obj) else: val = getattr(obj, filed) if filed in self.config.list_display_links: # "app01/userinfo/(\d+)/change" _url = self.config.get_change_url(obj) val = mark_safe("<a href='%s'>%s</a>" % (_url, val)) temp.append(val) new_data_list.append(temp) return new_data_list class ModelStark(object): # 默认的list_play[] list_display = ["__str__", ] list_display_links = [] modelform_class = None search_fields = [] actions = [] def __init__(self, model, site): self.model = model self.site = site # 配置表头: 删除 编辑,复选框 def edit(self, obj=None, header=False): """编辑""" if header: return "操作" # return mark_safe("<a href='%s/change'>编辑</a>"%obj.pk) _url = self.get_change_url(obj) return mark_safe("<a href='%s'>编辑</a>" % _url) def deletes(self, obj=None, header=False): """删除""" if header: return "操作" # return mark_safe("<a href='%s/change'>编辑</a>"%obj.pk) _url = self.get_delete_url(obj) return mark_safe("<a href='%s'>删除</a>" % _url) def checkbox(self, obj=None, header=False): """复选框""" if header: return mark_safe('<input id="choice" type="checkbox">') # value的值不能写死, return mark_safe('<input class="choice_item" type="checkbox" name="selected_pk" value="%s">' % obj.pk) # 获取配置类的表头信息 def get_modelform_class(self): """获取表的配置类""" if not self.modelform_class: # 如果表的配置类为空 class ModelFormDemo(ModelForm): class Meta: model = self.model fields = "__all__" labels = { "" } return ModelFormDemo else: return self.modelform_class # 添加的视图函数 def add_view(self, request): ModelFormDemo = self.get_modelform_class() if request.method == "POST": form = ModelFormDemo(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) return render(request, "add_view.html", locals()) form = ModelFormDemo() return render(request, "add_view.html", locals()) # 删除的视图函数 def delete_view(self, request, id): url = self.get_list_url() if request.method == "POST": self.model.objects.filter(pk=id).delete() return redirect(url) return render(request, "delete_view.html", locals()) # 编辑的视图函数 def change_view(self, request, id): ModelFormDemo = self.get_modelform_class() edit_obj = self.model.objects.filter(pk=id).first() if request.method == "POST": form = ModelFormDemo(request.POST, instance=edit_obj) if form.is_valid(): form.save() return redirect(self.get_list_url()) return render(request, "add_view.html", locals()) form = ModelFormDemo(instance=edit_obj) return render(request, "change_view.html", locals()) # 搜索的视图函数 def get_serach_conditon(self, request): key_word = request.GET.get("q", "") self.key_word = key_word search_connection = Q() if key_word: # self.search_fields # ["title","price"] search_connection.connector = "or" # 用Q的这种添加方法可以添加字符串 for search_field in self.search_fields: # search_field+"__contains" ----> title__contains="o" 就是title字段里面包含字母o的 search_connection.children.append((search_field + "__contains", key_word)) return search_connection # 查看的视图函数 def list_view(self, request): if request.method == "POST": # action print("POST:", request.POST) action = request.POST.get("action") selected_pk = request.POST.getlist("selected_pk") # 通过getattr()传入函数名的字符串和调用该函数的对象 拿到该函数的变量 action_func = getattr(self, action) queryset = self.model.objects.filter(pk__in=selected_pk) # 把request和queryset对象传给函数,然后执行,最后return给用户配置的那个函数 ret = action_func(request, queryset) return ret # 获取search的Q对象 search_connection = self.get_serach_conditon(request) # 筛选获取当前表所有数据 data_list = self.model.objects.all().filter(search_connection) # 【obj1,obj2,....】 # 按这ShowList展示页面 showlist=ShowList(self, data_list, request) # 构建一个查看URL add_url = self.get_add_url() return render(request, "list_view.html", locals()) # 获取用户配置类里面的list_play[] def new_list_play(self): temp = [] temp.append(ModelStark.checkbox) temp.extend(self.list_display) if not self.list_display_links: temp.append(ModelStark.edit) temp.append(ModelStark.deletes) return temp """把url进行反向解析,解耦到各自的函数中,函数中直接返回了对应的url""" # 获取修改页面的url def get_change_url(self, obj): model_name = self.model._meta.model_name app_label = self.model._meta.app_label _url = reverse("%s_%s_change" % (app_label, model_name), args=(obj.pk,)) return _url # 获删除改页面的url def get_delete_url(self, obj): model_name = self.model._meta.model_name app_label = self.model._meta.app_label _url = reverse("%s_%s_delete" % (app_label, model_name), args=(obj.pk,)) return _url # 获取添加页面的url def get_add_url(self): model_name = self.model._meta.model_name app_label = self.model._meta.app_label _url = reverse("%s_%s_add" % (app_label, model_name)) return _url # 获取查看页面的url def get_list_url(self): model_name = self.model._meta.model_name app_label = self.model._meta.app_label _url = reverse("%s_%s_list" % (app_label, model_name)) return _url # 二级url分发函数 def get_urls_2(self): temp = [] model_name = self.model._meta.model_name app_label = self.model._meta.app_label temp.append(url(r"^add/", self.add_view, name="%s_%s_add" % (app_label, model_name))) temp.append(url(r"^(\d+)/delete/", self.delete_view, name="%s_%s_delete" % (app_label, model_name))) temp.append(url(r"^(\d+)/change/", self.change_view, name="%s_%s_change" % (app_label, model_name))) temp.append(url(r"^$", self.list_view, name="%s_%s_list" % (app_label, model_name))) return temp @property def urls_2(self): print(self.model) return self.get_urls_2(), None, None class StarkSite(object): def __init__(self): self._registry = {} def register(self, model, stark_class=None): if not stark_class: stark_class = ModelStark self._registry[model] = stark_class(model, self) # 一级分发url函数 def get_urls(self): temp = [] for model, stark_class_obj in self._registry.items(): model_name = model._meta.model_name app_label = model._meta.app_label # 分发增删改查 temp.append(url(r"^%s/%s/" % (app_label, model_name), stark_class_obj.urls_2)) ''' url(r"^app01/userinfo/",UserConfig(Userinfo).urls_2), url(r"^app01/book/",ModelStark(Book).urls_2), ''' return temp @property def urls(self): return self.get_urls(), None, None # 创建stark的一个单例对象 site = StarkSite()
list.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery-1.12.4.min.js"></script> </head> <body> <h4>数据列表</h4> <div class="container"> <div class="row"> <div class="col-md-9"> <a href="{{ add_url }}" class="btn btn-primary">添加数据</a> {% if showlist.config.search_fields %} <form action="" class="pull-right"> <input type="text" name="q" value="{{ showlist.config.key_word }}"><button>submit</button> </form> {% endif %} <form action="" method="post"> {% csrf_token %} <select name="action" id="" style="width: 200px;padding: 5px 8px;display: inline-block"> {% for item in showlist.get_action_list %} <option value="">---------------</option> <option value="{{ item.name }}">{{ item.desc }}</option> {% endfor %} </select><button type="submit" class="btn btn-info">Go</button> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in showlist.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for data in showlist.get_body %} <tr> {% for item in data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> <nav class="pull-right"> <ul class="pagination"> {{ showlist.pagination.page_html|safe }} </ul> </nav> </form> </div> </div> </div> <script> $("#choice").click(function () { if($(this).prop("checked")){ $(".choice_item").prop("checked",true) }else { $(".choice_item").prop("checked",false) } }) </script> </body> </html>
app01/server.py
from stark.service.stark import site, ModelStark from django.shortcuts import HttpResponse from .models import * from django.forms import ModelForm class BookModelForm(ModelForm): class Meta: model = Book fields = "__all__" labels = { "title": "书籍名称", "price": "价格" } # Book表的配置类 class BookConfig(ModelStark): # 自定义显示字段 list_display = ["title", "price", "publishDate"] modelform_class = BookModelForm # 自定义搜索字段 search_fields = ["title", "price"] # 自定义action函数,在下拉框中显示 def patch_init(self, request, queryset): print(queryset, request) queryset.update(price=123) return HttpResponse("批量初始化OK") patch_init.short_description = "批量初始化" actions = [patch_init] site.register(Book,BookConfig) site.register(Publish) site.register(Author) site.register(AuthorDetail)
这个stark完完全仿照的admin !并且仿照的很成功!