【问题标题】:django admin action without selecting objects没有选择对象的 django 管理操作
【发布时间】:2011-05-28 21:45:20
【问题描述】:

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

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

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

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

【问题讨论】:

  • 出于什么目的需要不与模型对象交互的操作?
  • 很多原因。例如。按需运行一些自动化处理。
  • 您可能想看看django-object-tools它会处理权限和管理模板集成等杂乱的细节,因此您可以专注于有趣的事情。

标签: django django-admin


【解决方案1】:

接受的答案在 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实例。

【讨论】:

  • 我不会全选,但这是一个细节。最干净的解决方案!
  • 最简单的解决方案。在其他人没有的地方工作。挽救生命。 ;)
  • 注意:django.contrib.admin 中 django.contrib.admin.helpers.ACTION_CHECKBOX_NAME 的兼容性导入在 3.1 中被移除。请改用此导入:from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
  • ty @bubbassauro,你拯救了我的一天!
【解决方案2】:

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)

【讨论】:

  • 对我来说,在 django 1.3 中这个函数只有在有一个奇怪的对象被选中时才会被击中......
  • 在 django 1.8 上,response_action 甚至在错误检查之前都没有被触发,所以它对我不起作用。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 我只在 Django 1.9 上测试过
      【解决方案6】:

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


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

      【讨论】:

      • 在 django 1.8 上最后一行报错:SQL 变量太多
      • 只有当你的数据库中的记录少于我认为的 ORM 和数据库在 IN 子句中接受的记录时,这才会起作用。
      【解决方案7】:

      好的,对于那些固执地想要这个工作的人来说,这是一个丑陋的 hack(对于 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)
      

      因此,在您的模型管理员中,您覆盖 changelist_view 添加到 request.POST django 用于存储所选对象的 id 的键。

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

      if queryset == None:
          do_your_stuff()
      

      不用说你不应该这样做。

      【讨论】:

      • 要完成这项工作,请将 helpers 替换为 admin.helpers 并将 ContributionAdmin 替换为您的类名。话虽如此,它只是抑制了选择检查;它实际上并没有全选。
      【解决方案8】:

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

      制作自己的管理视图非常简单:

      1. 编写视图函数
      2. 在上面放一个@staff_member_required装饰器
      3. 在您的 URLconf 中添加一个指向该视图的模式
      4. 通过overriding the relevant admin template(s)添加指向它的链接

      您也可以使用a new 1.1 feature related to this,但您可能会发现按照我刚刚描述的方式操作更简单。

      【讨论】:

        【解决方案9】:

        有没有办法覆盖这个 行为并让动作运行 还是?

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

        如果您对错误消息进行 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

        response_action 功能复制到覆盖的changelist_view 中,该功能仅适用于指定特定标志的操作,然后返回“真实”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 以删除该操作。

        其他方式涉及入侵太多东西。我认为至少。

        【讨论】:

          猜你喜欢
          • 2014-12-29
          • 2010-12-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-23
          • 2012-11-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多