从哪里清除表单字段输入中的额外空格?

9

我刚刚发现Django不会自动清除表单字段输入中的额外空格,我认为我理解了这种做法的原因(“框架不应更改用户输入”)。

我知道如何使用Python的re库删除多余的空格:

#data = re.sub('\A\s+|\s+\Z', '', data)
data = data.strip()
data = re.sub('\s+', ' ', data)

问题是我应该在哪里做这个?大概应该在表单的一个干净阶段中进行,但是哪一个阶段呢?理想情况下,我想要清除所有字段中的额外空格。如果应该在clean_field()方法中完成,那么就意味着我必须有很多基本上做相同事情的clean_field()方法,这似乎有很多重复。
如果不是在表单的清洁阶段,那么可能是在表单所基于的模型中吗?

你可能还想考虑使用更简单的 strip 函数,而不是折腾正则表达式。 - Michael C. O'Connor
你可以使用 data = data.strip() 替换掉那个第一行(丑陋的)代码。 - juliomalegria
@ Michael, Julio - 谢谢!我原来以为strip只能去掉字符串末尾的空格...已经相应地修改了问题。 - Westerley
你的第二行代码可以使用一种常用方法来实现:data = ' '.join(data.split()) - mlissner
Python的strip()函数可以同时移除字符串开头和结尾的空格。@Westerley - Bryce
8个回答

11

我的方法借鉴自这里。但我使用了一个mixin而不是子类化django.forms.Form。这样我就可以同时与FormModelForm一起使用它。这里定义的方法覆盖了BaseForm_clean_fields方法。

class StripWhitespaceMixin(object):
    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))

            try:
                if isinstance(field, FileField):
                    initial = self.initial.get(name, field.initial)
                    value = field.clean(value, initial)
                else:
                    if isinstance(value, basestring):
                        value = field.clean(value.strip())
                    else:
                        value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self._errors[name] = self.error_class(e.messages)
                if name in self.cleaned_data:
                    del self.cleaned_data[name]

使用时,只需将mixin添加到您的表单中即可

class MyForm(StripeWhitespaceMixin, ModelForm):
    ...

还有,如果您想在保存没有表单的模型时修剪空白,可以使用以下mixin。默认情况下不验证没有表单的模型。当我基于从外部REST API调用返回的JSON数据创建对象时,我会使用它。

class ValidateModelMixin(object):
    def clean(self):
        for field in self._meta.fields:
            value = getattr(self, field.name)

            if value:
                # ducktyping attempt to strip whitespace
                try:
                    setattr(self, field.name, value.strip())
                except Exception:
                    pass

    def save(self, *args, **kwargs):
        self.full_clean()
        super(ValidateModelMixin, self).save(*args, **kwargs)

然后在您的models.py文件中

class MyModel(ValidateModelMixin, Model):
    ....

在我看来,这是最佳答案。谢谢! - mkoistinen
唯一缺少的是 super(ValidateModelMixin, self).clean(),如果应用它的模型已经有了一个 clean 方法。 - mkoistinen
1
StripWhitespaceMixin现在有问题了,因为较新版本的Django已经更改了_clean_fields方法的实现,因此它错过了基类中的某些行为。请参见我的答案,它不会有这个问题。 - spookylukey

9
创建一个自定义模型字段,这样你的自定义表单字段就会被自动使用。
class TrimmedCharFormField(forms.CharField):
    def clean(self, value):
        if value:
            value = value.strip()
        return super(TrimmedCharFormField, self).clean(value)

# (If you use South) add_introspection_rules([], ["^common\.fields\.TrimmedCharField"])
class TrimmedCharField(models.CharField):
    __metaclass__ = models.SubfieldBase

    def formfield(self, **kwargs):
        return super(TrimmedCharField, self).formfield(form_class=TrimmedCharFormField, **kwargs)

然后在你的模型中,只需将 django.db.models.CharField 替换为 TrimmedCharField


6

5
你可以使用 data = data.strip() 代替前面那两行(丑陋的)代码。 - juliomalegria
@Arthur - 在自定义的clean方法中,您是否还需要调用to_python()、validate()和run_validators()方法呢? - Westerley
2
cleaned_data = super(YourFormClass, self).clean(),因为第一行保持了父类的行为。 - Sean

4
请使用以下mixin:
class StripWhitespaceMixin(object):

    def full_clean(self):
        # self.data can be dict (usually empty) or QueryDict here.
        self.data = self.data.copy()
        is_querydict = hasattr(self.data, 'setlist')
        strip = lambda val: val.strip()
        for k in list(self.data.keys()):
            if is_querydict:
                self.data.setlist(k, map(strip, self.data.getlist(k)))
            else:
                self.data[k] = strip(self.data[k])
        super(StripWhitespaceMixin, self).full_clean()

将此作为mixin添加到您的表单中,例如:
class MyForm(StripWhitespaceMixin, Form):
    pass

这与pymarco的答案类似,但不涉及复制粘贴并修改Django代码(即_clean_fields方法的内容)。
相反,它重写full_clean,但在对输入数据进行一些调整后调用原始的full_clean方法。这使它不那么依赖于Django的Form类的实现细节,这些细节可能会发生变化(实际上自那个答案以来已经发生了变化)。

4
自Django 1.9起,您可以在表单定义的字段中使用strip关键字参数:strip

strip¶ New in Django 1.9.

If True (default), the value will be stripped of leading and trailing whitespace.
应该会得到类似这样的东西:
class MyForm(forms.Form):

    myfield = forms.CharField(min_length=42, strip=True)

由于其默认值为True这应该在django>=1.9版本中自动完成。

这也与RegexField相关。


0

在这种情况下,创建自己的表单字段可能很有用(听起来并不难)。在clean()方法中,您将删除额外的空格。

引用文档:

您可以轻松地创建自定义字段类。要做到这一点,只需创建一个django.forms.Field的子类。它唯一的要求是实现clean()方法,并且其__init__()方法接受核心参数(required、label、initial、widget、help_text)。

更多关于此的信息:https://docs.djangoproject.com/en/1.3/ref/forms/fields/#creating-custom-fields


这看起来非常有前途!它可以更接近源头解决问题。然而,我正在使用modelForms,因此要更改与每个模型字段关联的表单字段,我必须在表单规范中“重新声明”所有修改后的字段。 有没有什么办法可以避免这种情况?我是否需要自定义模型字段以使用修改后的表单字段? - Westerley

0
如果您想在项目中strip()每个CharField;最简单的方法可能是monkeypatch CharField的默认清理方法。
在: monkey_patch/__init__.py
from django.forms.fields import CharField

def new_clean(self, value):
    """ Strip leading and trailing whitespace on all CharField's """
    if value:
        # We try/catch here, because other fields subclass CharField. So I'm not totally certain that value will always be stripable.
        try:
            value = value.strip()
        except:
            pass
    return super(CharField, self).clean(value)

CharField.clean = new_clean

0
一种方法是指定自定义表单小部件,可以剥离空格:
>>> from django import forms
>>> class StripTextField(forms.CharField):
...     def clean(self,value):
...         return value.strip()
...         
>>> f = StripTextField()
>>> f.clean('  hello  ')
'hello'

然后在您的ModelForm中使用它:

class MyForm(ModelForm):
    strip_field = StripTextField()

    class Meta:
        model = MyModel

然而,最好的地方是在视图中进行此操作,在表单经过验证之后;如果您使用ModelForm,在执行任何插入到数据库或其他数据操作之前。

您始终可以创建自己的非ModelForm表单,并以这种方式控制字段和验证的每个方面。

ModelForm的验证添加了检查违反数据库约束的值;因此,如果该字段可以接受' hello '作为有效输入,则ModelFormis_valid()没有理由去除空格(因为它不会产生任意干净的逻辑,除了您提到的“框架不应更改用户的输入”)。


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