0

前言

我正在实现一个基于 Django 框架(目前稳定的 1.4.3)的“收藏管理系统” 。
我正在设计 ORM 的良好关系性质来强制一些讨厌的“OO 方面”,我试图提取一个最小的例子。(免责声明:完整的功能描述将是另一个冗长而无聊的阅读,所以我不会制作它,除非它被证明是设计讨论)。

提取的例子

假设我们使用 Django 模型松散地模拟 OO。我们有发布模型(=一个类),它可以由不同的属性(=类的数据成员)组成。然后可以为相同的Release实例化不同的实例(= 对象) 。每个Instance可以为其相关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 管理员会很棒。

  • 一切都是开箱即用的,可以添加Releases并使用Attributes组合它们。

  • 实例添加视图变得越来越复杂。(相关的Release id 可以通过 GET 形式的 url 转发:instance/add/?release=x)。当这个视图被加载时,我们需要提出一个InstanceAttribute的 InlineFormset :

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

我们可以通过覆盖ModelAdmin.get_formsets()返回一个inlineformset_factory并将额外参数设置为所需数字的方法来解决#1。
仔细查看add_view 源代码,我找不到实现#2 的好方法...

4

1 回答 1

0

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

基于全局状态的解决方案

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

  • 这个全局状态应该接收数据,例如必须呈现的属性列表,只要是当前构造的表单的计数器(用于索引属性列表)。
  • 然后,被覆盖的对象__init__使用这些全局变量将属性绑定到由表单集构造的每个表单。

编码

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挂钩到表单集工厂。为此,我们可以自定义Instance 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)
于 2013-03-04T23:05:39.530 回答