web-dev-qa-db-ja.com

djangoでのみ外部キーの選択を関連オブジェクトに制限するにはどうすればよいですか?

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

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"})

しかし、これにより、管理インターフェースは子をリストしません。

48
Jeff Mc

私はたまたま遭遇しました ForeignKey.limit_choices_to Django docs。これはどのように機能するかはまだわかりませんが、それはここで正しいことかもしれません。

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

呼び出し可能オブジェクト(関数またはクラスメソッド、または呼び出し可能オブジェクト)を使用すると、より有望に思えます。ただし、HttpRequestオブジェクトから必要な情報にアクセスする方法の問題は残ります。 thread local storage を使用することは解決策かもしれません。

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,
    },
)

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

31
Ber

それを行う「正しい」方法は、カスタムフォームを使用することです。そこから、現在のオブジェクトである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
28
s29

少なくとも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)

編集中の現在のオブジェクトを取得する方法がわからないだけです。私はそれが実際にどこかにあることを期待していますが、私にはわかりません。

14
White Box Dev

これは、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()を実行します

docshttp://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships を読んでください。

12
Eric Holscher

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

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

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

3
Ber

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

これは私が望むとおりに動作しますが、この検証が選択後に検証するのではなく、管理インターフェイスのドロップダウンの選択肢を制限できると本当に素晴らしいでしょう。

3
Jeff Mc

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

あなたがやりたかった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})

しかし、悲しいことに、関数はself argを渡されないためエラーが発生します:(

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

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

2
Anentropic

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

代わりに、Childにis_favouriteブールフィールドを持つことができます。

これが役立つ場合があります: https://github.com/anentropic/Django-exclusivebooleanfield

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

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

管理者では、is_favouriteチェックボックスを公開した子モデルのインラインを作成することもできます(親ごとに数人の子しかいない場合)。

1
Anentropic