【问题标题】:Add choice "self" to self-referential foreign key on Add form在添加表单上将选项“self”添加到自引用外键
【发布时间】:2013-04-19 04:19:06
【问题描述】:

我有一个自引用的 ForeignKey 字段:

class Thing(models.Model)
    special_thing = models.ForeignKey(
        'self',
        blank=True,
        null=True
    )

在“添加事物”表单上,除了其他现有事物外,我还需要提供“事物本身”选项,即尚未添加的事物。告诉用户添加然后重新访问该字段不是一种选择。

我该怎么办?

我目前的想法是覆盖表单:

  • 将“special_thing”从默认的 ModelChoiceField 更改为 ChoiceField
  • 在 __init__() 中的字段选项中添加新的特殊(“标记”)选项“*** THIS THING ***”
  • 提供 clean_special_thing(),它允许“*** THIS THING ***”或可以从查询集中查找的事物的 ID。
  • 在 save() 中,如果选择“*** THIS THING ***”,则使用 special_thing=None 保存 Thing,然后将其设置为自身并再次保存。否则,通过给定的 id 查找 Thing 并照常保存。

我正在为 ModelAdmin 的 ModelForm 执行此操作。有没有更简单的方法?

【问题讨论】:

    标签: django django-forms foreign-keys self-reference


    【解决方案1】:

    另一种可能的解决方案是使用 jQuery (example) 将另一个 ***THIS THING*** 添加到模板中的表单域。

    提交表单后,您可以在表单的clean-method 或视图中检查所选选项以存储它。

    例如:

    if request.POST:
        if request.POST['special_thing'] == 'myself':
            # do whatever should be done
            ...
    

    【讨论】:

    • 嗯...问题是“应该做的”是将 instance.special_thing 设置为指向事物本身,这对于 clean() 来说还为时过早
    • 根据我的回答, clean() 实际上能够将 instance.special_thing 设置为实例,即使此时 pk 不可用。添加新选项非常容易,我将其保留在服务器端,同时将所有内容放在一个位置。
    【解决方案2】:

    我已经按照这些想法进行了,最后一步是不必要的。相反,clean 方法可以将选项设置为表单的实例,并且整体工作保持非常合理:

    from django.contrib import admin
    from django import forms
    
    from mdoels import Thing
    
    MARKER_THIS_THING = '*** THIS THING ***'
    
    
    class ThingAdmin(admin.ModelAdmin):
    
        def get_form(self, request, obj=None, **kwargs):
            """
            Return the Thing ModelForm with an additional choice for the
            'special_thing' field (FK to self) that allows the Add Thing form
            to refer to the newly added instance itself.
            """
    
            form = super(ThingAdmin, self).get_form(request, obj, **kwargs)
    
            # The form still has a ModelChoiceField for special_thing, construct
            # new non-model choices from that so we can add the 'this thing' choice
            thing_choices = [c for c in form.base_fields['special_thing'].choices]
    
            if not obj:
                # Only the Add form needs the special choice
                thing_choices = [(MARKER_THIS_THING, MARKER_THIS_THING)] + thing_choices
    
            form.base_fields['special_thing'] = forms.ChoiceField(
                choices=thing_choices
            )
    
            def clean_special_thing(form):
                """
                Now just a simple ChoiceField, convert posted values to
                model instances like a ModelChoiceField does.
                Convert special new 'this thing' choice to be the newly added
                instance.
                """
    
                data = form.cleaned_data['special_thing']
                instance = getattr(form, 'instance', None)
    
                if data==MARKER_THIS_THING and not (instance and instance.pk):
                    # Referring to new instance itself on Add form
                    return instance
    
                # Return selected model like a ModelChoiceField does
                try:
                    data = Thing.objects.get(pk=data)
                except Thing.DoesNotExist:
                    raise forms.ValidationError('Invalid choice')
                return data
    
            # clean_* are not part of ModelAdmin, just of forms and models.
            # So we attach it to the form:
            form.clean_special_thing = clean_special_thing
    
            return form
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-13
      • 2016-12-10
      • 2012-04-19
      • 2019-10-18
      相关资源
      最近更新 更多