仿造Django中的admin自己实现增删改查、模糊搜索、批量操作、条件筛选、popup功能的插件

1.创建组件

  首先创建一个app,这里取名为stark,在settings.py中将其进行注册

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'stark.apps.StarkConfig',
]

settings.py

2.启动项

  在stark的apps中写入启动项

#在apps.py中:

from django.apps import AppConfig

#必须写的内容,是Django启动的时候会自动加载
class StarkConfig(AppConfig):
name = 'stark'
def ready(self):
from django.utils.module_loading import autodiscover_modules
autodiscover_modules('stark')

apps.py

3.创建插件文件

  在stark下创建一个叫service的包,包中创建一个文件,这里取名为v1.py。在v1中创建类似于AdminSite ModelAdmin的类,用于自己封装组件

class StarkConfig(object):
"""
用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类
日后会封装很多功能
"""
def __init__(self,model_class,site):
self.model_class = model_class
self.site = site class StarkSite(object):
'''
单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系
{model.UserInfo:StarkConfig(model.UserInfo,self)}
''' def __init__(self):
self._registey = {} def register(self,model_class,stark_config_class=None):
if not stark_config_class:
#stark_config_class,没写派生类时默认给予StarkConfig
stark_config_class = StarkConfig self._registey[model_class] = stark_config_class(model_class,self)
#字典{表名:stark_config_class(表名,self)} site = StarkSite()#单例模式

StarkConfig和 StarkSite

4.自动生成url

  在全局的ulrs.py 中

from django.conf.urls import url
from stark.service import v1 urlpatterns = [
url(r'^stark/', v1.site.urls),#自己创建的类似admin的功能,需在app里有相应的注册 ]

  在v1.py中,为每行需要操作的表生成增删改查4个url

class StarkConfig(object):

        def __init__(self,model_class,site):
self.model_class = model_class
self.site = site #装饰器,为了传参数request
def wrap(self,view_func):
def inner(request,*args,**kwargs):
self.request=request
return view_func(request,*args,**kwargs)
return inner def get_urls(self):#第五步
app_model_name=(self.model_class._meta.app_label,self.model_class._meta.model_name,)#元祖(app名,表名)
url_patterns=[
url(r'^$',self.wrap(self.changelist_view),name='%s_%s_changelist'%app_model_name),
url(r'^add/$',self.wrap(self.add_view),name='%s_%s_add'%app_model_name),
url(r'^(\d+)/delete/$',self.wrap(self.delete_view),name='%s_%s_delete'%app_model_name),
url(r'^(\d+)/change/$',self.wrap(self.change_view),name='%s_%s_change'%app_model_name),
]
url_patterns.extend(self.extra_url())#除增删改查外,想要自定义的新增的url
return url_patterns#最后就得到了需要用到的一堆url
def extra_url(self):
return []
########################################
@property
def urls(self):#第四步
return self.get_urls() def urls(self):
return self.get_urls() #########反向生成url#####################
def get_change_url(self, nid):
name = 'stark:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name, args=(nid,))
return edit_url def get_list_url(self):
name = 'stark:%s_%s_changelist' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name)
return edit_url def get_add_url(self):
name = 'stark:%s_%s_add' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name)
return edit_url def get_delete_url(self, nid):
name = 'stark:%s_%s_delete' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name, args=(nid,))
return edit_url ######视图函数(之后会具体扩展)####
def changelist_view(self,request,*args,**kwargs):
return HttpResponse('列表') def add_view(self,request,*args,**kwargs):
return HttpResponse('添加') def delete_view(self,request,nid,*args,**kwargs):
return HttpResponse('删除') def change_view(self,request,nid,*args,**kwargs):
return HttpResponse('修改') class StarkSite(object):
'''
单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系
{model.UserInfo:StarkConfig(model.UserInfo,self)}
'''
def __init__(self):
self._registry = {} def register(self,model_class,stark_config_class=None):
if not stark_config_class:
#stark_config_class即29,没写那个派生类的时候默认给予StarkConfig
stark_config_class=StarkConfig
self._registry[model_class]=stark_config_class(model_class,self)
#表名:stark_config_class(表名,self) def get_urls(self):#第三步,给url
url_pattern=[]
for model_class,stark_config_obj in self._registry.items():#去字典里取值
app_name=model_class._meta.app_label#app名
model_name=model_class._meta.model_name#表名
curd_url=url(r'^%s/%s/'%(app_name,model_name),(stark_config_obj.urls,None,None))
#拼接生成url,需执行stark_config_obj.urls———第四步
url_pattern.append(curd_url)
return url_pattern @property
def urls(self):#第二步,要url
return (self.get_urls(),None,'stark') site=StarkSite()#第一步,单例模式

对 StarkConfig 和 StarkSite 进行扩展

5.定制功能

  定制每一个列表页面(查)都会有的功能,若不具备该功能可在派生类中修改。

  这里用了分页功能,可在全局中创建文件夹utils,将自己写的分页器page.py放入其中,之后在v1.py中导入即可使用。

class StarkConfig(object):
"""
用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类
"""
def __init__(self,model_class,site):
self.model_class=model_class
self.site=site
self.request=None ############定制功能####################
#########1 默认每个tr都会拥有的td
def checkbox(self,obj=None,is_header=False):
if is_header:
return '选择'
return mark_safe('<input type="checkbox" name="pk" value="%s" >' %(obj.id,))
def edit(self,obj=None,is_header=False):
if is_header:
return '编辑操作'
#url地址栏的搜索条件
query_str=self.request.GET.urlencode()
if query_str:
#重新构造<button class="btn btn-primary"></button>
params=QueryDict(mutable=True)
params[self._query_param_key]=query_str
return mark_safe('<button class="btn btn-primary"><a href="%s?%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),params.urlencode(),))
return mark_safe('<button class="btn btn-primary"><a href="%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),))
def delete(self,obj=None,is_header=False):
if is_header:
return '删除操作'
query_str = self.request.GET.urlencode()
if query_str:
# 重新构造
params = QueryDict(mutable=True)
params[self._query_param_key] = query_str
return mark_safe('<button class="btn btn-danger"><a href="%s?%s" style="color:white;">删除</a></button>' % (self.get_delete_url(obj.id), params.urlencode(),)) return mark_safe('<button class="btn btn-danger"><a href="%s" style="color:white;">删除</a></button>'%(self.get_delete_url(obj.id),) ) list_display=[]
#得到派生类中自定义的list_display
def get_list_display(self):
data=[]
if self.list_display:#派生类中定义的要显示的字段
data.extend(self.list_display)#加入到data中
data.append(StarkConfig.edit)#加入编辑td
data.append(StarkConfig.delete)#加入删除td
data.insert(0,StarkConfig.checkbox)#在最前面插一个td
return data edit_link=[]
def get_edit_link(self):
result=[]
if self.edit_link:
result.extend(self.edit_link)
return result ######### 2是否显示add按钮
show_add_btn = True # 默认显示
def get_show_add_btn(self):
return self.show_add_btn #########3 关键字搜索
show_search_form = False#默认不显示
def get_show_search_form(self):
return self.show_search_form
search_fields = []#关键字默认为空
def get_search_fields(self):
result = []
if self.search_fields:
result.extend(self.search_fields)#派生类中自定义的关键字
return result def get_search_condition(self):
key_word = self.request.GET.get(self.search_key)#'_q'
search_fields = self.get_search_fields()#关键字
condition = Q()#创建Q对象用于与或
condition.connector = 'or'#搜索条件之间用或连接
if key_word and self.get_show_search_form():
for field_name in search_fields:
condition.children.append((field_name, key_word))
return condition
#############4 actions,批量功能,需拥有权限才可拥有此功能
show_actions = False#默认不显示
def get_show_actions(self):
return self.show_actions actions = []#默认批量操作内容为空
def get_actions(self):
result = []
if self.actions:
result.extend(self.actions)#加入派生类中自定制的批量操作
return result #############5 组合搜索
show_comb_filter = False
def get_show_comb_filter(self):
return self.show_comb_filter comb_filter=[]#默认为空
def get_comb_filter(self):
result=[]
if self.comb_filter:
result.extend(self.comb_filter)#得到派生类中的条件删选
return result #############6排序
order_by = []
def get_order_by(self):
result = []
result.extend(self.order_by)
return result

StarkConfig类中扩展

6.ChangeList

  因为列表展示页面(查)需要后端向前端传很多的参数,所以我们这里讲所有需要传的参数封装到一个额外的类中,这样我们在视图函数中只需要将这个类实例化,只用向前端传这个类,在前端只要   ChangeList.方法或字段   就可以了

class ChangeList(object):
'''
很牛逼的一个类,封装了所有视图函数想要往前端传的内容
功能:使视图函数中的代码变的简洁
'''
def __init__(self,config,queryset):
self.config=config#stark.py中写了派生类的话就是那个类,没写的话默认就是StarkConfig
self.list_display=config.get_list_display()
self.edit_link = config.get_edit_link()
self.model_class=config.model_class#数据库的表
self.request=config.request#StarkConfig中默认是None,不过程序运行后就会有
self.show_add_btn=config.get_show_add_btn()
# 搜索框
self.show_search_form = config.get_show_search_form()
self.search_form_val = config.request.GET.get(config.search_key, '')#搜索关键字“_q”,首次访问网页默认是空
# 批量操作
self.actions=config.get_actions()#得到派生类中写的actions的内容[]
self.show_actions=config.get_show_actions()#操作框
#组合搜索
self.show_comb_filter=config.get_show_comb_filter()
self.comb_filter=config.get_comb_filter() from utils.pager import Pagination
#分页器
current_page = self.request.GET.get('page', 1)#得到传入的page,没有默认为第一页
total_count = queryset.count()#要显示的数据的量,queryset在视图函数中有数据库操作的赋值
page_obj = Pagination(current_page, total_count, self.request.path_info, self.request.GET, per_page_count=5)
#当前页 数据量 当前url不带问号 ?后面的条件内容 设定的每页显示的数据量条数
self.page_obj = page_obj#得到最终生成的分页器对象 self.data_list = queryset[page_obj.start:page_obj.end]#得到分页后的数据,用于页面展示 #批量操作
def modify_actions(self):
result = []#批量操作内容,默认为空,去派生类中定义
for func in self.actions:#self.actions=config.get_actions(),默认为空
temp = {'name':func.__name__,'text':func.short_desc}#name是函数名,text是自加的描述
result.append(temp)
return result def add_url(self):#添加操作的url
query_str = self.request.GET.urlencode()
if query_str:
# 重新构造,用于跳转
params = QueryDict(mutable=True)
params[self.config._query_param_key] = query_str
return self.config.get_add_url()+'?'+params.urlencode()
return self.config.get_add_url() def head_list(self):
#构造表头
result = []
# [checkbox,'id','name',edit,del]
for field_name in self.list_display:
if isinstance(field_name, str):
# 根据类和字段名称,获取字段对象的verbose_name
verbose_name = self.model_class._meta.get_field(field_name).verbose_name
else:
verbose_name = field_name(self.config, is_header=True)# 去派生类中执行
result.append(verbose_name)
return result def body_list(self):
# 处理表中的数据
data_list = self.data_list#self.data_list = queryset[page_obj.start:page_obj.end]
new_data_list = []
for row in data_list:
# row是 每一条数据对象UserInfo(id=2,name='alex2',age=181)
temp = []
for field_name in self.list_display:
if isinstance(field_name,str):#派生类中定义的显示字段
val = getattr(row,field_name)
else:#每个td都拥有的功能,checkbox、edit、delete、
val = field_name(self.config,row)
# 用于定制编辑列
if field_name in self.edit_link:
val = self.edit_link_tag(row.pk, val)
temp.append(val)
new_data_list.append(temp)
return new_data_list def gen_comb_filter(self):
#生成器函数
"""
[
FilterRow(((1,'男'),(2,'女'),)),
FilterRow([obj,obj,obj,obj ]),
FilterRow([obj,obj,obj,obj ]),
]
"""
'''
comb_filter = [
v1.FilterOption('gender', is_choice=True),#关键字传参,代表是choice
v1.FilterOption('depart'),#, 筛选只显示id大于三的部门condition={'id__gt': 3}
v1.FilterOption('roles', True),#True传入,代表是多选
]
'''
from django.db.models import ForeignKey,ManyToManyField
for option in self.comb_filter:
_field = self.model_class._meta.get_field(option.field_name)#字段
if isinstance(_field,ForeignKey):
# 获取当前字段depart,关联的表 Department表并获取其所有数据
# print(field_name,_field.rel.to.objects.all())
row = FilterRow(option, option.get_queryset(_field), self.request)
elif isinstance(_field,ManyToManyField):
# print(field_name, _field.rel.to.objects.all())
# data_list.append( FilterRow(_field.rel.to.objects.all()) )
row = FilterRow(option,option.get_queryset(_field), self.request) else:
# print(field_name,_field.choices)
# data_list.append( FilterRow(_field.choices) )
row = FilterRow(option,option.get_choices(_field),self.request)
# 可迭代对象,迭代详细在FilterRow的__iter__中
yield row def edit_link_tag(self,pk,text):
query_str = self.request.GET.urlencode() # page=2&nid=1
params = QueryDict(mutable=True)
params[self.config._query_param_key] = query_str
return mark_safe('<a href="%s?%s">%s</a>' % (self.config.get_change_url(pk), params.urlencode(),text,)) # /stark/app01/userinfo/

v1.py中的ChangeList类

7.组合搜索(筛选)

  组合搜索(筛选功能)是一大难点,这里还需定义两个类FilterOption和FilterRow

  FilterOption:用于封装筛选条件的配置信息。FilterOption的实例化由派生类决定

  FilterRow:可迭代对象,封装了筛选中的每一行数据。

class FilterOption(object):
def __init__(self, field_name, multi=False, condition=None, is_choice=False,text_func_name=None, val_func_name=None):
"""
:param field_name: 字段
:param multi: 是否多选
:param condition: 显示数据的筛选条件
:param is_choice: 是否是choice
"""
self.field_name = field_name
self.multi = multi
self.condition = condition
self.is_choice = is_choice
self.text_func_name = text_func_name#组合搜索时,页面上生成显示的文本的函数
self.val_func_name = val_func_name#组合搜索时,页面上生成的a标签中的值的函数 def get_queryset(self, _field):
if self.condition:#是数据的筛选条件
return _field.rel.to.objects.filter(**self.condition)#拿到筛选后的对象
return _field.rel.to.objects.all()#默认“全部”按钮被选中,给出所有对象 def get_choices(self, _field):#是choices
return _field.choices #可迭代对象,封装了筛选中的每一行数据。
class FilterRow(object):
def __init__(self, option, data, request):
self.option = option
self.data = data#关联字段所关联的表的所有有关联的数据
# request.GET
self.request = request def __iter__(self):
params = copy.deepcopy(self.request.GET)#深拷贝?后面的内容,得到QueryDict
params._mutable = True#可修改
current_id = params.get(self.option.field_name) #params.get(字段),得到的是值
current_id_list = params.getlist(self.option.field_name) # [1,2,3] if self.option.field_name in params:#地址栏已存在筛选条件
# del params[self.option.field_name],先删除
origin_list = params.pop(self.option.field_name)#删除,并得到删除内容
url = "{0}?{1}".format(self.request.path_info, params.urlencode())
yield mark_safe('<a href="{0}">全部</a>'.format(url))#“全部按钮”不被选中
params.setlist(self.option.field_name, origin_list)#将本身已存在的筛选条件放入params中
else:
url = "{0}?{1}".format(self.request.path_info, params.urlencode())#地址栏不存在筛选条件
yield mark_safe('<a class="active" href="{0}">全部</a>'.format(url))#默认“全部”按钮被选中 for val in self.data:
if self.option.is_choice:# ( (1,男),(2,女) )
pk, text = str(val[0]), val[1]
else:#每个val都是对象
# pk, text = str(val.pk), str(val)
text = self.option.text_func_name(val) if self.option.text_func_name else str(val)
pk = str(self.option.val_func_name(val)) if self.option.val_func_name else str(val.pk)
# 当前URL?option.field_name
# 当前URL?gender=pk
#制定url的显示规则:
# self.request.path_info # http://127.0.0.1:8005/arya/crm/customer/?gender=1&id=2
# self.request.GET['gender'] = 1 # &id=2gender=1
if not self.option.multi:
# 单选
params[self.option.field_name] = pk#1,2
url = "{0}?{1}".format(self.request.path_info, params.urlencode())
if current_id == pk:#当前url筛选条件中的值
yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))#该筛选按钮被选中
else:
yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))#其余按钮,没被选中
else:
# 多选 current_id_list = ["1","2"]
_params = copy.deepcopy(params)
id_list = _params.getlist(self.option.field_name)#["1","2","3","4"] if pk in current_id_list:#值已存在,表示该按钮已被选中
id_list.remove(pk)#将该值从id_list中去除
_params.setlist(self.option.field_name, id_list)#["2","3","4"]
url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
#该按钮被选中,但其a标签的href中跳转的链接即当前url去除本身按钮id的状态,即该按钮,被再次点击时,就会恢复未选中状态
yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text)) else:#值未存在
id_list.append(pk)
# params中被重新赋值
_params.setlist(self.option.field_name, id_list)
# 创建URL,赋予其被点时,使其产生被选中
url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))

FilterOption和FilterRow

8.popup功能

  popup功能用于添加和编辑页面,在操作时选择外键关联的select框,可临时添加一个对象的功能

添加和编辑的视图函数中需要有以下代码:

    def xxx_view(self,request):

        _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
if request.method == 'POST':
form = model_form_class(request.POST)
if form.is_valid():
new_obj=form.save()
if _popbackid:
# 判断是否是来源于popup请求
# render一个页面,写自执行函数
# popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant')
from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel
result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid} model_name = request.GET.get('model_name') # customer
related_name = request.GET.get('related_name') # consultant, "None"
for related_object in new_obj._meta.related_objects:#关联表的对象
_model_name = related_object.field.model._meta.model_name
_related_name = related_object.related_name
# 判断外键关联字段是否是主键id
if (type(related_object) == ManyToOneRel):
_field_name = related_object.field_name
else:
_field_name = 'pk'
_limit_choices_to = related_object.limit_choices_to
if model_name == _model_name and related_name == str(_related_name):
is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists()
if is_exists:
# 如果新创建的用户是可查看的人,页面才增加
# 分门别类做判断:
result['status'] = True
result['text'] = str(new_obj)
result['id'] = getattr(new_obj, _field_name)
return render(request, 'stark/popup_response.html',
{'json_result': json.dumps(result, ensure_ascii=False)})
return render(request, 'stark/popup_response.html',
{'json_result': json.dumps(result, ensure_ascii=False)})
else:
list_query_str = request.GET.get(self._query_param_key)
list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url)
# return redirect(self.get_list_url())
return render(request, 'stark/add_view.html', {'form': form, 'config': self})

视图函数

  因为编辑和添加的前端页面代码大量冲重合,所以这里引入form.html和templatetags。

templatetages下创建change_form.py

from django.template import Library
from django.urls import reverse
from stark.service.v1 import site register = Library()
# 自定义标签
@register.inclusion_tag('stark/form.html')
def new_form(config,model_form_obj):
new_form = []
for bfield in model_form_obj:#model_form_obj是每一条记录
temp = {'is_popup': False, 'item': bfield}
# bfield.field是ModelForm读取对应的models.类,然后根据每一个数据库字段,生成Form的字段
from django.forms.boundfield import BoundField
from django.db.models.query import QuerySet
from django.forms.models import ModelChoiceField
if isinstance(bfield.field, ModelChoiceField):#是单选和多选————>外键字段
related_class_name = bfield.field.queryset.model#得到字段的field
if related_class_name in site._registry:#已注册
app_model_name = related_class_name._meta.app_label, related_class_name._meta.model_name
# FK,One,M2M: 当前字段所在的类名和related_name
model_name = config.model_class._meta.model_name
related_name = config.model_class._meta.get_field(bfield.name).rel.related_name
# print(model_name,related_name)
base_url = reverse("stark:%s_%s_add" % app_model_name)#应用名_类名_add,反向生成url
#bfield.auto_id是内置方法,得到该input框的id
# 带有回调参数的url
popurl = "%s?_popbackid=%s&model_name=%s&related_name=%s" % (base_url, bfield.auto_id,model_name,related_name) temp['is_popup'] = True
temp['popup_url'] = popurl
new_form.append(temp)#{'is_popup': True, 'item': bfield,'popup_url':popurl}
return {'new_form':new_form}

change_form.py

templates下的form.html(内含jQuery,处理popup回调)

<form method="post"  class="form-horizontal" novalidate>
{% csrf_token %}
{# dic={'is_popup': True, 'item': bfield,'popup_url':popurl}#}
{% for dic in new_form %}
<div class="col-sm-4 col-sm-offset-4">
<div class="form-group">
<label for="" class="col-sm-2 control-label">{{ dic.item.field.label }}:</label>
<div class="col-sm-9" style="position: relative">
{# modelform自动形成input#}
{{ dic.item }}
{% if dic.is_popup %}{# 单选或多选#}
<div style="position: absolute;top: 10px;left: 330px;">
<a href="" onclick="popUp('{{ dic.popup_url }}')"><i class="fa fa-arrows" aria-hidden="true"></i></a>
</div>
{% endif %}
<div style="position: absolute;font-size: 12px;top: 18px;right: 20px;color: #e4393c;background: #FFEBEB;">{{ dic.item.errors.0 }}</div>
</div>
</div>
</div>
{% endfor %}
<div class="col-sm-offset-7 col-sm-3">
<input type="submit" class="btn btn-primary" value="提交">
</div> </form> <script>
function popUp(url) {
var popupPage = window.open(url, url, "status=1, height:500, width:600, toolbar=0, resizeable=0");
}
function popupCallback(dic) {
if (dic.status) {
var op = document.createElement('option');
op.value = dic.id;
op.text = dic.text;
op.setAttribute('selected', 'selected');
document.getElementById(dic.popbackid).appendChild(op); }
} </script>

form.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>正在关闭</title>
</head>
<body>
<script>
(function () {
var dic = {{ json_result|safe }};
{# 发起popup请求的页面执行该回调函数#}
opener.popupCallback(dic);
window.close();
})() </script>
</body>
</html>

popup_response.html

添加和修改的html页面将会在之后给出

9.视图函数

9.1 静态文件

  创建一个static文件夹,用于存放静态文件。这里用到了bootstrap、font-awesome和jQuery

9.2 基板

  因为增删改查的前端页面代码重复量较多,所以我们设置一个基板,用于减少代码冗余。

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static "mycss/stark-form.css" %}" />
<link rel="stylesheet" href="{% static 'font-awesome/css/font-awesome.min.css' %}">
{% block css %} {% endblock %}
</head>
<body>
{% block body %} {% endblock %} {% block js %} {% endblock %}
</body>
</html>

基板base_temp

9.3 查

  注意组合搜索功能和批量功能

    # 默认列表页面
def changelist_view(self, request,*args, **kwargs):
#分页,已改写到ChangeList类中
# from utils.pager import Pagination
# current_page=request.GET.get('page',1)
# total_count=self.model_class.objects.all().count()
# page_obj=Pagination(current_page,total_count,request.path_info,request.GET,per_page_count=4) if request.method=='GET':
comb_condition = {}#筛选条件默认为空
option_list = self.get_comb_filter()#拿到派生类中定制的筛选条件
for key in request.GET.keys():#?后面的键
value_list = request.GET.getlist(key)#拿到键对应的值[1,2,3]
flag = False
for option in option_list:#option是每一个删选条件
if option.field_name == key:#该条件已存在于地址栏
flag = True
break
if flag:
#comb_condition = {"id__in":[1,2,3].......}
comb_condition["%s__in" % key] = value_list # 带搜索条件的数据,没有搜索条件的话就是全部数据。有筛选条件的话,还得处理成筛选后的数据
queryset=self.model_class.objects.filter(self.get_search_condition()).filter(**comb_condition).distinct() the_list=ChangeList(self,queryset)#封装好要向前端传的值
return render(request, 'stark/changelist.html', {'the_list':the_list})
elif request.method=='POST' and self.get_show_actions():#批量操作
func_name_str = request.POST.get('list_action')#前端传的操作name
action_func = getattr(self, func_name_str)#反射,得到处理的方式
ret = action_func(request)
if ret:
return ret

查页面(后端)

{% extends 'stark/base_temp.html' %}
{% block title %}列表页面{% endblock %}
{% block css %}
<style>
h1 {
margin-bottom: 50px;
} td, th {
text-align: center;
} .list-filter a {
display: inline-block;
padding: 3px 6px;
border: 1px solid #2e6da4;
margin: 3px 0;
} .list-filter a.active {
background-color: #2e6da4;
color: white;
} </style>
{% endblock %} {% block body %}
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h1 class="text-center">列表页面</h1>
{# 筛选栏#}
{% if the_list.show_comb_filter %}
<div class="list-filter">
{% for item in the_list.gen_comb_filter %}
<div>
{% for foo in item %}
{{ foo }}
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
{# 搜索栏#}
{% if the_list.show_search_form %}
<div class="form-group pull-right">
<form method="get">
<input name="{{ the_list.config.search_key }}" value="{{ the_list.search_form_val }}"
class="form-control"
placeholder="请输入搜索条件" type="text" style="display:inline-block;width: 200px;"/>
<button class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button> </form>
</div>
{% endif %}
{# 带有批量执行操作的表格#}
<form method="post">
{% csrf_token %}
{% if the_list.show_actions %}
<div class="form-group">
<select name="list_action" class="form-control" style="display:inline-block;width: 200px;">
{% for item in the_list.modify_actions %}
<option value="{{ item.name }}">{{ item.text }}</option>
{% endfor %} </select>
<button class="btn btn-danger">执行</button>
</div>
{% endif %} <table class="table table-bordered table-striped">
<thead>
<tr>
<th>编号</th>
{% for item in the_list.head_list %}
<th>{{ item }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for obj in the_list.body_list %}
<tr>
<td><b>({{ forloop.counter }})</b></td>
{% for col in obj %}
<td>{{ col }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</form>
{# 添加按钮#}
<div class="pull-left">
{% if the_list.show_add_btn %}
<a href="{{ the_list.add_url }}" class="btn btn-info">&nbsp;&nbsp;&nbsp;&nbsp;添加&nbsp;&nbsp;&nbsp;&nbsp;</a>
{% endif %}
</div>
{# 分页器#}
<div class="pager">
<nav aria-label="Page navigation">
<ul class="pagination"> {{ the_list.page_obj.page_html|safe }}
</ul>
</nav>
</div>
</div>
</div> </div>
{% endblock %}

查页面(前端)

9.4 modelform

  modelform有两种写法

model_form_class = None
def get_model_form_class(self):
if self.model_form_class:
return self.model_form_class
from django.forms import ModelForm #方式一:
# class TestModelForm(ModelForm):
# class Meta:
# model = self.model_class
# fields = "__all__"
#
# error_messages = {
# "__all__":{
#
# },
# 'email': {
# 'required': '',
# 'invalid': '邮箱格式错误..',
# }
# }
#方式二: type创建TestModelForm类
meta = type('Meta', (object,), {'model': self.model_class, 'fields': '__all__'})
TestModelForm = type('TestModelForm', (ModelForm,), {'Meta': meta})
return TestModelForm

modelform

9.5 增页面(含popup功能)

class StarkConfig(object):
#增
def add_view(self, request, *args, **kwargs):
# 添加页面
model_form_class = self.get_model_form_class()#根据modelform生成input
_popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
if request.method == 'GET':
form = model_form_class()
else:
form = model_form_class(request.POST)
if form.is_valid():
new_obj=form.save()
if _popbackid:
# 判断是否是来源于popup请求
# render一个页面,写自执行函数
# popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant')
from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel
result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid} model_name = request.GET.get('model_name') # customer
related_name = request.GET.get('related_name') # consultant, "None"
for related_object in new_obj._meta.related_objects:#关联表的对象
_model_name = related_object.field.model._meta.model_name
_related_name = related_object.related_name
# 判断外键关联字段是否是主键id
if (type(related_object) == ManyToOneRel):
_field_name = related_object.field_name
else:
_field_name = 'pk'
_limit_choices_to = related_object.limit_choices_to
if model_name == _model_name and related_name == str(_related_name):
is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists()
if is_exists:
# 如果新创建的用户是可查看的人,页面才增加
# 分门别类做判断:
result['status'] = True
result['text'] = str(new_obj)
result['id'] = getattr(new_obj, _field_name)
return render(request, 'stark/popup_response.html',
{'json_result': json.dumps(result, ensure_ascii=False)})
return render(request, 'stark/popup_response.html',
{'json_result': json.dumps(result, ensure_ascii=False)})
else:
list_query_str = request.GET.get(self._query_param_key)
list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url)
# return redirect(self.get_list_url())
return render(request, 'stark/add_view.html', {'form': form, 'config': self})

StarkConfig类中的add_view

{% extends 'stark/base_temp.html' %}
{% load change_form %} {% block title %}添加页面{% endblock %} {% block css %}{% endblock %} {% block body %}
<h1 class="text-center">添加页面</h1>
<form method="post" novalidate>
{% csrf_token %}
{# {% include 'stark/form.html' %}#}
{% new_form config form %}{# def new_form(model_form_obj):#}
</form>
{% endblock %}

add_view.html

9.6 改页面(含popup功能)

class StarkConfig(object):
def change_view(self, request, nid,*args, **kwargs):
# self.model_class.objects.filter(id=nid)
obj = self.model_class.objects.filter(pk=nid).first()
print(obj)
if not obj:
return redirect(self.get_list_url())
model_form_class = self.get_model_form_class()
_popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
# GET,显示标签+默认值
if request.method == 'GET':
form = model_form_class(instance=obj)
return render(request, 'stark/change_view.html', {'form': form,'config': self})
else:
form = model_form_class(instance=obj, data=request.POST)
if form.is_valid():
form.save()
list_query_str=request.GET.get(self._query_param_key)
list_url='%s?%s'%(self.get_list_url(),list_query_str,) return redirect(list_url)
return render(request, 'stark/change_view.html', {'form': form})

StarkConfig类中的add_view

{% extends 'stark/base_temp.html' %}
{% load change_form %} {% block title %}添加页面{% endblock %} {% block css %}{% endblock %} {% block body %}
<h1 class="text-center">修改页面</h1>
<form method="post" novalidate>
{# {% include 'stark/form.html' %}#}
{% new_form config form %}{# def new_form(model_form_obj):#}
</form>
{% endblock %}

change_view.html

9.7 删页面

  删页面最为简单,但也要注意跳转功能,总不能在第三页点删除键后跳到了第一页吧

class StarkConfig(object):
def delete_view(self, request, nid,*args, **kwargs):
self.model_class.objects.filter(pk=nid).delete()
list_query_str = request.GET.get(self._query_param_key)
list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
return redirect(list_url)

StarkConfig类中的delete_view

end

  至此,插件v1就已全部完成。在使用时,需要在app中导入这个文件,即创建一个stark.py文件,在文件中进行注册。

  在stark.py中,只注册的话就执行默认功能,即只具备查、删、改功能。若想要拥有更多功能,需在stark.py中自己写一个派生类,利用钩子函数进行扩展。

举例说明:

  这里进行对插件的使用的举例说明

  我们创建一个新的app并建表,然后在app下创建stark.py,无需写路由和视图函数即可得到增删改查以及模糊搜索、批量操作、条件筛选、popup等功能。具体是否拥有该功能,由权限决定。

from django.db import models

# Create your models here.
class UserInfo(models.Model):
name=models.CharField(max_length=32)
pwd=models.CharField(max_length=32)
email=models.EmailField(max_length=32)
tp=models.ForeignKey('Type') class Meta:
verbose_name_plural = "用户表"
def __str__(self):
return self.name
class Type(models.Model):
name=models.CharField(max_length=32)
role=models.ManyToManyField('Role') class Meta:
verbose_name_plural = "类型表" def __str__(self):
return self.name class Role(models.Model):
name=models.CharField(max_length=32)
salary=models.IntegerField(default=0) class Meta:
verbose_name_plural = "角色表"
def __str__(self):
return self.name class Host(models.Model):
hostname = models.CharField(verbose_name='主机名',max_length=32)
ip = models.GenericIPAddressField(verbose_name="IP",protocol='ipv4')
port = models.IntegerField(verbose_name='端口')

models.py

print('我是stark')
from django.shortcuts import HttpResponse,render,redirect
from django.utils.safestring import mark_safe
from django.conf.urls import url
from stark.service import v1
from app01 import models
from django.forms import ModelForm
class UserInfoModelForm(ModelForm):
class Meta:
model = models.UserInfo
fields = '__all__'
error_messages = {
'name':{
'required':'用户名不能为空'
}
} class UserinfoConfig(v1.StarkConfig):
'''
自己定义的派生类,可以有29种额外的显示方式,效果与admin相同
''' list_display=['id','name','pwd','email']
def extra_url(self):
url_list=[
#除增删改查外,想要新增的url
]
return url_list
show_add_btn = True
model_form_class = UserInfoModelForm
show_search_form = True#搜索框
search_fields = ['name__contains', 'email__contains']#模糊搜索
show_actions = True#批量操作框
#批量删除
def multi_del(self,request):
pk_list = request.POST.getlist('pk')#得到所有的勾选的项
self.model_class.objects.filter(id__in=pk_list).delete()
return HttpResponse('删除成功')
# return redirect("http://www.baidu.com")
multi_del.desc_text = "批量删除"#给函数内部加一个字段 def multi_init(self,request):
pk_list = request.POST.getlist('pk')
#self.model_class.objects.filter(id__in=pk_list).delete()
# return HttpResponse('删除成功')
#return redirect("http://www.baidu.com")
multi_init.desc_text = "初始化" actions = [multi_del, multi_init]#给actions加入定制的功能 class HostModelForm(ModelForm):
class Meta:
model = models.Host
fields = ['id','hostname','ip','port']
error_messages = {
'hostname':{
'required':'主机名不能为空',
},
'ip':{
'required': 'IP不能为空',
'invalid': 'IP格式错误',
} } class HostConfig(v1.StarkConfig):
def ip_port(self,obj=None,is_header=False):
if is_header:
return '自定义列'
return "%s:%s" %(obj.ip,obj.port,) list_display = ['id','hostname','ip','port',ip_port]
# get_list_display show_add_btn = True
# get_show_add_btn model_form_class = HostModelForm
# get_model_form_class
def extra_url(self):
urls = [
url('^report/$',self.report_view)
]
return urls def report_view(self,request):
return HttpResponse('自定义报表') def delete_view(self,request,nid,*args,**kwargs):
if request.method == "GET":
return render(request,'my_delete.html')
else:
self.model_class.objects.filter(pk=nid).delete()
return redirect(self.get_list_url()) #相应的注册
#第二个参数传入自己写的类时,可以拥有自己写的类中的额外的方法
v1.site.register(models.UserInfo,UserinfoConfig)
v1.site.register(models.Role)
v1.site.register(models.Type)
v1.site.register(models.Host,HostConfig)

stark.py

附:

import copy
import json
from django.shortcuts import HttpResponse,render,redirect
from django.urls import reverse
from django.conf.urls import url, include
from django.utils.safestring import mark_safe
from django.http import QueryDict
from django.db.models import Q #用于封装筛选条件的配置信息
class FilterOption(object):
def __init__(self, field_name, multi=False, condition=None, is_choice=False,text_func_name=None, val_func_name=None):
"""
:param field_name: 字段
:param multi: 是否多选
:param condition: 显示数据的筛选条件
:param is_choice: 是否是choice
"""
self.field_name = field_name
self.multi = multi
self.condition = condition
self.is_choice = is_choice
self.text_func_name = text_func_name#组合搜索时,页面上生成显示的文本的函数
self.val_func_name = val_func_name#组合搜索时,页面上生成的a标签中的值的函数 def get_queryset(self, _field):
if self.condition:#是数据的筛选条件
return _field.rel.to.objects.filter(**self.condition)#拿到筛选后的对象
return _field.rel.to.objects.all()#默认“全部”按钮被选中,给出所有对象 def get_choices(self, _field):#是choices
return _field.choices #可迭代对象,封装了筛选中的每一行数据。
class FilterRow(object):
def __init__(self, option, data, request):
self.option = option
self.data = data#关联字段所关联的表的所有有关联的数据
# request.GET
self.request = request def __iter__(self):
params = copy.deepcopy(self.request.GET)#深拷贝?后面的内容,得到QueryDict
params._mutable = True#可修改
current_id = params.get(self.option.field_name) #params.get(字段),得到的是值
current_id_list = params.getlist(self.option.field_name) # [1,2,3] if self.option.field_name in params:#地址栏已存在筛选条件
# del params[self.option.field_name],先删除
origin_list = params.pop(self.option.field_name)#删除,并得到删除内容
url = "{0}?{1}".format(self.request.path_info, params.urlencode())
yield mark_safe('<a href="{0}">全部</a>'.format(url))#“全部按钮”不被选中
params.setlist(self.option.field_name, origin_list)#将本身已存在的筛选条件放入params中
else:
url = "{0}?{1}".format(self.request.path_info, params.urlencode())#地址栏不存在筛选条件
yield mark_safe('<a class="active" href="{0}">全部</a>'.format(url))#默认“全部”按钮被选中 for val in self.data:
if self.option.is_choice:# ( (1,男),(2,女) )
pk, text = str(val[0]), val[1]
else:#每个val都是对象
# pk, text = str(val.pk), str(val)
text = self.option.text_func_name(val) if self.option.text_func_name else str(val)
pk = str(self.option.val_func_name(val)) if self.option.val_func_name else str(val.pk)
# 当前URL?option.field_name
# 当前URL?gender=pk
#制定url的显示规则:
# self.request.path_info # http://127.0.0.1:8005/arya/crm/customer/?gender=1&id=2
# self.request.GET['gender'] = 1 # &id=2gender=1
if not self.option.multi:
# 单选
params[self.option.field_name] = pk#1,2
url = "{0}?{1}".format(self.request.path_info, params.urlencode())
if current_id == pk:#当前url筛选条件中的值
yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))#该筛选按钮被选中
else:
yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))#其余按钮,没被选中
else:
# 多选 current_id_list = ["1","2"]
_params = copy.deepcopy(params)
id_list = _params.getlist(self.option.field_name)#["1","2","3","4"] if pk in current_id_list:#值已存在,表示该按钮已被选中
id_list.remove(pk)#将该值从id_list中去除
_params.setlist(self.option.field_name, id_list)#["2","3","4"]
url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
#该按钮被选中,但其a标签的href中跳转的链接即当前url去除本身按钮id的状态,即该按钮,被再次点击时,就会恢复未选中状态
yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text)) else:#值未存在
id_list.append(pk)
# params中被重新赋值
_params.setlist(self.option.field_name, id_list)
# 创建URL,赋予其被点时,使其产生被选中
url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
yield mark_safe("<a href='{0}'>{1}</a>".format(url, text)) class ChangeList(object):
'''
很牛逼的一个类,封装了所有视图函数想要往前端传的内容
功能:使视图函数中的代码变的简洁
'''
def __init__(self,config,queryset):
self.config=config#stark.py中写了派生类的话就是那个类,没写的话默认就是StarkConfig
self.list_display=config.get_list_display()
self.edit_link = config.get_edit_link()
self.model_class=config.model_class#数据库的表
self.request=config.request#StarkConfig中默认是None,不过程序运行后就会有
self.show_add_btn=config.get_show_add_btn()
# 搜索框
self.show_search_form = config.get_show_search_form()
self.search_form_val = config.request.GET.get(config.search_key, '')#搜索关键字“_q”,首次访问网页默认是空
# 批量操作
self.actions=config.get_actions()#得到派生类中写的actions的内容[]
self.show_actions=config.get_show_actions()#操作框
#组合搜索
self.show_comb_filter=config.get_show_comb_filter()
self.comb_filter=config.get_comb_filter() from utils.pager import Pagination
#分页器
current_page = self.request.GET.get('page', 1)#得到传入的page,没有默认为第一页
total_count = queryset.count()#要显示的数据的量,queryset在视图函数中有数据库操作的赋值
page_obj = Pagination(current_page, total_count, self.request.path_info, self.request.GET, per_page_count=5)
#当前页 数据量 当前url不带问号 ?后面的条件内容 设定的每页显示的数据量条数
self.page_obj = page_obj#得到最终生成的分页器对象 self.data_list = queryset[page_obj.start:page_obj.end]#得到分页后的数据,用于页面展示 #批量操作
def modify_actions(self):
result = []#批量操作内容,默认为空,去派生类中定义
for func in self.actions:#self.actions=config.get_actions(),默认为空
temp = {'name':func.__name__,'text':func.short_desc}#name是函数名,text是自加的描述
result.append(temp)
return result def add_url(self):#添加操作的url
query_str = self.request.GET.urlencode()
if query_str:
# 重新构造,用于跳转
params = QueryDict(mutable=True)
params[self.config._query_param_key] = query_str
return self.config.get_add_url()+'?'+params.urlencode()
return self.config.get_add_url() def head_list(self):
#构造表头
result = []
# [checkbox,'id','name',edit,del]
for field_name in self.list_display:
if isinstance(field_name, str):
# 根据类和字段名称,获取字段对象的verbose_name
verbose_name = self.model_class._meta.get_field(field_name).verbose_name
else:
verbose_name = field_name(self.config, is_header=True)# 去派生类中执行
result.append(verbose_name)
return result def body_list(self):
# 处理表中的数据
data_list = self.data_list#self.data_list = queryset[page_obj.start:page_obj.end]
new_data_list = []
for row in data_list:
# row是 每一条数据对象UserInfo(id=2,name='alex2',age=181)
temp = []
for field_name in self.list_display:
if isinstance(field_name,str):#派生类中定义的显示字段
val = getattr(row,field_name)
else:#每个td都拥有的功能,checkbox、edit、delete、
val = field_name(self.config,row)
# 用于定制编辑列
if field_name in self.edit_link:
val = self.edit_link_tag(row.pk, val)
temp.append(val)
new_data_list.append(temp)
return new_data_list def gen_comb_filter(self):
#生成器函数
"""
[
FilterRow(((1,'男'),(2,'女'),)),
FilterRow([obj,obj,obj,obj ]),
FilterRow([obj,obj,obj,obj ]),
]
"""
'''
comb_filter = [
v1.FilterOption('gender', is_choice=True),#关键字传参,代表是choice
v1.FilterOption('depart'),#, 筛选只显示id大于三的部门condition={'id__gt': 3}
v1.FilterOption('roles', True),#True传入,代表是多选
]
'''
from django.db.models import ForeignKey,ManyToManyField
for option in self.comb_filter:
_field = self.model_class._meta.get_field(option.field_name)#字段
if isinstance(_field,ForeignKey):
# 获取当前字段depart,关联的表 Department表并获取其所有数据
# print(field_name,_field.rel.to.objects.all())
row = FilterRow(option, option.get_queryset(_field), self.request)
elif isinstance(_field,ManyToManyField):
# print(field_name, _field.rel.to.objects.all())
# data_list.append( FilterRow(_field.rel.to.objects.all()) )
row = FilterRow(option,option.get_queryset(_field), self.request) else:
# print(field_name,_field.choices)
# data_list.append( FilterRow(_field.choices) )
row = FilterRow(option,option.get_choices(_field),self.request)
# 可迭代对象,迭代详细在FilterRow的__iter__中
yield row def edit_link_tag(self,pk,text):
query_str = self.request.GET.urlencode() # page=2&nid=1
params = QueryDict(mutable=True)
params[self.config._query_param_key] = query_str
return mark_safe('<a href="%s?%s">%s</a>' % (self.config.get_change_url(pk), params.urlencode(),text,)) # /stark/app01/userinfo/ class StarkConfig(object):
"""
用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类
"""
def __init__(self,model_class,site):
self.model_class=model_class
self.site=site
self.request=None
self._query_param_key='_listfilter'#?后面的条件内容
self.search_key='_q'#搜索关键字 #####################################定制功能######################################
#########1 默认每个tr都会拥有的td
def checkbox(self,obj=None,is_header=False):
if is_header:
return '选择'
return mark_safe('<input type="checkbox" name="pk" value="%s" >' %(obj.id,))
def edit(self,obj=None,is_header=False):
if is_header:
return '编辑操作'
#url地址栏的搜索条件
query_str=self.request.GET.urlencode()
if query_str:
#重新构造<button class="btn btn-primary"></button>
params=QueryDict(mutable=True)
params[self._query_param_key]=query_str
return mark_safe('<button class="btn btn-primary"><a href="%s?%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),params.urlencode(),))
return mark_safe('<button class="btn btn-primary"><a href="%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),))
def delete(self,obj=None,is_header=False):
if is_header:
return '删除操作'
query_str = self.request.GET.urlencode()
if query_str:
# 重新构造
params = QueryDict(mutable=True)
params[self._query_param_key] = query_str
return mark_safe('<button class="btn btn-danger"><a href="%s?%s" style="color:white;">删除</a></button>' % (self.get_delete_url(obj.id), params.urlencode(),)) return mark_safe('<button class="btn btn-danger"><a href="%s" style="color:white;">删除</a></button>'%(self.get_delete_url(obj.id),) ) list_display=[]
#得到派生类中自定义的list_display
def get_list_display(self):
data=[]
if self.list_display:#派生类中定义的要显示的字段
data.extend(self.list_display)#加入到data中
data.append(StarkConfig.edit)#加入编辑td
data.append(StarkConfig.delete)#加入删除td
data.insert(0,StarkConfig.checkbox)#在最前面插一个td
return data edit_link=[]
def get_edit_link(self):
result=[]
if self.edit_link:
result.extend(self.edit_link)
return result ######### 2是否显示add按钮
show_add_btn = True # 默认显示
def get_show_add_btn(self):
return self.show_add_btn #########3 关键字搜索
show_search_form = False#默认不显示
def get_show_search_form(self):
return self.show_search_form
search_fields = []#关键字默认为空
def get_search_fields(self):
result = []
if self.search_fields:
result.extend(self.search_fields)#派生类中自定义的关键字
return result def get_search_condition(self):
key_word = self.request.GET.get(self.search_key)#'_q'
search_fields = self.get_search_fields()#关键字
condition = Q()#创建Q对象用于与或
condition.connector = 'or'#搜索条件之间用或连接
if key_word and self.get_show_search_form():
for field_name in search_fields:
condition.children.append((field_name, key_word))
return condition
#############4 actions
show_actions = False#默认不显示
def get_show_actions(self):
return self.show_actions actions = []#默认批量操作内容为空
def get_actions(self):
result = []
if self.actions:
result.extend(self.actions)#加入派生类中自定制的批量操作
return result #############5 组合搜索
show_comb_filter = False
def get_show_comb_filter(self):
return self.show_comb_filter comb_filter=[]#默认为空
def get_comb_filter(self):
result=[]
if self.comb_filter:
result.extend(self.comb_filter)#得到派生类中的条件删选
return result #############6排序
order_by = []
def get_order_by(self):
result = []
result.extend(self.order_by)
return result ##################################访问相应网址时需要作数据处理的视图函数##########################
# 默认列表页面
def changelist_view(self, request,*args, **kwargs):
#分页,已改写到ChangeList类中
# from utils.pager import Pagination
# current_page=request.GET.get('page',1)
# total_count=self.model_class.objects.all().count()
# page_obj=Pagination(current_page,total_count,request.path_info,request.GET,per_page_count=4) if request.method=='GET':
comb_condition = {}#筛选条件默认为空
option_list = self.get_comb_filter()#拿到派生类中定制的筛选条件
for key in request.GET.keys():#?后面的键
value_list = request.GET.getlist(key)#拿到键对应的值[1,2,3]
flag = False
for option in option_list:#option是每一个删选条件
if option.field_name == key:#该条件已存在于地址栏
flag = True
break
if flag:
#comb_condition = {"id__in":[1,2,3].......}
comb_condition["%s__in" % key] = value_list # 带搜索条件的数据,没有搜索条件的话就是全部数据。有筛选条件的话,还得处理成筛选后的数据
queryset=self.model_class.objects.filter(self.get_search_condition()).filter(**comb_condition).distinct() the_list=ChangeList(self,queryset)#封装好要向前端传的值
return render(request, 'stark/changelist.html', {'the_list':the_list})
elif request.method=='POST' and self.get_show_actions():#批量操作
func_name_str = request.POST.get('list_action')#前端传的操作name
action_func = getattr(self, func_name_str)#反射,得到处理的方式
ret = action_func(request)
if ret:
return ret # 一劳永逸的modelform
model_form_class = None
def get_model_form_class(self):
if self.model_form_class:
return self.model_form_class
from django.forms import ModelForm
# class TestModelForm(ModelForm):
# class Meta:
# model = self.model_class
# fields = "__all__"
#
# error_messages = {
# "__all__":{
#
# },
# 'email': {
# 'required': '',
# 'invalid': '邮箱格式错误..',
# }
# }
# type创建TestModelForm类
meta = type('Meta', (object,), {'model': self.model_class, 'fields': '__all__'})
TestModelForm = type('TestModelForm', (ModelForm,), {'Meta': meta})
return TestModelForm #增
def add_view(self, request, *args, **kwargs):
# 添加页面
model_form_class = self.get_model_form_class()#根据modelform生成input
_popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
if request.method == 'GET':
form = model_form_class()
else:
form = model_form_class(request.POST)
if form.is_valid():
new_obj=form.save()
if _popbackid:
# 判断是否是来源于popup请求
# render一个页面,写自执行函数
# popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant')
from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel
result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid} model_name = request.GET.get('model_name') # customer
related_name = request.GET.get('related_name') # consultant, "None"
for related_object in new_obj._meta.related_objects:#关联表的对象
_model_name = related_object.field.model._meta.model_name
_related_name = related_object.related_name
# 判断外键关联字段是否是主键id
if (type(related_object) == ManyToOneRel):
_field_name = related_object.field_name
else:
_field_name = 'pk'
_limit_choices_to = related_object.limit_choices_to
if model_name == _model_name and related_name == str(_related_name):
is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists()
if is_exists:
# 如果新创建的用户是可查看的人,页面才增加
# 分门别类做判断:
result['status'] = True
result['text'] = str(new_obj)
result['id'] = getattr(new_obj, _field_name)
return render(request, 'stark/popup_response.html',
{'json_result': json.dumps(result, ensure_ascii=False)})
return render(request, 'stark/popup_response.html',
{'json_result': json.dumps(result, ensure_ascii=False)})
else:
list_query_str = request.GET.get(self._query_param_key)
list_url = '%s?%s' % (self.get_list_url(), list_query_str,) return redirect(list_url)
# return redirect(self.get_list_url())
return render(request, 'stark/add_view.html', {'form': form, 'config': self}) #删
def delete_view(self, request, nid,*args, **kwargs):
self.model_class.objects.filter(pk=nid).delete()
list_query_str = request.GET.get(self._query_param_key)
list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
return redirect(list_url)
#改
def change_view(self, request, nid,*args, **kwargs):
# self.model_class.objects.filter(id=nid)
obj = self.model_class.objects.filter(pk=nid).first()
print(obj)
if not obj:
return redirect(self.get_list_url())
model_form_class = self.get_model_form_class()
_popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
# GET,显示标签+默认值
if request.method == 'GET':
form = model_form_class(instance=obj)
return render(request, 'stark/change_view.html', {'form': form,'config': self})
else:
form = model_form_class(instance=obj, data=request.POST)
if form.is_valid():
form.save()
list_query_str=request.GET.get(self._query_param_key)
list_url='%s?%s'%(self.get_list_url(),list_query_str,) return redirect(list_url)
return render(request, 'stark/change_view.html', {'form': form}) ############################反向生成url##########################################
def get_change_url(self, nid):
name = 'stark:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name, args=(nid,))
return edit_url def get_list_url(self):
name = 'stark:%s_%s_changelist' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name)
return edit_url def get_add_url(self):
name = 'stark:%s_%s_add' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name)
return edit_url def get_delete_url(self, nid):
name = 'stark:%s_%s_delete' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
edit_url = reverse(name, args=(nid,))
return edit_url ##########################################################################################################
#装饰器,为了传参数request
def wrap(self,view_func):
def inner(request,*args,**kwargs):
self.request=request
return view_func(request,*args,**kwargs)
return inner def get_urls(self):#第五步
app_model_name=(self.model_class._meta.app_label,self.model_class._meta.model_name,)#元祖(app名,表名)
url_patterns=[
url(r'^$',self.wrap(self.changelist_view),name='%s_%s_changelist'%app_model_name),
url(r'^add/$',self.wrap(self.add_view),name='%s_%s_add'%app_model_name),
url(r'^(\d+)/delete/$',self.wrap(self.delete_view),name='%s_%s_delete'%app_model_name),
url(r'^(\d+)/change/$',self.wrap(self.change_view),name='%s_%s_change'%app_model_name),
]
url_patterns.extend(self.extra_url())#除增删改查外,想要自定义的新增的url
return url_patterns#最后就得到了需要用到的一堆url
def extra_url(self):
return []
#############################################################################################
@property
def urls(self):#第四步
return self.get_urls() ########传说中类与类之间的分界线############################################################################
class StarkSite(object):
'''
单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系
{model.UserInfo:StarkConfig(model.UserInfo,self)}
'''
def __init__(self):
self._registry = {} def register(self,model_class,stark_config_class=None):
if not stark_config_class:
#stark_config_class即29,没写那个派生类的时候默认给予StarkConfig
stark_config_class=StarkConfig
self._registry[model_class]=stark_config_class(model_class,self)
#表名:stark_config_class(表名,self) def get_urls(self):#第三步,给url
url_pattern=[]
for model_class,stark_config_obj in self._registry.items():#去字典里取值
app_name=model_class._meta.app_label#app名
model_name=model_class._meta.model_name#表名
curd_url=url(r'^%s/%s/'%(app_name,model_name),(stark_config_obj.urls,None,None))
#拼接生成url,需执行stark_config_obj.urls———第四步
url_pattern.append(curd_url)
return url_pattern @property
def urls(self):#第二步,要url
return (self.get_urls(),None,'stark') site=StarkSite()#第一步,单例模式

v1.py

最新文章

  1. Jquery的命名冲突
  2. 复习课程jdbc:使用配置文件properties进行连接数据库,数据库存取图片,批处理,时间戳,事物回滚等等
  3. css:map热点的应用
  4. Nlog 配置总结
  5. DesiredCapabilities参数配置及含义
  6. PyQt QFontDialog显示中文
  7. 关于VC++中virtual ~的含义
  8. Win10U盘启动盘制作及Win10系统安装
  9. meterpreter 持久后门
  10. 【C/C++】C++11 Variadic Templates
  11. IIS下配置跨域设置Access-Control-Allow-Origin
  12. [SPOJ375]QTREE - Query on a tree【树链剖分】
  13. C#读取Excel文件的简单方法
  14. Nest.js 守卫
  15. 2017ACM/ICPC广西邀请赛-重现赛
  16. Compoxure 微服务组合proxy 中间件
  17. PS插件CameraRaw-初次尝试
  18. Asp.Net_HttpModule的应用
  19. View类的XML属性、相关方法及说明
  20. html如何让label在div中的垂直方向居中显示?

热门文章

  1. Eclipse的Project Facets属性
  2. [PYTHON-TSNE]可视化Word Vector
  3. 深度学习之windows安装tensorflow
  4. vb学习基础之val函数与val(&amp;HFFFF) 的理解
  5. 【CF717G】Underfail 费用流
  6. Docker详解
  7. LIBXML2库使用指南2
  8. {Django基础六之ORM中的锁和事务}一 锁 二 事务
  9. xpath路径表达式
  10. listview控件专题