STARK_后台管理
项目配置
新建一个APP并且注册进settings中
在新建APP的配置类中定义ready方法,django启动时会自动执行执行该方法. 从django.utils.module_loading 中引入autodiscover_modules并且传参执行, 传入的参数就是会被执行的app下的py文件名.
这样我们新建的这个APP就能在项目启动时执行有APP下的start文件. 就像django自带的admin一样.
START要完成的功能
注册Model, 实现增删改查的操作
生成路由
可拓展
实现
像admin一样, 通过模块导入来实现单例模式
# 在stark组件中, 创建类并实例化 site = StarkSite() # 在路由和其他APP中的strak文件中导入他, 模仿admin的模型注册和生成路由 admin.site.register(UserInfo) url(r'^admin/', admin.site.urls)
StarkSite -- 仅实现了路由注册和模型注册
class StarkSite(object):
def __init__(self):
self._registry={}
def register(self,model,model_config=None): # 注册方法
if not model_config: # 使用默认的样式类
model_config=ModelSatrk
self._registry[model]=model_config(model,self) # 以模型类为键,他的样式类对象为值
def get_urls(self):
temp=[]
for model,model_config in self._registry.items():
model_name=model._meta.model_name
app_label=model._meta.app_label
# 根据模型类生成二级url,并分发给他的样式类对象
u=url("^%s/%s/"%(app_label,model_name),(model_config.urls,None,None))
temp.append(u)
return temp
@property
def urls(self): # url的静态方法
return self.get_urls(), None, None
# 实例化这个类,在其他文件中导入这个对象,由于文件只会导入一次,所以site是个单例对象,不会重新实例化成一个新的对象
site=StarkSite()
include其实就是返回了一个元组
def include(arg, namespace=None, app_name=None):
return (urlconf_module, app_name, namespace)
ModelSatrk -- 默认样式类
初始化
from django.db.models import Q
class ModelSatrk(object):
# 拓展用的属性
list_display=["__str__"]
modeform = None # 用来自定义modeform
list_display_links = [] # a标签
search_fields = [] # 用于搜索
actions = [] # 批量操作
list_filter = [] # 右侧分组
# 初始化
def __init__(self,model,site):
self.model=model # 用户访问的当前类
self.site=site # 唯一的site对象
# app_label和model_name经常用到, 所以直接放在这里
self.app_model_name=(self.model._meta.app_label,
self.model._meta.model_name)
路由的分发及处理
class ModelSatrk(object):
@property
def urls(self):
return self.get_urls()
# url的分发
def get_urls(self):
temp = []
temp.append(url("^$", self.show_list_view,name="%s_%s_showlist"%
self.app_model_name)) # 查
temp.append(url("^add/$", self.add_view,name="%s_%s_add"%
self.app_model_name)) # 增
temp.append(url("^(\d+)/change/$", self.change_view,name="%s_%s_change"%
self.app_model_name)) # 改
temp.append(url("^(\d+)/delete/$", self.del_view,name="%s_%s_delete"%
self.app_model_name)) # 删
temp.extend(self.extra_url()) # 用于拓展url, 自定义样式类可以自定义路由和方法
return temp
# 拓展url的接口
def extra_url(self):
return []
查看视图
# 增的视图函数
from django.db.models import Q
class ModelSatrk(object):
def show_list_view(self,request):
# 批量处理操作
if request.method=="POST":
# 获取选中的数据集合,在渲染时将选择框的name设置为_selected_action,value是主键值
pk_list=request.POST.getlist("_selected_action")
# 根据获得的主键集合获取model对象的集合
queryset=self.model.objects.filter(pk__in=pk_list)
# 获取函数名字
func_name=request.POST.get("action")
# 通过反射拿到该函数
func=getattr(self,func_name)
# 执行函数
func(queryset)
self.request = request
# 关于search的模糊查询
search_condition=self.get_search_condition() # 获取模糊查询的Q对象
# filter的多级过滤
get_filter_condition = self.get_filter_condition() # 获取多级过滤的Q对象
# 拿到过滤后的元素聚合
queryset=self.model.objects.filter(search_condition).filter(get_filter_condition)
# ShowList是用于渲染的数据类
sl=ShowList(self,request,queryset)
add_url = self.get_add_url() # 添加按钮的url
return render(request,"stark/show_list.html",locals())
# 模糊查询的Q对象
def get_search_condition(self):
search_condition = Q()
search_condition.connector = "or"
if self.search_fields: # 如果样式类中定义了模糊查询的字段
key_word = self.request.GET.get("q") # 从请求中获得模糊查询的值
if key_word:
for search_field in self.search_fields:
# 利用字符串添加条件
search_condition.children.append((search_field + "__contains", key_word))
# 返回Q对象
return search_condition
# 其他条件的查询
def get_filter_condition(self):
from django.db.models import Q
fiter_condition = Q()
for field ,val in self.request.GET.items():
if field != "page" and field != "q":
# 这里如果field是不能被模型类管理器过滤就会报错
fiter_condition.children.append((field,val))
return fiter_condition
#获取当前查看表的添加页面的url
def get_add_url(self):
add_url = reverse("%s_%s_add" % self.app_model_name)
return add_url
批量操作
class ModelSatrk(object):
# 批量操作的删除
def delete_action(self,queryset): # 接收一个queryset
queryset.delete()
delete_action.desc = "批量删除" # 提示信息--函数也是对象,可以为他设置属性
# 获取全部的批量操作选择框
def get_actions(self):
temp=[]
temp.extend(self.actions) # 接口
temp.append(ModelSatrk.delete_action)
return temp
获取所有展示的字段
class ModelSatrk(object):
# 获取真正展示的所有字段
def get_list_display(self):
new_list_dispaly=[] # [checkbox,"__str__",edit,delete]
new_list_dispaly.extend(self.list_display)
if not self.list_display_links and "edit" in self.request.permission_codes:
new_list_dispaly.append(ModelSatrk.edit)
# 根据权限判断,添加修改和删除
if "delete" in self.request.permission_codes:
new_list_dispaly.append(ModelSatrk.delete)
# 插入一个选择框
new_list_dispaly.insert(0,ModelSatrk.checkbox)
return new_list_dispaly # [checkbox,"__str__",edit,delete]
渲染的类
class ShowList(object):
def __init__(self,config,request,queryset):
self.request=request # 请求
self.config=config # 样式类
self.queryset=queryset # 满足条件的queryset
# 分页
current_page = self.request.GET.get("page", 1)
base_url = self.request.path_info
params = self.request.GET
from stark.utils.page import Pagination
all_count =queryset.count()
pagination = Pagination(current_page, all_count, base_url, params, per_page_num=10,
pager_count=11)
self.pagination=pagination
data_list = self.queryset[pagination.start:pagination.end]
self.data_list=data_list # 筛选后的数据
# 批量操作
self.actions=self.config.get_actions() # [patch_init,patch_delette]
# 可以过滤的条件
self.list_filter=self.config.list_filter
# 是否显示添加按钮
# self.show_add_btn=self.config.show_add_btn
def show_add_btn(self):
if not self.config.show_add_btn:
return False
# 用户权限相关
if "add" in self.request.permission_codes:
return True
# 返回右侧筛选的数据
def get_filter_link_tags(self):
for filter_field_name in self.list_filter: # ["state","publish","authors"]
filter_field_obj = self.config.model._meta.get_field(filter_field_name)
filter_field = FilterField(filter_field_name, filter_field_obj,self.config)
# print("filter_field",filter_field.get_data())
val=LinkTagsGen(filter_field.get_data(),filter_field,self.request)
yield val
def handle_actions(self):
temp=[]
for action_func in self.actions:
# 函数名和提示信息
temp.append({"name":action_func.__name__,"desc":action_func.desc})
return temp
# 表格标题
def get_header(self):
header_list = []
for field in self.config.get_list_display(): # [checkbox,"__str__",edit,delete]
if callable(field):
# header_list.append(field.__name__)
val = field(self, is_header=True) # 将函数的执行结果添加进header_list
header_list.append(val)
else:
if field == "__str__":
# 如果是__str__,就添加大写的类名
header_list.append(self.config.model._meta.model_name.upper())
else:
field_obj = self.config.model._meta.get_field(field)
header_list.append(field_obj.verbose_name.upper()) # # 添加字段的提示信息
return header_list
def get_body(self):
# 生成表单数据列表
# data_list=self.model.objects.all() # [obj1,obj2,....]
new_data_list = []
for obj in self.data_list: # 循环数据
temp = [] # [1,"python",111,"<a>编辑</a>"]
for field in self.config.get_list_display(): # 循环要展示的信息
if callable(field):
val = field(self.config, obj) # self.edit
else:
try:
# 字段信息
field_obj=self.config.model._meta.get_field(field)
if isinstance(field_obj,ManyToManyField): # 多对多取值
ret=getattr(obj,field).all()
t=[]
for i in ret:
t.append(str(i))
val="-".join(t)
else:
val = getattr(obj,field) # "__str__"
if field in self.config.list_display_links:
val = self.config.get_link_tag(obj, val)
except Exception as e:
val=str(obj)
temp.append(val)
new_data_list.append(temp)
#print("new_data_list", new_data_list)
return new_data_list
表格信息
class ModelSatrk(object):
# 编辑按钮
def edit(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href='%s'>编辑</a>" % self.get_edit_url(obj))
# 删除按钮
def delete(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href='%s'>删除</a>" % self.get_delete_url(obj))
# 复选框
def checkbox(self, obj=None, is_header=False):
if is_header:
return mark_safe("<input type='checkbox' id='action-toggle'>")
return mark_safe("<input type='checkbox' name='_selected_action' value='%s'>"
% obj.pk)
# 获取当前查看表的编辑url
def get_edit_url(self,obj):
edit_url = reverse("%s_%s_change" % self.app_model_name, args=(obj.pk,))
return edit_url
#获取当前表的删除url
def get_delete_url(self,obj):
del_url = reverse("%s_%s_delete" % self.app_model_name, args=(obj.pk,))
return del_url
#获取当前查看表的添加页面的url
def get_add_url(self):
add_url = reverse("%s_%s_add" % self.app_model_name)
return add_url
# 获取当前查看表的查看页面的url
def get_list_url(self):
list_url = reverse("%s_%s_showlist" % self.app_model_name)
return list_url
封装字段的类
class FilterField(object):
def __init__(self,filter_field_name,filter_field_obj,config):
self.filter_field_name=filter_field_name # 查询的名字
self.filter_field_obj=filter_field_obj # 关联的对象
self.config=config # 样式类
def get_data(self):
if isinstance(self.filter_field_obj,ForeignKey) or isinstance(self.filter_field_obj,ManyToManyField):
# 外键与他相对应的值
return self.filter_field_obj.rel.to.objects.all()
elif self.filter_field_obj.choices:
# 取choices
return self.filter_field_obj.choices
else:
# 取主键和查询的名字
return self.config.model.objects.values_list("pk",self.filter_field_name)
构造筛选信息的类
class LinkTagsGen(object):
def __init__(self,data,filter_field,request):
self.data=data # 一个字段值的信息
self.filter_field=filter_field # 封装字段的对象
self.request = request # 请求
def __iter__(self):
from django.db.models import ForeignKey,ManyToManyField
current_id = self.request.GET.get(self.filter_field.filter_field_name, 0)
import copy
params = copy.deepcopy(self.request.GET)
params._mutable = True # {"publish":1}
if params.get(self.filter_field.filter_field_name):
# 在路由中看有没有这个条件, 有就把他删除, 构造一个没被选中的全部选项
del params[self.filter_field.filter_field_name]
_url = "%s?%s" % (self.request.path_info, params.urlencode())
yield mark_safe("<a href='%s'>全部</a>" % _url)
else:
# 没有则构造一个被选中的全部选项
yield mark_safe("<a class='active' href='#'>全部</a>")
for item in self.data:
# print("item",item)
pk,text=None,None
# 针对三种情况, 设置pk和text
if self.filter_field.filter_field_obj.choices: # (2, '未出版')
pk,text=str(item[0]),item[1]
elif isinstance(self.filter_field.filter_field_obj,ForeignKey) or isinstance(self.filter_field.filter_field_obj,ManyToManyField):
pk, text = str(item.pk),item
else:
pk, text = item[1], item[1]
params[self.filter_field.filter_field_name]=pk
# 构造路由
_url="%s?%s"%(self.request.path_info,params.urlencode())
if current_id==pk:
# 如果与路由中取得的值相同, 设置被选中
link_tag="<a class='active' href='%s'>%s</a>"%(_url,text)
else:
link_tag="<a href='%s'>%s</a>"%(_url,text)
# yield 构造好的a标签
yield mark_safe(link_tag) # <a href='?state=1'>已出版</a>
展示页html
<!- 根据权限展示一个添加按钮 -->
{% if sl.show_add_btn %}
<a href="{{ add_url }}">
<button class="btn btn-primary">添加数据</button>
</a>
{% endif %}
<!- 模糊搜索的选择框 -->
{% if sl.config.search_fields %}
<div class="pull-right form-group">
<form action="" method="get" class="form-inline">
<input type="text" class="form-control" name="q" value="" >
<input type="submit" class="btn btn-info" value="search">
</form>
</div>
{% endif %}
<!- 内容主题 -->
<form action="" method="post">
{% csrf_token %}
<div>
<select class="form-control" name="action" id=""
style="width: 200px;margin: 8px 2px;display: inline-block;vertical-align: -1px">
<option value="">--------------</option>
<!- 批量操作 -->
{% for item in sl.handle_actions %}
<option value="{{ item.name }}">{{ item.desc }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-success">Go</button>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<!- 表格标题 -->
{% for foo in sl.get_header %}
<td>{{ foo }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
<!- 表格内容 -->
{% for data in sl.get_body %}
<tr>
{% for item in data %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</form>
<!- 右侧快速筛选 -->
{% for filter_link_tags in sl.get_filter_link_tags %}
<div class="well">
{% for link_tag in filter_link_tags %}
<p> {{ link_tag }}</p>
{% endfor %}
</div>
{% endfor %}
添加视图
class ModelSatrk(object):
def add_view(self,request):
if request.method =="GET":
objlist = self.get_modeform()() # self.get_modeform() 是获取modelform的函数
return render(request, "stark/addlist.html", locals())
else:
objlist = self.get_modeform()(request.POST)
if objlist.is_valid(): # 验证
obj = objlist.save() # 保存
if request.GET.get("pop_id"):
# 如果是从pop页面跳转过来则会有pop_id,pop_id是跳转过来的选择框的id值
obj.pop_id = request.GET.get("pop_id")
# 从请求中拿到跳转过来的那个名,和那个字段用于反向查询的名字,
model_name = request.GET.get("model_name")
related_name = request.GET.get("related_name")
# 循环所有关联了obj的反向字段对象
for r_field in obj._meta.related_objects:
# 获取关联obj的字段的related_name值和这个字段所在的表名
_model_name = r_field.field.model._meta.model_name
_related_name = str(r_field.related_name)
if model_name==_model_name and related_name==_related_name:
# 如果匹配成功,校验obj是否符合r_field的校验条件
filter = r_field.limit_choices_to
# 需要判断新添加的对象应不应该渲染到页面中,所以拿到那个字段的过滤条件来筛选
verify = self.model.objects.filter(pk=obj.pk,**filter)
if verify:
obj.verify = 1
else:
obj.verify = ""
try: # 这个obj是为了给添加的pop框传递一些信息
obj.val = getattr(obj,r_field.field_name)
except AttributeError: # 针对多对多
obj.val = obj.pk
return render(request,"stark/pop.html",locals())
return redirect(reverse("%s_%s_showlist"%self.app_model_name))
else:
return render(request, "stark/addlist.html", locals())
# 获取modelform类
def get_modeform(self):
if self.modeform: # 如果用户定义使用定义的
return self.modeform
else: # 没有返回默认的ModelForm类
class Modelform(ModelForm):
class Meta:
model = self.model #对应的Model中的类
fields = "__all__" #字段,如果是__all__,
# 就是表示列出所有的字段
return Modelform
前端pop页面并不需要写内容, 只需要调用父窗口的函数并且传递数据就可以了
<script>
opener.foo('{{ res|safe }}');
window.close();
</script>
添加页面, 这里用了一个inclusion_tag生成form
{% block content %}
<h3>添加数据</h3>
{% get_form form config%}
<div class="con"></div>
{% endblock %}
{% block js %}
<script>
function foo(res) {
console.log(res);
var res=JSON.parse(res);
if (res.state){
var ele_option=document.createElement("option");
ele_option.value=res.pk;
ele_option.innerHTML=res.text;
ele_option.selected="selected";
console.log(ele_option);
document.getElementById(res.pop_id).appendChild(ele_option)
}
}
</script>
{% endblock %}
自定义的inclusion_tag
from django import template
from django.urls import reverse
register=template.Library()
from django.forms.models import ModelChoiceField
@register.inclusion_tag("stark/form.html")
def get_form(form,config):
for bound_field in form: # form组件下的每一个字段信息对象:bound_field
# print(bound_field.name,bound_field.field)
# 普通多选框是TypedChoiceField
# 对多时ModelMultipleChoiceField
# 对一是ModelChoiceField, 不过ModelMultipleChoiceField继承了他
# 所以对于这种字段我们就给他加个属性
if isinstance(bound_field.field, ModelChoiceField):
bound_field.is_pop = True
app_label = bound_field.field.queryset.model._meta.app_label
model_name = bound_field.field.queryset.model._meta.model_name
_url = "%s_%s_add" % (app_label, model_name)
# print("model-name",config.model._meta.model_name)
current_model_name=config.model._meta.model_name
related_name=config.model._meta.get_field(bound_field.name).rel.related_name
bound_field.url = reverse(_url) + "? pop_id=id_%s
¤t_model_name=%s
&related_name=%s" %
(bound_field.name,
current_model_name,
related_name)
return {"form":form}
form页
{% for field in form %}
<div class="form-group" style="position: relative">
<label for="">{{ field.label }}</label>
<div class="input_style">
{{ field }}
<span class="error pull-right">{{ field.errors.0 }}</span>
</div>
{% if field.is_pop %}
<a onclick="pop('{{ field.url }}')"
style="position: absolute;top: 24px;right: -23px"
class="pop_btn">
<span class="pull-right" style="font-size: 22px;">+</span>
</a>
{% endif %}
</div>
{% endfor %}
修改删除
class ModelSatrk(object):
# 编辑数据视图
def change_view(self,request,id):
# 基于modelform
# print("self.model", self.model)
edit_obj=self.model.objects.filter(pk=id).first()
ModelFormClass = self.get_modelform_class()
if request.method=="GET":
form=ModelFormClass(instance=edit_obj)
return render(request, "stark/change_view.html", {"form":form, "config":self})
else:
form=ModelFormClass(data=request.POST,instance=edit_obj)
if form.is_valid():
form.save()
params=request.GET.get("list_filter")
if params:
# print("=====",params)
url="%s?%s"%(self.get_list_url(),params)
return redirect(url)
return redirect(self.get_list_url())
else:
return render(request, "stark/change_view.html", {"form":form, "config":self})
# 删除数据视图
def del_view(self,request,id):
del_obj = self.model.objects.filter(pk=id).first()
if request.method=="GET":
# print("self.model", self.model)
list_url=self.get_list_url()
return render(request, "stark/del_view.html", {"del_obj":del_obj, "list_url":list_url})
else:
del_obj.delete()
return redirect(self.get_list_url())

浙公网安备 33010602011771号