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重建帖子日期