Django MultiWidget 电话号码字段

Posted

技术标签:

【中文标题】Django MultiWidget 电话号码字段【英文标题】:Django MultiWidget Phone Number Field 【发布时间】:2010-12-19 03:28:46 【问题描述】:

我想为电话号码输入创建一个字段,该字段具有 2 个文本字段(分别为 3、3 和 4 号),并带有常见的“(”“)”“-”分隔符。下面是我的字段和小部件代码,在初始渲染期间尝试迭代表单中的字段时出现以下错误(当 for 循环到达我的电话号码字段时发生):

渲染时发生异常:“NoneType”对象不可订阅

class PhoneNumberWidget(forms.MultiWidget):
    def __init__(self,attrs=None):
        wigs = (forms.TextInput(attrs='size':'3','maxlength':'3'),\
                forms.TextInput(attrs='size':'3','maxlength':'3'),\
                forms.TextInput(attrs='size':'4','maxlength':'4'))
        super(PhoneNumberWidget, self).__init__(wigs, attrs)

    def decompress(self, value):
        return value or None

    def format_output(self, rendered_widgets):
        return '('+rendered_widgets[0]+')'+rendered_widgets[1]+'-'+rendered_widgets[2]

class PhoneNumberField(forms.MultiValueField):
    widget = PhoneNumberWidget
    def __init__(self, *args, **kwargs):
        fields=(forms.CharField(max_length=3), forms.CharField(max_length=3), forms.CharField(max_length=4))
        super(PhoneNumberField, self).__init__(fields, *args, **kwargs)
    def compress(self, data_list):
        if data_list[0] in fields.EMPTY_VALUES or data_list[1] in fields.EMPTY_VALUES or data_list[2] in fields.EMPTY_VALUES:
            raise fields.ValidateError(u'Enter valid phone number')
        return data_list[0]+data_list[1]+data_list[2]

class AdvertiserSumbissionForm(ModelForm):
    business_phone_number = PhoneNumberField(required=True)

【问题讨论】:

您不只是使用 us.models.PhoneNumberField 和 us.forms.USPhoneNumberField 的任何原因?如果您有美国电话号码,非常方便。 docs.djangoproject.com/en/dev/ref/contrib/localflavor/… 知道回溯发生在哪里会很有用——即提供比那一行更多的细节。 参考@hughdbrown 提出的建议,Django-Localflavor 在 Django 1.5 中被移出,现在位于github.com/django/django-localflavor 【参考方案1】:

我认为 value_from_datadict() 代码可以简化为:


class USPhoneNumberMultiWidget(forms.MultiWidget):
    """
    A Widget that splits US Phone number input into three  boxes.
    """
    def __init__(self,attrs=None):
        widgets = (
            forms.TextInput(attrs='size':'3','maxlength':'3', 'class':'phone'),
            forms.TextInput(attrs='size':'3','maxlength':'3', 'class':'phone'),
            forms.TextInput(attrs='size':'4','maxlength':'4', 'class':'phone'),
        )
        super(USPhoneNumberMultiWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            return value.split('-')
        return [None,None,None]

    def value_from_datadict(self, data, files, name):
        values = super(USPhoneNumberMultiWidget, self).value_from_datadict(data, files, name)
        return u'%s-%s-%s' % values

MultiValueWidget 的 value_from_datadict() 方法已经做了以下事情:


    def value_from_datadict(self, data, files, name):
        return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]

【讨论】:

这很好用,但我在 Django 1.3 中的 value_from_datadict 函数中出现错误,因为 values 变量不是元组而是列表。我必须使用 tuple(values) 转换列表以使其正常工作。我相应地编辑了答案。【参考方案2】:

有时修复原始问题比重做所有问题更有用。您得到的错误“渲染时捕获异常:'NoneType' 对象不可订阅”有一个线索。当需要一个可下标的值时,有一个返回为 None(unsubscriptable) 的值。 PhoneNumberWidget 类中的解压缩函数可能是罪魁祸首。我建议返回 [] 而不是 None。

【讨论】:

【参考方案3】:

这使用widget.value_from_datadict() 格式化数据,因此无需子类化字段,只需使用现有的USPhoneNumberField。数据存储在数据库中,如 XXX-XXX-XXXX。

from django import forms

class USPhoneNumberMultiWidget(forms.MultiWidget):
    """
    A Widget that splits US Phone number input into three <input type='text'> boxes.
    """
    def __init__(self,attrs=None):
        widgets = (
            forms.TextInput(attrs='size':'3','maxlength':'3', 'class':'phone'),
            forms.TextInput(attrs='size':'3','maxlength':'3', 'class':'phone'),
            forms.TextInput(attrs='size':'4','maxlength':'4', 'class':'phone'),
        )
        super(USPhoneNumberMultiWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            return value.split('-')
        return (None,None,None)

    def value_from_datadict(self, data, files, name):
        value = [u'',u'',u'']
        # look for keys like name_1, get the index from the end
        # and make a new list for the string replacement values
        for d in filter(lambda x: x.startswith(name), data):
            index = int(d[len(name)+1:]) 
            value[index] = data[d]
        if value[0] == value[1] == value[2] == u'':
            return None
        return u'%s-%s-%s' % tuple(value)

以这样的形式使用:

from django.contrib.localflavor.us.forms import USPhoneNumberField
class MyForm(forms.Form):
    phone = USPhoneNumberField(label="Phone", widget=USPhoneNumberMultiWidget())

【讨论】:

【参考方案4】:

我接受了 Hughdbrown 的建议并修改了 USPhoneNumberField 来满足我的需要。我最初没有使用它的原因是它在数据库中将电话号码存储为 XXX-XXX-XXXX,我将它们存储为 XXXXXXXXXX。所以我放弃了 clean 方法:

class PhoneNumberField(USPhoneNumberField):
    def clean(self, value):
        super(USPhoneNumberField, self).clean(value)
        if value in EMPTY_VALUES:
            return u''
        value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
        m = phone_digits_re.search(value)
        if m:
            return u'%s%s%s' % (m.group(1), m.group(2), m.group(3))
        raise ValidationError(self.error_messages['invalid'])

【讨论】:

以上是关于Django MultiWidget 电话号码字段的主要内容,如果未能解决你的问题,请参考以下文章

在 Django 中仅渲染 MultiWidget 的一部分

Django子类化multiwidget - 使用自定义multiwidget重建帖子日期

如何使用 Django 的 MultiWidget?

Django 电话号码表单字段出错

如何将电话号码字段添加到 django UserCreationForm?

自定义 django 小部件 - decompress() arg 未填充