2008-10-24 20 views
40

Tôi có một mối quan hệ ngoại hai cách tương tự như sauLàm thế nào để hạn chế các phím nước ngoài lựa chọn các đối tượng có liên quan chỉ trong 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) 

Làm thế nào để hạn chế sự lựa chọn cho Parent.favoritechild cho trẻ em chỉ có mẹ là chinh no? Tôi đã thử

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

nhưng điều đó khiến giao diện quản trị không liệt kê bất kỳ trẻ em nào.

+0

bạn không nên sử dụng "null = True", tôi nghĩ. Tra cứu nó trong django doc – Ber

+0

null = True đề cập đến CharFields. Ở đây, nó là hoàn toàn ok để có null = True (nếu không cha mẹ không thể được lưu mà không có con) –

Trả lời

27

Tôi vừa xem qua số ForeignKey.limit_choices_to trong tài liệu Django. Không chắc chắn cách thức hoạt động của nó, nhưng nó có thể chỉ là suy nghĩ đúng ở đây.

Cập nhật: ForeignKey.limit_choices_to cho phép chỉ định đối tượng liên tục, gọi hoặc đối tượng Q để hạn chế các lựa chọn cho phép cho khóa. Một hằng số rõ ràng là không sử dụng ở đây, vì nó không biết gì về các đối tượng liên quan.

Sử dụng hàm có thể gọi (hàm hoặc phương thức lớp hoặc bất kỳ đối tượng có thể gọi) nào có vẻ hứa hẹn hơn. Vấn đề vẫn là cách truy cập thông tin cần thiết từ đối tượng HttpRequest. Sử dụng thread local storage có thể là một giải pháp.

2. Cập nhật: Đây là những gì Ngài đã làm việc cho tôi:

Tôi tạo ra một middleware như mô tả trong liên kết ở trên. Nó trích xuất một hoặc nhiều đối số từ phần GET của yêu cầu, chẳng hạn như "product = 1" và lưu trữ thông tin này trong các địa phương chủ đề.

Tiếp theo có một phương thức lớp trong mô hình đọc biến cục bộ luồng và trả về danh sách các id để giới hạn lựa chọn trường khóa ngoài.

@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 

Điều quan trọng là trả về truy vấn chứa tất cả các id có thể nếu không có id nào được chọn sao cho các trang quản trị bình thường hoạt động tốt.

Các lĩnh vực trọng điểm nước ngoài sau đó được khai báo là:

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

Việc nắm bắt được rằng bạn cần phải cung cấp thông tin để hạn chế sự lựa chọn thông qua yêu cầu. Tôi không thấy một cách để truy cập "tự" ở đây.

+6

Một giải pháp thú vị, nhưng sử dụng chủ đề cảm thấy như một hack ... – Cerin

+0

@Cerin: Chủ đề đang được sử dụng anyway trong Django. threadlocals chỉ là một cách để truyền thông tin dạng yêu cầu tới một luồng an toàn cho các trường hợp mà 'self' không có sẵn để tham chiếu đến dữ liệu yêu cầu. – Ber

+2

Không có chủ đề thực tế nào được sử dụng trong ví dụ trên, nó chỉ sử dụng chuỗi luồng để mô phỏng phạm vi rộng hơn. Bạn có thể tương đương làm điều đó với một giá trị được lưu trữ trên, ví dụ, lớp Model hoặc bất cứ nơi nào khác mà sẽ có phạm vi đủ rộng để được truy cập ở cả hai nơi. – jbg

12

Đây không phải là cách django hoạt động. Bạn sẽ chỉ tạo ra mối quan hệ theo một cách.

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

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

Và nếu bạn đang cố gắng truy cập vào các trẻ em từ cha mẹ bạn sẽ làm gì parent_object.child_set.all(). Nếu bạn đặt một related_name trong trường myparent, thì đó là những gì bạn sẽ gọi nó là. Ví dụ: related_name='children', thì bạn sẽ làm parent_object.children.all()

Đọc docshttp://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships để biết thêm.

3

Bạn có muốn giới hạn các lựa chọn có sẵn trong giao diện quản trị khi tạo/chỉnh sửa một cá thể mô hình không?

Một cách để làm điều này là xác thực mô hình. Điều này cho phép bạn tăng lỗi trong giao diện quản trị nếu trường nước ngoài không phải là lựa chọn đúng.

Tất nhiên, câu trả lời của Eric là chính xác: Bạn chỉ thực sự cần một khóa ngoại, từ con này sang phụ huynh khác tại đây.

3

@Ber: Tôi đã thêm xác nhận với mô hình tương tự như sau

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) 

mà hoạt động chính xác làm thế nào tôi muốn, nhưng nó sẽ là thật sự tốt đẹp nếu xác nhận này có thể hạn chế sự lựa chọn trong menu thả xuống trong giao diện quản trị thay vì xác thực sau khi lựa chọn.

2

Tôi đang cố gắng làm điều gì đó tương tự. Có vẻ như tất cả mọi người đều nói 'bạn chỉ nên có một chìa khóa nước ngoài một chiều' có lẽ đã hiểu lầm những gì bạn đang cố gắng làm.

Thật đáng tiếc khi limit_choices_to = {"myparent": "self"} bạn muốn thực hiện không hoạt động ... điều đó đã được làm sạch và đơn giản. Thật không may là 'tự' không được đánh giá và đi qua như một chuỗi đơn giản.

Tôi nghĩ có lẽ tôi có thể làm:

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

Nhưng than ôi cung cấp cho một lỗi bởi vì chức năng không có được thông qua một tự arg :(

Nó có vẻ như cách duy nhất là đặt logic vào tất cả các biểu mẫu sử dụng mô hình này (ví dụ: thông qua một queryset trong các lựa chọn cho formfield của bạn) .Điều này có thể dễ dàng thực hiện, nhưng nó sẽ là DRY hơn để có điều này ở mức mô hình. mô hình có vẻ là một cách hay để ngăn chặn các lựa chọn không hợp lệ được thông qua.

Cập nhật
Xem câu trả lời sau đó của tôi cho một cách khác https://stackoverflow.com/a/3753916/202168

1

Một phương pháp khác sẽ không có 'favouritechild' fk như một lĩnh vực trên mô hình phụ huynh.

Thay vào đó bạn có thể có trường boolean is_favourite trên Child.

này có thể giúp: https://github.com/anentropic/django-exclusivebooleanfield

Bằng cách đó bạn muốn để bên ngoài toàn bộ vấn đề của trẻ em đảm bảo chỉ có thể được thực hiện các yêu thích của Phụ Huynh họ thuộc về.

Mã xem sẽ hơi khác nhưng logic lọc sẽ đơn giản.

Trong quản trị, bạn thậm chí có thể có nội tuyến cho mô hình con tiếp xúc với hộp kiểm is_favourite (nếu bạn chỉ có một vài con cho mỗi phụ huynh), nếu không thì quản trị viên phải được thực hiện từ phía bên con.

+0

Nhưng nó hợp lý mà mỗi phụ huynh có/bộ trẻ em yêu thích của riêng mình. Có một Boolean sẽ có nghĩa là tất cả các bậc cha mẹ có cùng một bộ yêu thích - mà tôi không nghĩ rằng Jeff muốn. – wasabigeek

+0

không, bởi vì mỗi 'Child' chỉ thuộc về một' Parent'. Nếu nó là một mối quan hệ nhiều-nhiều, nó sẽ giống như bạn nói, trong trường hợp đó boolean sẽ đi trên mô hình 'thông qua' – Anentropic

+0

Đó là sự thật. Làm thế nào bạn có nó hiển thị trong admin bằng cách sử dụng phương pháp này? Dựa trên câu hỏi, nó sẽ được hiển thị khi chỉnh sửa một phụ huynh. Tôi không nghĩ rằng limit_choices_to làm việc với thông qua các mô hình hoặc các khóa nước ngoài theo mặc định. – wasabigeek

13

Cách "đúng" mới để thực hiện việc này, ít nhất kể từ khi Django 1.1 bằng cách ghi đè AdminModel.formfield_for_foreignkey (tự, db_field, yêu cầu, ** kwargs).

Xem http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

Đối với những người không muốn làm theo các liên kết dưới đây là một chức năng ví dụ mà gần cho các câu hỏi mô hình trên.

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) 

Tôi chỉ không chắc chắn về cách lấy đối tượng hiện tại đang được chỉnh sửa. Tôi hy vọng nó thực sự là trên một nơi nào đó nhưng tôi không chắc chắn.

+0

một cách hack có thể là phân tích cú pháp pk từ url như thế này:' desired_id = request.META ['PATH_INFO']. strip ('/'). split ('/') [- 1] 'Tôi muốn thấy cách nào tốt hơn để làm điều này một cách rõ ràng. – andi

+1

Có một lỗi đánh máy trong dòng cuối cùng: 'formfield_for_manytomany' ->' formfield_for_foreignkey' – sebastibe

22

Cách 'đúng' để thực hiện việc này là sử dụng biểu mẫu tùy chỉnh. Từ đó, bạn có thể truy cập self.instance, là đối tượng hiện tại. Ví dụ -

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 
+0

Hey @ s29 Tôi đang theo gợi ý của bạn, nhưng tôi nhận được một 'tên toàn cầu 'thể hiện' không được định nghĩa', bạn có thể giúp tôi, xin vui lòng ? – slackmart

+0

@sgmart, có lẽ bạn đã bỏ qua "tự" khỏi self.instance? – s29

+0

Giải pháp này hoạt động rất tốt cho những trường hợp bạn cần 'self.instance' để thực hiện lựa chọn. – Bartvds

3

Nếu bạn chỉ cần giới hạn trong giao diện quản trị Django, điều này có thể hoạt động. Tôi dựa trên this answer từ một diễn đàn khác - mặc dù nó dành cho các mối quan hệ ManyToMany, bạn sẽ có thể thay thế formfield_for_foreignkey để nó hoạt động. Trong 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) 
+0

vậy ... làm thế nào để bạn thay đổi 'favouritechild'? bạn đã giới hạn queryset thành một hàng duy nhất – Anentropic

+1

Điểm tốt. Tôi đã chỉnh sửa truy vấn, đoán điều này sẽ hoạt động. – wasabigeek

+0

yep trông đúng – Anentropic

-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 
    ] 
+1

Thêm một số giải thích cho câu trả lời của bạn. Chỉ viết mã - không giúp ích nhiều và cũng không dạy nhiều. – admix

Các vấn đề liên quan