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. 实现的一个基本思路:

  1. 首先,在默认的基础配置类中一定要有一个可设置批量操作的属性,并规定一个实现的方式,自定义的一个格式。
  2. 在后端做一个基础的处理,如果用户为配置该属性,则默认实现批量删除的功能,如果用户自定义了,则优先显示用户自定义的属性,再在最后展示默认的批量删除操作
  3. 构建在前端的展示方式:使用select的下拉框展示option中value为批量操作的方法名(函数名)(备注:因为在前端中不能使用__name__这个属性,所以在后端构建),文本内容为对这个方法的描述内容
  4. 将所有的有效控件放置在一个form表单中,发送post请求,(有效控件包括:select标签中的option标签:所有的批量操作的方法action,表单中的每行的checkbox,代表该行数据的pk值)
  5. 取到对应的方法名(字符串形式的)和对应的数据,通过反射执行该方法,进行相应批量操作
  6. 返回处理结果,或者直接展示当前页面

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; 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.过滤器实现的基本思路:

  1. 基础配置类中配置过滤的相关属性,以及适用方式(输入要过滤的字段即可,一般输入FK字段,M2M字段,其他字段没有意义)
  2. 处理配置信息,构建展示方式(后端生成a标签,实现保存搜索条件和相应条件对应的对象显示突出,即用户点那个那个条件变色)
  3. 接收过滤对象,后端进行筛选数据,同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.实现的基本思路:

  1. 如何在一对多、多对多或者一对一的字段后显示添加按钮
  2. 如何弹出一个窗口
  3. 如何将新窗口中添加的数据绑定到老窗口

基本思路的相关实现:如何辨别这个字段是一个普通的字段还是一个特殊的字段?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; 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; 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/p/9147079.html