6

I have a view where I validate date and time on form submit making sure the date + time are not past. Date and Time are two separate fields. It works, but I know its wrong way of doing it and the date+time should be validated in Django Forms.

This is in my view.py

(Probably not done the right way but it works)

my_date = request.session['reservationdate'] #in "mm/dd/yyyy" format
my_time = request.session['reservationtime'] #in "hh:mm" format
my_date_time = (my_date + ' ' + my_time + ':00') #convert to "mm/dd/yyyy hh:mm:ss"
my_date_time = datetime.strptime(my_date_time, '%m/%d/%Y %H:%M:%S') #convert to valid datetime
if datetime.now() <= my_date_time:
    #do this
else:
     ...

now my goal is to have something like the above in Django forms:

class MyForm(forms.ModelForm):  

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)

        self.fields['my_date'].required = True
        self.fields['my_time'].required = True
        ...

    def clean_my_date(self):
        my_date = self.cleaned_data['my_date']
        my_time = self.cleaned_data['my_time']
        my_date_time = (my_date + ' ' + my_time + ':00')
        my_date_time = datetime.strptime(my_date_time, '%m/%d/%Y %H:%M:%S')
        if datetime.now() <= my_date_time:
            raise forms.ValidationError(u'Wrong Date!')
        return my_date

    class Meta:
        model = MyModel
        fields = ['my_date', 'my_time', ...]

Edit:

working code:

def clean_my_time(self):
   my_date = self.cleaned_data['my_date']
   my_time = self.cleaned_data['my_time']
   my_date_time = ('%s %s' % (my_date, my_time))
   my_date_time = datetime.strptime(my_date_time, '%Y-%m-%d %H:%M:%S')
   if datetime.now() >= my_date_time:
        raise forms.ValidationError(u'Wrong Date or Time! "%s"' % my_date_time)
   return my_time

Thanks to all for the help especially xyres for his work and for being patient with me!

John R Perry
  • 3,916
  • 2
  • 38
  • 62
WayBehind
  • 1,607
  • 3
  • 39
  • 58
  • There's actually no need to define `__init__` method in your forms. But where is the problem? – xyres Dec 18 '14 at 18:21
  • Its a `KeyError`. its complaining about the `my_time = self.cleaned_data['my_time']` – WayBehind Dec 18 '14 at 18:44
  • Try using `my_time = self.cleaned_data.get('my_time')`. – xyres Dec 18 '14 at 18:56
  • Thanks. It is accept that however now the next line is returning this error: `unsupported operand type(s) for +: 'datetime.date' and 'str'` – WayBehind Dec 18 '14 at 19:04
  • I understand but it is all that is in the traceback: 'TypeError at /url/url/ ... unsupported operand type(s) for +: 'datetime.date' and 'str''. APparently the use of the `+` is not allowed in forms. Any other way how to combine two fields in forms? – WayBehind Dec 18 '14 at 19:22
  • Well, you could use `my_date_time = ('%s %s:00' % (my_date, my_time))`. – xyres Dec 18 '14 at 19:43
  • But that is not the actual problem. When you were using `cleaned_data['my_time']`, you were getting `KeyError` because there is no field named `my_time` in your form. When you used `cleaned_data.get(...)` `KeyError` is not raise, because `get()` method returns an empty string when field is not there. – xyres Dec 18 '14 at 19:43
  • You are correct, the `my_time' is returning 'NONE'. So the question is how can I used the 'my_time' value that is being passed from the form? – WayBehind Dec 18 '14 at 19:56
  • Is there a field for `my_time` in your HTML template? When you visit the page can you see the field? Can you see `` in that page's source code? That is all I can say without looking at your templates. – xyres Dec 18 '14 at 20:07
  • As you can see from my OP the `my_time` field is there and is being correctly validated by the Djano Forms. Are you sure the value `self.cleaned_data['my_time']` is OK to use in there? – WayBehind Dec 18 '14 at 20:38
  • Yes, if you know for sure that the field is there in the form, then there should be no problem with `self.cleaned_data['my_time']`. And yes I'm sure because I've seen it in docs. [See this question](http://stackoverflow.com/questions/7592552/difference-between-cleaned-data-and-cleaned-data-get-in-django). – xyres Dec 18 '14 at 22:47

2 Answers2

6

You are trying to validate over multiple fields. This point is well covered by the django documentation, see https://docs.djangoproject.com/en/1.7/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other.

The job has to be done in the clean method. Assuming django 1.7, your code could look like

class MyForm(forms.ModelForm):  

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)

        self.fields['my_date'].required = True
        self.fields['my_time'].required = True
        ...

    def clean(self):
        cleaned_data = super(MyForm, self).clean()
        # here all fields have been validated individually,
        # and so cleaned_data is fully populated
        my_date = cleaned_data.get('my_date')
        my_time = cleaned_data.get('my_time')
        if my_date and my_time:
            my_date_time = (my_date + ' ' + my_time + ':00')
            my_date_time = datetime.strptime(my_date_time, '%m/%d/%Y %H:%M:%S')
            if datetime.now() <= my_date_time:
                msg = u"Wrong Date time !"
                self.add_error('my_date', msg)
                self.add_error('my_time', msg)
        return cleaned_data

    class Meta:
        model = MyModel
        fields = ['my_date', 'my_time', ...]
  • Thanks for the feedback. It returned the same error as my OP. I think with a little tweaking your code would work too! – WayBehind Dec 19 '14 at 15:15
4

I'll try to answer one last time. Instead of doing def clean_my_date(...), do this:

def clean_my_time(self):
    # rest of your code remains same

If the above solution doesn't work, try this answer.

Update

Since the above code worked, I feel I should try and explain why and how.

Let's look at the order of fields in your form

fields = ['my_date', 'my_time', ...]

As, you can see my_time field comes after my_date field. So, when your code was like

def clean_my_date(self) 

the clean() method of your form gets called and it returns a dictionary object called cleaned_data. This cleaned_data dict has all the keys i.e. fields of your form upto my_date field. Any field that is after my_date won't be in cleaned_data. As, my_time field is after my_date field, it was not in cleaned_data. That is why you got a KeyError.

After changing your code to

def clean_my_time(self) 

the clean() method returned cleaned_data with all the fields upto my_time. As my_date comes before my_time, it is, therefore, present in cleaned_data. Hence, no error.

So it depends on the order of your form fields. If you want to validate two fields together, do it in the clean_my_field(self) method of the field that comes later in order. The answer posted by Jérôme Thiard is also a good alternative.

Community
  • 1
  • 1
xyres
  • 20,487
  • 3
  • 56
  • 85
  • 1
    Thanks again for your help and your patience! With little tweaking I was able to solve it. Working code in my OP. Thanks again! – WayBehind Dec 19 '14 at 15:13
  • @WayBehind I'm glad I was able to help. :) – xyres Dec 19 '14 at 15:17
  • 1
    @WayBehind In case you run into the same problem again, I've updated my answer with some explanation. – xyres Dec 19 '14 at 16:04
  • Thanks for the detailed explanation. I didnt realize the Django form would not receive all data from the online form. I'm still fairly new to Django and Python as I came from many years of ColdFusion coding where things works just in different way and CF is not as sensitive to a "dirty" code. Thanks again! Much appreciated! – WayBehind Dec 19 '14 at 18:43