djangoでのみ外部キーの選択を関連オブジェクトに制限するにはどうすればよいですか
-
04-07-2019 - |
質問
次のような双方向の外交関係があります
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"})
ただし、管理インターフェースは子をリストしません。
解決
に出会いました。 DjangoドキュメントのForeignKey.limit_choices_to 。 これがどのように機能するかはまだわかりませんが、ここでは正しいかもしれません。
更新: ForeignKey.limit_choices_toでは、定数、呼び出し可能オブジェクト、またはQオブジェクトのいずれかを指定して、キーの選択を制限できます。定数は、関与するオブジェクトについて何も知らないため、ここでは明らかに役に立たない。
呼び出し可能オブジェクト(関数またはクラスメソッド、または呼び出し可能オブジェクト)を使用する方がより有望と思われます。ただし、HttpRequestオブジェクトから必要な情報にアクセスする方法の問題は残ります。 スレッドローカルストレージを使用することが解決策となる場合があります。
2。更新:これが私にとってうまくいったことです:
上記のリンクの説明に従ってミドルウェアを作成しました。 「product = 1」などの1つ以上の引数をリクエストのGET部分から抽出し、この情報をスレッドローカルに保存します。
次に、モデル内にスレッドローカル変数を読み取り、外部キーフィールドの選択を制限する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,
},
)
問題は、リクエストを介して選択肢を制限するための情報を提供する必要があることです。 「自己」にアクセスする方法がわかりません。こちら。
他のヒント
それを行う「正しい」方法は、カスタムフォームを使用することです。そこから、現在のオブジェクトである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
新しい" right"これを行う方法は、少なくともDjango 1.1以降はAdminModel.formfield_for_foreignkey(self、db_field、request、** kwargs)をオーバーライドすることです。
以下のリンクをたくない人には、上記の質問モデルに近い関数の例があります。
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)
編集中の現在のオブジェクトを取得する方法がわからないだけです。私はそれが実際にどこかにあることを期待していますが、よくわかりません。
これは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/ ja / dev / topics / db / models /#many-to-one-relationships を参照してください。
Django管理インターフェイスの制限のみが必要な場合、これは機能する可能性があります。 この回答に基づいています。 a>別のフォーラムから-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)
モデルインスタンスを作成/編集するときに、管理インターフェイスで使用可能な選択肢を制限しますか?
これを行う1つの方法は、モデルの検証です。これにより、外部フィールドが適切な選択ではない場合、管理インターフェイスでエラーを発生させることができます。
もちろん、エリックの答えは正解です。ここでは、子から親まで、実際に必要な外部キーは1つだけです。
@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)
これは私が望むとおりに動作しますが、この検証により、選択後に検証するのではなく、管理インターフェイスのドロップダウンの選択肢を制限できれば本当に素晴らしいでしょう。
同様のことをしようとしています。 「外部キーは一方向にしか持つべきではない」と言っている人はみな、あなたがやろうとしていることを誤解しているようです。
limit_choices_to = {" myparent&quot ;:" 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を渡されないためにエラーが発生します:(
唯一の方法は、このモデルを使用するすべてのフォームにロジックを配置することであるようです(つまり、フォームセットの選択肢にクエリセットを渡します)。これは簡単に実行できますが、モデルレベルでこれを行う方がよりドライになります。モデルのsaveメソッドをオーバーライドすることは、無効な選択が行われないようにする良い方法のようです。
更新
別の方法については私の後の回答をご覧ください https://stackoverflow.com/a/3753916/202168
別のアプローチは、「favouritechild」fkを親モデルのフィールドとして使用しないことです。
代わりに、Childにis_favouriteブールフィールドを持つことができます。
これは次の場合に役立ちます。 https://github.com/anentropic/django-exclusivebooleanfield
そうすることで、子が属する親のお気に入りにしかなれないようにするという問題全体を回避できます。
ビューのコードは少し異なりますが、フィルタリングロジックは簡単です。
管理者では、is_favouriteチェックボックスを公開する子モデルのインラインを作成することもできます(親ごとに数人の子しかいない場合)。
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
]