在admin中以内联形式限制select中的外键选项

时间:2009-12-01 06:01:19

标签: django foreign-keys inline limit admin

该模型的逻辑是:

  • Building有许多Rooms
  • Room可能在另一个Room内(例如,一个壁橱 - 'self'上的ForeignKey)
  • Room只能位于同一建筑物内的另一个Room内(这是棘手的部分)

这是我的代码:

#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

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

内联将仅显示当前建筑物中的房间(这就是我想要的)。但问题是,对于inside_room下拉列表,它会显示Rooms表中的所有房间(包括其他建筑物中的房间)。

rooms的内联中,我需要将inside_room选项限制为仅rooms当前building(当前正在修改的建筑记录)主BuildingAdmin形式)。

我无法找到一种方法来使用模型中的limit_choices_to,也无法弄清楚如何正确覆盖管理员的内联formset(我觉得我应该以某种方式创建一个自定义内联表单,将主窗体的building_id传递给自定义内联,然后根据该字段限制字段选择的查询集 - 但我无法理解如何做到这一点。

对于管理网站来说,这可能过于复杂,但它似乎通常很有用......

10 个答案:

答案 0 :(得分:95)

使用的请求实例作为obj的临时容器。 覆盖内联方法formfield_for_foreignkey以修改查询集。 这至少在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 :(得分:15)

阅读完这篇文章并进行了大量实验后,我想我已经找到了一个相当确定的答案。由于这是一个使用过的设计模式,我写了一个Mixin for the Django admin来使用它。

(动态地)限制ForeignKey字段的查询集现在就像子类化LimitedAdminMixin和定义get_filters(obj)方法以返回相关过滤器一样简单。或者,如果不需要动态过滤,则可以在管理员上设置filters属性。

使用示例:

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

此处,<field_name>是要过滤的FK字段的名称,<filters>是参数列表,正如您通常在查询集的filter()方法中指定它们一样。

答案 2 :(得分:14)

limit_choices_to ForeignKey选项允许限制对象的可用管理员选择

答案 3 :(得分:8)

您可以创建一些自定义类,然后将对父实例的引用传递给表单。

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)
在RoomInline类中

只需添加:

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

在您的表单中,您现在可以使用init方法访问self.parent_instance! parent_instance现在可用于过滤选择等等

类似的东西:

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

答案 4 :(得分:4)

This question and answer is very similar, and works for a regular admin form

内联内部 - 这就是它崩溃的地方......我只是无法获取主窗体的数据来获取我在限制中需要的外键值(或者内联的一个记录来获取价值)。

这是我的admin.py。我想我正在寻找替代????的魔力? with - 如果我插入一个硬编码值(比如1),它可以正常工作并正确限制内联中的可用选项......

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

答案 5 :(得分:4)

我发现fairly elegant solution适用于内嵌表单。

应用于我的模型,我将过滤inside_room字段,仅返回同一建筑物内的房间:

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

基本上,如果将'instance'关键字传递给表单,那么它就是在内联中显示的现有记录,因此我可以从实例中获取构建。如果不是实例,则它是内联中空白的“额外”行之一,因此它通过内联的隐藏表单字段将隐式关系存储回主页,并从中获取id值。然后,它基于该building_id抓取构建对象。最后,现在有了建筑物,我们可以设置下拉列表的查询集,只显示相关项目。

比我的原始解决方案更优雅,它以内联方式崩溃并烧毁(但是如果您不介意将表单保存到中途以使下拉填充 - 对于单个表单):

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

对于非内联,如果房间已经存在,它会起作用。如果没有,它会抛出一个错误(DoesNotExist),所以我会抓住它然后隐藏字段(因为没有办法,从管理员,限制它到正确的建筑,因为整个房间记录是新的,还没有建筑物设置!)...一旦你点击保存,它就会保存建筑物并重新加载它可能会限制选择......

我只需要找到一种方法在新记录中将外键过滤器从一个字段级联到另一个字段 - 即新记录,选择一个建筑物,它会自动限制在inside_room选择框中的选择 - 之前记录得到保存。但那是另一天......

答案 6 :(得分:3)

@nogus中的问题回答弹出窗口中的错误网址/?_to_field=id&_popup=1

允许用户在弹出窗口中选择错误的项目

为了最终使它工作,我不得不改变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)

答案 7 :(得分:2)

如果丹尼尔在编辑你的问题之后没有回答 - 我认为我没有多大帮助......: - )

我将建议您尝试强制适应django管理员一些逻辑,最好将其作为您自己的一组视图,表单和模板实现。

我认为不可能将这种过滤应用于InlineModelAdmin。

答案 8 :(得分:2)

在django 1.6中:

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

答案 9 :(得分:1)

我不得不承认,我并没有完全遵循你想要做的事情,但我认为这很复杂,你可能想要考虑不要让你的网站不在管理员的基础上。

我曾经用简单的管理界面建立了一个网站,但最终变得如此定制,以至于在管理员的约束下变得非常困难。如果我刚从头开始,我会更好的 - 一开始就做更多的工作,但最后会有更多的灵活性和更少的痛苦。我的经验法则是,如果您正在尝试做的事情没有记录(即涉及覆盖管理方法,窥视管理源代码等),那么您可能最好不要使用管理员。只有我两美分。 :)