django 管理操作而不选择对象

发布于 2024-10-08 19:09:57 字数 227 浏览 2 评论 0原文

是否可以为 django 管理员创建一个不需要选择某些对象来运行它的自定义管理操作?

如果您尝试在不选择对象的情况下运行操作,您会收到消息:

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

是否有办法覆盖此行为并让操作继续运行?

Is it possible to create a custom admin action for the django admin that doesn't require selecting some objects to run it on?

If you try to run an action without selecting objects, you get the message:

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

Is there a way to override this behaviour and let the action run anyway?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

苍风燃霜 2024-10-15 19:09:57

接受的答案在 django 1.6 中对我不起作用,所以我最终得到了这样的结果:

from django.contrib import admin
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME

class MyModelAdmin(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(ACTION_CHECKBOX_NAME):
                post = request.POST.copy()
                for u in MyModel.objects.all():
                    post.update({ACTION_CHECKBOX_NAME: str(u.id)})
                request._set_post(post)
        return super(MyModelAdmin, self).changelist_view(request, extra_context)

当调用 my_action 且未选择任何内容时,选择 db 中的所有 MyModel 实例。

The accepted answer didn't work for me in django 1.6, so I ended up with this:

from django.contrib import admin
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME

class MyModelAdmin(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(ACTION_CHECKBOX_NAME):
                post = request.POST.copy()
                for u in MyModel.objects.all():
                    post.update({ACTION_CHECKBOX_NAME: str(u.id)})
                request._set_post(post)
        return super(MyModelAdmin, self).changelist_view(request, extra_context)

When my_action is called and nothing is selected, select all MyModel instances in db.

标点 2024-10-15 19:09:57

我想要这个,但最终决定不使用它。在此发布以供将来参考。


向操作添加额外的属性(如 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)

I wanted this but ultimately decided against using it. Posting here for future reference.


Add an extra property (like acts_on_all) to the action:

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

 

In your ModelAdmin, override changelist_view to check for your property.

If the request method was POST, and there was an action specified, and the action callable has your property set to True, modify the list representing selected objects.

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)
长亭外,古道边 2024-10-15 19:09:57

Yuji 走在正确的轨道上,但我使用了一个可能适合您的更简单的解决方案。如果您按照下面的方式覆盖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)

Yuji is on the right track, but I've used a simpler solution that may work for you. If you override response_action as is done below you can replace the empty queryset with a queryset containing all objects before the check happens. This code also checks which action you're running to make sure it's approved to run on all objects before changing the queryset, so you can restrict it to only happen in some cases.

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)
画离情绘悲伤 2024-10-15 19:09:57

有没有办法覆盖这个
行为并让行动运行
无论如何?

我要说不,没有简单的方法。

如果您 grep 错误消息,您会看到代码位于 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

在覆盖的 changelist_viewresponse_action 功能复制到一定程度> 仅适用于指定了特定标志的操作,然后返回“真实”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's 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's 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, {})

当调用“真实”操作时,您仍然会收到错误。如果调用了覆盖的操作,您可能会修改 request.POST 以删除该操作。

其他方法涉及破解太多的东西。我认为至少。

Is there a way to override this
behaviour and let the action run
anyway?

I'm going to say no there is no easy way.

If you grep your error message, you see that the code is in django.contrib.admin.options.py and the problem code is deep inside the 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

It's also used in the response_action function as well, so you can't just override the changelist_template and use that either -- it's going to be easiest to define your own action-validity checker and runner.


If you really want to use that drop down list, here's an idea with no guarantees.

How about defining a new attribute for your selection-less admin actions: myaction.selectionless = True

Copy the response_action functionality to some extent in your overridden changelist_view that only works on actions with a specific flag specified, then returns the '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's 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's 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, {})

You'd still get errors when the 'real' action is called. You could potentially modify the request.POST to remove the action IF the overridden action is called.

Other ways involve hacking way too much stuff. I think at least.

孤者何惧 2024-10-15 19:09:57

我对 @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)

I made a change to @AndyTheEntity response, to avoid calling the action once per row.

        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)
屋顶上的小猫咪 2024-10-15 19:09:57

我使用以下 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

I use the following mixin to create actions that do not require the user to select at least one object. It also allow you to get the queryset that the user just filtered: https://gist.github.com/rafen/eff7adae38903eee76600cff40b8b659

here an example of how to use it (there's more info of how to use it on the link):

@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
冷默言语 2024-10-15 19:09:57

由于对象选择不是您所需要的一部分,因此听起来创建您自己的管理视图可能是最好的服务。

创建您自己的管理视图非常简单:

  1. 编写视图函数
  2. 在其上放置一个 @staff_member_required 装饰器
  3. 在指向该视图的 URLconf 中添加一个模式
  4. 通过 覆盖相关管理模板

您还可以使用 与此相关的新 1.1 功能,但您可能会发现按照我刚才描述的那样做更简单。

Since object selection isn't part of what you need, it sounds like you might be best served by creating your own admin view.

Making your own admin view is pretty simple:

  1. Write the view function
  2. Put a @staff_member_required decorator on it
  3. Add a pattern to your URLconf that points to that view
  4. Add a link to it by overriding the relevant admin template(s)

You can also use a new 1.1 feature related to this, but you may find it simpler to do as I just described.

原来分手还会想你 2024-10-15 19:09:57

好吧,对于那些顽固地想要这个工作的人来说,这是一个丑陋的黑客(对于 django 1.3),即使你没有选择任何东西,它也会允许任何操作运行。

你必须欺骗原来的changelist_view,让它认为你已经选择了一些东西。

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)

因此,在您的 modeladmin 中,您覆盖了添加到 request.POST django 用于存储所选对象 id 的密钥的changelist_view。

在您的操作中,您可以检查是否没有选定的项目:

if queryset == None:
    do_your_stuff()

不用说,您不应该这样做。

Ok, for those of you stubborn enough to want this working, this is an ugly hack(for django 1.3) that will allow ANY action to run even if you didn't select anything.

You have to fool the original changelist_view into thinking that you have something selected.

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)

So, in your modeladmin you override the changelist_view adding to the request.POST a key that django uses to store the ids of the selected objects.

In your actions you can check if there are no selected items with:

if queryset == None:
    do_your_stuff()

It goes without saying that you are not supposed to do this.

梦回旧景 2024-10-15 19:09:57

我发现的最简单的解决方案是根据 django 创建 django 管理功能docs 然后在您的网站管理员中随机选择任何对象并运行该函数。这会将项目传递给您的函数,但您根本不会在任何地方使用它,因此它是多余的。为我工作。

The simplest solution I found was to create your django admin function as per django docs then in your website admin select any object randomly and run the function. This will pass the item through to your function but you simply don't use it anywhere so it is redundant. Worked for me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文