Reputation: 1057
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
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