Django管理员视图:如何在每个表单中单独控制InlineFormset中的FormFields属性?

时间:2013-03-04 22:45:35

标签: python django django-admin

前言

我正在实施基于Django框架的'收集管理系统'(目前,稳定的1.4.3)。
我正在设想ORM的良好关系性质,以强制一些讨厌的“OO方面”,我试图提取一个最小的例子。 (免责声明:完整的功能描述将是另一个漫长而无聊的读物,所以除非结果是设计讨论,否则我不会产生它。)

提取的示例

让我们假设我们使用Django模型松散地模拟OO。我们有 Release 模型(=一个类),它可以由不同的属性(=类的数据成员)组成。然后可以针对相同的发布实例化不同的实例(=对象)。每个实例都可以存储其相关发布中存在的属性的全部/部分/全部值。
这将给我们以下模型:

#An attribute is a name, that can be given a value at instance level.
class Attribute(models.Model):
    name = models.CharField(max_length=60)

#A release can be compared to a class in OO :
# it's a data structure description (the attributes' list).
class Release(models.Model):
    name = models.CharField(max_length=60)
    #A release is caracterized by a list of attributes
    # the same attribute can be present in several releases
    attributes = models.ManyToManyField(Attribute, blank=True, null=True)

#An instance entry can be compared to an object in OO :
# it instantiates a release
# it can hold its own value for each of the attributes in this release.
#Nb : Attributes are all optional.
class Instance(models.Model):
    #the instantiated release
    release = models.ForeignKey(Release)

#Store the actual attribute-value pairs for the different instances.
class InstanceAttribute(models.Model):
    instance = models.ForeignKey(Instance)
    attribute = models.ForeignKey(Attribute)
    value = models.CharField(max_length=60)

问题

使用强大的Django管理员来实现这种方法会很棒。

  • 开箱即用的所有内容都可以添加版本,并使用属性进行组合。

  • 实例添加视图越来越复杂。 (相关的发布 ID可以通过GET转发到以下格式的网址: instance / add /?release = x )。加载此视图时,我们需要使用以下内容提出 InstanceAttribute 的InlineFormset:

    1. 与构成相关发布属性的数量匹配的表单数量
    2. 对于这些表单中的每一个, Attribute 字段应初始化为Release的 Attribute ,并且其查询集仅限于显示此属性

我们可以通过覆盖ModelAdmin.get_formsets()来解决#1,以便将 extra 参数设置为所需的数字,从而返回inlineformset_factory
仔细看看add_view source,我找不到实施#2的好方法......

1 个答案:

答案 0 :(得分:0)

我正在用一个工作的解决方案回答我自己的问题,但至少可以说不满意。我认为通过展示我想要达到的目标,它也可以帮助读者理解上述问题。

基于全球状态的解决方案

所以(坏)的想法是将视图使用的 InstanceAttribute 表单子类化。它允许覆盖表单__init__,同时引入具有 InstanceAttributeForm 静态数据成员的全局状态。

  • 此全局状态应接收必须显示的属性列表等数据,只要当前构造的表单的计数器(用于索引属性)列表。
  • 重写的__init__然后使用这些全局变量将属性绑定到由formset构造的每个表单。

代码

class InstanceAttributeForm(forms.ModelForm):
    #form_id count the initializations (to know which form is currently constructed by the formset)
    form_id = 0
    total = 0
    #the list of attributes in the instantiated release
    #it should be populated before the formset start the constructor calls
    attributes = []

    def __init__(self, *args, **kwargs):
        super(InstanceAttributeForm, self).__init__(*args, **kwargs)
        #"deferred template rendering' is trying to construct 3 additional forms
        #bonus point if you can comment on the reason for this behavior  ; )
        if InstanceAttributeForm.form_id==InstanceAttributeForm.total:
            return

        attribute = InstanceAttributeForm.attributes[InstanceAttributeForm.form_id]
        self.initial = {'attribute' : attribute}
        self.fields['attribute'].queryset = Attribute.objects.filter(id=attribute.id)
        InstanceAttributeForm.form_id += 1

我们必须在实际输入表单的构造函数之前初始化这些全局变量,并将 InstanceAttributeForm 挂钩到formset工厂。为此,我们可以自定义实例 ModelAdmin:

class InstanceAdmin(admin.ModelAdmin):  
    #Responsible for re-initializing the global state each time before calling the super add_view
    def add_view(self, request, form_url='', extra_context=None):
        initialize_globalstate(request)
        return super(InstanceAdmin, self).add_view(request, form_url, extra_context)

    #Return a formset factory specifying the exact number of required forms
    # and hooking our specialised ModelForm class.
    def get_formsets(self, request, obj=None):
        yield inlineformset_factory(
            Instance,
            InstanceAttribute,
            form=InstanceAttributeForm,
            can_delete=False,
            extra=InstanceAttributeForm.total)

为了完整性,全局状态初始化过程(在上面重写的add_view()中调用):

def initialize_globalstate(request):
    instantiated_release = Release.objects.get(id=request.GET['release'])

    InstanceAttributeForm.form_id = 0
    InstanceAttributeForm.attributes = instantiated_release.attributes.all()
    InstanceAttributeForm.total = len(InstanceAttributeForm.attributes)