Django: Как добавить пользовательскую кнопку на страницу изменения формы администратора, которая выполняет действие администратора?

Я уже определил пользовательское действие администратора для моей модели, которое работает отлично, как и ожидалось. Я также рассмотрел несколько способов добавления кнопки для изменения формы страницы администратора здесь, на SO. Единственный пропущенный мной шаг - как заставить кнопку на странице формы изменений выполнить моё пользовательское действие администратора с текущим объектом.

Цель состоит в том, чтобы позволить администратору осматривать каждый объект индивидуально и выполнять над ним действие, не возвращаясь к представлению списка, не выбирая проверенный объект и не выполняя действие из списка.

Мое пользовательское действие администратора выглядит так:

def admin_apply_change(modeladmin, request, queryset):
    # loop over objects in query set and perform action

Я предполагаю, что существует простой и понятный способ вызова этого действия в форме изменения администратора, где queryset будет содержать только открытый в данный момент объект, на который смотрит администратор.

ПРИМЕЧАНИЕ. Желательно, чтобы кнопка находилась внизу формы изменений, рядом с Save кнопка вместо того, чтобы быть на вершине с History что не очень видно.

Решение

Смотрите ответ Remi Smirra для решения. Для того, чтобы это работало, необходимы следующие исправления:

1: в переопределении response_change отсутствует инициализация некоторых переменных:

opts = self.model._meta
pk_value = obj._get_pk_val()
preserved_filters = self.get_preserved_filters(request)

2: Новый тег включения custom_submit_row должны быть помещены в теги-шаблоны, а не в администратор (см. документы для пользовательских тегов-шаблонов)

3: Это упущение, на которое вы можете потерять некоторое время. В change_form.html вам нужно не только изменить предложенную строку:

{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}

но и более важная линия внизу, где submit_row появляется:

{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}

(он расположен чуть выше блока javascript в change_form.html)

5 ответов

Решение

Вы можете взглянуть на change_form_template и установить его на свой собственный шаблон и переопределить response_change метод:

class MyModelAdmin(admin.ModelAdmin):

    # A template for a customized change view:
    change_form_template = 'path/to/your/custom_change_form.html'

    def response_change(self, request, obj):
        opts = self.model._meta
        pk_value = obj._get_pk_val()
        preserved_filters = self.get_preserved_filters(request)

        if "_customaction" in request.POST:
            # handle the action on your obj
            redirect_url = reverse('admin:%s_%s_change' %
                               (opts.app_label, opts.model_name),
                               args=(pk_value,),
                               current_app=self.admin_site.name)
             redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
             return HttpResponseRedirect(redirect_url)
        else:
             return super(MyModelAdmin, self).response_change(request, obj)

Скопируйте change_form.html от твоего site-packages/django/contrib/admin/templates/change_form.html и отредактируйте строку 44

 {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}

в

 {% if save_on_top %}{% block submit_buttons_top %}{% custom_submit_row %}{% endblock %}{% endif %}

Также проверьте строку:

 {% block submit_buttons_bottom %}{% submit_row %}{% endblock %}

чуть выше блока javascript.

Затем вы можете зарегистрировать новый тег включения где-нибудь в вашем admin.py или добавить его в templatetags:

@register.inclusion_tag('path/to/your/custom_submit_line.html', takes_context=True)
def custom_submit_row(context):
    """
    Displays the row of buttons for delete and save.
    """
    opts = context['opts']
    change = context['change']
    is_popup = context['is_popup']
    save_as = context['save_as']
    ctx = {
        'opts': opts,
        'show_delete_link': (
            not is_popup and context['has_delete_permission'] and
            change and context.get('show_delete', True)
        ),
        'show_save_as_new': not is_popup and change and save_as,
        'show_save_and_add_another': (
            context['has_add_permission'] and not is_popup and
            (not save_as or context['add'])
        ),
        'show_save_and_continue': not is_popup and context['has_change_permission'],
        'is_popup': is_popup,
        'show_save': True,
        'preserved_filters': context.get('preserved_filters'),
    }
    if context.get('original') is not None:
        ctx['original'] = context['original']
    return ctx

Содержание вашего custom_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_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 %}

<input type="submit" value="{% trans 'Custom Action' %}"  name="_customaction" />

</div>

Это много кода, но в основном копировать / вставить. Надеюсь, это поможет.

Большинство людей, вероятно, делают это, не задумываясь, хотя из ответа не было ясно, что форму смены администратора следует просто расширять, а не перезаписывать полностью.

custom_change_form.html

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

{% if save_on_top %}{% block submit_buttons_top %}{% custom_submit_row %}{% endblock %}{% endif %}

{% block submit_buttons_bottom %}{% custom_submit_row %}{% endblock %}

Кроме того, вы можете просто расширить файл submit_line.html, добавив свою пользовательскую кнопку (как вверху, так и внизу страницы изменений).

Ваши шаблоны файлов /adminyour_app_name/your_model_name.html будут начинаться с:

{% extends "admin/submit_line.html" %}
{% load i18n admin_urls %}
<div class="submit-row">
  {% block submit-row %}  
     ... YOUR BUTTONS HERE ...
  {% endblock %}
</div>

На страницу в администраторе Django вы можете добавить специальную кнопку, которая запускает действие администратора.

Например, сначала скопируйте изdjango/contrib/admin/templates/admin/submit_line.htmlв вашей виртуальной среде на или переопределить его, как показано ниже. * вtemplates/admin/,templates/admin/app1/илиtemplates/admin/app1/person/применяется ко всем администраторам во всех приложениях, ко всем администраторам только или толькоpersonтолько администраторapp1соответственно, и вы можете увидеть исходный submit_line.html:

      Django Project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py 
 |-app2
 └-templates
    └-admin
       |-app1
       |  |-person
       |  |  └-submit_line.html # Or
       |  |-model1
       |  |-model2
       |  └-submit_line.html # Or
       |-app2
       └-submit_line.html # Or

Затем добавьте{% if custom_button %}<input ...кsubmit_line.htmlкак показано ниже:

      # "templates/admin/submit_line.html" Or
# "templates/admin/app1/submit_line.html" Or
# "templates/admin/app1/person/submit_line.html"

# ...
{% if show_delete_link and original %}
    {% 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">{% translate "Delete" %}</a></p>
{% endif %} 
{# ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ #}
{% if custom_button %}<input type="submit" value="{% translate 'Custom button' %}" name="_custom_button">{% endif %}
{# ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ #}
{% if show_save_as_new %}<input type="submit" value="{% translate 'Save as new' %}" name="_saveasnew">{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% translate 'Save and add another' %}" name="_addanother">{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% translate 'Save and add another' %}" name="_addanother">{% endif %}
# ...

И, установитеBASE_DIR / 'templates'к'DIRS'вTEMPLATESвsettings.pyкак показано ниже:

      # "core/settings.py"

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates' # Here
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

И есть модель вmodels.pyкак показано ниже:

      # "app1/models.py"

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

И есть администратор с действием администратора вadmin.pyкак показано ниже:

      # "app1/admin.py"

from django.contrib import admin, messages
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    actions = ("uppercase", )

    # Here
    @admin.action(description='Make selected persons uppercase')
    def uppercase(modeladmin, request, queryset):
        for obj in queryset:
            obj.name = obj.name.upper()
            obj.save()
        messages.success(request, "Successfully made uppercase!")

Итак, если вы используетеuppercaseдействие администратора, как показано ниже:

Затем вы можете перевести выбранных людей в верхний регистр, как показано ниже:

Пользовательская кнопка еще не отображается на странице, как показано ниже:

Теперь переопределите Change_view() и response_change() в администраторе, как показано ниже. *Вы можете увидеть исходные функции Change_view() и response_change():

      # "app1/admin.py"

from django.contrib import admin, messages
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    actions = ("uppercase", )

    @admin.action(description='Make selected persons uppercase')
    def uppercase(modeladmin, request, queryset):
        for obj in queryset:
            obj.name = obj.name.upper()
            obj.save()
        messages.success(request, "Successfully made uppercase!")

    # Here
    def change_view(self, request, object_id, form_url="", extra_context=None):
        extra_context = extra_context or {}
        extra_context['custom_button'] = True
        return self.changeform_view(request, object_id, form_url, extra_context)

    # Here
    def response_change(self, request, obj):
        if "_custom_button" in request.POST:
            queryset = self.get_queryset(request).filter(id=obj.id)
            self.uppercase(request, queryset)
        return super().response_change(request, obj)

Затем на экране появится пользовательская кнопка.changeстранице, как показано ниже, затем, если вы нажмете на пользовательскую кнопку:

Затем вы можете перевести имя человека в верхний регистр, как показано ниже:

Кроме того, переопределите add_view() и response_add() вPersonадминистратор, как показано ниже. *Вы можете увидеть оригинальные add_view() и response_add():

      # "app1/admin.py"

from django.contrib import admin, messages
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    actions = ("uppercase", )

    @admin.action(description='Make selected persons uppercase')
    def uppercase(modeladmin, request, queryset):
        for obj in queryset:
            obj.name = obj.name.upper()
            obj.save()
        messages.success(request, "Successfully made uppercase!")

    # Here
    def add_view(self, request, form_url="", extra_context=None):
        extra_context = extra_context or {}
        extra_context['custom_button'] = True
        return self.changeform_view(request, None, form_url, extra_context)

    # Here
    def response_add(self, request, obj, post_url_continue=None):
        if "_custom_button" in request.POST:
            queryset = self.get_queryset(request).filter(id=obj.id)
            self.uppercase(request, queryset)
        return super().response_add(request, obj, post_url_continue)

Затем на экране появится пользовательская кнопка.addстранице, как показано ниже, затем, если вы нажмете на пользовательскую кнопку:

Затем вы можете перевести имя человека в верхний регистр, как показано ниже:

На основе ответа Реми более чистое решение для отмены шаблонов submit-row в шаблоне.

      {% extends "admin/submit_line.html" %}
{% load i18n admin_urls %}
<div class="submit-row">
{% block submit-row %}
{{ block.super }}

{% if custom_buttons_template %}{% include custom_buttons_template %}{% endif %}

{% endblock %}

В change_view вы можете добавить в контекст, чтобы настраивать пользовательские кнопки для каждой модели отдельно.

      def change_view(self, request, object_id, form_url='', extra_context=None):
    extra_context = extra_context or {}
    extra_context['custom_buttons_template'] = 'admin/test.html'
    return super(TransactionFileAdmin, self).change_view(
        request, object_id, form_url, extra_context=extra_context,
    )

После этого мой шаблон загрузится admin/test.html и вставьте контент прямо в строку отправки перед Saveкнопка. Если вы хотите разместить свои кнопки в другом месте, вы можете скопировать полностью submit_line.html и положи custom_buttons_template в любом месте.

Другие вопросы по тегам