如何在Django Admin中向更改模型表单添加自定义操作?

17
我正在使用Django admin,希望能够利用现有的模型实例作为制作新对象的模板,这样使用Django admin的人在创建新对象时就不需要重新输入所有相同的属性。我想象一下,在更新单个对象的Django admin表单底部会是这样:

bottom bar of a django admin view

Django文档解释了如何添加批量操作,通过向模型上的操作添加内容,如下所示:
class ArticleAdmin(admin.ModelAdmin):

    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(status='p')

    make_published.short_description = "Mark selected stories as published"

然而,对于一个对象上的单个“更改模型”表单,我不太清楚如何进行操作,因为我只想对一个模型应用操作。
我该怎么做?
我猜我可能需要对change_model form进行一些修改,但除此之外,我不太确定。
有没有快速的方法可以在不覆盖大量模板的情况下完成这个操作?
2个回答

18
"Django Admin没有提供在更改表单中添加自定义操作的方法。但是,您可以通过一些修改来实现您想要的功能。首先,您需要覆盖提交行。"

your_app/templates/admin/submit_line.html

{% load i18n admin_urls %}
<div class="submit-row">
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_delete_link %}
    {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
    <p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
{% endif %}
{% if show_save_and_copy %}<input type="submit" value="{% trans 'Create a new item based on this one' %}" name="_save_and_copy" />{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
</div>

在上面的模板中,我只是添加了这一行:{% if show_save_and_copy %}<input type="submit" value="{% trans 'Create a new item based on this one' %}" name="_save_and_copy" />{% endif %}。其他所有行都是来自默认的Django实现
然后你需要处理你的按钮'_save_and_copy'。

your_app/admin.py

from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect

class ArticleAdmin(admin.ModelAdmin):

    def render_change_form(self, request, context, *args, **kwargs):
        """We need to update the context to show the button."""
        context.update({'show_save_and_copy': True})
        return super().render_change_form(request, context, *args, **kwargs)

    def response_post_save_change(self, request, obj):
        """This method is called by `self.changeform_view()` when the form
        was submitted successfully and should return an HttpResponse.
        """
        # Check that you clicked the button `_save_and_copy`
        if '_save_and_copy' in request.POST:
            # Create a copy of your object
            # Assuming you have a method `create_from_existing()` in your manager
            new_obj = self.model.objects.create_from_existing(obj)

            # Get its admin url
            opts = self.model._meta
            info = self.admin_site, opts.app_label, opts.model_name
            route = '{}:{}_{}_change'.format(*info)
            post_url = reverse(route, args=(new_obj.pk,))

            # And redirect
            return HttpResponseRedirect(post_url)
        else:
            # Otherwise, use default behavior
            return super().response_post_save_change(request, obj)

这个例子是针对您的特定情况的,如果需要,您可以将其更加通用。话虽如此,对于您的特定情况,您也可以直接点击“保存并继续”来保存您的工作,然后点击“另存为新文件”来复制它。您同意吗?

感谢你的指引,Antoine。 "另存为新" 的行为非常接近理想情况,通常也是最好的选择,但在这种情况下,我需要清除一些内联模型,否则我会使用 "另存为新" 选项。不过,这里已经有足够的信息让我继续工作了,所以我将接受你的答案。 - Chris Adams
我按照这个例子操作,但是无论我在update()中加入什么新内容,在submit_line.html模板中都没有任何效果。怎么回事?我正在使用Django 1.5.1。 - pinghsien422
1
@pinghsien422,这是因为submit_line.html模板标签没有传递上下文:https://github.com/django/django/blob/1.5.1/django/contrib/admin/templatetags/admin_modify.py#L22-L46。你应该考虑升级Django版本。Django 1.5.1已经过时且不再受支持。 - Antoine Pinsard
我之所以没有升级是因为新的事务机制需要我将旧代码迁移到使用transaction.commit_manually到新的transaction.atomic机制。 - pinghsien422
嗨,如果我只想在用户“更改”对象而不是“添加”对象时显示按钮怎么办? - Asym
render_change_form 中,您可以确定您是处于添加还是更改上下文中,并相应地设置 "show_save_and_copy"。您可以在 render_change_form 的完整签名中访问 addchange 布尔值:https://github.com/django/django/blob/2.1/django/contrib/admin/options.py#L1124 - Antoine Pinsard

10

正如所指出的,没有一种方法能够直接实现这个功能而需要进行黑客攻击。下面是我认为的一种优雅的黑客方法,可以在列表和更改表单视图中添加自定义操作。它并不实际保存表单,只是执行任何你想要对当前对象执行的自定义操作,并将你返回到相同的更改表单页面。

from django.db.models import Model

from django.contrib import admin, messages
from django.contrib.admin.options import (
    unquote,
    csrf_protect_m,
    HttpResponseRedirect,
)


class ArticleAdmin(admin.ModelAdmin):
    change_form_template = 'book/admin_change_form_book.html'

    actions = ['make_published']

    def make_published(self, request, queryset):
        if isinstance(queryset, Model):
            obj = queryset
            obj.status = 'p'
            obj.save()
            updated_count = 1
        else:
            updated_count = queryset.update(status='p')

        msg = "Marked {} new objects from existing".format(updated_count)
        self.message_user(request, msg, messages.SUCCESS)

    make_published.short_description = "Mark selected stories as published"

    @csrf_protect_m
    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
        if request.method == 'POST' and '_make_published' in request.POST:
            obj = self.get_object(request, unquote(object_id))
            self.make_published(request, obj)
            return HttpResponseRedirect(request.get_full_path())

        return admin.ModelAdmin.changeform_view(
            self, request,
            object_id=object_id,
            form_url=form_url,
            extra_context=extra_context,
        )

现在您可以为自定义模板视图添加一个 <input> 用于操作(此示例使用 book/admin_change_form_book.html 在 change_form_template 中)

{% extends 'admin/change_form.html' %}

{% block form_top %}
<input
    type="submit"
    name="_make_published"            
    value="Mark Published"
    class="grp-button grp-default"
>
{% endblock %}

如果你查看 admin/change_form.html(即 "django/contrib/admin/templates/admin/change_form.html"),你可以在页面的 <form...> </form> 标签之间的任何地方插入这个自定义的 <input> 动作。这些块包括:

  • {% block form_top %}
  • {% block after_related_objects %}
  • {% block submit_buttons_bottom %}

干净!非常好用。 - Asimov4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接