Carrie
Carrie

Reputation: 1057

Django: how to validate inlineformset against another related form

I have looked around quite a bit but can't quite figure out how to make this work. I basically have a Document form and an Item inlineformset in a view, and I need to perform some validation dependent on the field values in each form. For example, if the Item's copyright_needed field is YES then the Document's account field is required.

How can I pass a reference to the Document form, so that inside ItemForm's clean method, I can look at the Document form's cleaned_data? I'm trying to use curry, as I've seen recommended in other SO answers, but it's not working quite right.

Models.py

class Document(models.Model):
   account = models.CharField(max_length=22, blank=True, null=True)

class Item(models.Model):
    copyright_needed = models.CharField(max_length=1)
    # Document foreign key
    document = models.ForeignKey(Document)

It's the ItemForm clean method that shows what I'd like to accomplish, and the error I'm getting.

Forms.py -- EDIT - added init to ItemForm

class ItemForm(forms.ModelForm):
    class Meta:
        model = Item
        fields=[..., 'copyright_needed' ]
    def __init__(self, *args, **kwargs):
        self.doc_form = kwargs.pop('doc_form')
        super(ItemForm, self).__init__(*args, **kwargs)
    def clean(self):
        cleaned_data = super(ItemForm, self).clean()
        msg_required = "This field is required."

        cr = cleaned_data.get("copyright_needed")
        # This line generates this error: DocumentForm object has no attribute cleaned_data
        acct_num = self.doc_form.cleaned_data.get("account")
        if cr and cr == Item.YES:
            if not acct_num:
                self.doc_form.add_error("account", msg_required)
        return cleaned_data


class DocumentForm(forms.ModelForm):
      ...
      account = forms.CharField(widget=forms.TextInput(attrs={'size':'25'}), required=False)
    class Meta:
        model = Document
        fields = [ ..., 'account' ]

views.py

def create_item(request):
    # create empty forms
    form=DocumentForm()
    ItemFormSet = inlineformset_factory(Document, Item, 
        form=ItemForm,
        can_delete=False,
        extra=1 )
    # This is my attempt to pass the DocumentForm to each ItemForm, but its not working
    ItemFormSet.form = staticmethod(curry(ItemForm, doc_form=form)) 
    item_formset=ItemFormSet(instance=Document())
    if request.POST:

        d = Document()
        form=DocumentForm(request.POST, instance=d)
        if form.is_valid():
            new_document=form.save(commit=False)
            item_formset=ItemFormSet(request.POST, instance=new_document)
            if item_formset.is_valid():
                new_document.save()
                new_item=item_formset.save()
                    return HttpResponseRedirect(...)
        item_formset=ItemFormSet(request.POST)
    return render(request,...)

Upvotes: 0

Views: 140

Answers (1)

professorDante
professorDante

Reputation: 2405

I'm not even sure what the view is doing - it looks like you're confused on the role of the inlineformset and curry. Firstly, you're currying the init method of ItemForm with the doc_form, but you haven't written an init.

Secondly, it looks like you want to be able to edit the Items inside the Document form. So you need the modelformset_factory, and pass in a custom Formset, on which you write a clean method, that has access to everything you need.

from django.forms.models import modelformset_factory

ItemFormSet = modelformset_factory(Item, form=ItemForm, formset=MyCustomFormset)

then in your customformset -

class MyCustomFormset(BaseInlineFormset):

    def clean():
        super(MyCustomFormset, self).clean()
        for form in self.forms:
            #do stuff

Note the clean method on each ItemForm has already been called - this is similar to writing your own clean() on a normal modelform.

EDIT:

OK, so ignore the formset clean, I misunderstood. Just make your document form in the view, pass it along with the formset, then put them all in the same form tag.

<form method="post" action=".">
    {%for field in doc_form %}
         {{field}}
    {%endfor%}
    {%for form in formset%}
        {{form.as_p}}
    {%endfor%}
</form>

Then you have access to all the fields in your request.POST, and you can do whatever you want

doc_form = DocumentForm(request.POST)
formset = ItemFormSet(request.POST)
if all([doc_form.is_valid(), formset.is_valid()]):
     #do some stuff

Upvotes: 1

Related Questions