Xadmin相关实现
一,保存搜索条件(Save search conditions)
kept conditions(保存条件)的应用场景比较常见,在我们查询时,可以实现多条件的筛选查询,比如:在淘宝上,选择了其中的一个条件后,我们可以继续选择其他的一些过滤条件,然后就可以实现多条件的查询。
那么代码时怎么实现的呢?
我们在用户提交查询条件请求时,一般都是GET请求,在相关路径后拼上?条件的形式,在Django中,我们在后端接收是通过request.GET这个属性得到所有的条件,在Django中,request.GET是一个QueryDict类型的数据。<QueryDict: {}>
#访问一个请求 print(request.GET,type(request.GET)) #<QueryDict: {}> <class 'django.http.request.QueryDict'>
我们点击它的源码,看看这个QueryDict的内部都做了些什么(from django.http.request import QueryDict)
class QueryDict(MultiValueDict): _mutable = True _encoding = None def __init__(self, query_string=None, mutable=False, encoding=None): super(QueryDict, self).__init__() if not encoding: encoding = settings.DEFAULT_CHARSET self.encoding = encoding query_string = query_string or '' self._mutable = mutable
这是截取的QueryDict中的一部分,在QueryDict中,有一个属性_mutable,这个值是True时,表示QueryDict可以被修改,否则表示无法被修改,通过源码可以看出,QueryDict在实例一个对象时,self._mutable是False,是不可修改的
而request.GET是一个QueryDict的实例,所以,request.GET是不可以被修改的
如果我们要修改或者添加request.GET中的值时,我们必须设置一个属性值request.GET._mutable=True,这样就可以修改了。
那我们如何保存搜索条件呢,要保存搜索条件,我们首先要得到用户访问时携带的条件,那这个条件怎么获取呢?
在Django中,提供了一个方法用于或者请求的字符串格式的搜索条件,这个方法就是:request.GET.urlencode()
比如:
#我们访问路径:http://127.0.0.1:8000/Xadmin/demo/order/?id=1&page=2 #得到的是这个 print(request.GET,request.GET.urlencode()) <QueryDict: {'id': ['1'], 'page': ['2']}> id=1&page=2
通过这个方法我们可以把获取的这个参数拼接到相应路径后面。
如果我们对request.GET这个参数修改时,我们最好是copy一个同样的数据,而不是在原数据上进行修改,这样可以保证数据的安全性,因为在其他地方,我们肯定还会使用这个request.GET这个参数。
注意:在copy时,我们不需要设置_mutable=True这个参数就可以修改,因为Django已经为我们想到了这个情况,在QueryDict的源码中,对这两个copy有重写,默认的参数就是mutable=True,所以我们使用copy后,可以直接对原数据修改。
def __copy__(self): result = self.__class__('', mutable=True, encoding=self.encoding) for key, value in six.iterlists(self): result.setlist(key, value) return result def __deepcopy__(self, memo): result = self.__class__('', mutable=True, encoding=self.encoding) memo[id(self)] = result for key, value in six.iterlists(self): result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo)) return result
所以,我们在取到这个值时,直接对这个值进行copy,然后在做修改
params = request.GET import copy params = copy.deepcopy(params) #可以直接对这个值做修改 params["要修改的值"] = value
二、搜索框功能的实现(search_fields)
相关实现的视图函数部分
#配置类的实现代码 from django.db.models import Q from django.shortcuts import HttpResponse, render # 配置类 class ModelXadmin(object): # 配置类中默认的搜索内容 search_fields = [] def __init__(self, model, site): self.model = model self.site = site self.model_name = "" self.app_name = "" # 将搜索的实现单独的封装到一个函数中 def get_search_condition(self, request): # 实例化一个Q对象 search_condition = Q() # 设置多条件搜索的关系为或 search_condition.connector = "or" # print("search_fields", self.search_fields) # ["title","price"] # 获取输入的搜索内容 key_word = request.GET.get('q') if key_word: # 对搜索内容判断,如果为空,models.XXX.objects.filter()时 会报错 for search_field in self.search_fields: search_condition.children.append((search_field + "__icontains", key_word)) return search_condition # 查看视图函数 def list_view(self, request): """ self.model: 用户访问哪张表,self.model就是谁 data_list: 查看表下的数据 :param request: :return: """ search_condition = self.get_search_condition(request) data_list = self.model.objects.filter(search_condition) model_name = self.model._meta.model_name return render(request, 'list_view.html', locals())
备注:示例化Q对象值为空时,filter(Q对象) 等同于all()
相关实现的模板部分
{% if search_fields %} <form action="" class="form-inline"> <div style="margin-bottom: 20px;" class="input-group"> <input type="text" class="form-control" placeholder="请输入内容" name="q"> <span class="input-group-btn"> <button class="btn btn-info" type="submit">搜索</button> </span> </div> </form> {% endif %}
三、批量操作的实现(actions)
批量操作中相关视图函数的实现
1. 实现的一个基本思路:
- 首先,在默认的基础配置类中一定要有一个可设置批量操作的属性,并规定一个实现的方式,自定义的一个格式。
- 在后端做一个基础的处理,如果用户为配置该属性,则默认实现批量删除的功能,如果用户自定义了,则优先显示用户自定义的属性,再在最后展示默认的批量删除操作
- 构建在前端的展示方式:使用select的下拉框展示option中value为批量操作的方法名(函数名)(备注:因为在前端中不能使用__name__这个属性,所以在后端构建),文本内容为对这个方法的描述内容
- 将所有的有效控件放置在一个form表单中,发送post请求,(有效控件包括:select标签中的option标签:所有的批量操作的方法action,表单中的每行的checkbox,代表该行数据的pk值)
- 取到对应的方法名(字符串形式的)和对应的数据,通过反射执行该方法,进行相应批量操作
- 返回处理结果,或者直接展示当前页面
2.具体实现代码:
class ModelXadmin(object): actions=[] # 默认的批量删除操作,默认出入两个参数:1.request 2.包含所有执行批量操作数据的queryset集 def patch_delete(self,request,queryset): queryset.delete() # 方法的描述内容 patch_delete.short_description="批量删除" # action的基础处理 优先展示用户的配置信息 def get_actions(self): temp=[] temp.extend(self.actions) temp.append(self.patch_delete) return temp def __init__(self,model,site): self.model=model self.site=site self.model_name="" self.app_name=""
构建展示方式:
class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # 封装的展示类中 self.actions=self.config.get_actions() # 处理前端的展示信息 构建一个包含每一个action字典的列表 def new_actions(self): temp=[] for action in self.actions: temp.append({ "name":action.__name__, "desc":action.short_description }) return temp
批量操作中相关模板部分的实现
<form action="" method="post"> {% csrf_token %} <div> <select name="action" id="" class="form-control" style="display: inline-block;width: 300px"> <option value="">---------------</option> {% for action_dict in show_list.new_actions %} <option value="{{ action_dict.name }}">{{ action_dict.desc }}</option> {% endfor %} </select> <input type="submit" value="Go" class="btn btn-warning"> </div> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in show_list.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for new_data in show_list.get_body %} <tr> {% for item in new_data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form>
发送post请求后,后端处理:
class ModelXadmin(object): def __init__(self, model, site): self.model = model self.site = site def list_view(self, request): """ self.model: 用户访问哪张表,self.model就是谁 data_list: 查看表下的数据 ShowList(self,data_list) # self: 当前查看表的配置类对象 :param request: :return: """ if request.method == "POST": # action action = request.POST.get("action") selected_pk_list = request.POST.getlist("selected") # 多选框时使用getlist方法接收所有的值 queryset = self.model.objects.filter(pk__in=selected_pk_list) # 过滤符合条件的所有的数据 action = getattr(self, action) # 通过反射获取结果 ret = action(request, queryset) return ret
四、过滤器的实现(list_filter)
1.过滤器实现的基本思路:
- 基础配置类中配置过滤的相关属性,以及适用方式(输入要过滤的字段即可,一般输入FK字段,M2M字段,其他字段没有意义)
- 处理配置信息,构建展示方式(后端生成a标签,实现保存搜索条件和相应条件对应的对象显示突出,即用户点那个那个条件变色)
- 接收过滤对象,后端进行筛选数据,同search_fields
2.具体的代码实现:
class ModelXadmin(object): list_filter=[]
构建展示内容:
from copy import deepcopy from django.utils.safestring import mark_safe class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # list_filter self.list_filter=self.config.list_filter # ["publish","auhtors] def get_filter_link_tags(self): # 构建的最终方式:link_tags={"publish":["a标签","a标签"],"author":["a标签","a标签"]} link_tags={} # 循环每一个过滤条件 for filter_field in self.list_filter: # ["publish","auhtors] # 保存过滤条件 不要对原数据修改 保证数据的安全性 params = deepcopy(self.request.GET) # {"authors":2} # 构建方式中a标签的href是:?过滤字段名 = 选中对象的pk值 # 获取当前选中的对象 注意:必须从原数据中取,原数据是不变的 deepcopy的数据是可变的 current_id=self.request.GET.get(filter_field) # print("current_id",current_id) # 获取当前字段对象 filter_field_obj=self.config.model._meta.get_field(filter_field) # 获取当前字段对象对应的所有的数据 related_data_list=filter_field_obj.rel.to.objects.all() temp=[] for obj in related_data_list: params[filter_field]=obj.pk # 保存搜索条件用 _url=params.urlencode() # 获取字符串形式的参数 # 如果当前对象 = 搜索条件中的对象 做特殊展示 if current_id==str(obj.pk): # 备注:obj.pk 是数字类型,一定要做类型转换 s="<a class='item' href='?%s'>%s</a>"%(_url,str(obj)) else: s = "<a href='?%s'>%s</a>" % (_url, str(obj)) temp.append(mark_safe(s)) link_tags[filter_field]=temp return link_tags
对用户选择的过滤条件做筛选处理
class ModelXadmin(object): def list_view(self, request): filter_condition = Q() for filter_field, val in request.GET.items(): # 报存搜索条件处理时,因为还会有其他的干扰条件,必须过滤掉,比如:分页的page参数 搜索框的q参数等 if filter_field not in ["page", "q"]: filter_condition.children.append((filter_field, val)) data_list = self.model.objects.filter(filter_condition)
五、其他功能实现(展示字段的实现、展示字段可点击跳转的实现、增删改查)
from django.shortcuts import HttpResponse,render,redirect from django.urls import reverse from django.utils.safestring import mark_safe class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # 分页器组件相关配置 current_page=request.GET.get("page") all_count=self.data_list.count() pagination=Pagination(current_page,all_count,request.GET) self.pagination=pagination self.page_data_list=self.data_list[pagination.start:pagination.end] def get_header(self): # 处理表头 # header_list=["ID","书籍名称","出版社"] header_list = [] for field in self.config.new_list_display(): # [check,"nid","title","publish",edit,delete] if isinstance(field, str): if field == "__str__": val = self.config.model._meta.model_name.upper() else: field_obj = self.config.model._meta.get_field(field) val = field_obj.verbose_name else: val = field(self.config, is_header=True) # 获取表头,传is_header=True header_list.append(val) return header_list def get_body(self): # 处理表单数据 new_data_list = [] for obj in self.page_data_list: # data_list [book_obj,book_obj2,...] temp = [] for field in self.config.new_list_display(): # ["nid","title","publish","authors"] if isinstance(field, str): try: from django.db.models.fields.related import ManyToManyField field_obj = self.config.model._meta.get_field(field) if isinstance(field_obj, ManyToManyField): t = [] for i in getattr(obj, field).all(): t.append(str(i)) val = ",".join(t) else: if field in self.config.list_display_link: edit_url = self.config.get_edit_url(obj) val = mark_safe("<a href='%s'>%s</a>" % (edit_url, getattr(obj, field))) else: val = getattr(obj, field) except Exception as e: val = getattr(obj, field) else: val = field(self.config, obj) temp.append(val) new_data_list.append(temp) ''' new_data_list=[ ["北京折叠",122,<a href=''>编辑</a>,<a href=''>删除</a>], ["三体", 222,<a href=''>编辑</a>,<a href=''>删除</a>], ] ''' return new_data_list class ModelXadmin(object): list_display=["__str__",] list_display_link=[] model_form_class=None def __init__(self,model,site): self.model=model self.site=site self.model_name="" self.app_name="" # 选择按钮 编辑 删除 def edit(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_edit_url(obj) return mark_safe("<a href='%s'>编辑</a>" % _url) def delete(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_delete_url(obj) return mark_safe("<a href='%s'>删除</a>"%_url) def checkbox(self, obj=None, is_header=False): if is_header: return "选择" return mark_safe("<input type='checkbox' name='selected' value='%s'>"%obj.pk) # 反向解析当前表的增删改查的url def get_edit_url(self,obj): # 反向解析:url url_name = "%s_%s_change" % (self.app_name, self.model_name) # http://127.0.0.1:8008/Xadmin/app01/book/(\d+)/change _url = reverse(url_name, args=(obj.pk,)) # return mark_safe("<a href='%s/change/'>编辑</a>"%obj.pk) return _url def get_list_url(self): # 反向解析:url url_name = "%s_%s_list" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_add_url(self): # 反向解析:url url_name = "%s_%s_add" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_delete_url(self,obj): # 反向解析:url url_name = "%s_%s_delete" % (self.app_name, self.model_name) _url = reverse(url_name, args=(obj.pk,)) return _url # 构建新的展示列表,默认加入选择按钮 编辑 删除 def new_list_display(self): temp=[] temp.append(ModelXadmin.checkbox) temp.extend(self.list_display) if not self.list_display_link: temp.append(ModelXadmin.edit) temp.append(ModelXadmin.delete) return temp # 查看视图函数 def list_view(self, request): """ self.model: 用户访问哪张表,self.model就是谁 data_list: 查看表下的数据 ShowList(self,data_list) # self: 当前查看表的配置类对象 :param request: :return: """ data_list = self.model.objects.all() show_list=ShowList(self,request,data_list) add_url = self.get_add_url() model_name = self.model._meta.model_name return render(request, 'list_view.html',locals()) # 获取modelForm类 def get_model_form_class(self): if self.model_form_class: return self.model_form_class else: # 使用Django中ModelForm实现展示所有字段 from django.forms import ModelForm class DemoModelForm(ModelForm): class Meta: model=self.model fields="__all__" return DemoModelForm # 添加视图函数 def add_view(self, request): DemoModelForm = self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'add_view.html', locals()) form=DemoModelForm return render(request, 'add_view.html',locals()) # 编辑视图函数 def change_view(self, request, id): edit_obj=self.model.objects.get(pk=id) DemoModelForm=self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST,instance=edit_obj) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'change_view.html', locals()) form=DemoModelForm(instance=edit_obj) return render(request, 'change_view.html',locals()) # 删除视图函数 def delete_view(self, request, id): list_url = self.get_list_url() if request.method=="POST": self.model.objects.filter(pk=id).delete() return redirect(list_url) return render(request, 'delete_view.html',{"list_url":list_url})
六、添加页面中特殊字段的小窗口的动态添加的实现
1.实现的基本思路:
- 如何在一对多、多对多或者一对一的字段后显示添加按钮
- 如何弹出一个窗口
- 如何将新窗口中添加的数据绑定到老窗口
基本思路的相关实现:如何辨别这个字段是一个普通的字段还是一个特殊的字段?Django中我们可以循环每一个字段对象,判断是不是这个类型的对象即可,以ModelForm为例,我们使用ModelForm示例一个对象,form_obj=ModelForm(),循环这个对象,一个元素就是一个字段对象,print(type(field)) 得到的是一个BoundField的对象:<class 'django.forms.boundfield.BoundField'>
from django.forms import ModelForm form_obj = ModelForm() for field in form_obj: print(type(field)) 结果为: <class 'django.forms.boundfield.BoundField'> <class 'django.forms.boundfield.BoundField'> <class 'django.forms.boundfield.BoundField'> <class 'django.forms.boundfield.BoundField'>
... # 每一个字段对象都是一个boundfield对象。
显而易见,boundfield对原本的字段对象做了封装 ,我们打开它的源码,看看都有哪些可用的东西:
class BoundField(object): "A Field plus data" def __init__(self, form, field, name): self.form = form self.field = field self.name = name self.html_name = form.add_prefix(name) self.html_initial_name = form.add_initial_prefix(name) self.html_initial_id = form.add_initial_prefix(self.auto_id) if self.field.label is None: self.label = pretty_name(name) else: self.label = self.field.label self.help_text = field.help_text or '' @property def auto_id(self): """ Calculates and returns the ID attribute for this BoundField, if the associated Form has specified auto_id. Returns an empty string otherwise. """ auto_id = self.form.auto_id if auto_id and '%s' in force_text(auto_id): return force_text(auto_id) % self.html_name elif auto_id: return self.html_name return '' @property def id_for_label(self): """ Wrapper around the field widget's `id_for_label` method. Useful, for example, for focusing on this field regardless of whether it has a single widget or a MultiWidget. """ widget = self.field.widget id_ = widget.attrs.get('id') or self.auto_id return widget.id_for_label(id_)
这是截取的一段源码:我们可以利用的有一个对象属性:self.field = field,还有一个静态方法:auto_id 它们有什么作用呢 ?print一下就知道了:
for boundfield in form_obj: print(boundfield.field) print(boundfield.auto_id) 结果为: <django.forms.fields.CharField object at 0x000002AED2E33BA8> id_title <django.forms.fields.CharField object at 0x000002AED2E33A58> id_desc <django.forms.models.ModelChoiceField object at 0x000002AED2E33B00> id_publish <django.forms.models.ModelMultipleChoiceField object at 0x000002AED2E33B38> id_authors
由此可见:boundfield.field 返回的是该字段的对象 boundfield.auto_id 返回一个 id_字段名 这种格式的字符串 也就是对应生成HTML标签时的id值
回到正题,我们的目的是通过代码过滤出这些特殊的一对多、多对多或者一对一字段,那怎么实现呢:直接判断是不是这个类型,额外的一个知识点就是,这几个特殊的字段在ModelForm中都是ModelChoiceField的对象,或者继承这个类的对象,我们判断是不是这个类的对象,就可以实现对这个的多虑,
代码实现:
from django.forms.models import ModelChoiceField
for boundfield in form_obj: if isinstance(boundfield.field,ModelChoiceField): # 如果是这个类的对象,就给这个对象设置一个区分其他对象的属性,比如: boundfield.is_field = True # obj.is_field=值
HTML的实现:
{% for field in form_obj %} <label for="">{{ field.label }}</label> {% if field.is_field %} <p>{{ field }} <span style="font-size:32px;font-weight: 500">+添加按钮在此</span></p> {% else %} <p>{{ field }}</p> {% endif %} <span>{{ field.errors.0 }}</span> {% endfor %}
这样我们就实现了第一步,那接下来第二步:打开一个窗口
基本思路:点击添加按钮,触发一个打开一个新窗口的事件:
那么这个新窗口的路径是什么?应该是我们点击那个字段,就打开那个字段相对应的添加页面,怎么实现呢?还是回到上面,我们循环每一个字段时,如果为特殊的字段,就获取这个字段所对应的模型表,和所在的app,然后通过反向解析url,来或者当前这个字段的添加url,然后添加到这个对象的属性中,直接在模板语言中获取。
备注:这里有个小问题,那就是,如果我们没有将当前字段所在的模型表注册时,就不应该显示可添加按钮,同时,在反向解析url时,就会报错,因为没有注册,就没有这个模型类的相关url,也就没有反向解析这个东西了。处理很简单,加一个判断,那怎么判断?一个思路是,判断当前字段所在的模型表,在不在注册的字典中 site._registry,首先,怎么获取当前字段所在模型表:一个知识点:取得字段对象,有一个queryset的属性,这个queryset会获取到当前字段在数据库中所有的数据,在.model 就可以或者当前表,这个方法只对这些特殊字段可用!!!
for boundfield in form_obj: if isinstance(boundfield.field,ModelChoiceField): print(boundfield.field.queryset.model) 输出结果为: <class 'blogs.models.Publisher'> <class 'blogs.models.Author'>
我们通过这个结果: ._meta.app_label 获得当前字段所在app的字符串名, ._meta.model_name 得到当前字段所在的模型表的字符串名。
通过这个模型类,判断在不在注册的模型字典中:
视图函数的相关实现:
for boundfield in form_obj: if isinstance(boundfield.field,ModelChoiceField):
# 如果在注册表中,就添加属性 if boundfield.field.queryset.model in site._registry: boundfield.is_field = True ret = boundfield.field.queryset.model._meta root = '%s_%s_add'%(ret.app_label,ret.model_name) root_url = reverse(root) boundfield.root_url = root_url+"?pop_back_id=%s"%boundfield.auto_id
# 拼接一个参数,用于将添加的数据添加到老窗口中,服务于第三步
HTML页面的相关实现:
<form action="" method="post" novalidate> {% csrf_token %} {% for filed in form %} <div class="form-group" style="position: relative"> <label for="">{{ filed.label }}</label> {{ filed }} <span>{{ filed.errors.0 }}</span> {% if filed.is_field %} <a onclick="pop('{{ filed.root_url }}')"><span style="font-size: 22px;color: #1b6d85;
position: absolute;top: 27px;right:-23px">+</span></a> {% endif %} </div> {% endfor %} <input type="submit" class="btn btn-default"> </form> <script> function pop(url) { window.open(url,"","wdith=600,height=300,top=200,left=200") } </script>
第三步的实现:提交添加数据后,数据创建成功后我们可以获取到一个新数据的obj,然后通过判断路径中有没有参数pop_back_id对应的值,上面设置路径时,添加的一个区分的一个标记。如果有值,表示是小窗口提交的值,否则表示正常的一个添加页面,如果是正常的添加页面,不做其他处理,如果是小窗口提交的数据,我们可以返回一个关闭窗口的HTML代码,同时可以向老窗口传参,将pk值,str(obj),以及是哪个字段发起的添加,就把数据添加到哪个数据下的标识:
视图函数部分:
def add_view(self, request): DemoModelForm = self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST) if form.is_valid(): obj=form.save() pop_back_id=request.GET.get("pop_back_id") if pop_back_id: # 小窗口 text=str(obj) return render(request,"pop.html",locals()) else: # 大窗口 return redirect(self.get_list_url())
HTML部分:
pop.html 页面:用于关闭这个小窗口,同时向老窗口传值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> window.opener.handle_pop("{{ obj.pk }}","{{ text }}","{{ pop_back_id }}"); window.close() </script> </body> </html>
原添加HTML页面执行对应的添加数据函数
<script> function handle_pop(pk,text,pop_id) { var $option=$("<option>"); // <option></option> 创建一个option标签 $option.html(text); // 添加文本值 $option.attr("value",pk); $option.attr("selected","selected"); // 添加默认选中的效果 $("#"+pop_id).append($option); // 将这个数据添加到对应的标签中 } </script>
总结:
from django.conf.urls import url from django.shortcuts import HttpResponse,render,redirect from django.urls import reverse from django.utils.safestring import mark_safe from Xadmin.utils.page import Pagination from django.db.models import Q class ShowList(object): def __init__(self,config,request,data_list): self.config=config self.data_list=data_list self.request=request # 分页器组件相关配置 current_page=request.GET.get("page") all_count=self.data_list.count() pagination=Pagination(current_page,all_count,request.GET) self.pagination=pagination self.page_data_list=self.data_list[pagination.start:pagination.end] self.actions=self.config.get_actions() # list_filter self.list_filter=self.config.list_filter # ["publish","auhtors] def get_filter_link_tags(self): #link_tags={"publish":["a","a"],"author":["a","a"]} link_tags={} from copy import deepcopy for filter_field in self.list_filter: # ["publish","auhtors] params = deepcopy(self.request.GET) # {"authors":2} current_id=self.request.GET.get(filter_field) print("current_id",current_id) filter_field_obj=self.config.model._meta.get_field(filter_field) related_data_list=filter_field_obj.rel.to.objects.all() temp=[] for obj in related_data_list: params[filter_field]=obj.pk _url=params.urlencode() if current_id==str(obj.pk): s="<a class='item' href='?%s'>%s</a>"%(_url,str(obj)) else: s = "<a href='?%s'>%s</a>" % (_url, str(obj)) temp.append(mark_safe(s)) link_tags[filter_field]=temp return link_tags def new_actions(self): temp=[] for action in self.actions: temp.append({ "name":action.__name__, "desc":action.short_description }) print("temp",temp) return temp def get_header(self): # 处理表头 # header_list=["ID","书籍名称","出版社"] header_list = [] for field in self.config.new_list_display(): # [check,"nid","title","publish",edit,delete] if isinstance(field, str): if field == "__str__": val = self.config.model._meta.model_name.upper() else: field_obj = self.config.model._meta.get_field(field) val = field_obj.verbose_name else: val = field(self.config, is_header=True) # 获取表头,传is_header=True header_list.append(val) return header_list def get_body(self): # 处理表单数据 new_data_list = [] for obj in self.page_data_list: # data_list [book_obj,book_obj2,...] temp = [] for field in self.config.new_list_display(): # ["nid","title","publish","authors"] if isinstance(field, str): try: from django.db.models.fields.related import ManyToManyField field_obj = self.config.model._meta.get_field(field) if isinstance(field_obj, ManyToManyField): t = [] for i in getattr(obj, field).all(): t.append(str(i)) val = ",".join(t) else: if field in self.config.list_display_link: edit_url = self.config.get_edit_url(obj) val = mark_safe("<a href='%s'>%s</a>" % (edit_url, getattr(obj, field))) else: val = getattr(obj, field) except Exception as e: val = getattr(obj, field) else: val = field(self.config, obj) temp.append(val) new_data_list.append(temp) ''' new_data_list=[ ["北京折叠",122,<a href=''>编辑</a>,<a href=''>删除</a>], ["三体", 222,<a href=''>编辑</a>,<a href=''>删除</a>], ] ''' return new_data_list class ModelXadmin(object): list_display=["__str__",] list_display_link=[] search_fields=[] actions=[] list_filter=[] def patch_delete(self,request,queryset): queryset.delete() patch_delete.short_description="批量删除" def get_actions(self): temp=[] temp.extend(self.actions) temp.append(self.patch_delete) return temp model_form_class=None def __init__(self,model,site): self.model=model self.site=site self.model_name="" self.app_name="" # 选择按钮 编辑 删除 def edit(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_edit_url(obj) return mark_safe("<a href='%s'>编辑</a>" % _url) def delete(self, obj=None, is_header=False): if is_header: return "操作" _url=self.get_delete_url(obj) return mark_safe("<a href='%s'>删除</a>"%_url) def checkbox(self, obj=None, is_header=False): if is_header: return "选择" return mark_safe("<input type='checkbox' name='selected' value='%s'>"%obj.pk) # 反向解析当前表的增删改查的url def get_edit_url(self,obj): # 反向解析:url url_name = "%s_%s_change" % (self.app_name, self.model_name) # http://127.0.0.1:8008/Xadmin/app01/book/(\d+)/change _url = reverse(url_name, args=(obj.pk,)) # return mark_safe("<a href='%s/change/'>编辑</a>"%obj.pk) return _url def get_list_url(self): # 反向解析:url url_name = "%s_%s_list" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_add_url(self): # 反向解析:url url_name = "%s_%s_add" % (self.app_name, self.model_name) _url = reverse(url_name) return _url def get_delete_url(self,obj): # 反向解析:url url_name = "%s_%s_delete" % (self.app_name, self.model_name) _url = reverse(url_name, args=(obj.pk,)) return _url # 构建新的展示列表,默认加入选择按钮 编辑 删除 def new_list_display(self): temp=[] temp.append(ModelXadmin.checkbox) temp.extend(self.list_display) if not self.list_display_link: temp.append(ModelXadmin.edit) temp.append(ModelXadmin.delete) return temp def get_search_condition(self,request): search_condition = Q() search_condition.connector = "or" print("search_fields", self.search_fields) # ["title","price"] key_word = request.GET.get('q') if key_word: for search_field in self.search_fields: search_condition.children.append((search_field + "__icontains", key_word)) return search_condition # 查看视图函数 def list_view(self, request): """ self.model: 用户访问哪张表,self.model就是谁 data_list: 查看表下的数据 ShowList(self,data_list) # self: 当前查看表的配置类对象 :param request: :return: """ if request.method=="POST":# action print(request.POST) action=request.POST.get("action") selected_pk_list=request.POST.getlist("selected") queryset=self.model.objects.filter(pk__in=selected_pk_list) action=getattr(self,action) ret=action(request,queryset) return ret search_condition=self.get_search_condition(request) filter_condition=Q() for filter_field,val in request.GET.items(): if filter_field not in ["page","q"]: filter_condition.children.append((filter_field,val)) data_list = self.model.objects.filter(filter_condition).filter(search_condition) show_list=ShowList(self,request,data_list) add_url = self.get_add_url() model_name = self.model._meta.model_name return render(request, 'list_view.html', {"model_name":model_name,"add_url":add_url,"show_list":show_list}) # 获取modelForm类 def get_model_form_class(self): if self.model_form_class: return self.model_form_class else: from django.forms import ModelForm class DemoModelForm(ModelForm): class Meta: model=self.model fields="__all__" return DemoModelForm # 添加视图函数 def add_view(self, request): DemoModelForm = self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'add_view.html', locals()) form=DemoModelForm return render(request, 'add_view.html',locals()) # 编辑视图函数 def change_view(self, request, id): edit_obj=self.model.objects.get(pk=id) DemoModelForm=self.get_model_form_class() if request.method=="POST": form=DemoModelForm(request.POST,instance=edit_obj) if form.is_valid(): form.save() return redirect(self.get_list_url()) else: return render(request, 'change_view.html', locals()) form=DemoModelForm(instance=edit_obj) return render(request, 'change_view.html',locals()) # 删除视图函数 def delete_view(self, request, id): list_url = self.get_list_url() if request.method=="POST": self.model.objects.filter(pk=id).delete() return redirect(list_url) class Per(): def __init__(self): pass return render(request, 'delete_view.html',{"list_url":list_url}) def get_urls2(self): temp = [] self.app_name = self.model._meta.app_label # "app01" self.model_name = self.model._meta.model_name # "book" temp.append(url(r"^$", self.list_view,name="%s_%s_list"%(self.app_name,self.model_name))) temp.append(url(r"^add/$", self.add_view,name="%s_%s_add"%(self.app_name,self.model_name))) temp.append(url(r"^(\d+)/change/$", self.change_view,name="%s_%s_change"%(self.app_name,self.model_name))) temp.append(url(r"^(\d+)/delete/$", self.delete_view,name="%s_%s_delete"%(self.app_name,self.model_name))) return temp # 路由二级分发 @property def urls2(self): return self.get_urls2(), None, None class XadminSite(object): def __init__(self, name='admin'): self._registry = {} def get_urls(self): print(self._registry) # {Book:modelAdmin(Book),.......} temp = [] for model, admin_class_obj in self._registry.items(): # 获取当前循环的model的字符串与所在app的字符串 app_name = model._meta.app_label # "app01" model_name = model._meta.model_name # "book" temp.append(url(r'^{0}/{1}/'.format(app_name, model_name),admin_class_obj.urls2), ) ''' url(r"app01/book",ModelXadmin(Book,site).urls2) url(r"app01/publish",ModelXadmin(Publish,site).urls2) url(r"app02/order",ModelXadmin(Order,site).urls2) ''' return temp @property def urls(self): return self.get_urls(),None,None def register(self, model, admin_class=None, **options): if not admin_class: admin_class = ModelXadmin self._registry[model] = admin_class(model, self) # {Book:ModelAdmin(Book),Publish:ModelAdmin(Publish)} site=XadminSite()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/list_view.css"> </head> <body> <h3>查看{{ model_name }}数据</h3> <div class="container"> <div class="row"> <div class="col-md-9"> <a href="{{ add_url }}" class="btn btn-info add_btn">添加</a> {% if show_list.config.search_fields %} <form action="" class="pull-right" method="get"> <input style="display: inline-block;width: 300px" type="text" name="q" class="form-control"><input type="submit" value="search" class="btn btn-success"> </form> {% endif %} <form action="" method="post"> {% csrf_token %} <div> <select name="action" id="" class="form-control" style="display: inline-block;width: 300px"> <option value="">---------------</option> {% for action_dict in show_list.new_actions %} <option value="{{ action_dict.name }}">{{ action_dict.desc }}</option> {% endfor %} </select> <input type="submit" value="Go" class="btn btn-warning"> </div> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in show_list.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for new_data in show_list.get_body %} <tr> {% for item in new_data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form> <div class="pull-right"> {{ show_list.pagination.page_html|safe }}</div> </div> <div class="col-md-3"> {% for filter_filed,link_tag_list in show_list.get_filter_link_tags.items %} <p>By {{ filter_filed.upper }}</p> {% for link_tag in link_tag_list %} <p>{{ link_tag }}</p> {% endfor %} {% endfor %} </div> </div> </div> </body> </html>
作者:赵盼盼
出处:https://www.cnblogs.com/zhaopanpan/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
⇩ 关注或点个喜欢就行 ^_^
关注我