2008-11-15 25 views
188

Nói rằng tôi đã điều sau đây trong models.py tôi:Làm cách nào để lọc các lựa chọn ForeignKey trong một Django ModelForm?

class Company(models.Model): 
    name = ... 

class Rate(models.Model): 
    company = models.ForeignKey(Company) 
    name = ... 

class Client(models.Model): 
    name = ... 
    company = models.ForeignKey(Company) 
    base_rate = models.ForeignKey(Rate) 

Tức là có nhiều Companies, mỗi dải có một dải là RatesClients. Mỗi Client phải có một cơ sở Rate được chọn từ số gốc là Company's Rates, không phải là một số Company's Rates khác.

Khi tạo một form để thêm một Client, tôi muốn loại bỏ các Company lựa chọn (như đã được lựa chọn thông qua một nút "Add Client" trên trang Company) và hạn chế Rate lựa Company đó là tốt .

Tôi làm cách nào để thực hiện điều này trong Django 1.0?

hiện forms.py tập tin của tôi chỉ là soạn sẵn tại thời điểm này:

from models import * 
from django.forms import ModelForm 

class ClientForm(ModelForm): 
    class Meta: 
     model = Client 

views.py cũng là cơ bản:

from django.shortcuts import render_to_response, get_object_or_404 
from models import * 
from forms import * 

def addclient(request, company_id): 
    the_company = get_object_or_404(Company, id=company_id) 

    if request.POST: 
     form = ClientForm(request.POST) 
     if form.is_valid(): 
      form.save() 
      return HttpResponseRedirect(the_company.get_clients_url()) 
    else: 
     form = ClientForm() 

    return render_to_response('addclient.html', {'form': form, 'the_company':the_company}) 

Trong Django 0,96 tôi đã có thể hack này trong bằng cách làm một cái gì đó như các nội dung sau đây trước khi hiển thị mẫu:

manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)] 

ForeignKey.limit_choices_to có vẻ đầy hứa hẹn nhưng tôi không biết cách vượt qua trong the_company.id và tôi không rõ liệu điều đó có hoạt động bên ngoài giao diện Quản trị hay không.

Cảm ơn. (Điều này có vẻ như một yêu cầu khá cơ bản nhưng nếu tôi nên thiết kế lại một cái gì đó tôi đang mở để gợi ý.)

Trả lời

200

ForeignKey được đại diện bởi django.forms.ModelChoiceField, là một ChoiceField có sự lựa chọn là một mô hình QuerySet. Xem tài liệu tham khảo cho ModelChoiceField.

Vì vậy, hãy cung cấp QuerySet cho thuộc tính queryset của trường. Phụ thuộc vào cách thức xây dựng biểu mẫu của bạn. Nếu bạn xây dựng một biểu mẫu rõ ràng, bạn sẽ có các trường được đặt tên trực tiếp.

form.rate.queryset = Rate.objects.filter(company_id=the_company.id) 

Nếu bạn lấy đối tượng ModelForm mặc định, form.fields["rate"].queryset = ...

này được thực hiện một cách rõ ràng trong giao diện. Không có hack xung quanh.

+0

Ok, mà âm thanh đầy hứa hẹn. Làm cách nào để truy cập đối tượng Field có liên quan? form.company.QuerySet = Rate.objects.filter (company_id = the_company.id)? hoặc thông qua một từ điển? – Tom

+1

Ok, cảm ơn vì đã mở rộng ví dụ, nhưng tôi dường như phải sử dụng form.fields ["rate"]. Queryset để tránh đối tượng "ClientForm" không có thuộc tính 'rate' ", tôi có thiếu gì đó không? (và ví dụ của bạn nên được form.rate.queryset để được nhất quán quá.) – Tom

+0

Tuyệt vời, nhờ làm rõ. Để tham khảo trong tương lai, có thể đáng chú ý khi bạn chỉnh sửa câu trả lời của mình qua nhận xét vì các chỉnh sửa không hiển thị trong tab phản hồi của trang người dùng của tôi. – Tom

115

Ngoài câu trả lời của S.Lott và khi trở thànhGuru được đề cập trong nhận xét, có thể thêm bộ lọc truy vấn bằng cách ghi đè hàm ModelForm.__init__. (Điều này có thể dễ dàng áp dụng cho các biểu mẫu thông thường) nó có thể giúp tái sử dụng và giữ chức năng xem gọn gàng.

class ClientForm(forms.ModelForm): 
    def __init__(self,company,*args,**kwargs): 
     super (ClientForm,self).__init__(*args,**kwargs) # populates the post 
     self.fields['rate'].queryset = Rate.objects.filter(company=company) 
     self.fields['client'].queryset = Client.objects.filter(company=company) 

    class Meta: 
     model = Client 

def addclient(request, company_id): 
     the_company = get_object_or_404(Company, id=company_id) 

     if request.POST: 
      form = ClientForm(the_company,request.POST) #<-- Note the extra arg 
      if form.is_valid(): 
       form.save() 
       return HttpResponseRedirect(the_company.get_clients_url()) 
     else: 
      form = ClientForm(the_company) 

     return render_to_response('addclient.html', 
            {'form': form, 'the_company':the_company}) 

Điều này có thể hữu ích để sử dụng lại nếu bạn có bộ lọc thông thường cần thiết trên nhiều mô hình (thông thường tôi tuyên bố lớp Biểu mẫu trừu tượng). Ví dụ.

class UberClientForm(ClientForm): 
    class Meta: 
     model = UberClient 

def view(request): 
    ... 
    form = UberClientForm(company) 
    ... 

#or even extend the existing custom init 
class PITAClient(ClientForm): 
    def __init__(company, *args, **args): 
     super (PITAClient,self).__init__(company,*args,**kwargs) 
     self.fields['support_staff'].queryset = User.objects.exclude(user='michael') 

Khác hơn là tôi chỉ đang lưu giữ tài liệu blog Django trong đó có nhiều tài liệu hay ở ngoài đó.

+0

Có lỗi đánh máy trong đoạn mã đầu tiên của bạn, bạn định nghĩa args hai lần trong __init __() thay vì args và kwargs. – tpk

+0

chúc mừng, thats cập nhật – michael

+5

Tôi thích câu trả lời này tốt hơn, tôi nghĩ rằng nó sạch hơn để đóng gói logic khởi tạo biểu mẫu trong lớp biểu mẫu, thay vì trong phương thức xem. Chúc mừng! – Symmetric

2

Vì vậy, tôi đã thực sự cố gắng để hiểu điều này, nhưng có vẻ như Django vẫn không thực hiện điều này rất đơn giản. Tôi không phải là tất cả những người ngu ngốc, nhưng tôi chỉ không thể nhìn thấy bất kỳ (phần nào) giải pháp đơn giản.

Tôi thấy nó khá xấu xí khi phải ghi đè chế độ xem Quản trị viên cho loại điều này và mọi ví dụ tôi thấy không bao giờ áp dụng đầy đủ cho chế độ xem Quản trị viên.

Đây là ví dụ một tình huống thường gặp với các mô hình tôi làm mà tôi tìm thấy nó đáng sợ rằng không có giải pháp rõ ràng để này ...

Tôi đã có các lớp:

# models.py 
class Company(models.Model): 
    # ... 
class Contract(models.Model): 
    company = models.ForeignKey(Company) 
    locations = models.ManyToManyField('Location') 
class Location(models.Model): 
    company = models.ForeignKey(Company) 

Điều này tạo ra sự cố khi thiết lập Quản trị viên cho Công ty, bởi vì nó có nội tuyến cho cả Hợp đồng và Vị trí và tùy chọn m2m của Hợp đồng cho Vị trí không được lọc đúng theo Công ty mà bạn hiện đang chỉnh sửa.

Nói tóm lại, tôi sẽ cần một số tùy chọn admin để làm một cái gì đó như thế này:

# admin.py 
class LocationInline(admin.TabularInline): 
    model = Location 
class ContractInline(admin.TabularInline): 
    model = Contract 
class CompanyAdmin(admin.ModelAdmin): 
    inlines = (ContractInline, LocationInline) 
    inline_filter = dict(Location__company='self') 

Cuối cùng, tôi sẽ không quan tâm nếu quá trình lọc được đặt trên CompanyAdmin cơ sở, hoặc nếu nó được đặt trên ContractInline. (Đặt nó trên nội tuyến có ý nghĩa hơn, nhưng nó làm cho nó khó để tham khảo các hợp đồng cơ bản là 'tự'.)

Có ai biết điều gì đơn giản như lối tắt nặng cần thiết này không? Quay lại khi tôi thực hiện quản trị viên PHP cho loại điều này, điều này được coi là chức năng cơ bản! Trong thực tế, nó luôn luôn tự động, và đã bị vô hiệu hóa nếu bạn thực sự không muốn nó!

3

Nếu bạn chưa tạo hình thức và muốn thay đổi queryset bạn có thể làm:

formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...) 

này là khá hữu ích khi bạn đang sử dụng quan điểm chung!

15

Để làm điều này với một cái nhìn tổng quát, như createView ...

class AddPhotoToProject(CreateView): 
    """ 
    a view where a user can associate a photo with a project 
    """ 
    model = Connection 
    form_class = CreateConnectionForm 


    def get_context_data(self, **kwargs): 
     context = super(AddPhotoToProject, self).get_context_data(**kwargs) 
     context['photo'] = self.kwargs['pk'] 
     context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user) 
     return context 
    def form_valid(self, form): 
     pobj = Photo.objects.get(pk=self.kwargs['pk']) 
     obj = form.save(commit=False) 
     obj.photo = pobj 
     obj.save() 

     return_json = {'success': True} 

     if self.request.is_ajax(): 

      final_response = json.dumps(return_json) 
      return HttpResponse(final_response) 

     else: 

      messages.success(self.request, 'photo was added to project!') 
      return HttpResponseRedirect(reverse('MyPhotos')) 

phần quan trọng nhất của điều đó ...

context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user) 

, read my post here

37

Đây là đơn giản, và làm việc với Django 1.4:

class ClientAdminForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs): 
     super(ClientAdminForm, self).__init__(*args, **kwargs) 
     # access object through self.instance... 
     self.fields['base_rate'].queryset = Rate.objects.filter(company=self.instance.company) 

class ClientAdmin(admin.ModelAdmin): 
    form = ClientAdminForm 
    .... 

Bạn không cần phải xác định này trong một lớp học hình thức, nhưng có thể làm điều đó trực tiếp trong ModelAdmin, như Django đã bao gồm xây dựng trong phương pháp này trên ModelAdmin (từ các tài liệu):

ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)¶ 
'''The formfield_for_foreignkey method on a ModelAdmin allows you to 
    override the default formfield for a foreign keys field. For example, 
    to return a subset of objects for this foreign key field based on the 
    user:''' 

class MyModelAdmin(admin.ModelAdmin): 
    def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     if db_field.name == "car": 
      kwargs["queryset"] = Car.objects.filter(owner=request.user) 
     return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

Một thậm chí còn nhiều cách để thực hiện điều này (ví dụ trong việc tạo giao diện quản trị mặt trước mà người dùng có thể truy cập) là phân lớp ModelAdmin và sau đó thay đổi các phương thức bên dưới. Kết quả thực là giao diện người dùng CHỈ hiển thị cho họ nội dung liên quan đến họ, trong khi cho phép bạn (siêu người dùng) xem mọi thứ.

Tôi đã ghi đè bốn phương pháp, hai phương pháp đầu tiên khiến người dùng không thể xóa bất kỳ thứ gì và nó cũng xóa các nút xóa khỏi trang quản trị.

Các override thứ ba lọc bất kỳ truy vấn có chứa một tham chiếu đến (trong ví dụ 'người sử dụng' hoặc 'nhím' (cũng giống như một minh hoạ).

Các override ngoái lọc bất kỳ lĩnh vực ForeignKey trong mô hình để lọc Một cách dễ dàng để quản lý trang web quản trị mặt trước cho phép người dùng gây rối với các đối tượng của riêng họ và bạn không phải nhớ nhập vào. bộ lọc ModelAdmin cụ thể mà chúng tôi đã nói ở trên.

class FrontEndAdmin(models.ModelAdmin): 
    def __init__(self, model, admin_site): 
     self.model = model 
     self.opts = model._meta 
     self.admin_site = admin_site 
     super(FrontEndAdmin, self).__init__(model, admin_site) 

remove 'xóa' nút:

def get_actions(self, request): 
     actions = super(FrontEndAdmin, self).get_actions(request) 
     if 'delete_selected' in actions: 
      del actions['delete_selected'] 
     return actions 

ngăn chặn xóa phép

def has_delete_permission(self, request, obj=None): 
     return False 

bộ lọc đối tượng có thể được xem trên site admin:

def get_queryset(self, request): 
     if request.user.is_superuser: 
      try: 
       qs = self.model.objects.all() 
      except AttributeError: 
       qs = self.model._default_manager.get_queryset() 
      return qs 

     else: 
      try: 
       qs = self.model.objects.all() 
      except AttributeError: 
       qs = self.model._default_manager.get_queryset() 

      if hasattr(self.model, ‘user’): 
       return qs.filter(user=request.user) 
      if hasattr(self.model, ‘porcupine’): 
       return qs.filter(porcupine=request.user.porcupine) 
      else: 
       return qs 

bộ lọc lựa chọn cho tất cả các lĩnh vực ForeignKey trên trang quản trị:

def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     if request.employee.is_superuser: 
      return super(FrontEndAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

     else: 
      if hasattr(db_field.rel.to, 'user'): 
       kwargs["queryset"] = db_field.rel.to.objects.filter(user=request.user) 
      if hasattr(db_field.rel.to, 'porcupine'): 
       kwargs["queryset"] = db_field.rel.to.objects.filter(porcupine=request.user.porcupine) 
      return super(ModelAdminFront, self).formfield_for_foreignkey(db_field, request, **kwargs) 
+1

Và tôi nên thêm rằng điều này hoạt động tốt như một biểu mẫu tùy chỉnh chung cho nhiều modeladmins với các trường tham chiếu tương tự nhau. – nemesisfixx

+0

Đây là câu trả lời hay nhất nếu bạn đang sử dụng Django 1.4+ –

0

Cách công khai hơn là gọi get_form trong lớp Quản trị. Nó cũng hoạt động cho các trường không phải là cơ sở dữ liệu. Ví dụ ở đây tôi đã là một lĩnh vực được gọi là '_terminal_list' về hình thức mà có thể được sử dụng trong trường hợp đặc biệt cho việc lựa chọn một số mặt hàng thiết bị đầu cuối từ get_list (theo yêu cầu), sau đó lọc dựa trên request.user:

class ChangeKeyValueForm(forms.ModelForm): 
    _terminal_list = forms.ModelMultipleChoiceField( 
queryset=Terminal.objects.all()) 

    class Meta: 
     model = ChangeKeyValue 
     fields = ['_terminal_list', 'param_path', 'param_value', 'scheduled_time', ] 

class ChangeKeyValueAdmin(admin.ModelAdmin): 
    form = ChangeKeyValueForm 
    list_display = ('terminal','task_list', 'plugin','last_update_time') 
    list_per_page =16 

    def get_form(self, request, obj = None, **kwargs): 
     form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs) 
     qs, filterargs = Terminal.get_list(request) 
     form.base_fields['_terminal_list'].queryset = qs 
     return form 
Các vấn đề liên quan