web-dev-qa-db-ja.com

djangoオブジェクトを選択せず​​に管理アクション

Django adminに対して、実行するオブジェクトを選択する必要のないカスタム管理アクションを作成することは可能ですか?

オブジェクトを選択せず​​にアクションを実行しようとすると、次のメッセージが表示されます。

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

この動作をオーバーライドして、とにかくアクションを実行させる方法はありますか?

33
m000

ユウジは正しい方向に進んでいますが、私はあなたのために働くかもしれないより簡単な解決策を使用しました。以下のように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)
6
Tim Saylor

受け入れられた答えは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が呼び出され、何も選択されていない場合は、db内のすべてのMyModelインスタンスを選択します。

13
AndyTheEntity

私はこれが欲しかったのですが、最終的には使用しないことにしました。今後の参考のためにここに投稿してください。


アクションにプロパティ(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)
8
Anson

この動作をオーバーライドして、とにかくアクションを実行させる方法はありますか?

簡単な方法はありません。

エラーメッセージを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_actionchangelist_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を変更してアクションを削除できる可能性があります。

他の方法では、ハッキングが多すぎます。少なくとも私は思います。

さて、これを機能させたいと思うほど頑固な人にとっては、これは醜いハック(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に追加するchangelist_viewをオーバーライドします。このキーは、選択したオブジェクトのIDを保存するためにDjangoが使用します。

アクションでは、次の項目が選択されていないかどうかを確認できます。

if queryset == None:
    do_your_stuff()

言うまでもなく、これを行うことは想定されていません。

オブジェクトの選択は必要なものの一部ではないため、独自の管理ビューを作成することをお勧めします。

独自の管理ビューを作成するのは非常に簡単です。

  1. ビュー関数を書く
  2. @staff_member_requiredデコレータ
  3. そのビューを指すパターンをURLconfに追加します
  4. 関連する管理テンプレートをオーバーライドする によってリンクを追加します

これに関連する新しい1.1機能 を使用することもできますが、今説明したように実行する方が簡単な場合があります。

0
Paul Bissex

次のミックスインを使用して、ユーザーが少なくとも1つのオブジェクトを選択する必要のないアクションを作成します。また、ユーザーがフィルタリングしたクエリセットを取得することもできます: 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
0
Rafen

行ごとに1回アクションを呼び出さないように、@ 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)
0
maxicecilia

私が見つけた最も簡単な解決策は、Django admin関数をDjango docs に従って作成し、Webサイトの管理者が任意のオブジェクトをランダムに選択することです。関数を実行します。これにより、アイテムが関数に渡されますが、どこでも使用しないため、冗長になります。

0
Josh