Alex
Alex

Reputation: 198

"This field cannot be null" error in a django 1.5 ModelForm

I managed to create a ModelForm that basically insert an Alert object in DB linked to an Application on an Environment that will send mail to a Contact (managed with ForeignKey). My form (based on a CreateView class-based form) is composed of 3 fields:

The CreateView is mixed-in with some AJAX logic to make the submit thing overall dynamic (i.e. drawing tooltips for errors without reloading the page next to each field instead of having a big error above the form).

The logic of the ModelForm is to:

  1. Check if the application already exists in DB. Otherwise it raises a ValidationError
  2. Check if the contact mail address exists in DB. Otherwise it creates it
  3. Check if the resulting Alert object to create already exists in DB. If it exists it raises a ValidationError.

Everything works fine so far except when I want to submit my form with a non existing contact mail address, the ModelForm raises an "This field cannot be null" validation error.

I really didn't find what I'm doing wrong since I use get_or_create() method in clean_contact() method of my ModelForm to insert this contact if needed then return the resulting object to update self.cleaned_data dictionary. Worst thing is that when I submit a second time the form without changing any field everything runs smooth (no more validation error)...

When I submit the form with an existing mail address in database everything is working fine at the first form submit.

I'd really appreciate if you guys can help to point out what is wrong in my code and why this error is raised whereas every POSTed data is correct.

However I've a little doubt about the AJAX mixin with my CreateView since maybe it could be that when a mail address is not known, get_or_create() create and returns it but - I can't imagine why - the creation of the Alert object could not yet reference the newly created Contact object. This could explain why a second submit works... I'm sure you guys will get the final word on this :-)

Below different application parts involved in the aforementioned issue. I voluntarily removed some Model fields that are not used for this application as well as LoginRequiredMixin inherited from my CreateView.

Again I'd really appreciate your help on that and thank you in advance for every piece of advice.

Models

class UmsAlerting(models.Model):
    alert_id = models.IntegerField(primary_key=True, editable=False)
    appli = models.ForeignKey('UmsApplication')
    env = models.ForeignKey('UmsEnvironment')
    contact = models.ForeignKey('UmsContacts')
    class Meta:
        db_table = 'ums_alerting'
    def __unicode__(self):
        return u'Alert_Id %d on %s(%s)' %(self.alert_id, self.appli.trigram_ums, self.env.env_name)


class UmsApplication(models.Model):
    appli_id = models.IntegerField(primary_key=True)
    trigram_ums = models.CharField(max_length=4L)
    class Meta:
        db_table = 'ums_application'


class UmsContacts(models.Model):
    contact_id = models.IntegerField(primary_key=True)
    mail_addr = models.CharField(max_length=100L)
    class Meta:
        db_table = 'ums_contacts'


class UmsEnvironment(models.Model):
    env_id = models.IntegerField(primary_key=True)
    env_name = models.CharField(max_length=5L)
    class Meta:
        db_table = 'ums_environment'

    def __unicode__(self):
        return self.env_name

ModelForm

class AlertForm(ModelForm):
    class Meta:
        model = UmsAlerting
        exclude = ('custom_rule')

    appli = forms.CharField(required=True, max_length=3)
    env = forms.ModelChoiceField(required=True,
                                 queryset=UmsEnvironment.objects.all())
    contact = forms.EmailField(required=True)

    def clean_appli(self):
        data = self.cleaned_data['appli']

        try:
            UmsApplication.objects.get(trigram_ums=data)
        except ObjectDoesNotExist:
            msg = 'Trigram must be known and valid.'
            self._errors['appli'] = self.error_class([msg])
            raise forms.ValidationError(msg)

        return UmsApplication.objects.get(trigram_ums=data)

    def clean_contact(self):
        data = self.cleaned_data['contact']
        c, created = UmsContacts.objects.get_or_create(mail_addr=data)

        return c


    def clean(self):
        cleaned_data = super(AlertForm, self).clean()
        app = cleaned_data.get('appli')
        contact = cleaned_data.get('contact')
        env = cleaned_data.get('env')

        # Do not insert a new alert if it already exists
        if UmsAlerting.objects.filter(appli=app, env=env, contact=contact).count() > 0:
            msg = 'Alert is already configured.'
            self._errors['contact'] = self.error_class([msg])
            raise forms.ValidationError(msg)

        # Return the parent's clean method finally
        return cleaned_data

CreateView

class AlertView(LoginRequiredMixin, AjaxResponseMixin, CreateView):
     template_name = 'tools/alert_form.html'
     form_class = AlertForm
     success_url = reverse_lazy('alerts_configure')

AjaxResponseMixin

class AjaxResponseMixin(object):
     def render_to_json_response(self, context, **kwargs):
         data = json.dumps(context)
         kwargs['content_type'] = 'application/json'
         return HttpResponse(data, **kwargs)

     def form_invalid(self, form):
         response = super(AjaxResponseMixin, self).form_invalid(form)
         if self.request.is_ajax():
             return self.render_to_json_response(form.errors, status=400)
         else:
             return response

     # Not really useful actually (yet)
     def form_valid(self, form):
         response = super(AjaxResponseMixin, self).form_valid(form)
         if self.request.is_ajax():
             return self.render_to_json_response(json.dumps({}))
         else:
             return response

Upvotes: 1

Views: 3806

Answers (1)

Alex
Alex

Reputation: 198

Answer is described here

In a nutshell the UmsContacts model is using an IntegerField so it has to be specified to create a new object like this one even if the auto-increment is set on the database table. The solution was to change it to AutoField as well as to return modified self.cleaned_data['contact'] in clean_contact method.

Upvotes: 2

Related Questions