web-dev-qa-db-ja.com

Django:ModelAdmin内からモデルインスタンスにアクセスしますか?

注文は複数の注文に分割できますが、元の注文との関係を維持する必要があるため、自動インクリメントの主キーとそれ自体への外部キーを使用して、webshopアプリケーションに注文のモデルを用意しました。

class Order(models.Model):
    ordernumber = models.AutoField(primary_key=True)
    parent_order = models.ForeignKey('self', null=True, blank=True, related_name='child_orders')
    # .. other fields not relevant here

管理サイトのOrderAdminクラスを登録しました。詳細ビューでは、fieldsets属性にparent_orderを含めました。もちろん、デフォルトではすべての注文が選択ボックスにリストされますが、これは望ましい動作ではありません。代わりに、親注文がない注文(つまり、別の注文から分割されていない、parent_orderはNULL /なし)の場合、注文は表示されません。分割された注文の場合、これは単一の親注文のみを表示します。

クエリセットは内部でフィルター処理できるため、これに最適なように見えるかなり新しいModelAdminメソッドformfield_for_foreignkeyがあります。注文#11208から分割された注文#11234の詳細ビューを見ているところを想像してみてください。コードは以下です

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == 'parent_order':
        # kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=11234)
        return db_field.formfield(**kwargs)
    return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

コメント付きの行は、Pythonシェルで実行すると機能し、#11234の注文#11208およびそれから分割された可能性のある他のすべての注文を含む単一アイテムクエリセットを返します。

もちろん、注文番号をハードコードすることはできません。詳細ページを表示している注文インスタンスのordernumberフィールドへの参照が必要です。このような:

kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=?????)

?????を置き換えるための有効な方法が見つかりませんでした。 「現在の」Orderインスタンスへの参照を使用して、かなり深く掘り下げました。 self内部formfield_for_foreignkeyはModelAdminインスタンスを参照し、model属性はありますが、注文モデルインスタンスではありません(ModelBase参照です。self.model()が返します)インスタンスですが、その注文番号はNoneです)。

解決策の1つは、request.path(/ admin/orders/order/11234 /)から注文番号を取得することですが、これは実際には醜いです。もっと良い方法があればと思います。

45
JK Laiho

管理クラスではなく、ModelFormを変更することで、少し異なる方法でこれに取り組む必要があると思います。このようなもの:

class OrderForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(OrderForm, self).__init__(*args, **kwargs)
        self.fields['parent_order'].queryset = Order.objects.filter(
            child_orders__ordernumber__exact=self.instance.pk)

class OrderAdmin(admin.ModelAdmin):
    form = OrderForm
59
Daniel Roseman

この方法でインラインクラスをモデル化しました。インラインデータをフィルター処理するために親フォームIDを取得する方法は少し醜いですが、機能します。親フォームから会社別にユニットをフィルタリングします。

元のコンセプトはここで説明されています http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-Django-admin

class CompanyOccupationInline(admin.TabularInline):

    model = Occupation
    # max_num = 1
    extra = 0
    can_delete = False
    formset = RequiredInlineFormSet

    def formfield_for_dbfield(self, field, **kwargs):

        if field.name == 'unit':
            parent_company = self.get_object(kwargs['request'], Company)
            units = Unit.objects.filter(company=parent_company)
            return forms.ModelChoiceField(queryset=units)
        return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs)

    def get_object(self, request, model):
        object_id = resolve(request.path).args[0]
        try:
            object_id = int(object_id)
        except ValueError:
            return None
        return model.objects.get(pk=object_id)
8
Erwin Julius

上記のErwin Juliusからの回答はうまくいきましたが、「get_object」という名前はDjango関数と競合するため、「my_get_object」という名前を付けます。

class CompanyOccupationInline(admin.TabularInline):

    model = Occupation
    # max_num = 1
    extra = 0
    can_delete = False
    formset = RequiredInlineFormSet

    def formfield_for_dbfield(self, field, **kwargs):

        if field.name == 'unit':
            parent_company = self.my_get_object(kwargs['request'], Company)
            units = Unit.objects.filter(company=parent_company)
            return forms.ModelChoiceField(queryset=units)
        return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs)

    def my_get_object(self, request, model):
        object_id = request.META['PATH_INFO'].strip('/').split('/')[-1]
        try:
            object_id = int(object_id)
        except ValueError:
            return None
        return model.objects.get(pk=object_id)

他の人の答えに「返信」しないように言われましたが、まだ「返信」することはできません。しばらく探していたので、他の人の役に立つと思います。私もまだ賛成投票することはできません。

3
jenniwren