1 后台管理修改--增加栏目

在左侧增加自定义的栏目是网站项目的常见需求。因为Django后台栏目是根据Model自动生成的,所以可以在model.py中定义一个model,然后在admin.py中定义对应的类,并注册上去,这样就会在左侧自动生成一个栏目。
model.py中添加如下类:

class CheckAll(models.Model):
    class Meta:
        app_label = 'case_check'
        verbose_name = '校对批量分配(按来源)'
        verbose_name_plural = '校对批量分配(按来源)'

在admin.py中添加如下的类(此类的功能还没有实现,后期再实现):

@admin.register(CheckAll)
class CheckAllAdmin(admin.ModelAdmin):
    def changelist_view(self, request, extra_content=None):
        return request

这样,左侧就有了“校对批量分配(按来源)”的栏目。
image.png

2 后台管理修改--change_list页面修改

需求:需要在admin后台的list页面上增加链接(或者是按钮)“显示已校对”、“显示未校对”、“显示所有”,点击之后list页面按照对应要求显示内容。

2.1 用action的方法实现--失败

由于对admin后台不熟悉,开始在action里面添加一个动作,想通过action完成此功能。结果发现,在点击go之后,虽然会去执行action对应的函数里面的代码,但是还是返回到当前的model list页面执行所有记录的查询。所以此路不通。
添加action方法,在admin.py文件对应的XXModelAdmin类中添加如下代码:

# 修改action内容
    actions = ['show_all']
    def show_all(self, request, queryset):
        pass
    show_all.short_description = "显示已校对"
    show_all.acts_on_all = True

image.png
action还有一个问题,就是在执行go操作之前必须选择至少一条记录进行操作,去掉此限制,可以在changelist_view()中加入如下代码实现:

    # 移除action必须选择一个记录的限制
    def changelist_view(self, request, extra_context=None):
        try:
            action = self.get_actions(request)[request.POST['action']][0]
            action_acts_on_all = action.acts_on_all
        except (KeyError, AttributeError):
            action_acts_on_all = False
        if action_acts_on_all:
            post = request.POST.copy()
            post.setlist(admin.helpers.ACTION_CHECKBOX_NAME,
                         self.model.objects.values_list('id', flat=True))
            request.POST = post
            

        return super(OriginCaseAdmin, self).changelist_view(request, extra_context)

2.2 在change_list页面上添加链接

为了能够在后台列表页面上进行修改,必须修改对应的模板文件,admin后台的模板文件没有放在项目里面,而是放在Django的源码中,其体路径是在你项目的虚拟路径下:venv4network\Lib\site-packages\django\contrib\admin\templates\admin
image.png
Django的admin后台列表页面对应的是change_list.html页面,需要将此文件复制到模板文件夹下。在项目的templates目录下,新建admin文件夹,在新建项目名称的文件夹(我这里是case_check),在新建你对应的Model类名称的文件夹(我这里是origincase),然后把change_list.html文件复制在此文件夹中。
image.png
打开change_list.html,在对应位置添加一个div,把我们对应的链接添加上去,下面截取部分代码:

        {% block result_list %}
          {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
          <div><a href="?mode=show_all"> 显示所有</a>&nbsp;&nbsp;<a href="?mode=show_check"> 仅显示已校对</a>&nbsp;&nbsp;<a href="?mode=show_nocheck"> 仅显示未校对</a></div>

          {% result_list cl %}
          {% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
        {% endblock %}

image.png
image.png

2.3 用GET传递参数,在get_queryset()方法中进行判断--失败

想到get_queryset()方法是返回数据集的最终方法,如果在这个函数里面做一个条件判断,那么应该可以实现。但是,如何触发条件呢?以action的方式已经不行,那么可以通过链接的方式,在GET上传递一个参数,根据这个参数触发条件。但还是失败了。

这个我的链接http://127.0.0.1:8000/admin/c...,通过request.GET获得mode对应的show_all这个值。但是,每次点击完这个链接之后,都会跳转到一个带错误的链接地址上:
http://127.0.0.1:8000/admin/c...
为了找到失败原因,查看了Django的changelist_view()方法的源码,终于发现了问题所在。下面是一段源码,里面对GET参数进行了检查,把不合法的参数进行了重定向。当然,这是Django对于防注入攻击的一个很好的功能,却是把我整晕了好一阵。

        try:
            cl = self.get_changelist_instance(request)
        except IncorrectLookupParameters:
            # Wacky lookup parameters were given, so redirect to the main
            # changelist page, without parameters, and pass an 'invalid=1'
            # parameter via the query string. If wacky parameters were given
            # and the 'invalid=1' parameter was already in the query string,
            # something is screwed up with the database, so display an error
            # page.
            if ERROR_FLAG in request.GET:
                return SimpleTemplateResponse('admin/invalid_setup.html', {
                    'title': _('Database error'),
                })
            return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')

2.3 传递GET参数,写入session,在在get_queryset()方法中进行判断--成功

2.2中,每一次传递的参数都被自动重定向了,我不可能去改Django的源码啊,也不推荐这么去做。怎么办呢?
第一,需要对GET传递的参数进行获取和判断,并对这个判断结果进行状态保持;
第二,避免触发重定向;
第三,在get_queryset()方法对状态进行判断,以返回对应的数据集。
所以第一步的状态必须在第三步中能够使用,而request已经不适合作为参数传递了,所以想到用session来保持这个参数。把GET中获取的参数保持在session中,最后利用session中的参数,在get_queryset()中进行判断。同时,为了避免触发重定向,我们需要修改request。
下面分别来实现:
第一、第二步:获取GET参数,这个需要在admin的业务逻辑中实现,这个对应在changelist_view()方法中实现。获取到show_all之后,写入到session,然后必须把GET里面内容删除,删除之后就不会触发重定向;否则super(OriginCaseAdmin, self).changelist_view(request, extra_context)之后,还是会重定向页面。

    def changelist_view(self, request, extra_context=None):
        if request.method == "GET" and (mode := request.GET.get('mode')) == "show_all":
            request.session["mode"] = "show_all"
            get = request.GET.copy()
            del get["mode"]
            request.GET = get
        return super(OriginCaseAdmin, self).changelist_view(request, extra_context)

第三步,在get_queryset()方法中对session值进行判断,过滤数据集对应的值。

    def get_queryset(self, request):
        qs = super(OriginCaseAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            if request.session.get("mode") == "show_all":
                request.session["mode"] = None
            if request.session.get("mode") == "show_check":
                request.session["mode"] = None
                return qs.filter(check_flag="是")
            if request.session.get("mode") == "show_nocheck":
                request.session["mode"] = None
                return qs.filter(check_flag="")
            return qs
        else:
            if request.session.get("mode") == "show_all":
                request.session["mode"] = None
            if request.session.get("mode") == "show_check":
                request.session["mode"] = None
                return qs.filter(check_flag="是").filter(user = request.user)
            if request.session.get("mode") == "show_nocheck":
                request.session["mode"] = None
                return qs.filter(check_flag="").filter(user = request.user)
            return qs.filter(user=request.user)

总觉得实现的怪怪的,代码也冗长,但好歹实现了功能。由于对Django不熟悉,目前想到的是这种方法,也许还有更好的、更方便的方法来实现。

参考文献

几个比较好的参考文献保存一下:
3.Python Django之GET请求和POST请求及响应处理
Django定制Admin页面(展示页面和编辑页面)
这篇告诉定义列表行中的自定义按钮或链接:关于在django框架中在admin页面下添加自定义按钮并实现功能
如何在Django admin中创建自定义按钮,它会创建许多对象?
Django Admin 管理后台添加自定义信息及定制化页面


1 声望0 粉丝