Xudonax
Xudonax

Reputation: 439

Saving a Django ModelFormset doesn't save new objects

Update: Found the error...

class BaseContactPersonFormSet(forms.models.BaseModelFormSet):
    def __init__(self, company, data=None, **kwargs):
        super(BaseContactPersonFormSet, self).__init__(**kwargs)
        for form in self:
                    form.company = company
 
ContactPersonFormSet = forms.models.modelformset_factory(model=Person, form=SingleContactPersonForm, formset=BaseContactPersonFormSet, can_delete=True, extra=0)

The piece above caused the form to never save, I dunno why. Removing this and doing a relatively hacky workaround fixed it for me. Now I have a ModelSelect named company in the form which is filled in using JavaScript.


Original question:

I have a Django view that basically only displays and saves a formset, the code of that is:

def manage_company_contact_persons(request, company_id):
    company = get_object_or_404(Company, id=company_id)

    if request.method == 'POST':
        print request.POST
        cpfs = forms.ContactPersonFormSet(queryset=company.contact_persons.all(), data=request.POST)
        if cpfs.is_valid():
            for cpf in cpfs:
                print cpf.errors
                cpf.save()
    else:
        cpfs = forms.ContactPersonFormSet(queryset=company.contact_persons.all())

    return render_to_response('company/manage/admin-crm-company-contactpersons-new.html',
                              {'contactpersons': cpfs,
                               'company': company,},
                              context_instance=RequestContext(request))

The purpose of this is to allow the user to create/update/delete contractpersons from the database. The ContactPersonFormSet is defined as follows:

class SingleContactPersonForm(forms.ModelForm):
    # Fields for the User model
    first_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'w170'}), required=True, label=_(u'First name'))
    last_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'w170'}), required=True, label=_(u'Last name'))
    email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'w170'}), required=True, label=_(u'E-Mail'))
    password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'w170'}), required=False, label=_(u'Password'))

    # Fields for the Address model
    zip_code = forms.CharField(widget=forms.TextInput(attrs={'class': 'w100 address'}), required=False, label=_(u'Zip code + house number'))
    house_number = forms.CharField(widget=forms.TextInput(attrs={'class': 'w60 address'}), required=False, label=_(u'Zip code + house number'))
    street = forms.CharField(widget=forms.TextInput(attrs={'class': 'w170 address'}), required=False, label=_(u'Street'))
    city = forms.CharField(widget=forms.TextInput(attrs={'class': 'w170 address'}), required=False, label=_(u'City'))
    country = forms.ModelChoiceField(queryset=Country.objects.all(), widget=forms.Select(attrs={'class address': 'w170'}), required=False, label=_(u'Country'))

    def __init__(self, *args, **kwargs):
        super(SingleContactPersonForm, self).__init__(*args, **kwargs)

        ### If there is an instance, get it to fill extra fields

    def save(self, commit=True, *args, **kwargs):
        '''Saves the whole contact Person to the database, including the address and the user.'''
        super(SingleContactPersonForm, self).save(*args, commit=commit, **kwargs)
        ### save data in other models            

        if commit:
            super(SingleContactPersonForm, self).save()
        return self

    class Meta:
        model = Person
        fields = ['job_title', 'telephone_number', 'mobile_number', 'fax']
        widgets = {'job_title': forms.TextInput(attrs={'class': 'w170'}),
                   'telephone_number': forms.TextInput(attrs={'class': 'w170'}),
                   'mobile_number': forms.TextInput(attrs={'class': 'w170'}),
                   'fax': forms.TextInput(attrs={'class': 'w170'}),
                   }

ContactPersonFormSet = forms.models.modelformset_factory(model=Person, form=SingleContactPersonForm, can_delete=True, extra=0)

The issue is that for some reason only the original contact person is saved to the database. New contact persons are never created, as the save() method for the new ones is never called. All the data is in the POST as far as I can see. The formset is also valid, because the original items do get their save() function called.

Is it maybe falling over the fact that I include an empty 'id' in the POST data?

The data in the POST is:

{u'csrfmiddlewaretoken': [u'056d5042e73322c8166011e27108d362'],
 u'form-0-city': [u'Culemborg'],
 u'form-0-country': [u'155'],
 u'form-0-email': [u'[email protected]'],
 u'form-0-fax': [u'0345750249'],
 u'form-0-first_name': [First'],
 u'form-0-house_number': [u'7'],
 u'form-0-id': [u'69'],
 u'form-0-job_title': [u'Job'],
 u'form-0-last_name': [u'Name'],
 u'form-0-mobile_number': [u''],
 u'form-0-password': [u''],
 u'form-0-street': [u'Street'],
 u'form-0-telephone_number': [u'0123456789'],
 u'form-0-zip_code': [u'4104AN'],
 u'form-1-city': [u'Culemborg'],
 u'form-1-country': [u'155'],
 u'form-1-email': [u'[email protected]'],
 u'form-1-fax': [u''],
 u'form-1-first_name': [u'Second'],
 u'form-1-house_number': [u'7'],
 u'form-1-id': [u''],
 u'form-1-job_title': [u'Function'],
 u'form-1-last_name': [u'Name'],
 u'form-1-mobile_number': [u''],
 u'form-1-password': [u''],
 u'form-1-street': [u'Nijverheidsweg'],
 u'form-1-telephone_number': [u'0987654321'],
 u'form-1-zip_code': [u'4104AN'],
 u'form-INITIAL_FORMS': [u'1'],
 u'form-MAX_NUM_FORMS': [u'1000'],
 u'form-TOTAL_FORMS': [u'1'],
 u'form-__prefix__-city': [u''],
 u'form-__prefix__-country': [u''],
 u'form-__prefix__-email': [u''],
 u'form-__prefix__-fax': [u''],
 u'form-__prefix__-first_name': [u''],
 u'form-__prefix__-house_number': [u''],
 u'form-__prefix__-id': [u''],
 u'form-__prefix__-job_title': [u''],
 u'form-__prefix__-last_name': [u''],
 u'form-__prefix__-mobile_number': [u''],
 u'form-__prefix__-password': [u''],
 u'form-__prefix__-street': [u''],
 u'form-__prefix__-telephone_number': [u''],
 u'form-__prefix__-zip_code': [u'']}

Upvotes: 2

Views: 2084

Answers (1)

marianobianchi
marianobianchi

Reputation: 8488

Edit:

I think i found your error. You forgot to pass "data" arg to that "super" call. You wrote this:

class BaseContactPersonFormSet(forms.models.BaseModelFormSet):
    def __init__(self, company, data=None, **kwargs):
        super(BaseContactPersonFormSet, self).__init__(**kwargs)
        for form in self:
            form.company = company

ContactPersonFormSet = forms.models.modelformset_factory(model=Person, form=SingleContactPersonForm, formset=BaseContactPersonFormSet, can_delete=True, extra=0)

but you should write this:

class BaseContactPersonFormSet(forms.models.BaseModelFormSet):
    def __init__(self, company, data=None, **kwargs):
        super(BaseContactPersonFormSet, self).__init__(data, **kwargs)
        for form in self:
            form.company = company

ContactPersonFormSet = forms.models.modelformset_factory(model=Person, form=SingleContactPersonForm, formset=BaseContactPersonFormSet, can_delete=True, extra=0)

OLD ANSWER:

Try changing these lines:

if cpfs.is_valid():
    for cpf in cpfs:
        print cpf.errors
        cpf.save()

for these lines:

if cpfs.is_valid():
    cpfs.save()

Taken from here

Upvotes: 1

Related Questions