Question

I have the following form:

class TripForm(IntranetForm):
    def __init__(self, *args, **kwargs):
        super(TripForm, self).__init__(*args, **kwargs)
        self.helper.layout = Layout(
            Field('reason', css_class='input-xlarge'),
            Field('departure_date'),
            Field('return_date'),
            Field(
                'date_and_time_of_first_appointment',
            ),
            Field(
                'date_and_time_final_appointment_finishes',
            ),
            Field(
                'departure_location',
                template='travel/related_departure.html',
            ),
            Field(
                'destination',
                template='travel/related_destination.html',
            ),
            Field('mode_of_transport', css_class='input-xlarge'),
            Field('seating_preference', css_class='input-xlarge'),
            Field('special_requests', css_class='input-xlarge'),
            FormActions(
                Submit(
                    'save_changes',
                    'Save changes',
                    css_class = "btn-primary",
                ),
                Button(
                    'cancel',
                    'Cancel',
                    onclick = 'history.go(-1);'
                ),
            ),
        )

    def clean(self):
        cleaned_data = super(TripForm, self).clean()
        departure_date = cleaned_data.get("departure_date")
        return_date = cleaned_data.get("return_date")
        a1 = cleaned_data.get("date_and_time_of_first_appointment")
        af = cleaned_data.get("date_and_time_final_appointment_finishes")

        if departure_date < datetime.date.today():
            msg = u"Must be a date in the future."
            self._errors["departure_date"] = self.error_class([msg])

            del cleaned_data["departure_date"]

        if a1.date() < departure_date:
            msg = u"Must be after the departure date."
            self._errors["date_and_time_of_first_appointment"] = self.error_class([msg])

            del cleaned_data["date_and_time_of_first_appointment"]

        if return_date < departure_date:
            msg = u"Must be after the departure date."
            self._errors["return_date"] = self.error_class([msg])

            del cleaned_data["return_date"]

        if af < a1 or af.date() > return_date:
            msg = u"Must be after the first appointment and before the return date."
            self._errors["date_and_time_final_appointment_finishes"] = self.error_class([msg])

            del cleaned_data["date_and_time_final_appointment_finishes"]

        return cleaned_data

    class Meta:
        model = Trip
        fields = (
            'reason',
            'departure_date',
            'return_date',
            'date_and_time_of_first_appointment',
            'date_and_time_final_appointment_finishes',
            'departure_location',
            'destination',
            'mode_of_transport',
            'seating_preference',
            'special_requests',
        )
        widgets = {
            'date_and_time_of_first_appointment': SplitDateTimeWidget(),
            'date_and_time_final_appointment_finishes': SplitDateTimeWidget(),
        }

Tests similar to the one below all work fine when I don't use the SplitDateTimeWidget.

def test_trip_form_with_good_data(self):
    form_data = {
        'reason': 'HE',
        'departure_date': timezone.datetime.today(),
        'return_date': timezone.datetime.today(),
        'date_and_time_of_first_appointment': timezone.now(),
        'date_and_time_final_appointment_finishes': timezone.now(),
        'departure_location': 1,
        'destination': 1,
        'mode_of_transport': 'TR',
        'seating_preference': 'Near the front',
        'special_requests': 'Make it nice',
    }
    form = TripForm(data=form_data)
    self.assertTrue(form.is_valid())

But the tests no longer run when the SplitDateTimeWidget is being used for the DateTime fields. They throw errors rather than failing, specifically:

AttributeError: 'NoneType' object has no attribute 'date'

whenever the a1 variable is accessed in my overridden form clean method.

I've looked at the relevant source code and couldn't see why using the SelectDateTimeWidget would necessitate different input at the command line or in tests, but clearly it does. How should my tests provide data in a way that the form clean method will be able to access?

EDIT: Taking my cue from the rendered html I've also tried getting separate dates and times from fields like date_and_time_of_first_appointment_0 and date_and_time_of_first_appointment_1 and combining them before comparison in the clean method but to no avail.

Was it helpful?

Solution

I solved this. The rendered html was indeed telling me what to do but I put my solution in the wrong place.

Instead of looking for separate fields in the clean method, which is what I originally tried, you need to put them in your test data. Django will have combined them by the time the data is passed to clean(). My tests now look something like:

def test_trip_form_with_good_data(self):
    form_data = {
        'reason': 'HE',
        'departure_date': timezone.datetime.today(),
        'return_date': timezone.datetime.today(),
        'date_and_time_of_first_appointment_0': timezone.now().date(),
        'date_and_time_of_first_appointment_1': timezone.now().time(),
        'date_and_time_final_appointment_finishes_0': timezone.now().date(),
        'date_and_time_final_appointment_finishes_1': timezone.now().time(),
        'departure_location': 1,
        'destination': 1,
        'mode_of_transport': 'TR',
        'seating_preference': 'Near the front',
        'special_requests': 'Make it nice',
    }
    form = TripForm(data=form_data)
    self.assertTrue(form.is_valid())

And everything passes again.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top