仿造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

可快速生成增删curd改查功能的插件的更多相关文章

  1. 帮助快速生成页面固定显示元素的jQuery插件 - sticky-kit

    来源:GBin1.com 如果需要在用户滚动页面的时候,保持特定元素始终可见的话,今天这里我们介绍的Sticky-Kit是一个不错的选择. 它是一个开源的jQuery插件,可以帮助大家快速针对页面元素 ...

  2. 企业信息化-Excel快速生成系统

    企业信息化,主要是指对企业生产运营过程所形成的信息数字化,最终形成了数字资产.大型企业为了节约成本,提高协同工作效率,都会定制ERP.办公OA.流程审批等系统做信息化支撑.但是中小企业精力投入到生成中 ...

  3. django ajax增 删 改 查

    具于django ajax实现增 删 改 查功能 代码示例: 代码: urls.py from django.conf.urls import url from django.contrib impo ...

  4. Eclipse快速生成一个JavaBean类的方法

    原文: https://jingyan.baidu.com/article/948f5924156866d80ff5f921.html Eclipse快速生成一个JavaBean类的方法 听语音 | ...

  5. sublim text3快速生成html代码时,tab键失效问题

    sublime text3是一款非常强大的文本编辑器,个人觉得做前端的话这款工具很好用.便携,秒启.唯一让我觉得不是特别爽的就是插件啊,都需要自己安装.不过瑕不掩瑜,这款编辑器是很适合开发前端和PHP ...

  6. JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(四):自定义T4模板快速生成页面

    前言:上篇介绍了下ko增删改查的封装,确实节省了大量的js代码.博主是一个喜欢偷懒的人,总觉得这些基础的增删改查效果能不能通过一个什么工具直接生成页面效果,啥代码都不用写了,那该多爽.于是研究了下T4 ...

  7. Vue.js-简单的增删查功能

    1.Vue.js是什么? Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图 ...

  8. 具有增、删、改、查功能的vue-tree树组件

    最近写了一个具有增删改查功能的多级树组件,感觉很实用,啦啦啦啦, 废话不多说,看代码: tree.vue <template> <div> <div class=&quo ...

  9. Mock.js简易教程,脱离后端独立开发,实现增删改查功能(转)

    在我们的生产实际中,后端的接口往往是较晚才会出来,并且还要写接口文档,于是我们的前端的许多开发都要等到接口给我们才能进行,这样对于我们前端来说显得十分的被动,于是有没有可以制造假数据来模拟后端接口呢, ...

随机推荐

  1. 网络编程 -- RPC实现原理 -- NIO多线程 -- 迭代版本V1

    网络编程 -- RPC实现原理 -- 目录 啦啦啦 V1——设置标识变量selectionKey.attach(true);只处理一次(会一直循环遍历selectionKeys,占用CPU资源). ( ...

  2. 【转载】技巧:Vim 的纵向编辑模式

    如果要我选一个Vim中让我觉得Life Changing功能的话,我一定会选Vim的块编辑功能,也就是Ctrl+V,虽然还有些别的编辑器也有这功能,但目前为止,我从中受益颇大 原文地址:技巧:Vim ...

  3. DBeaver连接达梦数据库

    1.连接类型选择ODBC. 2.编辑驱动设置: 1)Class Name:dm.jdbc.driver.DmDriver 2)URL Template:jdbc:dm://{dbserver}/{da ...

  4. ffmpeg 视频转ts切片 生成m3u8视频播放列表

    近期做视频点播,要求将视频文件切片成ts文件.经搜索得到以下两个命令,可完成这个任务. 一  首先将视频文件转为视频编码h264,音频编码aac格式的mp4文件      1.可以预先使用ffprob ...

  5. codeforces#525 Div2---ABC

    A---Ehab and another constriction problem https://codeforc.es/contest/1088/problem/A 题意:给定一个数$x$找两个在 ...

  6. 自动化运维工具-pdsh工具安装配置及简单使用讲解

    1.先决条件: 安装pssh工具的主机针对远程主机需要配置免秘钥认证: ssh-keygen -t rsa ssh-copy-id [remotehost] 2.下载pssh工具安装介质: https ...

  7. 查看dsym的报错信息(友盟)

    //搜索显示报错信息的命令在终端直接运行(来源老版提示) export dSYMPath="$(find ~/Library/Developer/Xcode -iname '*.dSYM' ...

  8. ArcGIS API for JavaScript

    以3.14版本为例: 1.部署环境: 下载:https://developers.arcgis.com/downloads/apis-and-sdks?product=javascript# 部署:h ...

  9. java发送get,post请求

    方法里面有注释:参照csdn里面的,项目用时自己改 package com.bst.express; import java.io.BufferedReader; import java.io.Dat ...

  10. [python2] python 打印表格 prettytable

    rpm包: [root@D129 cli]# yum info python-prettytable Loaded plugins: fastestmirror Loading mirror spee ...