71

私は次のような双方向の外国関係を持っています

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

Parent.favoritechild の選択肢を、親が自分自身である子のみに制限するにはどうすればよいですか? 私は試した

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"})

しかし、これにより、管理インターフェイスに子が表示されなくなります。

4

11 に答える 11

45

Django ドキュメントでForeignKey.limit_choices_toに出会いました。これがどのように機能するかはまだわかりませんが、ここでは正しいかもしれません。

更新: ForeignKey.limit_choices_to を使用すると、定数、呼び出し可能オブジェクト、または Q オブジェクトのいずれかを指定して、キーの許容可能な選択肢を制限できます。関連するオブジェクトについて何も知らないので、定数はここでは明らかに役に立ちません。

呼び出し可能 (関数またはクラス メソッド、または任意の呼び出し可能オブジェクト) を使用すると、より有望に思えます。ただし、HttpRequest オブジェクトから必要な情報にどのようにアクセスするかという問題が残ります。スレッド ローカル ストレージを使用することが解決策になる場合があります。

2.更新:これが私のために働いたものです:

上記のリンクで説明されているように、ミドルウェアを作成しました。「product=1」など、リクエストの GET 部分から 1 つ以上の引数を抽出し、この情報をスレッド ローカルに格納します。

次に、スレッド ローカル変数を読み取り、外部キー フィールドの選択を制限するための ID のリストを返すモデル内のクラス メソッドがあります。

@classmethod
def _product_list(cls):
    """
    return a list containing the one product_id contained in the request URL,
    or a query containing all valid product_ids if not id present in URL

    used to limit the choice of foreign key object to those related to the current product
    """
    id = threadlocals.get_current_product()
    if id is not None:
        return [id]
    else:
        return Product.objects.all().values('pk').query

通常の管理ページが正常に機能するように、何も選択されていない場合は、可能なすべての ID を含むクエリを返すことが重要です。

次に、外部キー フィールドを次のように宣言します。

product = models.ForeignKey(
    Product,
    limit_choices_to={
        id__in=BaseModel._product_list,
    },
)

問題は、リクエストを介して選択肢を制限するための情報を提供する必要があることです。ここで「自己」にアクセスする方法がわかりません。

于 2008-10-30T23:07:01.880 に答える
39

それを行う「正しい」方法は、カスタムフォームを使用することです。そこから、現在のオブジェクトである self.instance にアクセスできます。例 -

from django import forms
from django.contrib import admin 
from models import *

class SupplierAdminForm(forms.ModelForm):
    class Meta:
        model = Supplier
        fields = "__all__" # for Django 1.8+


    def __init__(self, *args, **kwargs):
        super(SupplierAdminForm, self).__init__(*args, **kwargs)
        if self.instance:
            self.fields['cat'].queryset = Cat.objects.filter(supplier=self.instance)

class SupplierAdmin(admin.ModelAdmin):
    form = SupplierAdminForm
于 2013-10-24T03:25:58.113 に答える
18

これを行う新しい「正しい」方法は、少なくとも Django 1.1 以降では、AdminModel.formfield_for_foreignkey(self, db_field, request, **kwargs) をオーバーライドすることです。

http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkeyを参照してください。

以下のリンクをたどりたくない人のために、上記の質問モデルに近い関数の例があります。

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "favoritechild":
            kwargs["queryset"] = Child.objects.filter(myparent=request.object_id)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

編集中の現在のオブジェクトを取得する方法がわかりません。私はそれが実際にどこかにあると思っていますが、よくわかりません。

于 2011-01-11T01:44:54.443 に答える
13

これはdjangoの仕組みではありません。一方向にのみ関係を作成します。

class Parent(models.Model):
  name = models.CharField(max_length=255)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

そして、あなたが親から子供たちにアクセスしようとしていたなら、あなたはそうするでしょう parent_object.child_set.all()。myparentフィールドにrelated_nameを設定すると、それが参照されます。例:related_name='children'、それならあなたはparent_object.children.all()

詳細については、ドキュメント http://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationshipsをお読みください。

于 2008-10-24T06:28:47.740 に答える
12

Django 管理インターフェースの制限のみが必要な場合は、これでうまくいく可能性があります。別のフォーラムからのこの回答に基づいています-これはManyToManyの関係用ですが、機能するように置き換えることができるはずですformfield_for_foreignkey。でadmin.py

class ParentAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        self.instance = obj
        return super(ParentAdmin, self).get_form(request, obj=obj, **kwargs)

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        if db_field.name == 'favoritechild' and self.instance:       
            kwargs['queryset'] = Child.objects.filter(myparent=self.instance.pk)
        return super(ChildAdmin, self).formfield_for_foreignkey(db_field, request=request, **kwargs)
于 2015-04-05T08:24:42.950 に答える
4

@Ber:これに似たモデルに検証を追加しました

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)
  def save(self, force_insert=False, force_update=False):
    if self.favoritechild is not None and self.favoritechild.myparent.id != self.id:
      raise Exception("You must select one of your own children as your favorite")
    super(Parent, self).save(force_insert, force_update)

これはまさに私が望むように機能しますが、この検証により、選択後に検証するのではなく、管理インターフェイスのドロップダウンでの選択を制限できると本当に便利です.

于 2008-10-24T16:44:40.307 に答える
4

私は似たようなことをしようとしています。「外部キーは一方向にしか持てない」と言っている人は、あなたがやろうとしていることを誤解しているようです。

あなたがやりたかった limit_choices_to={"myparent": "self"} が機能しないのは残念です...それはきれいでシンプルだったでしょう. 残念ながら、「自己」は評価されず、プレーンな文字列として通過します。

私は多分私ができると思った:

class MyModel(models.Model):
    def _get_self_pk(self):
        return self.pk
    favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk})

しかし、悲しいことに、関数に自己引数が渡されないため、エラーが発生します:(

唯一の方法は、このモデルを使用するすべてのフォームにロジックを配置することです (つまり、フォームフィールドの選択肢にクエリセットを渡します)。これは簡単に実行できますが、モデル レベルでこれを行うと、より DRY になります。モデルの save メソッドをオーバーライドすることは、無効な選択が通過するのを防ぐ良い方法のようです。

更新
別の方法については、後の回答を参照してください https://stackoverflow.com/a/3753916/202168

于 2009-11-17T14:40:04.950 に答える
3

モデル インスタンスの作成/編集時に、管理インターフェイスで使用できる選択肢を制限しますか?

これを行う 1 つの方法は、モデルの検証です。これにより、外部フィールドが適切な選択でない場合に、管理インターフェイスでエラーを発生させることができます。

もちろん、Eric の答えは正しいです。子から親まで、実際に必要な外部キーは 1 つだけです。

于 2008-10-24T10:01:15.360 に答える
2

別のアプローチは、「favouritechild」fk を親モデルのフィールドとして持たないことです。

代わりに、子に is_favourite ブール値フィールドを設定できます。

これが役立つかもしれません: https://github.com/anentropic/django-exclusivebooleanfield

そうすれば、Children が所属する Parent のお気に入りにしかならないようにするという問題全体を回避できます。

ビューのコードは少し異なりますが、フィルタリング ロジックは簡単です。

管理者では、is_favourite チェックボックスを公開する子モデルのインラインを作成することもできます (親ごとに少数の子しかない場合)。それ以外の場合、管理者は子側から実行する必要があります。

于 2010-09-20T17:36:57.720 に答える
-1
from django.contrib import admin
from sopin.menus.models import Restaurant, DishType

class ObjInline(admin.TabularInline):
    def __init__(self, parent_model, admin_site, obj=None):
        self.obj = obj
        super(ObjInline, self).__init__(parent_model, admin_site)

class ObjAdmin(admin.ModelAdmin):

    def get_inline_instances(self, request, obj=None):
        inline_instances = []
        for inline_class in self.inlines:
            inline = inline_class(self.model, self.admin_site, obj)
            if request:
                if not (inline.has_add_permission(request) or
                        inline.has_change_permission(request, obj) or
                        inline.has_delete_permission(request, obj)):
                    continue
                if not inline.has_add_permission(request):
                    inline.max_num = 0
            inline_instances.append(inline)

        return inline_instances



class DishTypeInline(ObjInline):
    model = DishType

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        field = super(DishTypeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == 'dishtype':
            if self.obj is not None:
                field.queryset = field.queryset.filter(restaurant__exact = self.obj)  
            else:
                field.queryset = field.queryset.none()

        return field

class RestaurantAdmin(ObjAdmin):
    inlines = [
        DishTypeInline
    ]
于 2016-02-14T18:04:35.537 に答える