stark组件
介绍:
stark组件,是一个帮助开发者快速实现数据库表的增删改查。
目标:
10s 钟完成一张表的增删改查
前戏
项目启动前加载指定文件
-
Django 启动时,在执行 url.py之前执行一个指定程序
# 在已注册的APP中 app.py 里面 # django 内部有两个线程在运行,一个是运行Django,一个是检测代码是否发生变化 #runserver --norload 不启动检测线程 class appconfig(appconfig): name = 'app' def ready(self): # 路由加载之前 在已注册的所有APP中自动寻找xxxx.py文件,并导入 # 如果执行两次,是因为Django内部自动重启导致 autodiscover_modules("xxxx") # 提示: 如果xxxx.py 执行的代码向“某个神奇的地方”放入了一些值,之后的路由加载时,可以去'某个神奇的地方'读取原理的值
单例模式
-
一个实例,一个对象
# 多实例 class foo(object): pass obj1 = foo() print(obj1) #<__main__.foo object at 0x02B9E670> obj2 = foo() print(obj2) #<__main__.foo object at 0x02B9E538> 这是两个实例
-
通过python 模块导入的方式,实现单例模式
xx.py class AdminSite(object): pass site = AdminSite() # 为AdminSite类创建了一个对象(实例) app.py import utils print(utils.site) import utils print(utils.site) """ 在Python中,如果已经导入过的文件再次被重新导入时候,python不会再重新解释一遍,而是选择从内存中直接将原来导入的值拿来用。 """
-
补充
- 如果以后存在一个单例模式的对象,可以先在此对象中放入一个值,然后在其他的文件中导入该对象,通过对象再次将值获取到
Dango路由分发的本质,include
-
方式一
from django.conf.urls import url,include urlpatterns = [ url(url(r'^web/', include('app01.urls')), # include 返回一个元组 (urlconf_module, app_name, namespace)) ]
-
方式二
# include 函数主要返回有三个元素的元组 from django.conf.urls import url,include from app01 import urls urlpatterns = [ url(r'^web/', (urls, app_name, namespace)), ]
开始:
1.创建 Django project
2.创建基础业务表
-
app01/models.py
部门表
用户表 -
app02/models.py
主机表
3.对以上的三张表做增删改查
a.分析
-
为每张表创建4个url
-
为每张表创建4个视图函数
# 避免url重复,默认以应用名为开头 app01/models.py Depart /app01/depart/list/ /app01/depart/add/ /app01/depart/edit/(d+)/ /app01/depart/del/(d+)/ UserInfo /app01/userinfo/list/ /app01/userinfo/add/ /app01/userinfo/edit/(d+)/ /app01/userinfo/del/(d+)/ app02/models.py Host /app02/host/list/ /app02/host/add/ /app02/host/edit/(d+)/ /app02/host/del/(d+)/
b.为app中的每个model类自动创建URL以及相关视图函数
-
将视图提取到基类
# v1.py class StarkSite(object): def __init__(self): self._registry = [] self.app_name = 'stark' self.namespace = 'stark' def register(self, model_class, handler_class): """ :param model_class: 是models中的数据库表对应的类。 models.UserInfo :param handler_class: 处理请求的视图函数所在的类 :return: """ self._registry.append({'model_class': model_class, 'handler': handler_class(model_class)}) def get_urls(self): patterns = [] for item in self._registry: model_class = item['model_class'] handler = item['handler'] app_label, model_name = model_class._meta.app_label, model_class._meta.model_name # model_class._meta.app_label 获取models类所在的app名称 # model_class._meta.model_name 获取models类的名称 # patterns.append(url(r'x1/',lambda request:HttpResponse("1")),) patterns.append(url(r'%s/%s/list/$' % (app_label, model_name,), handler.changelist_view)) patterns.append(url(r'%s/%s/add/$' % (app_label, model_name,), handler.add_view)) patterns.append(url(r'%s/%s/change/(d+)/$' % (app_label, model_name,), handler.change_view)) patterns.append(url(r'%s/%s/del/(d+)/$' % (app_label, model_name,), handler.change_view)) return patterns @property def urls(self): return self.get_urls(), self.app_name, self.namespace site = StarkSite()
#url.py from django.contrib import admin from django.urls import path from stark.service.v1 import site print(site._registry) urlpatterns = [ path('admin/', admin.site.urls), path('^stark/', site.urls), #(site.get_urls(), site.app_name, site.namespace) ]
-
URL分发扩展&后缀
-
为url设置别名
-
url的别名进行重新生成
c.定制页面显示的列
-
基本列表页面的定制
-
未定义list_display字段的页面,默认显示对象
class StarkHandler(object): list_display = [] def changelist_view(self, request): if list_display: for i in list_display: pass else: pass
-
为页面显示的列预留一个钩子函数
class StarkHandler(object): list_display = [] def get_list_display(self): value = [] value.extend(self.list_display) return value def changelist_view(self, request): list_display = self.get_list_display() if list_display: for i in list_display: pass else: pass
-
为页面提供自定义显示的函数
v1.pyclass StarkHandler(object): def display_edit(self, obj=None, is_header=None): """ 自定义页面显示的列(表头和内容) :param obj: :param is_header: :return: """ if is_header: return "编辑" name = "%s:%s" % (self.site.namespace, self.get_change_url_name,) return mark_safe('<a href="%s">编辑</a>' % reverse(name, args=(obj.pk,))) # 根据名称空间,别名,反向生成url def display_del(self, obj=None, is_header=None): if is_header: return "删除" name = "%s:%s" % (self.site.namespace, self.get_delete_url_name,) return mark_safe('<a href="%s">删除</a>' % reverse(name, args=(obj.pk,))) def changelist_view(self, request): if list_display: for key_or_func in list_display: if isinstance(key_or_func, FunctionType): # key_or_func虽然本来是hander中的方法, #但在传入时是以函数传入的,所以要把self也传入 verbose_name = key_or_func(self, obj=None, is_header=True) else: verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name header_list.append(verbose_name)
stark.py
from stark.service.v1 import site, StarkHandler,get_choice_text class UserInfoHandler(StarkHandler): list_display = ['name', 'age', 'email', 'depart', StarkHandler.display_edit, StarkHandler.display_del] site.register(models.UserInfo,UserInfoHandler)
-
应用
d.后台应用模板样式
e.列表页面添加分页的功能
f.添加按钮
-
如何显示添加按钮
-
如何添加按钮的url
from django.http import QueryDict import functools class StarkHandler(object): list_display = [] def __init__(self, site, model_class, prev): self.site = site self.model_class = model_class self.prev = prev self.request = None self.has_add_btn = True # 默认要显示添加按钮 def reverse_add_url(self): """ 生成带有原搜索条件的添加URL :return: """ name = "%s:%s" % (self.site.namespace, self.get_add_url_name,) base_url = reverse(name) if not self.request.GET: add_url = base_url else: param = self.request.GET.urlencode() new_query_dict = QueryDict(mutable=True) new_query_dict['_filter'] = param add_url = "%s?%s" % (base_url, new_query_dict.urlencode()) return add_url def get_add_btn(self): # 返回添加按钮,href 通过 reverse_add_url()获得反向生成的url if self.has_add_btn: # 判断是否要显示添加按钮 return "<a class='btn btn-primary' href='%s'>添加</a>" % self.reverse_add_url() return None def changelist_view(self, request): add_btn = self.get_add_btn() return render(request,'stark/changelist.html', {'add_btn': add_btn,}) def wrapper(self, func): @functools.wraps(func) # @functools.wraps 保留原函数的参数信息 def inner(request, *args, **kwargs): # 通过装饰器,使每一个请求进入视图函数后都能保存自己的request self.request = request return func(request, *args, **kwargs) return inner def get_urls(self): patterns = [ url(r'^list/$', self.wrapper(self.changelist_view), name=self.get_list_url_name), url(r'^add/$', self.wrapper(self.add_view), name=self.get_add_url_name), url(r'^change/(?P<pk>d+)/$', self.wrapper(self.change_view), name=self.get_change_url_name), url(r'^delete/(?P<pk>d+)/$', self.wrapper(self.delete_view), name=self.get_delete_url_name), ] patterns.extend(self.extra_urls()) return patterns
-
添加页面进行添加数据
可以添加显示的字段,接口供用户扩展
需求 :少显示字段,保存时,减少的字段也得设置,否则存储出错from django import forms class StarkModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(StarkModelForm, self).__init__(*args, **kwargs) # 统一给ModelForm生成字段添加样式 for name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control' class StarkHandler(object): def save(self, form, is_update=False): """ 在使用ModelForm保存数据之前预留的钩子方法 """ form.save() model_form_class = None def get_model_form_class(self): if self.model_form_class: # 如果自定义了model_form_class,及用自定义的,如果没有,用默认的 return self.model_form_class class DynamicModelForm(StarkModelForm): #继承StarkModelForm,来继承bootstrap样式 class Meta: model = self.model_class # 根据当前的model,来显示 fields = "__all__" return DynamicModelForm def reverse_list_url(self): name = "%s:%s" % (self.site.namespace, self.get_list_url_name,) base_url = reverse(name) param = self.request.GET.get('_filter') if not param: return base_url return "%s?%s" % (base_url, param,) def add_view(self, request): """ 添加页面 :param request: :return: """ model_form_class = self.get_model_form_class() if request.method == 'GET': form = model_form_class() return render(request, 'stark/change.html', {'form': form}) form = model_form_class(data=request.POST) # 验证传回的form表单信息 if form.is_valid(): self.save(form, is_update=False) # 在数据库保存成功后,跳转回列表页面(携带原来的参数)。 return redirect(self.reverse_list_url()) return render(request, 'stark/change.html', {'form': form})
stark.py
class UserInfoModelForm(StarkModelForm): class Meta: model = models.UserInfo fields = ['name', 'gender', 'classes', 'age', 'email'] class UserInfoHandler(StarkHandler): model_form_class = UserInfoModelForm # 自定义要渲染的form表单,由于在显示的时候可以 site.register(models.UserInfo,UserInfoHandler)
g.编辑功能实现
根据pk值,来展示编辑页面,所有要传入pk
看到的东西有默认值
class StarkHandler(object):
list_display = []
def display_edit(self, obj=None, is_header=None):
"""
自定义页面显示的列(表头和内容)
"""
if is_header:
return "编辑"
return mark_safe('<a href="%s">编辑</a>' % self.reverse_change_url(pk=obj.pk))
def reverse_change_url(self, *args, **kwargs):
"""
# 在原基础上, *args, **kwargs
生成带有原搜索条件的编辑URL
:param args:
:param kwargs:
:return:
"""
name = "%s:%s" % (self.site.namespace, self.get_change_url_name,)
base_url = reverse(name, args=args, kwargs=kwargs)
if not self.request.GET:
add_url = base_url
else:
param = self.request.GET.urlencode()
new_query_dict = QueryDict(mutable=True)
new_query_dict['_filter'] = param
add_url = "%s?%s" % (base_url, new_query_dict.urlencode())
return add_url
# 在最开始的时候要获取到pk值
def get_urls(self):
# 正则获取pk值
patterns = [
url(r'^list/$', self.wrapper(self.changelist_view), name=self.get_list_url_name),
url(r'^add/$', self.wrapper(self.add_view), name=self.get_add_url_name),
url(r'^change/(?P<pk>d+)/$', self.wrapper(self.change_view),
name=self.get_change_url_name),
url(r'^delete/(?P<pk>d+)/$', self.wrapper(self.delete_view), name=self.get_delete_url_name),
]
patterns.extend(self.extra_urls())
return patterns
def change_view(self, request, pk):
"""
编辑页面
:param request:
:param pk:
:return:
"""
current_change_object = self.model_class.objects.filter(pk=pk).first()
if not current_change_object:
return HttpResponse('要修改的数据不存在,请重新选择!')
model_form_class = self.get_model_form_class()
if request.method == 'GET':
form = model_form_class(instance=current_change_object)
# 获取当前models的form组件来渲染form表单
return render(request, 'stark/change.html', {'form': form})
form = model_form_class(data=request.POST, instance=current_change_object)
if form.is_valid():
self.save(form, is_update=False)
# 在数据库保存成功后,跳转回列表页面(携带原来的参数)。
return redirect(self.reverse_list_url())
return render(request, 'stark/change.html', {'form': form})
h.删除
class StarkHandler(object):
def display_del(self, obj=None, is_header=None):
if is_header:
return "删除"
return mark_safe('<a href="%s">删除</a>' % self.reverse_delete_url(pk=obj.pk))
def reverse_delete_url(self, *args, **kwargs):
"""
生成带有原搜索条件的删除URL
:param args:
:param kwargs:
:return:
"""
name = "%s:%s" % (self.site.namespace, self.get_delete_url_name,)
base_url = reverse(name, args=args, kwargs=kwargs)
if not self.request.GET:
add_url = base_url
else:
param = self.request.GET.urlencode()
new_query_dict = QueryDict(mutable=True)
new_query_dict['_filter'] = param
add_url = "%s?%s" % (base_url, new_query_dict.urlencode())
return add_url
def delete_view(self, request, pk):
"""
删除页面
:param request:
:param pk:
:return:
"""
origin_list_url = self.reverse_list_url()
if request.method == 'GET':
return render(request, 'stark/delete.html', {'cancel': origin_list_url})
self.model_class.objects.filter(pk=pk).delete()
return redirect(origin_list_url)
4.其它功能
a.排序
order_list = []
def get_order_list(self):
return self.order_list or ['-id', ]
def changelist_view(self, request):
"""
列表页面
:param request:
:return:
"""
# ########## 1. 获取排序 ##########
order_list = self.get_order_list()
queryset = self.model_class.objects.all().order_by(*order_list)
# 以下的数据都经过 排序,以下都用queryset
b.模糊搜索
实现思路:
在页面设置form表单,搜索:以get形式提交到后台,后台获取数据然后进行筛选
在后端获取关键字后,根据
from django.db.models import Q
search_list = ['name__contains',] # 如果不加__contains就算精确查找
def get_search_list(self):
return self.search_list
def changelist_view(self, request):
"""
列表页面
:param request:
:return:
"""
# ########## 0. 模糊查询 ##########
#1,如果search_list 中没有值,则不显示搜索框
#2.获取用户提交的关键字
# 3.构造条件
# Django Q 对象,用户构造复杂的orm查询条件
search_list = self.get_search_list()
search_value = request.GET.get('q', '')
conn = Q()
conn.connector = 'OR'
if search_value:
# 如果用户没有搜索,就不构造
for item in search_list:
conn.children.append((item, search_value))
# ########## 1. 获取排序 ##########
order_list = self.get_order_list()
queryset = self.model_class.objects.filter(conn).order_by(*order_list)
# filter.all()变成了filter(conn)
c.批量操作
-
添加checkbox列
-
生成批量操作的按钮
-
在display_list中添加一列
-
将函数传入 html,中,函数会自动执行
-
将提交的函数名,通过反射来找函数执行
-
扩展,handler的函数预留参数接口 *args, **kwargs
-
扩展,点击批量操作的执行后,能够跳转某个页面去查看某些内容
change_listhtml.
{% if action_dict %} // 如果下拉框有值才显示, <div style="float: left;margin: 5px 10px 5px 0;"> <div class="form-inline"> <div class="form-group"> <select class="form-control" name="action"> <option value="">请选择操作</option> {% for func_name,func_text in action_dict.items %} <option value="{{ func_name }}">{{ func_text }}</option> {% endfor %} </select> <input class="btn btn-primary" type="submit" value="执行"/> </div> </div> </div> {% endif %}
def display_checkbox(self, obj=None, is_header=None): """ :param obj: :param is_header: :return: """ if is_header: return "选择" return mark_safe('<input type="checkbox" name="pk" value="%s" />' % obj.pk) action_list = [] #在里面放上函数 def get_action_list(self): return self.action_list def action_multi_delete(self, request, *args, **kwargs): """ 批量删除(如果想要定制执行成功后的返回值,那么就为action函数设置返回值即可。) return redirect('html') :return: """ pk_list = request.POST.getlist('pk') self.model_class.objects.filter(id__in=pk_list).delete() action_multi_delete.text = "批量删除" def changelist_view(self, request, *args, **kwargs): """ 列表页面 :param request: :return: """ # ########## 1. 处理Action ########## action_list = self.get_action_list() # 将函数传入 html,中,函数会自动执行,所以将函数改为字典 action_dict = {func.__name__: func.text for func in action_list} # {'multi_delete':'批量删除','multi_init':'批量初始化'} if request.method == 'POST': action_func_name = request.POST.get('action') if action_func_name and action_func_name in action_dict: # 用户提交的函数必须是我们定义过的,避免用户修改html代码来破坏 action_response = getattr(self, action_func_name)(request, *args, **kwargs) if action_response: # 如果函数有返回值,就证明要跳转到某个页面 return action_response
d.组合搜索
-
什么是组合搜索
-
如何实现组合搜索
-
实现思路
-
第一步:根据字段找到其关联的数据,choice,FK,M2M
-
第二步:根据配置获取关联数据,不写,就不显示
-
第三步:根据配置获取相关数据(含条件)
-
第四步:
-
第五步:为组合
-
生成url时,不影响其他组影响、
1. 手动拼接,"/stark/app01/userinfo/list" + '?depart=1' 2. 根据request.get来生成,每点击一次,都能生成根据这次点击,而形成的url query_dict = self.rquests.copy() query_dict._mutable = True query_dict['depart'] = 1
-
class SearchGroupRow(object): def __init__(self, title, queryset_or_tuple, option): """ # 将querysert,或元组,组装成一个统一的可迭代对象 :param title: 组合搜索的列名称 :param queryset_or_tuple: 组合搜索关联获取到的数据 :param option: 配置 """ self.title = title self.queryset_or_tuple = queryset_or_tuple self.option = option def __iter__(self): yield '<div class="whole">' yield self.title yield '</div>' yield '<div class="others">' yield "<a>全部</a>" for item in self.queryset_or_tuple: text = self.option.get_text(item) yield "<a href='#'>%s</a>" % text #此处写死,不易扩展,比如要对字符串进行加工,所以取一个函数返回值 yield '</div>' class Option(object): def __init__(self, field, db_condition=None, text_func=None): """ :param field: 组合搜索关联的字段 :param db_condition: 数据库关联查询时的条件 :param text_func: 此函数用于显示组合搜索按钮页面文本 """ self.field = field if not db_condition: db_condition = {} self.db_condition = db_condition self.text_func = text_func self.is_choice = False def get_db_condition(self, request, *args, **kwargs): return self.db_condition def get_queryset_or_tuple(self, model_class, request, *args, **kwargs): """ 根据字段去获取数据库关联的数据 :return: """ # 根据gender或depart字符串,去自己对应的Model类中找到 字段对象 field_object = model_class._meta.get_field(self.field) title = field_object.verbose_name # 获取关联数据 if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField): # FK和M2M,应该去获取其关联表中的数据: QuerySet db_condition = self.get_db_condition(request, *args, **kwargs) return SearchGroupRow(title, field_object.rel.model.objects.filter(**db_condition), self) else: # 获取choice中的数据:元组 self.is_choice = True return SearchGroupRow(title, field_object.choices, self) def get_text(self, field_object): """ 获取文本函数, :param field_object: :return: """ if self.text_func: return self.text_func(field_object) if self.is_choice: return field_object[1] return str(field_object) class StarkHandler(object): search_group = [] def get_search_group(self): return self.search_group def changelist_view(self, request, *args, **kwargs): """ 列表页面 :param request: :return: """ search_group_row_list = [] search_group = self.get_search_group() # ['gender', 'depart'] for option_object in search_group: # 根据gender或depart 字符串,去自己对应的model类中找到字段对象 option_object.get_queryset_or_tuple(self.model_class, request, *args, **kwargs) search_group_row_list.append(row)
stark.py
class UserInfoHandler(StarkHandler): search_group = [ Option('gender'), MyOption('depart', {'id__gt': 2}), ] site.register(models.UserInfo, UserInfoHandler)
-
-
总结:
列表页面
添加页面
编辑页面
删除页面
关键字搜索
批量操作
组合搜索
保留原函数的信息
后台make_safe,和前端管道符safe效果一样