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

4

我有一个表单,包含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个回答

5

首先,你可以简化测试流程以验证只有两个字段中的一个被填写的情况。你可以这样实现逻辑 xor:

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

或者这样做:
if bool(description2) ^ bool(location2):

我认为,如果你按照文档中所解释的,为每个字段分别实现一个清洗方法,会更加清晰明了。这样可以确保错误信息出现在正确的字段上,并且只需引发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,系统会自动处理。

我在Django方面有些新手,您能详细说明一下创建装饰器的作用吗?(指向正确方向的链接也会有所帮助) - GrumpyCanuck
实际上,装饰器更多的是Python的东西,而不是Django的东西。但是,经过进一步研究,我并不认为装饰器会比制作一个帮助方法更简单。我已经更新了我的答案以展示一个例子。 - TM.

0

至少可以减少一些代码。将'location1'和'description1'的Required属性设置为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

尽管如此仍然很丑陋...


将location1和description1的required属性设置为True,可以省略中间部分(不要忘记调整xranges)。 - stefanw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接