11

I have a model that looks like:

class MySchedule(models.Model):
  start_datetime=models.DateTimeField()
  name=models.CharField('Name',max_length=75)

With it comes its ModelForm:

class MyScheduleForm(forms.ModelForm):
  startdate=forms.DateField()
  starthour=forms.ChoiceField(choices=((6,"6am"),(7,"7am"),(8,"8am"),(9,"9am"),(10,"10am"),(11,"11am"),
      (12,"noon"),(13,"1pm"),(14,"2pm"),(15,"3pm"),(16,"4pm"),(17,"5pm"),
      (18,"6pm"
  startminute=forms.ChoiceField(choices=((0,":00"),(15,":15"),(30,":30"),(45,":45")))),(19,"7pm"),(20,"8pm"),(21,"9pm"),(22,"10pm"),(23,"11pm")))

  class Meta:
    model=MySchedule

  def clean(self):
    starttime=time(int(self.cleaned_data.get('starthour')),int(self.cleaned_data.get('startminute')))
    return self.cleaned_data

  try:
    self.instance.start_datetime=datetime.combine(self.cleaned_data.get("startdate"),starttime)

  except TypeError:
    raise forms.ValidationError("There's a problem with your start or end date")

Basically, I'm trying to break the DateTime field in the model into 3 more easily usable form fields -- a date picker, an hour dropdown, and a minute dropdown. Then, once I've gotten the three inputs, I reassemble them into a DateTime and save it to the model.

A few questions:

1) Is this totally the wrong way to go about doing it? I don't want to create fields in the model for hours, minutes, etc, since that's all basically just intermediary data, so I'd like a way to break the DateTime field into sub-fields.

2) The difficulty I'm running into is when the startdate field is blank -- it seems like it never gets checked for non-blankness, and just ends up throwing up a TypeError later when the program expects a date and gets None. Where does Django check for blank inputs, and raise the error that eventually goes back to the form? Is this my responsibility? If so, how do I do it, since it doesn't evaluate clean_startdate() since startdate isn't in the model.

3) Is there some better way to do this with inheritance? Perhaps inherit the MyScheduleForm in BetterScheduleForm and add the fields there? How would I do this? (I've been playing around with it for over an hours and can't seem to get it)

Thanks!

[Edit:] Left off the return self.cleaned_data -- lost it in the copy/paste originally

Cyclic
  • 163
  • 2
  • 8
  • In general, the ModelForm can contain whatever fields you want. It's just like a normal Form in that regard. The only concern is that you'll need to implement the initial data, the appropriate clean() methods and appropriate save() method if those fields don't exist in the model, since a ModelForm attempts to generate those things auto-magically using the model. – Cerin Jan 04 '14 at 01:28

4 Answers4

1
  1. If I were you, I would have used the customised Django-admin date/time widget(s) for entering date/time entries.

  2. Regarding form validation, make sure you pass the form associated with the request for it to show up form-based errors. (Sample code below)

  3. As for using inheritance, it would be a overkill for this use-case as it will not serve any purpose and it would be better to keep things simple.

Sample code:

if request.POST:
    form = MyScheduleForm(request.POST)
    if form.is_valid():
        # Specific stuff with the variables here
        pass
else:
    form = MyScheduleForm()
Community
  • 1
  • 1
sidhshar
  • 1,063
  • 1
  • 10
  • 18
  • Yep, I'm using a similar construct. The problem is that non-model fields never seem to get checked to see if they're blank, so if they are blank, they don't throw an exception. Fields that are in the model DO get checked, and I get an error passed back to my form if they are missing – Cyclic Jan 13 '11 at 01:54
  • There is more than it meets the eye. I can run the above code and it does get validated! – sidhshar Jan 13 '11 at 11:45
1

Ok, I think I figured it out:

As of Django 1.2, running is_valid() triggers MODEL validation on ModelForms. I had assumed that fields would be checked for blank values BEFORE hitting the model clean() function, so my clean function doesn't check for blank values or None types. Basically, my clean() in my model looks something like:

def clean(self):
  if self.start_datetime >  datetime.now():
        raise ValidationError('Start date can\'t be in the future')

So I suppose that mostly answer my question. However, I have 1 remaining question:

Is it best to check for blank values in the model clean(), or is there a better way to do this? Seems hackish to check for blanks in the model instead of in the ModelForm -- is the validation on the form field supposed to flag missing inputs on required fields?

Thanks for everyone's help.

Soviut
  • 88,194
  • 49
  • 192
  • 260
Cyclic
  • 163
  • 2
  • 8
  • Definitely use the `clean_field` methods on `ModelForm`. I'm very confused because when I reproduce your form, I do get a `ValidationError` for extra fields added to my ModelForm. I created a ModelForm, added a clean method just like yours, and Validation triggered.. Is there anything else to your ModelForm than you have shown? – Yuji 'Tomita' Tomita Jan 13 '11 at 03:34
  • I think the issue here (after messing around with it) -- if the field is blank, it never gets added to cleaned_data, and the clean_field method never runs. It then runs the form clean() and the model clean(). In my case, the model clean() caused a TypeError exception due to a comparison I was making in clean(), which caused my code to fail. I think this issue here (correct me if I'm wrong) -- blank values don't trigger exceptions, they just cause is_valid() to fail and they add an error to the error_list. Since I had another error (which actually DID throw an excpt), that caused my problem – Cyclic Jan 14 '11 at 04:44
0

1: I don't think it's wrong, because you have some very specific stuff going on there:

  • Specific time entries (noon, ending at 5PM..)
  • 15 minute increments for startminutes

2: Update: comment below says your field should be required=True by default. It's true, you should be getting a ValidationError with your form if the field is left blank.

Can you post the TypeError you're speaking about? Is it happening outside the clean() block? Because if you don't return cleaned_data from your clean function like in your example, your form won't have any data to work with even if it initially checks out by not raising any ValidationErrors.

Anyways, you can explore the clean_ methods for per field validation.

def clean_startdate(self):  
    if not self.cleaned_data['startdate']:
            raise forms.ValidationError("Must enter a start date")

http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#overriding-the-clean-method

3: Can you clarify here what you're trying to do with inheritance? It looks like your field definitions are very specific to this form, so it belongs right here in the MyScheduleForm. Inheritance is for re-using code : )

If you are looking to reuse this for multiple DateTimeFields, yes you can use form inheritance. You could define a ModelForm like you have now, subclass it, and override the parent's Meta as shown here in the docs to use it on multiple models: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#form-inheritance

I'd also check out how the django does its SplitDateTimeWidget (check the source): http://docs.djangoproject.com/en/dev/ref/forms/widgets/#django.forms.SplitDateTimeWidget

There are some other '3rd party' split date time widgets worth taking a look at on the interwebs too!

Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • required=True is the default setting for django form fields. – sidhshar Jan 12 '11 at 07:11
  • Good point, but then he should not be having the problem with #2. I wonder why that field is not validating? hmm – Yuji 'Tomita' Tomita Jan 12 '11 at 08:07
  • Thanks for your response -- here are a few responses to your questions: 1) I know that the default is required=True. The problem is, if the non-model fields (startdate, starthour, etc) are left blank, it doesn't throw an exception and return the "missing field" info to my form. I want it to check for blankness and balk if there is a blank field, but it seems to miss this until the blank field causes problems later in the clean() 2)I tried adding a clean_startdate() function, but it seems to never get called. The clean_ functions for the fields in my model DO get called, however. – Cyclic Jan 13 '11 at 01:50
  • SplitDateTimeWidget does exactly what I need, thanks for pointing it out – Cyclic Jan 13 '11 at 02:59
0

For form fields that may contain blank values, you must declare the field as follows:

start_datetime=models.DateTimeField(blank=True, null=True)

This tells the form that it can be blank, and that the database field can be null. That may fix that problem.

Why are you using a ModelForm if you're trying to include fields that are not a part of the Model? ModelForms are designed to quickly create forms that bind directly to your model. Of course they have various customizations, but changing the actual fields seems to me something that a regular Form is for.

Otherwise, if you just want to split the VIEW of the formm, not the form itself, create a custom widget to display the DateTime field, such as the SplitDateTimeWidget. Subclass it, and provide your CHOICES for the values of the drop down.

Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
  • -1 what if I want to use most of this functionality and customize a little bit? – Alvaro Jul 16 '14 at 16:36
  • @Alvaro I mentioned that in my answer. There are hooks for certain customisations, but adding a whole lot of form fields that don't map to model fields is *not* what model forms are for. – Josh Smeaton Jul 17 '14 at 12:29