Limit foreign key choices in select in an inline f

2019-01-05 08:37发布

The logic is of the model is:

  • A Building has many Rooms
  • A Room may be inside another Room (a closet, for instance--ForeignKey on 'self')
  • A Room can only be inside another Room in the same building (this is the tricky part)

Here's the code I have:

#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

and:

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

The inline will display only rooms in the current building (which is what I want). The problem, though, is that for the inside_room drop down, it displays all of the rooms in the Rooms table (including those in other buildings).

In the inline of rooms, I need to limit the inside_room choices to only rooms which are in the current building (the building record currently being altered by the main BuildingAdmin form).

I can't figure out a way to do it with either a limit_choices_to in the model, nor can I figure out how exactly to override the admin's inline formset properly (I feel like I should be somehow create a custom inline form, pass the building_id of the main form to the custom inline, then limit the queryset for the field's choices based on that--but I just can't wrap my head around how to do it).

Maybe this is too complex for the admin site, but it seems like something that would be generally useful...

10条回答
我只想做你的唯一
2楼-- · 2019-01-05 09:08

If Daniel, after editing your question, hasn't answered - I don't think I will be much help... :-)

I'm going to suggest that you are trying to force fit into the django admin some logic that would be better off implemented as your own group of views, forms and templates.

I don't think it is possible to apply that sort of filtering to the InlineModelAdmin.

查看更多
Deceive 欺骗
3楼-- · 2019-01-05 09:08

The problem in @nogus answer there's still wrong url in popup /?_to_field=id&_popup=1

which allow user to select wrong item in popup

To finally make it work I had to change field.widget.rel.limit_choices_to dict

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)
查看更多
ゆ 、 Hurt°
4楼-- · 2019-01-05 09:10

I have to admit, I didn't follow exactly what you're trying to do, but I think it's complex enough that you might want to consider not basing your site off of the admin.

I built a site once that started out with the simple admin interface, but eventually became so customized that it became very difficult to work with within the constraints of the admin. I would have been better off if I'd just started from scratch--more work at the beginning, but a lot more flexibility and less pain at the end. My rule-of-thumb would be if that what you're trying to do is not documented (ie. involves overriding admin methods, peering into the admin source code etc.) then you're probably better off not using the admin. Just me two cents. :)

查看更多
ゆ 、 Hurt°
5楼-- · 2019-01-05 09:15

In django 1.6:

 form = SpettacoloForm( instance = spettacolo )
 form.fields['teatro'].queryset = Teatro.objects.filter( utente = request.user ).order_by( "nome" ).all()
查看更多
forever°为你锁心
6楼-- · 2019-01-05 09:16

Used request instance as temporary container for obj. Overrided Inline method formfield_for_foreignkey to modify queryset. This works at least on 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)
查看更多
在下西门庆
7楼-- · 2019-01-05 09:19

There is limit_choices_to ForeignKey option that allows to limit the available admin choices for the object

查看更多
登录 后发表回答