2008-10-24 30 views
40

限制外國鍵選擇應用到相關對象我有類似以下如何,我只在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"}) 

但這會導致管理界面不列出任何子項。

+0

你不應該使用 「空=真」,我想。在django doc中查找它 – Ber 2008-10-24 10:02:17

+0

null = True引用CharFields。在這裏,完全可以有null = True(否則父母不能在沒有孩子的情況下保存) – 2016-06-03 23:08:21

回答

27

我剛剛在Django文檔中遇到了ForeignKey.limit_choices_to。 不知道這是如何工作的,但它可能只是在這裏想的。

更新: ForeignKey.limit_choices_to允許指定一個常量,一個可調用對象或一個Q對象來限制該對象的允許選項。顯然這裏沒有什麼用處,因爲它對涉及的對象一無所知。

使用callable(函數或類方法或任何可調用對象)似乎更有希望。問題仍然是如何訪問HttpRequest對象的必要信息。使用thread local storage可能是一個解決方案。

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

美中不足的是,你必須提供的信息通過請求限制的選擇。我沒有看到在這裏訪問「自我」的方法。

+6

一個有趣的解決方案,但使用線程感覺就像這樣一個黑客... – Cerin 2012-12-04 18:36:37

+0

@Cerin:線程正在Django中使用。 threadlocals只是一種將請求中的信息以安全的方式傳遞給線程的方式,用於`self`不可用於引用請求數據的情況。 – Ber 2012-12-05 08:17:48

+2

在上面的例子中沒有使用實際的線程,它只是使用threadlocals來模擬更廣的範圍。您可以等效地使用存儲在例如Model類或任何其他地方的值,這些值將具有足夠寬的範圍以在兩個地方都可以訪問。 – jbg 2013-08-27 23:46:21

12

這不是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瞭解更多。

3

您是否想要在創建/編輯模型實例時限制管理界面中的可用選項?

一種方法是驗證模型。如果外部字段不是正確的選擇,這可以讓您在管理界面中引發錯誤。

當然,埃裏克的答案是正確的:你真的只需要一個外鍵,從孩子到家長在這裏。

3

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

模型我多麼希望這工作完全,但是這將是非常好的,如果這個驗證可以在管理界面限制在下拉菜單中選擇而不是在選擇之後進行驗證。

2

我正在嘗試做類似的事情。看起來每個人都說'你應該只有一個外鍵',可能誤解了你正在嘗試做的事情。

這是一個恥辱的limit_choices_to = {「myparent」:「自我」}你想要做的不工作......這將是乾淨和簡單。不幸的是,'自我'並沒有被評估,而是以一個普通的字符串進行。

我想也許我可以這樣做:

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

但可惜的是給出了一個錯誤,因爲該功能不獲得通過自ARG :(

這似乎是唯一的辦法就是把將邏輯轉換爲所有使用此模型的表單(即將查詢集傳遞給您的表單域的選項)。這很容易完成,但是在模型級別執行此操作會更加乾燥。該模型似乎是防止無效選擇通過的一種好方法。

更新
看到我後回答另一種方式https://stackoverflow.com/a/3753916/202168

1

另一種辦法是不要有「favouritechild」 FK作爲父模型的字段。

相反,您可以在Child上有一個is_favourite布爾型字段。

這可能會幫助: https://github.com/anentropic/django-exclusivebooleanfield

你會迴避確保兒童的整個問題的方式只能做他們屬於母公司的喜愛。

視圖代碼會略有不同,但過濾邏輯會很直接。

在管理員中,您甚至可以爲暴露is_favourite複選框的子模型內聯(如果每個父級只有少數子級),否則管理員必須從子級完成。

13

這樣做的新「正確」方式,至少從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) 

我只是不確定如何獲取正在編輯的當前對象。我希望它實際上在某處,但我不確定。

22

「正確」的方法是使用自定義表單。從那裏,你可以訪問self.instance,它是當前對象。示例 -

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

class SupplierAdminForm(forms.ModelForm): 
    class Meta: 
     model = Supplier 

    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 
3

如果您只需要Django管理界面中的限制,則可能會有效。我將其基於this answer從另一個論壇 - 雖然它是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
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 
    ]