Django form is only valid after second request

398 views Asked by At

I have a very strange problem with django forms, I display a form which includes an additional formset so that the user can also submit data for a foreign key relation at the same time.

The template always displays a form for the original model and one form for the second model.

I now want to submit the two forms without filling in anything in the second form. On the first submission the seond form does not validate and the page is redisplayed, but on the second submission the second form is valid! Even so the POST data is identical. How can this be possible?

Or maybe I am doing this completely wrong, how can you discern if the user did not fill in anything in the formset or if he filled in something invalid?

Here the models:

class Software(models.Model):
    creation_date = models.DateTimeField(default=datetime.now)
    creator = models.ForeignKey(User)
    version = models.CharField(max_length=300, unique=True, editable=False)
    major_version = models.IntegerField()
    minor_version = models.IntegerField()
    [...]

    def save(self, **kwargs):
        """
        This updates the version string to the combined representation.
        """
        self.version = Software.combine_version_string (self.major_version, self.minor_version)
        super(Software, self).save(**kwargs)

class SoftwarePatch(models.Model):
    file  = models.FileField(upload_to='software_patches')
    file_name = models.CharField(max_length=255, editable=False)
    file_date = models.DateTimeField(default=datetime.now)
    upload_date = models.DateTimeField(default=datetime.now)
    software = models.ForeignKey('Software', related_name='patches')
    firmware_patch = models.BooleanField(default=True)
    target_path = models.CharField(max_length=255, blank=True)

    class Meta:
        unique_together = ('software', 'file_name')
        verbose_name_plural = "software patches"

    def __unicode__(self):        
        return self.file_name

    def clean(self):
          if self.file and not self.file_name:
              self.file_name = self.file.file.name 

Here my forms:

SoftwarePatchFormSet = inlineformset_factory(Software, 
    SoftwarePatch, 
    extra=1)


class SoftwareForm(forms.ModelForm):
    """
    A simple form for creating a new software.
    """
    class Meta:
        model = Software

And finally my view function:

def software_add(request, software_id=None):
    if software_id == None:
        software = Software()
    else:
        software = Software.objects.get(id=software_id)

    if request.POST:        
        form = SoftwareForm(request.POST, instance=software)        

        if form.is_valid():
            software = form.save(commit=False)
            softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES, instance=software)

            if softwarepatch_formset.is_valid():
                software = form.save()
                softwarepatch_formset.save()

                # Redirect, in case of a popup close it
                if request.POST.has_key("_popup"):
                    pk_value = software._get_pk_val()
                    return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
                        # escape() calls force_unicode.
                        (escape(pk_value), escape(software)))
                if 'next' in request.POST:
                    return HttpResponseRedirect(request.POST['next'])
                else:
                    return HttpResponseRedirect(reverse('index'))
    else:
        form = SoftwareForm(instance=software)
        softwarepatch_formset = SoftwarePatchFormSet(instance=software)

    is_popup = request.GET.has_key("_popup") or request.POST.has_key("_popup")

    return render_to_response(
        'main/software_edit.html',
        {'form': form,
         'softwarepatch_formset': softwarepatch_formset,
         'add': True,
         'is_popup': is_popup,
        },
        context_instance = RequestContext(request)
    )
2

There are 2 answers

2
Fabian On BEST ANSWER

Ok I finally found my problem!

I have the following model field: file_date = models.DateTimeField(default=datetime.now)

This sets the innital-file-date to a value like this: u'2011-10-18 08:14:30.242000' After being rendered through the html widget the value will be: u'2011-10-18 08:14:30' So django will think the form was changed and therefore not save.

On the second load django will automatically set the truncated value as initial-file-date and then nothing is changed and the save works as expected.

So now I only have to figure out what to use instead of datetime.now. I will update this post when I have figured it out.

2
Simon Kagwi On

First of all, you should set the instance argument only when creating a form / formset for an existing object i.e. one already in the DB. So for example if software_id = None and it's a GET request, you should only do form = SoftwareForm().

Also, after doing software = form.save(commit=False), you should do software.save() instead of software = form.save(). [I don't think it's really a problem though, just that you're redoing a save]. Remember that if you have a ManyToManyField in the Software model, you need to do form.save_m2m() after software = form.save() as well.

Here's what I think you should have:

def software_add(request, software_id=None):
    if request.POST: 
        if software_id:
            software = Software.objects.get(id=software_id)
            form = SoftwareForm(request.POST, instance=software)
        else:
            form = SoftwareForm(request.POST)

        if form.is_valid():
            software = form.save(commit=False)
            softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES, instance=software)

            if softwarepatch_formset.is_valid():
                software.save()
                softwarepatch_formset.save()

                # Redirect, in case of a popup close it
                if request.POST.has_key("_popup"):
                    pk_value = software._get_pk_val()
                    return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
                        # escape() calls force_unicode.
                        (escape(pk_value), escape(software)))
                if 'next' in request.POST:
                    return HttpResponseRedirect(request.POST['next'])
                else:
                    return HttpResponseRedirect(reverse('index'))
        else:
            softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES)     
    else:
        if software_id:
            software = Software.objects.get(id=software_id)
            form = SoftwareForm(instance=software)
            softwarepatch_formset = SoftwarePatchFormSet(instance=software)
        else:
            form = SoftwareForm()
            softwarepatch_formset = SoftwarePatchFormSet()


    is_popup = request.GET.has_key("_popup") or request.POST.has_key("_popup")

    return render_to_response(
        'main/software_edit.html',
        {'form': form,
         'softwarepatch_formset': softwarepatch_formset,
         'add': True,
         'is_popup': is_popup,
        },
        context_instance = RequestContext(request)
    )