質問

I have a self-referential ForeignKey field:

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

On the "Add Thing" form, in addition to other existing things I need to offer a choice "this thing itself", i.e. the Thing which hasn't been added yet. Telling users to Add and then revisit that field is not an option.

How should I go about this?

My current thought is to override the form:

  • change "special_thing" from being the default ModelChoiceField to ChoiceField
  • add new special ("marker") choice "*** THIS THING ***" to the field's choices in __init__()
  • provide clean_special_thing() which allows either "*** THIS THING ***" or the id of a Thing that can be looked up from the queryset.
  • In save(), if "*** THIS THING ***" was the choice, save Thing with special_thing=None, afterwards set it to itself and save again. Otherwise look up the Thing by the given id and save as usual.

I'm doing this for the ModelForm of a ModelAdmin. Is there an easier way?

役に立ちましたか?

解決 2

I've proceeded along those thoughts and the last step is not necessary. Instead, the clean method can set the choice to the form's instance, and the overall effort stays very reasonable:

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

他のヒント

Another possible solution could be to add with jQuery (example) another ***THIS THING*** to the formfield within the template.

After the form has been submitted you could check the selected option within the clean-method of the form or within the view to store it.

For example:

if request.POST:
    if request.POST['special_thing'] == 'myself':
        # do whatever should be done
        ...
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top