23

I have a problem with a model's clean() method and basic field validation. here's my model and the clean() method.

class Trial(models.Model):

    trial_start = DurationField()
    movement_start = DurationField()
    trial_stop = DurationField()


    def clean(self):
        from django.core.exceptions import ValidationError
        if not (self.movement_start >= self.trial_start):
            raise ValidationError('movement start must be >= trial start')
        if not (self.trial_stop >= self.movement_start):
            raise ValidationError('trial stop must be >= movement start')
        if not (self.trial_stop > self.trial_start):
            raise ValidationError('trial stop must be > trial start')

My clean() method checks whether certain values are in the correct range. If the user forget to fill out a field, e.g. movement_start, then I get an error:

can't compare datetime.timedelta to NoneType

I'm surprised that I get this error, since the original clean() function should be catching that missing entry (after all movement_start is a required field). So how can I the basic checking for missing values, and my custom check whether values are in certain ranges? Can this be done with model's clean() method, or do I need to use Forms?

EDIT1 to make it more clear: trial_start, movement_start and trial_stop are all required fields. I need to write a clean() method which first checks that all three fields have been filled out, and then, check whether the values are in a certain range.

The following code for example DOES NOT work, since trial_start might be empty. I want to avoid having to check for the existence of each field - django should do that for me.

class TrialForm(ModelForm):

    class Meta:
        model = Trial

    def clean_movement_start(self):
        movement_start = self.cleaned_data["movement_start"]
        trial_start = self.cleaned_data["trial_start"]
        if not (movement_start >= trial_start):
            raise forms.ValidationError('movement start must be >= trial start')
        return self.cleaned_data["movement_start"] 

EDIT2 The reason that I wanted to add this check to the model's clean() method is that objects that are created on the python shell, will automatically be checked for correct values. A form will be fine for views, but I need the value check also for the shell.

Benyamin Jafari
  • 27,880
  • 26
  • 135
  • 150
memyself
  • 11,907
  • 14
  • 61
  • 102

4 Answers4

11

I guess that's the way to go:

class TrialForm(ModelForm):

    class Meta:
        model = Trial

    def clean(self):
        data = self.cleaned_data
        if not ('movement_start' in data.keys() and 'trial_start' in data.keys()  and 'trial_stop' in data.keys()):
            raise forms.ValidationError("Please fill out missing fields.")

        trial_start = data['trial_start']
        movement_start = data['movement_start']
        trial_stop = data['trial_stop']

        if not (movement_start >= trial_start):
            raise forms.ValidationError('movement start must be >= trial start')

        if not (trial_stop >= movement_start):
            raise forms.ValidationError('trial stop must be >= movement start')

        if not (trial_stop > trial_start):
            raise forms.ValidationError('trial stop must be > trial start')

        return data

EDIT the downside of this approach is, that value checking will only work if I create objects through the form. Objects that are created on the python shell won't be checked.

Seonghyeon Cho
  • 171
  • 1
  • 3
  • 11
memyself
  • 11,907
  • 14
  • 61
  • 102
  • You can add `def save(self, *args, **kwargs):\n self.full_clean() \n return super().save(*args, **kwargs)` in your model. With this wherever you create your object (form, shell, test) the validation will be called – Hippolyte BRINGER Sep 15 '21 at 16:05
  • @HippolyteBRINGER Can you refer us to some documentation wrt calling self.full_clean() for wider validation? – Chandragupta Borkotoky Mar 03 '22 at 10:13
6

I know this is late, but to answer why this might be happening for people who end up here: There's no "original clean". The clean method is a hook for custom validation, so you are the one who provides its code. Not sure how OP was using the clean hook, but: After defining it you should be calling full_clean() over your models so that Django runs model validation in its entirety. See the details in the docs for the order in which it calls the different validation methods and such https://docs.djangoproject.com/en/1.11/ref/models/instances/#validating-objects (perhaps important to note: full_clean() isn't automatically called by a model's save() which is part of why when you use the shell and save straight away you'll be skipping the validation)

(note ModelForms also have various validation steps and will call a model's full_clean: https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#validation-on-a-modelform )

Lyserg Zeroz
  • 301
  • 2
  • 6
1

I'm struggling with a similar issue but with a ForeignKey. In your case, I would just check that the fields are not empty, and I would simplify your Boolean expressions:

class Trial(models.Model):

    trial_start = DurationField()
    movement_start = DurationField()
    trial_stop = DurationField()


    def clean(self):
        from django.core.exceptions import ValidationError
        if self.trial_start:
            if self.movement_start and self.movement_start < self.trial_start:
                raise ValidationError('movement start must be >= trial start')
            if self.trial_stop:
                if self.trial_stop <= self.trial_start:
                    raise ValidationError('trial stop must be > trial start')
                if self.movement_start:
                    if self.trial_stop < self.movement_start:
                        raise ValidationError('trial stop must be >= movement start')
Bobort
  • 3,085
  • 32
  • 43
1

You can add the following code in your model:

def save(self, *args, **kwargs):
    self.full_clean()
    return super().save(*args, **kwargs)

With this wherever you create your object (form, view, shell, test) the validation will be called.

Hippolyte BRINGER
  • 792
  • 1
  • 8
  • 30