Django自定义表单验证最佳实践?

时间:2010-01-31 20:31:03

标签: django validation django-forms

我有一个包含5对位置和描述的表单。我有三组需要完成的验证

  • 您需要输入至少一个位置
  • 对于第一个位置,您必须有说明
  • 每对剩余的位置和描述

在阅读Django文档后,我想出了以下代码来进行这些自定义验证

def clean(self):
    cleaned_data = self.cleaned_data
    location1 = cleaned_data.get('location1')
    location2 = cleaned_data.get('location2')
    location3 = cleaned_data.get('location3')
    location4 = cleaned_data.get('location4')
    location5 = cleaned_data.get('location5')
    description1 = cleaned_data.get('description1')
    description2 = cleaned_data.get('description2')
    description3 = cleaned_data.get('description3')
    description4 = cleaned_data.get('description4')
    description5 = cleaned_data.get('description5')
    invalid_pairs_msg = u"You must specify a location and description"

    # We need to make sure that we have pairs of locations and descriptions
    if not location1:
        self._errors['location1'] = ErrorList([u"At least one location is required"])

    if location1 and not description1:
        self._errors['description1'] = ErrorList([u"Description for this location required"])

    if (description2 and not location2) or (location2 and not description2):
        self._errors['description2'] = ErrorList([invalid_pairs_msg])

    if (description3 and not location3) or (location3 and not description3):
        self._errors['description3'] = ErrorList([invalid_pairs_msg])

    if (description4 and not location4) or (location4 and not description4):
        self._errors['description4'] = ErrorList([invalid_pairs_msg])

    if (description5 and not location5) or (location5 and not description5):
        self._errors['description5'] = ErrorList([invalid_pairs_msg])

    return cleaned_data     

现在,它工作,但看起来真的很难看。我正在寻找一种更“Pythonic”和“Djangoist”(?)方式来做到这一点。提前谢谢。

2 个答案:

答案 0 :(得分:5)

您可以做的第一件事是简化您希望查看是否只填充两个字段中的一个的情况的测试。您可以通过以下方式实现逻辑xor

if bool(description2) != bool(location2): 

或者这样:

if bool(description2) ^ bool(location2):

我还认为如果你为每个字段单独实现一个干净的方法会更清楚,如the docs中所述。这样可以确保错误显示在右侧字段中,并且您只需引发forms.ValidationError而不是直接访问_errors对象。

例如:

def _require_together(self, field1, field2):
    a = self.cleaned_data.get(field1)
    b = self.cleaned_data.get(field2)
    if bool(a) ^ bool(b):
        raise forms.ValidationError(u'You must specify a location and description')
    return a

# use clean_description1 rather than clean_location1 since
# we want the error to be on description1
def clean_description1(self):
    return _require_together('description1', 'location1')

def clean_description2(self):
    return _require_together('description2', 'location2')

def clean_description3(self):
    return _require_together('description3', 'location3')

def clean_description4(self):
    return _require_together('description4', 'location4')

def clean_description5(self):
    return _require_together('description5', 'location5')

为了获得需要location1的行为,只需对该字段使用required=True即可自动处理。

答案 1 :(得分:0)

至少你可以减少一些代码。 'location1'和'description1'必需= True(正如TM和stefanw指出的那样)。然后,

def clean(self):
   n=5
   data = self.cleaned_data
   l_d = [(data.get('location'+i),data.get('description'+i)) for i in xrange(1,n+1)]
   invalid_pairs_msg = u"You must specify a location and description"

   for i in xrange(1,n+1):
      if (l_d[i][1] and not l_d[i][0]) or (l_d[i][0] and not l_d[i][1]):
         self._errors['description'+i] = ErrorList([invalid_pairs_msg])
   return data

虽然仍然很难看......