![](/img/trans.png)
[英]Django - How to restrict Foreign Key choices to a ManyToMany field in another model
[英]How do I restrict foreign keys choices to related objects only in 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"})
但這會導致管理界面沒有列出任何孩子。
我剛剛在 Django 文檔中遇到ForeignKey.limit_choices_to 。 尚不確定這是如何工作的,但這可能是正確的做法。
更新: ForeignKey.limit_choices_to 允許指定常量、可調用對象或 Q 對象來限制鍵的允許選擇。 一個常量在這里顯然沒有用,因為它對所涉及的對象一無所知。
使用可調用(函數或類方法或任何可調用對象)似乎更有希望。 但是,如何從 HttpRequest 對象訪問必要信息的問題仍然存在。 使用線程本地存儲可能是一種解決方案。
2.更新:這是對我有用的:
我創建了一個中間件,如上面鏈接中所述。 它從請求的 GET 部分中提取一個或多個參數,例如“product=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,
},
)
問題是您必須提供信息以通過請求限制選擇。 我在這里看不到訪問“自我”的方法。
“正確”的方法是使用自定義表單。 從那里,您可以訪問 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
這樣做的新“正確”方式,至少從 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/en/dev/topics/db/models/#many-to-one-relationships了解更多信息。
如果您只需要 Django 管理界面中的限制,這可能會奏效。 我基於另一個論壇的這個答案- 盡管它是針對多對多關系的,但您應該能夠替換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)
我正在嘗試做類似的事情。 似乎每個人都說“你應該只以一種方式使用外鍵”可能誤解了你正在嘗試做什么。
很遺憾,你想做的 limit_choices_to={"myparent": "self"} 不起作用......那本來是干凈和簡單的。 不幸的是,'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
@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)
這完全符合我的要求,但是如果此驗證可以限制管理界面下拉列表中的選擇而不是在選擇后進行驗證,那就太好了。
您想在創建/編輯模型實例時限制管理界面中可用的選項嗎?
一種方法是驗證模型。 如果外部字段不是正確的選擇,這可以讓您在管理界面中引發錯誤。
當然,Eric 的回答是正確的:你真的只需要一個外鍵,在這里從孩子到父母。
另一種方法是不將 'favouritechild' fk 作為 Parent 模型上的字段。
相反,您可以在 Child 上有一個 is_favourite 布爾字段。
這可能有幫助: https : //github.com/anentropic/django-exclusivebooleanfield
這樣,您就可以避免確保 Children 只能成為他們所屬的 Parent 的最愛的整個問題。
視圖代碼會略有不同,但過濾邏輯會很簡單。
在管理員中,您甚至可以對暴露 is_favourite 復選框的 Child 模型進行內聯(如果每個父級只有幾個孩子),否則必須從 Child 一側完成管理。
@s29 答案的一個更簡單的變體:
您可以簡單地從視圖中限制表單字段中可用的選項,而不是自定義表單:
對我有用的是:在 forms.py 中:
class AddIncomingPaymentForm(forms.ModelForm):
class Meta:
model = IncomingPayment
fields = ('description', 'amount', 'income_source', 'income_category', 'bank_account')
在views.py中:
def addIncomingPayment(request):
form = AddIncomingPaymentForm()
form.fields['bank_account'].queryset = BankAccount.objects.filter(profile=request.user.profile)
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
]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.