KeyError when selecting date in the past

Question:

I am making a members management app. I have a activation form and I wrote a script to check if date is valid (clean_start_date & clean_end_date). ie todays date or greater. Script is working fine (if I select today’s date or greater). However, if I selected an old date only end_date works fine, start_date Throws KeyError.

forms.py

class ActiveMemberForm(ModelForm):
    
    def __init__(self, *args, **kwargs):
        super(ActiveMemberForm, self).__init__(*args, **kwargs)
        pass
    
    class Meta:
        model = ActiveMember
        fields = (
            'member',
            'start_date',
            'end_date',
            'status',
        )
        widgets = {
            'start_date': widgets.DateInput(attrs={'type': 'date'}),
            'end_date': widgets.DateInput(attrs={'type': 'date'}),
        }
  
    def clean_start_date(self):
        start_date = self.cleaned_data['start_date']
        if start_date < timezone.now().date():
            raise ValidationError('Please enter a valid start date!')
        return start_date

        
    def clean_end_date(self):
        #start_date = self.cleaned_data['start_date']
        end_date = self.cleaned_data['end_date']
        if end_date < timezone.now().date() or end_date < self.clean_start_date():
            raise ValidationError('Please enter a valid end date!')
        return end_date

models.py

class ActiveMember(models.Model):
    member = models.OneToOneField(Member, on_delete=models.CASCADE, related_name='is_member')
    start_date = models.DateField(default=django.utils.timezone.now)
    end_date = models.DateField(default=django.utils.timezone.now)
    status = models.CharField(max_length=2, choices=(('1','Active'), ('2','Inactive')), default = '1', blank=True, null=True)
    
    def __str__(self): 
        return str(f"{self.member}")

Console Error Message

Internal Server Error: /activate/
Traceback (most recent call last):
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvlibsite-packagesdjangocorehandlersexception.py", line 55, in inner
    response = get_response(request)
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvlibsite-packagesdjangocorehandlersbase.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvdjango_projectmembersviews.py", line 130, in activate
    if form.is_valid():
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvlibsite-packagesdjangoformsforms.py", line 205, in is_valid
    return self.is_bound and not self.errors
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvlibsite-packagesdjangoformsforms.py", line 200, in errors
    self.full_clean()
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvlibsite-packagesdjangoformsforms.py", line 437, in full_clean
    self._clean_fields()
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvlibsite-packagesdjangoformsforms.py", line 452, in _clean_fields
    value = getattr(self, "clean_%s" % name)()
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvdjango_projectmembersforms.py", line 58, in clean_end_date
    if end_date < timezone.now().date() or end_date < self.clean_start_date():
  File "C:UserstimmehsourcePython ProjectsDjango Projectsenvdjango_projectmembersforms.py", line 49, in clean_start_date
    start_date = self.cleaned_data['start_date']
KeyError: 'start_date'
[12/Aug/2022 14:55:53] "POST /activate/ HTTP/1.1" 500 89452

KeyError Debug Screenshot
enter image description here

Asked By: wormy505

||

Answers:

The error you get is because the method clean_start_date() is called on form validation, and this:

if start_date < timezone.now().date():
            raise ValidationError('Please enter a valid start date!')

Excludes field from data validation, making this form invalid. This is why probably you don’t get data in self.cleaned_field['start_date']

Also I don’t really get why you call clean_start_date twice – this method is called once in invisible way by form, second by you. When you write clean_FIELD_NAME methods they are called directly by Django form logic.

So… the simple, and most obvious way to fix your issue is to write clean_end_date() method correctly, without calling clean methods:

        start_date = self.cleaned_data['start_date']
        end_date = self.cleaned_data['end_date']
        # not necessary data clean on start date field
        # if end_date < timezone.now().date() or end_date < self.clean_start_date():
        if end_date < timezone.now().date() or end_date < start_date:
            raise ValidationError('Please enter a valid end date!')
        return end_date

Update – the correct way of cleaning multiple field data with possible raises is (I did not noticed the dict value call…):

    def clean(self):
        start_date = self.cleaned_data['start_date']
        end_date = self.cleaned_data['end_date']
        if start_date < timezone.now().date():
            raise ValidationError('Please enter a valid start date!')
        if end_date < timezone.now().date() or end_date < start_date:
            raise ValidationError('Please enter a valid end date!')
        return self.cleaned_data