在pipe理中限制select内联表单中的外键选项
模型的逻辑是:
- A
Building
有很多Rooms
- 一个
Room
可能在另一个Room
(例如,一个Room
– “自我”上的ForeignKey)。 - 一个
Room
只能在同一build筑物的另一个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)
内联将只显示当前build筑中的房间(这是我想要的)。 但问题在于, inside_room
下拉显示“房间”表中的所有房间(包括其他build筑物中的房间)。
在rooms
的内联中,我需要将inside_room
select限制在当前building
中的rooms
(当前正在由BuildingAdmin
主窗体修改的build筑logging)。
我无法想出在模型中使用limit_choices_to
来做到这一点的方法,也不知道如何恰当地重写pipe理员的内联窗体集(我觉得我应该以某种方式创build一个自定义内联窗体,通过building_id的主要forms的自定义内联,然后限制该字段的select基于这个查询集 – 但我不能包装我的头周围如何做到这一点)。
也许这对pipe理网站来说太复杂了,但是看起来好像是一些有用的东西。
使用请求实例作为obj的临时容器。 Overrided Inline方法formfield_for_foreignkey修改queryset。 这个至less在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)
通过阅读这篇文章并进行了很多实验后,我想我已经find了一个相当确定的答案。 由于这是一个使用的devise模式,我已经为Djangopipe理员编写了一个Mixin来使用它。
(dynamic地)为ForeignKey字段限制查询集现在与子类LimitedAdminMixin
一样简单,并定义一个get_filters(obj)
方法来返回相关的filter。 或者,如果不需要dynamic过滤,则可以在admin上设置filters
属性。
用法示例:
class MyInline(LimitedAdminInlineMixin, admin.TabularInline): def get_filters(self, obj): return (('<field_name>', dict(<filters>)),)
这里, <field_name>
是要过滤的FK字段的名称, <filters>
是一个参数列表,正如您在查询集的filter()
方法中通常指定的那样。
有限制_choices_to外键选项,允许限制对象的可用pipe理员选项
您可以创build几个自定义类,然后将对父实例的引用传递给表单。
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来过滤select和什么
就像是:
class RoomInlineForm(ParentInlineModelForm): def __init__(self, *args, **kwargs): super(RoomInlineForm, self).__init__(*args, **kwargs) building = self.parent_instance #Filtering and stuff
这个问题和答案是非常相似的,并为常规的pipe理forms
内联 – 这是它分崩离析的地方…我只是无法得到主窗体的数据,以获得我所需要的外键值在我的极限(或内联的logging之一来获取值) 。
这是我的admin.py。 我想我正在寻找魔术来取代? 与 – 如果我插入一个硬编码的值(比如说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)
我发现了一个相当优雅的解决scheme ,适用于内联表单。
应用到我的模型中,我正在过滤inside_room字段以仅返回位于同一build筑物中的房间:
#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)
基本上,如果一个“实例”关键字被传递给表单,这是一个现有的logging显示在内联,所以我可以从实例中抓取build筑物。 如果不是一个实例,它是内联中空白的“额外”行之一,所以它通过内联的隐藏表单字段将隐式关系存储回主页面,并从中获取id值。 然后,它根据building_id抓取build筑物。 最后,现在有了这个build筑物,我们可以设置下拉列表的查询集来只显示相关的项目。
比我最初的解决scheme更优雅,这个解决scheme在内联中崩溃和烧毁(但是工作 – 如果你不介意保存表单的一部分,使下拉列表填入各个表单):
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),所以我会赶上它,然后隐藏该字段(因为没有办法,从pipe理员,限制到正确的build筑,因为整个房间logging是新的,没有build筑物尚未设置!)…一旦你保存,它节省了build设和重新加载它可能会限制select…
我只需要find一种方法来将外键filter从一个字段级联到另一个字段中 – 即新logging,select一个build筑物,并自动限制在内部空间select框中的select – 在logging获得之前保存。 但那是另一天…
如果丹尼尔在编辑你的问题之后,还没有回答 – 我想我不会有太多的帮助… 🙂
我将build议您试图强制适合djangopipe理员的一些逻辑,将更好地实现为您自己的视图,表单和模板组。
我不认为有可能将这种过滤应用到InlineModelAdmin。
在Django 1.6中:
form = SpettacoloForm( instance = spettacolo ) form.fields['teatro'].queryset = Teatro.objects.filter( utente = request.user ).order_by( "nome" ).all()
我不得不承认,我没有完全按照你要做的事情做,但是我觉得这很复杂,你可能会考虑不把你的网站build立在pipe理员之外。
我build立了一个网站,一开始只有简单的pipe理界面,但最终变得如此自定义,以至于在pipe理员的约束下工作变得非常困难。 如果我只是从头开始 – 如果开始的时候做更多的工作,那么我的状况会好一些,但是最终会有更多的灵活性和更less的痛苦。 我的经验法则是,如果你正在做的是没有logging(即涉及覆盖pipe理方法,窥视到pipe理源代码等),那么你最好不要使用pipe理员。 只有我两分钱。 🙂