Подтвердить что ты не робот

Действия администратора django без выбора объектов

Возможно ли создать пользовательское действие администратора для администратора django, который не требует выбора некоторых объектов для его запуска?

Если вы попытаетесь запустить действие без выбора объектов, вы получите сообщение:

Items must be selected in order to perform actions on them. No items have been changed.

Есть ли способ переопределить это поведение и позволить запустить действие в любом случае?

4b9b3361

Ответ 1

Юджи на правильном пути, но я использовал более простое решение, которое может работать для вас. Если вы переопределите response_action, как это сделано ниже, вы можете заменить пустой набор запросов набором запросов, содержащим все объекты, прежде чем произойдет проверка. Этот код также проверяет, какое действие вы выполняете, чтобы убедиться, что он одобрен для выполнения на всех объектах перед изменением набора запросов, поэтому вы можете ограничить его выполнение только в некоторых случаях.

def response_action(self, request, queryset):
    # override to allow for exporting of ALL records to CSV if no chkbox selected
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    if request.META['QUERY_STRING']:
        qd = dictify_querystring(request.META['QUERY_STRING'])
    else:
        qd = None
    data = request.POST.copy()
    if len(selected) == 0 and data['action'] in ('export_to_csv', 'extended_export_to_csv'):
        ct = ContentType.objects.get_for_model(queryset.model)
        klass = ct.model_class()
        if qd:
            queryset = klass.objects.filter(**qd)[:65535] # cap at classic Excel maximum minus 1 row for headers
        else:
            queryset = klass.objects.all()[:65535] # cap at classic Excel maximum minus 1 row for headers
        return getattr(self, data['action'])(request, queryset)
    else:
        return super(ModelAdminCSV, self).response_action(request, queryset)

Ответ 2

Принятый ответ не работал у меня в django 1.6, поэтому я закончил с этим:

from django.contrib import admin

class AdvWebUserAdmin(admin.ModelAdmin):

    ....

    def changelist_view(self, request, extra_context=None):
        if 'action' in request.POST and request.POST['action'] == 'your_action_here':
            if not request.POST.getlist(admin.ACTION_CHECKBOX_NAME):
                post = request.POST.copy()
                for u in MyModel.objects.all():
                    post.update({admin.ACTION_CHECKBOX_NAME: str(u.id)})
                request._set_post(post)
        return super(AdvWebUserAdmin, self).changelist_view(request, extra_context)

Когда вызывается my_action и ничего не выбрано, выберите все MyModel экземпляры в db.

Ответ 3

Я хотел этого, но в конечном итоге решил не использовать его. Публикация здесь для справок в будущем.


Добавьте в действие дополнительное свойство (например, acts_on_all):

def my_action(modeladmin, request, queryset):
    pass
my_action.short_description = "Act on all %(verbose_name_plural)s"
my_action.acts_on_all = True

 

В ModelAdmin, переопределить changelist_view, чтобы проверить ваше свойство.

Если метод запроса был POST, и было указано действие, а для вызываемого действия установлено значение True, измените список, представляющий выбранные объекты.

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 admin.ModelAdmin.changelist_view(self, request, extra_context)

Ответ 4

Есть ли способ переопределить это поведения и пусть действие выполняется в любом случае?

Я скажу, что нет простого способа.

Если вы выгрузили сообщение об ошибке, вы увидите, что код находится в django.contrib.admin.options.py, а код проблемы находится глубоко внутри changelist_view.

action_failed = False
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)

# Actions with no confirmation
if (actions and request.method == 'POST' and
        'index' in request.POST and '_save' not in request.POST):
    if selected:
        response = self.response_action(request, queryset=cl.get_query_set())
        if response:
            return response
        else:
            action_failed = True
    else:
        msg = _("Items must be selected in order to perform "
                "actions on them. No items have been changed.")
        self.message_user(request, msg)
        action_failed = True

Он также используется в функции response_action, поэтому вы не можете просто переопределить файл changelist_template и использовать его либо: это будет проще всего определить вашу собственную проверку действия и бегун.


Если вы действительно хотите использовать этот раскрывающийся список, вот идея без каких-либо гарантий.

Как насчет определения нового атрибута для ваших действий администратора без выбора: myaction.selectionless = True

Скопируйте функциональность response_action в некоторой степени в ваш переопределенный changelist_view, который работает только с действиями с определенным определенным флагом, а затем возвращает "real" changelist_view

    # There can be multiple action forms on the page (at the top
    # and bottom of the change list, for example). Get the action
    # whose button was pushed.
    try:
        action_index = int(request.POST.get('index', 0))
    except ValueError:
        action_index = 0

    # Construct the action form.
    data = request.POST.copy()
    data.pop(helpers.ACTION_CHECKBOX_NAME, None)
    data.pop("index", None)

    # Use the action whose button was pushed
    try:
        data.update({'action': data.getlist('action')[action_index]})
    except IndexError:
        # If we didn't get an action from the chosen form that invalid
        # POST data, so by deleting action it'll fail the validation check
        # below. So no need to do anything here
        pass

    action_form = self.action_form(data, auto_id=None)
    action_form.fields['action'].choices = self.get_action_choices(request)

    # If the form valid we can handle the action.
    if action_form.is_valid():
        action = action_form.cleaned_data['action']
        select_across = action_form.cleaned_data['select_across']
        func, name, description = self.get_actions(request)[action]

        if func.selectionless:
             func(self, request, {})

Вы все равно получите ошибки, когда вызывается "реальное" действие. Вы могли бы потенциально изменить запрос .POST, чтобы удалить действие, если вызывается переопределенное действие.

Другие способы включают взломать слишком много вещей. По крайней мере, я думаю.

Ответ 5

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

Создание собственного просмотра администратора довольно просто:

Вы также можете использовать новую функцию 1.1, связанную с этим, но вы можете найти ее проще, как я только что описал.

Ответ 6

Хорошо, для тех из вас, кто достаточно упрям, чтобы это работать, это уродливый взлом (для django 1.3), который позволит ЛЮБЫМ действиям работать, даже если вы ничего не выбрали.

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

class UsersAdmin(admin.ModelAdmin):

    def changelist_view(self, request, extra_context=None):
        post = request.POST.copy()
        if helpers.ACTION_CHECKBOX_NAME not in post:
            post.update({helpers.ACTION_CHECKBOX_NAME:None})
            request._set_post(post)
        return super(ContributionAdmin, self).changelist_view(request, extra_context)

Итак, в вашем modadmin вы переопределяете changelist_view, добавляя к запросу .POST ключ, который django использует для хранения идентификаторов выбранных объектов.

В ваших действиях вы можете проверить, нет ли выбранных элементов с помощью:

if queryset == None:
    do_your_stuff()

Само собой разумеется, что вы не должны это делать.

Ответ 7

Я использую следующий mixin для создания действий, которые не требуют от пользователя выбора хотя бы одного объекта. Он также позволяет вам получить запрос, который пользователь просто фильтрует: https://gist.github.com/rafen/eff7adae38903eee76600cff40b8b659

вот пример того, как его использовать (там больше информации о том, как его использовать по ссылке):

@admin.register(Contact)
class ContactAdmin(ExtendedActionsMixin, admin.ModelAdmin):
    list_display = ('name', 'country', 'state')
    actions = ('export',)
    extended_actions = ('export',)

    def export(self, request, queryset):
        if not queryset:
            # if not queryset use the queryset filtered by the URL parameters
            queryset = self.get_filtered_queryset(request)

        # As usual do something with the queryset

Ответ 8

Я внес изменения в ответ @AndyTheEntity, чтобы избежать вызова действия один раз в строке.

        def changelist_view(self, request, extra_context=None):
                actions = self.get_actions(request)
                if (actions and request.method == 'POST' and 'index' in request.POST and
                        request.POST['action'].startswith('generate_report')):
                    data = request.POST.copy()
                    data['select_across'] = '1'
                    request.POST = data
                    response = self.response_action(request, queryset=self.get_queryset(request))
                    if response:
                        return response
                return super(BaseReportAdmin, self).changelist_view(request, extra_context)

Ответ 9

Самым простым решением, которое я нашел, было создание функции администратора django в соответствии с django docs, а затем в администраторе вашего сайта случайным образом выбирал любой объект и запускал функцию. Это передаст элемент вашей функции, но вы просто нигде его не используете, поэтому он является избыточным. Работал на меня.