在Django ModelForm中添加不在模型中的字段

11 投票
4 回答
8835 浏览
提问于 2025-04-16 09:49

我有一个模型,长得像这样:

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

它还有一个对应的表单模型:

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")

简单来说,我想把模型里的日期时间字段拆分成三个更容易使用的表单字段——一个日期选择器,一个小时下拉框,还有一个分钟下拉框。然后,在我得到这三个输入后,我会把它们重新组合成一个日期时间格式,并保存到模型里。

我有几个问题:

1) 这样做是不是完全错误的方向?我不想在模型里为小时、分钟等创建字段,因为这些基本上只是中间数据,所以我想找个办法把日期时间字段拆分成子字段。

2) 我遇到的困难是,当开始日期字段是空的时候——似乎它从来没有被检查是否为空,结果在程序期待一个日期的时候却得到了None,导致抛出一个类型错误。Django在哪里检查空输入,并抛出最终回到表单的错误?这是我的责任吗?如果是的话,我该怎么做,因为它并不会评估clean_startdate(),因为开始日期不在模型里。

3) 有没有更好的办法用继承来实现这个?也许可以在BetterScheduleForm中继承MyScheduleForm,然后在那儿添加字段?我该怎么做?(我已经试了一个多小时,还是搞不定)

谢谢!

[编辑:] 忘记了返回self.cleaned_data——最开始复制粘贴的时候丢掉了

4 个回答

0

1: 我觉得这没什么错,因为你这里有一些非常具体的内容:

  • 特定的时间输入(中午,结束于下午5点...)
  • 开始时间是15分钟为一个单位

2: 更新:下面的评论提到你的字段默认应该是 required=True。确实如此,如果这个字段留空,你的表单应该会出现 ValidationError 错误。

你能把你提到的 TypeError 发出来吗?这个错误是在 clean() 这个块之外发生的吗?因为如果你在清理函数中没有像你示例那样返回 cleaned_data,那么即使最初没有抛出任何 ValidationErrors,你的表单也没有数据可以使用。

无论如何,你可以查看 clean_ 方法来进行每个字段的验证。

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: 你能在这里澄清一下你想用继承做什么吗?看起来你的字段定义非常特定于这个表单,所以它就应该放在 MyScheduleForm 里。继承是为了重用代码 : )

如果你想把这个用于多个 DateTimeField,是的,你可以使用表单继承。你可以像现在这样定义一个 ModelForm,然后继承它,并重写父类的 Meta,就像文档中展示的那样,这样可以在多个模型上使用: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#form-inheritance

我还建议你看看 Django 是如何实现它的 SplitDateTimeWidget 的(查看源代码): http://docs.djangoproject.com/en/dev/ref/forms/widgets/#django.forms.SplitDateTimeWidget

网上还有一些其他的“第三方”分割日期时间小部件,值得一看!

1

好的,我想我明白了:

从 Django 1.2 开始,运行 is_valid() 会在 ModelForms 上触发模型的验证。我原本以为在调用模型的 clean() 函数之前,字段会先检查是否为空,所以我的 clean 函数没有检查空值或 None 类型。基本上,我模型中的 clean() 函数看起来像这样:

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

所以我想这基本上回答了我的问题。不过,我还有一个问题:

在模型的 clean() 函数中检查空值是最好的做法吗,还是有更好的方法?在模型中检查空值感觉有点不太妥当,难道不是应该在 ModelForm 中进行验证,以标记必填字段的缺失输入吗?

感谢大家的帮助。

1
  1. 如果我是你,我会使用定制的Django-admin日期/时间控件来输入日期和时间。

  2. 关于表单验证,确保你传递与请求相关的表单,这样才能显示基于表单的错误信息。(下面有示例代码)

  3. 至于使用继承,这种情况用上继承就有点多余了,因为它没有什么实际意义,保持简单更好。

示例代码:

if request.POST:
    form = MyScheduleForm(request.POST)
    if form.is_valid():
        # Specific stuff with the variables here
        pass
else:
    form = MyScheduleForm()

撰写回答