django custom form validation with foreign keys

class Question(models.Model):

class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name=“choices”)

The choice model is TabularInline with Question on admin.
When saving this form, want to add validation check that if no. of choices is less than 2, it should raise form Validation error. Ideas on how can I do it?

When a django form is saved, it goes through a default cleaning/sanitation process to assure the saved fields meet the constraints set forth in the models.py file. By default, this function is called ‘clean’ inside a custom form class.

For example:

class CustomForm(forms.ModelForm):
     def clean(self):
           # do your form validation here

     class Meta:
          ...

If you want to assure the number of choices cannot be less than two, you can write a custom clean function that does the server-side validation for you.

class CustomForm(forms.ModelForm):
     def clean_choices(self):  
     # a clean function for each form field is automatically generated, you just have to override it
      choices = self.cleaned_data['choices']
      if NUM_OF_CHOICES < 2:
         raise forms.ValidationError(...)
      return choices     

     class Meta:
          ...

Hope this helps :slight_smile:

class QuestionForm(forms.ModelForm):
def clean_choices(self):

Tried this already but doesn’t work. Debugger doesn’t come up there.
Maybe because choices is not a field of Question model but rather a related name to Choice model.

Flow which I’ve noticed - Question is created first, then for every choice Choice model is called and a backlinking to Question is created.

If that’s the case, I would suggest looking into how to override the init function of the class you’re using. You should be able to prepopulate the choices of this form field you’re using. From there, once you render this field into your template, you can grab the submitted data through the GET or POST request dictionary and apply update your models accordingly. Not on a computer right now so I can’t easily post examples.

You referenced using the admin in your first post - have you specified your custom form in your ModelAdmin specification? (It might be best if you posted your admin.py file for these two classes.)

Also, when posting code here, please enclose it between lines consisting only of three backtick (`) characters. In other words, you’ll have a line of ```, followed by your code, followed by another line of ```. Make sure you use the backtick - ` and not the apostrophe - ’

class ChoiceAdmin(admin.TabularInline):
    model = Choice


class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = '__all__'

    def clean_choices(self):
        # do something


class QuestionAdmin(admin.ModelAdmin):
    inlines = [ChoiceAdmin, ]
    form = QuestionForm

This is an interesting issue. The function clean_<fieldname> isn’t going to do you any good, because the inline formset is not a field in that form. It’s an instance of a model formset. There is also the issue that the base class needs to be saved before the formset can be saved because the formset needs the base model pk before those instances can be saved.

You could try working along these lines -
in admin.py:

class ChoiceAdmin(admin.TabularInline):
    model = Choice
    formset  = forms.MyInlineFormSet

in forms.py

class MyInlineFormSet(forms.BaseInlineFormSet):
    def clean(self):
        super().clean()
        number_of_entries = len(self.cleaned_data) - len(self.deleted_forms)
        if number_of_entries < 2:
            # Do something because not enough entries are present

What I don’t know at this point is whether or not the base class has been saved.

Ken

1 Like

Thanks a lot Ken, that worked !!! :slightly_smiling_face: