Giới hạn các lựa chọn khóa ngoại trong lựa chọn trong biểu mẫu nội tuyến trong quản trị


75

Logic của mô hình là:

  • A Buildingcó nhiềuRooms
  • A Roomcó thể ở bên trong cái khác Room(ví dụ: một tủ quần áo - ForeignKey trên 'self')
  • A Roomchỉ có thể ở bên Roomtrong một tòa nhà khác trong cùng một tòa nhà (đây là phần khó khăn)

Đây là mã tôi có:

#spaces/models.py
from django.db import models    

class Building(models.Model):
    name=models.CharField(max_length=32)
    def __unicode__(self):
        return self.name

class Room(models.Model):
    number=models.CharField(max_length=8)
    building=models.ForeignKey(Building)
    inside_room=models.ForeignKey('self',blank=True,null=True)
    def __unicode__(self):
        return self.number

và:

#spaces/admin.py
from ex.spaces.models import Building, Room
from django.contrib import admin

class RoomAdmin(admin.ModelAdmin):
    pass

class RoomInline(admin.TabularInline):
    model = Room
    extra = 2

class BuildingAdmin(admin.ModelAdmin):
    inlines=[RoomInline]

admin.site.register(Building, BuildingAdmin)
admin.site.register(Room)

Nội tuyến sẽ chỉ hiển thị các phòng trong tòa nhà hiện tại (đó là những gì tôi muốn). Tuy nhiên, vấn đề là đối với trình đơn inside_roomthả xuống, nó hiển thị tất cả các phòng trong bảng Phòng (bao gồm cả những phòng trong các tòa nhà khác).

Trong dòng nội tuyến của rooms, tôi cần giới hạn các inside_roomlựa chọn chỉ roomstrong hiện tại building(hồ sơ tòa nhà hiện đang được thay đổi bởi BuildingAdminbiểu mẫu chính ).

Tôi không thể tìm ra cách để làm điều đó với một limit_choices_totrong mô hình, cũng như không thể tìm ra cách chính xác để ghi đè bộ định dạng nội tuyến của quản trị viên một cách chính xác (tôi cảm thấy như bằng cách nào đó tôi nên tạo một biểu mẫu nội tuyến tùy chỉnh, chuyển building_id của biểu mẫu chính cho dòng nội tuyến tùy chỉnh, sau đó giới hạn bộ truy vấn cho các lựa chọn của trường dựa trên đó - nhưng tôi không thể hiểu cách thực hiện).

Có thể điều này quá phức tạp đối với trang quản trị, nhưng có vẻ như điều gì đó nói chung sẽ hữu ích ...

Câu trả lời:


101

Đã sử dụng trường hợp yêu cầu làm vùng chứa tạm thời cho obj. Ghi đè phương thức Nội tuyến formfield_for_foreignkey để sửa đổi bộ truy vấn. Điều này hoạt động ít nhất trên django 1.2.3.

class RoomInline(admin.TabularInline):

    model = Room

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):

        field = super(RoomInline, self).formfield_for_foreignkey(db_field, request, **kwargs)

        if db_field.name == 'inside_room':
            if request._obj_ is not None:
                field.queryset = field.queryset.filter(building__exact = request._obj_)  
            else:
                field.queryset = field.queryset.none()

        return field



class BuildingAdmin(admin.ModelAdmin):

    inlines = (RoomInline,)

    def get_form(self, request, obj=None, **kwargs):
        # just save obj reference for future processing in Inline
        request._obj_ = obj
        return super(BuildingAdmin, self).get_form(request, obj, **kwargs)

1
Điều này ngay tại đây đã tiết kiệm cho tôi rất nhiều rắc rối. Tôi cần lọc các lựa chọn, nhưng bằng một biến phiên. Câu trả lời này hãy để tôi làm điều đó với 5 dòng mã. Cảm ơn bạn.
Peter G

4
Cảm ơn rất nhiều! Một cách khác là kwargs assign [ 'queryset'] trước khi gọi siêu theo tài liệu: docs.djangoproject.com/en/dev/ref/contrib/admin/...
powlo

Mã này cũng đã tiết kiệm cho tôi HÀNG TẤN thời gian. Cảm ơn rất nhiều đã gửi bài này
fangsterr

ĐIỀU NÀY! Tôi đang tìm kiếm một cái gì đó như thế này cho vấn đề của tôi. Tôi đã mất nhiều ngày để tìm thấy điều này.
Miguel Ike

1
Nhưng người dùng vẫn có thể chọn sai Roomtrong cửa sổ bật lên. Xem stackoverflow.com/a/50298577/2207154 để biết giải pháp
Daniil Mashkin

17

Sau khi đọc qua bài đăng này và thử nghiệm rất nhiều, tôi nghĩ rằng tôi đã tìm ra câu trả lời khá chắc chắn cho câu hỏi này. Vì đây là một mẫu thiết kế đã được sử dụng nên tôi đã viết một Mixin để quản trị viên Django sử dụng nó.

(Về mặt động lực) việc giới hạn bộ truy vấn cho các trường ForeignKey giờ đây đơn giản như phân lớp con LimitedAdminMixinvà xác định một get_filters(obj)phương thức để trả về các bộ lọc liên quan. Ngoài ra, một thuộc filterstính có thể được đặt trên quản trị viên nếu không yêu cầu lọc động.

Ví dụ sử dụng:

class MyInline(LimitedAdminInlineMixin, admin.TabularInline):
    def get_filters(self, obj):
        return (('<field_name>', dict(<filters>)),)

Đây, <field_name>là tên của trường FK sẽ được lọc và <filters>là danh sách các tham số như bạn thường chỉ định chúng trong filter()phương thức tập truy vấn.


1
Cảm ơn, hoạt động tuyệt vời! Sạch hơn nhiều. (Và btw, bạn để lại một số báo cáo khai thác gỗ trong mã của bạn rằng đừng đi đâu cả)
Dave

17

Có tùy chọn limit_choices_to ForeignKey cho phép giới hạn các lựa chọn quản trị có sẵn cho đối tượng


2
Điều này không hữu ích vì truy vấn chạy trong limit_choices_to không có tham chiếu đến "lớp cha". Tức là, nếu một mô hình A có khóa ngoại đối với B, cũng như C, và C có khóa ngoại đối với B, và chúng tôi muốn đảm bảo A chỉ tham chiếu đến C tham chiếu đến B giống như A , truy vấn cần biết về A-> B, điều này thì không.
Chris Cogdon

1
Nó có thể hữu ích với kết hợp câu trả lời hàng đầu, xem stackoverflow.com/a/50298577/2207154
Daniil Mashkin

8

Bạn có thể tạo một vài lớp tùy chỉnh sau đó sẽ chuyển cùng một tham chiếu đến cá thể mẹ vào biểu mẫu.

from django.forms.models import BaseInlineFormSet
from django.forms import ModelForm

class ParentInstInlineFormSet(BaseInlineFormSet):
    def _construct_forms(self):
        # instantiate all the forms and put them in self.forms
        self.forms = []
        for i in xrange(self.total_form_count()):
            self.forms.append(self._construct_form(i, parent_instance=self.instance))

    def _get_empty_form(self, **kwargs):
        return super(ParentInstInlineFormSet, self)._get_empty_form(parent_instance=self.instance)
    empty_form = property(_get_empty_form)


class ParentInlineModelForm(ModelForm):
    def __init__(self, *args, **kwargs):
        self.parent_instance = kwargs.pop('parent_instance', None)
        super(ParentInlineModelForm, self).__init__(*args, **kwargs)

trong lớp RoomInline chỉ cần thêm:

class RoomInline(admin.TabularInline):
      formset = ParentInstInlineFormset
      form = RoomInlineForm #(or something)

Trong biểu mẫu của bạn, bây giờ bạn có quyền truy cập trong phương thức init vào self.parent_instance! parent_instance hiện có thể được sử dụng để lọc các lựa chọn và không

cái gì đó như:

class RoomInlineForm(ParentInlineModelForm):
    def __init__(self, *args, **kwargs):
        super(RoomInlineForm, self).__init__(*args, **kwargs)
        building = self.parent_instance
        #Filtering and stuff

Cảm ơn vì điều này! Đây là phiên bản đầu tiên hoạt động cho ứng dụng của tôi và nó cũng đẹp và rõ ràng.
Justin

8

Sự cố trong câu trả lời @nogus vẫn có url sai trong cửa sổ bật lên /?_to_field=id&_popup=1

cho phép người dùng chọn sai mục trong cửa sổ bật lên

Để cuối cùng nó hoạt động, tôi đã phải thay đổi field.widget.rel.limit_choices_todict

class RoomInline(admin.TabularInline):
    model = Room

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):

        field = super(RoomInline, self).formfield_for_foreignkey(
            db_field, request, **kwargs)

        if db_field.name == 'inside_room':
            building = request._obj_
            if building is not None:
                field.queryset = field.queryset.filter(
                    building__exact=building)
                # widget changed to filter by building
                field.widget.rel.limit_choices_to = {'building_id': building.id}
            else:
                field.queryset = field.queryset.none()

        return field

class BuildingAdmin(admin.ModelAdmin):

    inlines = (RoomInline,)

    def get_form(self, request, obj=None, **kwargs):
        # just save obj reference for future processing in Inline
        request._obj_ = obj
        return super(BuildingAdmin, self).get_form(request, obj, **kwargs)

Điều này làm việc cho tôi trong django 2.2 mà không cần sử dụngfield.widget.rel.limit_choices_to = {'building_id': building.id}
Twitch

4

Câu hỏi và câu trả lời này rất giống nhau và hoạt động đối với biểu mẫu quản trị viên thông thường

Bên trong của một nội tuyến - và đó là nơi nó bị phá vỡ ... Tôi chỉ không thể lấy dữ liệu của biểu mẫu chính để nhận giá trị khóa ngoại mà tôi cần trong giới hạn của mình (hoặc đến một trong các bản ghi của nội tuyến để lấy giá trị) .

Đây là admin.py của tôi. Tôi đoán tôi đang tìm kiếm phép thuật để thay thế ???? với - nếu tôi cắm một giá trị được mã hóa cứng (giả sử, 1), nó hoạt động tốt và giới hạn đúng các lựa chọn có sẵn trong nội tuyến ...

#spaces/admin.py
from demo.spaces.models import Building, Room
from django.contrib import admin
from django.forms import ModelForm


class RoomInlineForm(ModelForm):
  def __init__(self, *args, **kwargs):
    super(RoomInlineForm, self).__init__(*args, **kwargs)
    self.fields['inside_room'].queryset = Room.objects.filter(
                               building__exact=????)                       # <------

class RoomInline(admin.TabularInline):
  form = RoomInlineForm
  model=Room

class BuildingAdmin(admin.ModelAdmin):
  inlines=[RoomInline]

admin.site.register(Building, BuildingAdmin)
admin.site.register(Room)

4

Tôi đã tìm thấy một giải pháp khá thanh lịch hoạt động tốt cho các biểu mẫu nội tuyến.

Được áp dụng cho mô hình của tôi, nơi tôi đang lọc trường inside_room để chỉ trả lại các phòng trong cùng một tòa nhà:

#spaces/admin.py
class RoomInlineForm(ModelForm):
  def __init__(self, *args, **kwargs):
    super(RoomInlineForm, self).__init__(*args, **kwargs)  #On init...
  if 'instance' in kwargs:
    building = kwargs['instance'].building
  else:
    building_id = tuple(i[0] for i in self.fields['building'].widget.choices)[1]
    building = Building.objects.get(id=building_id)
  self.fields['inside_room'].queryset = Room.objects.filter(building__exact=building)

Về cơ bản, nếu một từ khóa 'instance' được chuyển đến biểu mẫu, thì đó là một bản ghi hiện có hiển thị trong dòng nội tuyến và vì vậy tôi có thể lấy tòa nhà từ phiên bản. Nếu không phải là một phiên bản, thì đó là một trong các hàng "bổ sung" trống trong dòng nội tuyến và do đó nó đi qua các trường biểu mẫu ẩn của dòng nội tuyến lưu trữ mối quan hệ ngầm trở lại trang chính và lấy giá trị id từ đó. Sau đó, nó lấy đối tượng xây dựng dựa trên building_id đó. Cuối cùng, bây giờ có tòa nhà, chúng tôi có thể đặt bộ truy vấn của trình đơn thả xuống để chỉ hiển thị các mục có liên quan.

Thanh lịch hơn so với giải pháp ban đầu của tôi, đã bị lỗi và bị cháy như nội tuyến (nhưng hoạt động - tốt, nếu bạn không ngại lưu phần biểu mẫu để điền vào trình đơn thả xuống - cho các biểu mẫu riêng lẻ):

class RoomForm(forms.ModelForm): # For the individual rooms
  class Meta:
mode = Room
  def __init__(self, *args, **kwargs):  # Limits inside_room choices to same building only
    super(RoomForm, self).__init__(*args, **kwargs)  #On init...
try:
  self.fields['inside_room'].queryset = Room.objects.filter( 
    building__exact=self.instance.building)   # rooms with the same building as this room
    except:                  #and hide this field (why can't I exclude?)
    self.fields['inside_room']=forms.CharField( #Add room throws DoesNotExist error
        widget=forms.HiddenInput,   
        required=False,
        label='Inside Room (save room first)')

Đối với không phải nội tuyến, nó hoạt động nếu phòng đã tồn tại. Nếu không, nó sẽ xuất hiện một lỗi (DoesNotExist), vì vậy tôi sẽ bắt nó và sau đó ẩn trường (vì không có cách nào, từ Quản trị viên, để giới hạn nó ở đúng tòa nhà, vì bản ghi toàn bộ phòng là mới, và chưa có tòa nhà nào được thiết lập!) ... khi bạn nhấn lưu, nó sẽ lưu tòa nhà và khi tải lại, nó có thể giới hạn các lựa chọn ...

Tôi chỉ cần tìm cách phân tầng các bộ lọc khóa ngoại từ trường này sang trường khác trong bản ghi mới - tức là bản ghi mới, chọn một tòa nhà và nó tự động giới hạn các lựa chọn trong hộp chọn inside_room - trước khi bản ghi được đã lưu. Nhưng đó là một ngày khác ...


2

Nếu Daniel, sau khi chỉnh sửa câu hỏi của bạn, vẫn chưa trả lời - tôi không nghĩ mình sẽ giúp được nhiều ... :-)

Tôi sẽ gợi ý rằng bạn đang cố gắng buộc phải phù hợp với quản trị viên django một số logic sẽ tốt hơn nếu được triển khai dưới dạng nhóm chế độ xem, biểu mẫu và mẫu của riêng bạn.

Tôi không nghĩ rằng có thể áp dụng kiểu lọc đó cho InlineModelAdmin.


2

Trong django 1.6:

 form = SpettacoloForm( instance = spettacolo )
 form.fields['teatro'].queryset = Teatro.objects.filter( utente = request.user ).order_by( "nome" ).all()

1
Bạn có thể vui lòng điều chỉnh giải pháp cho các mô hình hiện có trong câu hỏi không?
raratiru

1

Tôi phải thừa nhận rằng, tôi đã không làm theo chính xác những gì bạn đang cố gắng làm, nhưng tôi nghĩ nó đủ phức tạp để bạn có thể muốn xem xét không dựa vào quản trị viên trang web của mình.

Tôi đã từng xây dựng một trang web bắt đầu với giao diện quản trị đơn giản, nhưng cuối cùng đã trở nên tùy chỉnh đến mức rất khó hoạt động trong phạm vi ràng buộc của quản trị viên. Tôi sẽ tốt hơn nếu tôi chỉ bắt đầu lại từ đầu - nhiều việc hơn vào đầu, nhưng linh hoạt hơn rất nhiều và ít đau hơn khi kết thúc. Nguyên tắc chung của tôi sẽ là nếu những gì bạn đang cố gắng làm không được ghi lại (ví dụ: liên quan đến ghi đè các phương thức quản trị, xem xét mã nguồn quản trị viên, v.v.) thì có lẽ bạn không nên sử dụng quản trị viên. Chỉ cho tôi hai xu. :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.