Amin Ba
Amin Ba

Reputation: 2436

How to use model clean method in django and iterate over fields

I asked a question and, as I have read a little, I now can better express what I need: How to do model level custom field validation in django?

I have this model:

class StudentIelts(Model):

    SCORE_CHOICES = [(i/2, i/2) for i in range(0, 19)]

    student = OneToOneField(Student, on_delete=CASCADE)
    has_ielts = BooleanField(default=False,)
    ielts_listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    ielts_reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    ielts_writing = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    ielts_speaking = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )

and have this model form:

class StudentIeltsForm(ModelForm):

    class Meta:
        model = StudentIelts
        exclude = ('student')

    def clean(self):
        cleaned_data = super().clean()
        has_ielts = cleaned_data.get("has_ielts")

        if has_ielts:
            msg = "Please enter your score."
            for field in self.fields:
                if not self.cleaned_data.get(str(field)):
                    self.add_error(str(field), msg)

        else:
            for field in self.fields:
                self.cleaned_data[str(field)] = None
                self.cleaned_data['has_ielts'] = False

        return cleaned_data

What I am doing here is that checking if has_ielts is True, then all other fields should be filled. If has_ielts is True and even one field is not filled, I get an error. If has_ielts is False, an object with has_ielts=False and all other fields Null should be saved. I now want to do it on the model level:

class StudentIelts(Model):

    SCORE_CHOICES = [(i/2, i/2) for i in range(0, 19)]

    student = OneToOneField(Student, on_delete=CASCADE)
    has_ielts = BooleanField(default=False,)
    ielts_listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    ielts_reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    ielts_writing = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    ielts_speaking = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )

    def clean(self):
    # I do not know what to write in here

and have this model form:

class StudentIeltsForm(ModelForm):

    class Meta:
        model = StudentIelts
        exclude = ('student')

In the clean method of my model I want something with this logic(this is psedue code):

def clean(self):
    msg = "Please enter your score."

    if self.has_ielts:
        my_dic = {}
        for f in model_fields:
             if f is None:
                  my_dic.update{str(field_name): msg}
        raise ValidationError(my_dic)

How can I do this?
How can I get the same result as my modelform but at the model level?

Upvotes: 0

Views: 2820

Answers (1)

dirkgroten
dirkgroten

Reputation: 20692

You need to explicitly declare the fields that should be non-empty, otherwise you're cycling through fields that are not related to your clean method, like id and student. Someone might want to add a field later one that's not mandatory and wonder why it raises a validation error.

class StudentIelts(Model):

    # fields

    non_empty_fields = ['ielts_reading', ...]

    def clean(self):
        errors = {}
        if self.has_ielts:
            for field_name in self.non_empty_fields:
                if not getattr(self, field_name):
                    errors[field_name] = msg
        if errors:
            raise ValidationError(errors)

Upvotes: 1

Related Questions