I have a bytestring that i need to store.
Since Django does not support BlobFields I thought I'd create my own Base64Field,
that encodes and decodes to base64 upon interaction with the db.
So I overrode (?) the to_python and get_db_prep_save methods for that exact purpose.
Problem is that to_python gets called in various different scenarios, not just once and
there is no way to tell if the string is already decoded or not. If it is already decoded,
an error obviously occurs.
What solutions are there for my dilema?
Possible solutions that seem ugly to me: try except the decoding process, return value if decoding fails, using an instance variable only allow for 1 to_python (this seems even worse)
You can use some kind of PickledObjectField.
class PickledObjectField(models.Field):
__metaclass__ = models.SubfieldBase
marker_re = re.compile(r'^T\[(?P<type>\w+)\](?P<value>.*)$', re.DOTALL)
markable_types = dict((t.__name__, t) for t in (str, int, unicode))
def __init__(self, *args, **kwargs):
self.compress = kwargs.pop('compress', True)
self.protocol = kwargs.pop('protocol', 2)
kwargs.setdefault('null', True)
kwargs.setdefault('editable', False)
super(PickledObjectField, self).__init__(*args, **kwargs)
def generate_type_marked_value(self, value):
return PickledObject(u"T[%s]%s" % (type(value).__name__, value))
def read_marked_value(self, value):
m = self.marker_re.match(value)
if m:
marker = m.group('type')
value = m.group('value')
if marker in self.markable_types:
value = self.markable_types[marker](value)
return value
def get_default(self):
if self.has_default():
if callable(self.default):
return self.default()
return self.default
return super(PickledObjectField, self).get_default()
def to_python(self, value):
if value is not None:
try:
if value.startswith("T["):
value = self.read_marked_value(value)
else:
value = dbsafe_decode(value, self.compress)
except:
if isinstance(value, PickledObject):
raise
return value
def get_db_prep_value(self, value):
if value is not None and not isinstance(value, PickledObject):
if type(value).__name__ in self.markable_types and not (isinstance(value, basestring) and len(value
) > MAX_MARKABLE_STRING_LENGTH):
value = unicode(self.generate_type_marked_value(value))
else:
value = unicode(dbsafe_encode(value, self.compress))
return value
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
def get_internal_type(self):
return 'TextField'
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type not in ['exact', 'in', 'isnull']:
raise TypeError('Lookup type %s is not supported.' % lookup_type)
return super(PickledObjectField, self).get_db_prep_lookup(lookup_type, value)
Related
I have the following method declaration:
def method(alpha, size=10, logged_in=False, interactions=False):
if not isinstance(logged_in, bool):
logged_in = str(logged_in).lower() in ['true']
if not isinstance(interactions, bool):
interactions = str(interactions).lower() in ['true']
The if statements are needed because, for example, interactions would be falsely set to True if I pass interaction="False" as a parameter (so, I pass a String). While the above does the trick, I wonder if there is a more elegant way to achieve it (note that I have a lot of boolean parameters, beside logged_in and interactions, and I also have some int and float parameters. How could decorators be used here? Could one decorator be used for all bool parameters, and how could I make a general one (i.e., supporting bool, float, int, string)? I found the answer to this question potentially useful, but still wanted to check if there are better ways.
You could try something like this.
def bool_fixer(func):
table = {"true": True, "false": False}
def wrapper(*args, **kwargs):
for key, val in kwargs.items():
if isinstance(val, str) and val.lower() in table:
kwargs[key] = table[val.lower()]
return func(*args, **kwargs)
return wrapper
#bool_fixer
def method(...):
do something
after the decorator does its magic then all of the string "true" and "false" will turn into their python bool equivalent and you can skip all the checking in the method function.
If you want it to cover ints and floats too:
import re
def converter(func):
patterns = {r"\d*": int, r"\d*?\.\d*": float}
table = {"true": True, "false": False}
def wrapper(*args, **kwargs):
for key, val in kwargs.items():
if isinstance(val, str):
if val.lower() in table:
kwargs[key] = table[val.lower()]
continue
for pattern in patterns:
if re.match(pattern, val):
kwargs[key] = patterns[pattern](val)
return func(*args, **kwargs)
return wrapper
or
def converter(func):
table = {"true": True, "false": False}
def wrapper(*args, **kwargs):
for key, val in kwargs.items():
if isinstance(val, str):
if val.lower() in table:
kwargs[key] = table[val.lower()]
elif val.isdigit():
kwargs[key] = int(val)
elif re.match(r"\d*?\.\d*", val):
kwargs[key] = float(val)
return func(*args, **kwargs)
return wrapper
This decorator should reliably handle bools, ints and floats. As written this will modify keyword arguments that are str type. It first checks to see if the str is the words "[Tt]rue" or "[Ff]alse". The next test is checking to see if the str is an integer (a simple cast test works). Int is checked first because the formatting is more particular than float, since int must be only numbers, but float can contain decimal points. Finally, it tries a cast to float. If all of these casts fail, then we assume it is an actual str that shouldn't be tampered with.
def cast_arguments(method):
"""decorator"""
def wrapper(*args, **kwargs):
for key, value in kwargs.items():
# change special strings to bools
if isinstance(value, str):
if value.lower() == "true":
kwargs[key] = True
elif value.lower() == "false":
kwargs[key] = False
else:
try:
# change ints to int (int cannot have ".")
kwargs[key] = int(value)
except ValueError:
try:
# change floats to float (can have ".")
kwargs[key] = float(value)
except ValueError:
# this is an actual string
pass
return method(*args, **kwargs)
return wrapper
class Test:
#cast_arguments
def method(self, logged_in=False, interactions=False, other=False):
print("logged_in: ", logged_in, type(logged_in))
print("interactions: ", interactions, type(interactions))
print("other: ", other, type(other))
c = Test()
c.method(logged_in="1", interactions="True", other="3.14")
output:
$ python test.py
logged_in: 1 <class 'int'>
interactions: True <class 'bool'>
other: 3.14 <class 'float'>
I have an Object(retrieved from a database), with multiple attributes:
db_obj.default_attr= "textdefault"
db_obj.additional = {
"alpha": "texta",
"beta": "textb",
"gama": "textg",
"teta": "textt",
...
}
db_obj.name: "some_name"
....
additional property is also an Object can be empty/null/not having all values, in db is null or json
And types an Array: ["alpha", "gama", ...]
I have the following function, that is called:
set_att(obj=db_object, types=types)
I need to create a new Object based on types array:
Example of properties:
new_obj.alpha = "texta"
new_obj.gama = "textdefault" # because gama was not found in additional
I defined the function:
def set_att(db_obj=None, types=None):
new_obj = types.SimpleNamespace()
try:
add = db_obj.getattr(additional)
# cycle thru types, and assign the value from the db_obj if exist or the default_attr value
for item_type in types:
try:
setattr(new_obj, item_type, add.getattr(item_type))
except AttributeError:
setattr(new_obj, item_type, obj.getattr(default_attr))
# if there is not addtional I still set default for type
except AttributeError:
for item_type in types:
setattr(new_obj, item_type, obj.getattr(default_attr)
It looks naive, and I'm looking for a more pythonic option.
You can use hasattr to check if the object has an attribute instead of catching the AttributeException. It will make the code easier to read because it is explicitly handling the expected case of the attribute not being present. Using an exception makes it seem as if it is an error case.
for item_type in types:
if hasattr(add, item_type):
value = getattr(add, item_type)
else:
value = getattr(obj, default_attr)
setattr(new_obj, item_type, value)
You can make a class like this to access the array keys as object properties
class CustomDict(dict):
def __getattr__(self, key):
return self[key]
def __getitem__(self, key):
try:
return super(CustomDict, self).__getitem__(key)
except KeyError:
return None
def get(self, key, default=None):
try:
return super(CustomDict, self).__getitem__(key)
except KeyError:
return self.build(default)
#classmethod
def build(cls, orig):
if isinstance(orig, basestring):
return orig
if isinstance(orig, Sequence):
return [cls.build(item) for item in orig]
elif isinstance(orig, Mapping):
new = cls()
for key, value in orig.iteritems():
new[key] = cls.build(value)
return new
return orig
Use like this::
additional = {
"alpha": "texta",
"beta": "textb",
"gama": "textg",
"teta": "textt",
}
new=CustomDict(additional)
Then you can acces to the attributes :
print(new.alpha) # print texta
print(new.xyz) # print None
Running the command pip2.7 show enum34 outputs the version and where is installed correctly:
Name: enum34
Version: 1.1.2
Summary: Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4
Home-page: https://pypi.python.org/pypi/enum34
Author: Ethan Furman
Author-email: ethan#stoneleaf.us
License: BSD License
Location: /usr/lib/python2.7/dist-packages
Requires:
Required-by:
Nevertheless I am having the following error with enum:
AttributeError: 'enum' object has no attribute 'IntFlag'
This error happens when you dont have enum34 installed in python 2.7, but it is already installed.
I can import enum and running enum.__file__ outputs the path:
/usr/lib/python2.7/dist-packages/enum/__init__.pyc
Why is IntFlag missing in enum if enum34 is installed?
If you look at the docs for 3.4 enum, you'll see that IntFlag is not there. That is because it was added in 3.6.
So the error you are seeing is indeed correct and not a problem with your enum34 installation.
It also looks like the enum package in pypi also doesn't have IntFlag.
You can monkey patch this pretty easily though by copying the source for IntFlag into a module in your project:
from enum import Enum
class Flag(Enum):
"""Support for flags"""
def _generate_next_value_(name, start, count, last_values):
"""
Generate the next value when not given.
name: the name of the member
start: the initital start value or None
count: the number of existing members
last_value: the last value assigned or None
"""
if not count:
return start if start is not None else 1
for last_value in reversed(last_values):
try:
high_bit = _high_bit(last_value)
break
except Exception:
raise TypeError('Invalid Flag value: %r' % last_value) from None
return 2 ** (high_bit+1)
#classmethod
def _missing_(cls, value):
original_value = value
if value < 0:
value = ~value
possible_member = cls._create_pseudo_member_(value)
if original_value < 0:
possible_member = ~possible_member
return possible_member
#classmethod
def _create_pseudo_member_(cls, value):
"""
Create a composite member iff value contains only members.
"""
pseudo_member = cls._value2member_map_.get(value, None)
if pseudo_member is None:
# verify all bits are accounted for
_, extra_flags = _decompose(cls, value)
if extra_flags:
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
# construct a singleton enum pseudo-member
pseudo_member = object.__new__(cls)
pseudo_member._name_ = None
pseudo_member._value_ = value
# use setdefault in case another thread already created a composite
# with this value
pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
return pseudo_member
def __contains__(self, other):
if not isinstance(other, self.__class__):
import warnings
warnings.warn(
"using non-Flags in containment checks will raise "
"TypeError in Python 3.8",
DeprecationWarning, 2)
return False
return other._value_ & self._value_ == other._value_
def __repr__(self):
cls = self.__class__
if self._name_ is not None:
return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_)
members, uncovered = _decompose(cls, self._value_)
return '<%s.%s: %r>' % (
cls.__name__,
'|'.join([str(m._name_ or m._value_) for m in members]),
self._value_,
)
def __str__(self):
cls = self.__class__
if self._name_ is not None:
return '%s.%s' % (cls.__name__, self._name_)
members, uncovered = _decompose(cls, self._value_)
if len(members) == 1 and members[0]._name_ is None:
return '%s.%r' % (cls.__name__, members[0]._value_)
else:
return '%s.%s' % (
cls.__name__,
'|'.join([str(m._name_ or m._value_) for m in members]),
)
def __bool__(self):
return bool(self._value_)
def __or__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.__class__(self._value_ | other._value_)
def __and__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.__class__(self._value_ & other._value_)
def __xor__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.__class__(self._value_ ^ other._value_)
def __invert__(self):
members, uncovered = _decompose(self.__class__, self._value_)
inverted = self.__class__(0)
for m in self.__class__:
if m not in members and not (m._value_ & self._value_):
inverted = inverted | m
return self.__class__(inverted)
class IntFlag(int, Flag):
"""Support for integer-based Flags"""
#classmethod
def _missing_(cls, value):
if not isinstance(value, int):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
new_member = cls._create_pseudo_member_(value)
return new_member
#classmethod
def _create_pseudo_member_(cls, value):
pseudo_member = cls._value2member_map_.get(value, None)
if pseudo_member is None:
need_to_create = [value]
# get unaccounted for bits
_, extra_flags = _decompose(cls, value)
# timer = 10
while extra_flags:
# timer -= 1
bit = _high_bit(extra_flags)
flag_value = 2 ** bit
if (flag_value not in cls._value2member_map_ and
flag_value not in need_to_create
):
need_to_create.append(flag_value)
if extra_flags == -flag_value:
extra_flags = 0
else:
extra_flags ^= flag_value
for value in reversed(need_to_create):
# construct singleton pseudo-members
pseudo_member = int.__new__(cls, value)
pseudo_member._name_ = None
pseudo_member._value_ = value
# use setdefault in case another thread already created a composite
# with this value
pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
return pseudo_member
def __or__(self, other):
if not isinstance(other, (self.__class__, int)):
return NotImplemented
result = self.__class__(self._value_ | self.__class__(other)._value_)
return result
def __and__(self, other):
if not isinstance(other, (self.__class__, int)):
return NotImplemented
return self.__class__(self._value_ & self.__class__(other)._value_)
def __xor__(self, other):
if not isinstance(other, (self.__class__, int)):
return NotImplemented
return self.__class__(self._value_ ^ self.__class__(other)._value_)
__ror__ = __or__
__rand__ = __and__
__rxor__ = __xor__
def __invert__(self):
result = self.__class__(~self._value_)
return result
def _high_bit(value):
"""returns index of highest bit, or -1 if value is zero or negative"""
return value.bit_length() - 1
def unique(enumeration):
"""Class decorator for enumerations ensuring unique member values."""
duplicates = []
for name, member in enumeration.__members__.items():
if name != member.name:
duplicates.append((name, member.name))
if duplicates:
alias_details = ', '.join(
["%s -> %s" % (alias, name) for (alias, name) in duplicates])
raise ValueError('duplicate values found in %r: %s' %
(enumeration, alias_details))
return enumeration
def _decompose(flag, value):
"""Extract all members from the value."""
# _decompose is only called if the value is not named
not_covered = value
negative = value < 0
# issue29167: wrap accesses to _value2member_map_ in a list to avoid race
# conditions between iterating over it and having more pseudo-
# members added to it
if negative:
# only check for named flags
flags_to_check = [
(m, v)
for v, m in list(flag._value2member_map_.items())
if m.name is not None
]
else:
# check for named flags and powers-of-two flags
flags_to_check = [
(m, v)
for v, m in list(flag._value2member_map_.items())
if m.name is not None or _power_of_two(v)
]
members = []
for member, member_value in flags_to_check:
if member_value and member_value & value == member_value:
members.append(member)
not_covered &= ~member_value
if not members and value in flag._value2member_map_:
members.append(flag._value2member_map_[value])
members.sort(key=lambda m: m._value_, reverse=True)
if len(members) > 1 and members[0].value == value:
# we have the breakdown, don't need the value member itself
members.pop(0)
return members, not_covered
def _power_of_two(value):
if value < 1:
return False
return value == 2 ** _high_bit(value)
Although, you're probably just better off copying the whole module into your own module (so that you aren't depending on undocumented under methods).
Further as #user2357112 mentioned, the error you're seeing indicates that you've probably overwritten the enum module in your code accidentally. Just to be clear, something like this will break, because you've assigned the name sys some other value than the module you imported:
import sys
sys = 1 # doesn't have to be 1, can be anything that's not sys
# sys is no longer the module you imported, this will fail
sys.stdout.write('hello world!\n')
I suspect in your code you're probably doing something like this:
import enum
class Foo(enum.Enum):
BAR = 1
enum = Foo.Bar # or something like this
class Baz(enum.Enum): # failure happens here, enum is now Foo.Bar
pass
I have an application that deals with ~1-2 megabyte XML files. Doesn't sound like much, but I've run into a performance problem nonetheless.
Since I've some compute bound tasks that I'd like to speed up I've tried using multiprocessing.imap to do that - which requires pickling this XML data. Pickling the datastructures containing references into this DOM turns out to be slower than those compute bound processes, and the culprit seems to be recursions - I had to set the recursion limit to 10'000 in order to get pickle to work in the first place :-S.
Anyways, my question is:
If I wanted to attack this problem from the referential performance angle, what should I replace minidom with? Criterias are both pickling performance but also ease of transition.
To give you an idea of what kind of methods are needed, I have pasted a wrapper class (written sometimes earlier in order to speed up getElementsByTagName calls). It would be acceptable to replace all minidom nodes with nodes that adhere to the same interface as this class, i.e. I don't need all the methods from minidom. Getting rid of the parentNode method would also be acceptable (and probably a good idea in order to improve pickling performance).
And yes, if I'd be designing this nowadays I wouldn't go for XML node references in the first place, but it would be a lot of work to rip all of this out now, so I hope this can be patched instead.
Should I just write the damn thing myself using python built-ins or the collections library?
class ImmutableDOMNode(object):
def __init__(self, node):
self.node = node
self.cachedElementsByTagName = {}
#property
def nodeType(self):
return self.node.nodeType
#property
def tagName(self):
return self.node.tagName
#property
def ownerDocument(self):
return self.node.ownerDocument
#property
def nodeName(self):
return self.node.nodeName
#property
def nodeValue(self):
return self.node.nodeValue
#property
def attributes(self):
return self.node.attributes
#property
def parentNode(self):
return ImmutableDOMNode(self.node.parentNode)
#property
def firstChild(self):
return ImmutableDOMNode(self.node.firstChild)
#property
def childNodes(self):
return [ImmutableDOMNode(node) for node in self.node.childNodes]
def getElementsByTagName(self, name):
result = self.cachedElementsByTagName.get(name)
if result != None:
return result
uncachedResult = self.node.getElementsByTagName(name)
cachedResult = [ImmutableDOMNode(node) for node in uncachedResult]
self.cachedElementsByTagName[name] = cachedResult
return cachedResult
def getAttribute(self, qName):
return self.node.getAttribute(qName)
def toxml(self, encoding=None):
return self.node.toxml(encoding)
def toprettyxml(self, indent="", newl="", encoding=None):
return self.node.toprettyxml(indent, newl, encoding)
def appendChild(self, node):
raise Exception("cannot append child to immutable node")
def removeChild(self, node):
raise Exception("cannot remove child from immutable node")
def cloneNode(self, deep):
raise Exception("clone node not implemented")
def createElement(self, tagName):
raise Exception("cannot create element for immutable node")
def createTextNode(self, tagName):
raise Exception("cannot create text node for immutable node")
def createAttribute(self, qName):
raise Exception("cannot create attribute for immutable node")
So I decided to just make my own DOM implementation that meets my requirements, I've pasted it below in case it helps someone. It depends on lru_cache from memoization library for python 2.7 and #Raymond Hettinger's immutable dict from Immutable dictionary, only use as a key for another dictionary. However, these dependencies are easy to remove if you don't mind less safety/performance.
class CycleFreeDOMNode(object):
def __init__(self, minidomNode=None):
if minidomNode is None:
return
if not isinstance(minidomNode, xml.dom.minidom.Node):
raise ValueError("%s needs to be instantiated with a minidom.Node" %(
type(self).__name__
))
if minidomNode.nodeValue and minidomNode.childNodes:
raise ValueError(
"both nodeValue and childNodes in same node are not supported"
)
self._tagName = minidomNode.tagName \
if hasattr(minidomNode, "tagName") else None
self._nodeType = minidomNode.nodeType
self._nodeName = minidomNode.nodeName
self._nodeValue = minidomNode.nodeValue
self._attributes = dict(
item
for item in minidomNode.attributes.items()
) if minidomNode.attributes else {}
self._childNodes = tuple(
CycleFreeDOMNode(cn)
for cn in minidomNode.childNodes
)
childNodesByTagName = defaultdict(list)
for cn in self._childNodes:
childNodesByTagName[cn.tagName].append(cn)
self._childNodesByTagName = ImmutableDict(childNodesByTagName)
#property
def nodeType(self):
return self._nodeType
#property
def tagName(self):
return self._tagName
#property
def nodeName(self):
return self._nodeName
#property
def nodeValue(self):
return self._nodeValue
#property
def attributes(self):
return self._attributes
#property
def firstChild(self):
return self._childNodes[0] if self._childNodes else None
#property
def childNodes(self):
return self._childNodes
#lru_cache(maxsize = 100)
def getElementsByTagName(self, name):
result = self._childNodesByTagName.get(name, [])
for cn in self.childNodes:
result += cn.getElementsByTagName(name)
return result
def cloneNode(self, deep=False):
clone = CycleFreeDOMNode()
clone._tagName = self._tagName
clone._nodeType = self._nodeType
clone._nodeName = self._nodeName
clone._nodeValue = self._nodeValue
clone._attributes = copy.copy(self._attributes)
if deep:
clone._childNodes = tuple(
cn.cloneNode(deep)
for cn in self.childNodes
)
childNodesByTagName = defaultdict(list)
for cn in clone._childNodes:
childNodesByTagName[cn.tagName].append(cn)
clone._childNodesByTagName = ImmutableDict(childNodesByTagName)
else:
clone._childNodes = tuple(cn for cn in self.childNodes)
clone._childNodesByTagName = self._childNodesByTagName
return clone
def toxml(self):
def makeXMLForContent():
return self.nodeValue or "".join([
cn.toxml() for cn in self.childNodes
])
if not self.tagName:
return makeXMLForContent()
return "<%s%s>%s</%s>" %(
self.tagName,
" " + ", ".join([
"%s=\"%s\"" %(k,v)
for k,v in self.attributes.items()
]) if any(self.attributes) else "",
makeXMLForContent(),
self.tagName
)
def getAttribute(self, name):
return self._attributes.get(name, "")
def setAttribute(self, name, value):
self._attributes[name] = value
I want to create a field for phone number input that has 2 text fields (size 3, 3, and 4 respectively) with the common "(" ")" "-" delimiters. Below is my code for the field and the widget, I'm getting the following error when trying to iterate the fields in my form during initial rendering (it happens when the for loop gets to my phone number field):
Caught an exception while rendering: 'NoneType' object is unsubscriptable
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)
This uses widget.value_from_datadict() to format the data so no need to subclass a field, just use the existing USPhoneNumberField. Data is stored in db like 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)
use in a form like so:
from django.contrib.localflavor.us.forms import USPhoneNumberField
class MyForm(forms.Form):
phone = USPhoneNumberField(label="Phone", widget=USPhoneNumberMultiWidget())
I think the value_from_datadict() code can be simplified to:
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
The value_from_datadict() method for MultiValueWidget already does the following:
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)]
I took hughdbrown's advise and modified USPhoneNumberField to do what I need. The reason I didn't use it initially was that it stores phone numbers as XXX-XXX-XXXX in the DB, I store them as XXXXXXXXXX. So I over-rode the clean method:
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'])
Sometimes it is useful to fix the original problem rather than redoing everything. The error you got, "Caught an exception while rendering: 'NoneType' object is unsubscriptable" has a clue. There is a value returned as None(unsubscriptable) when a subscriptable value is expected. The decompress function in PhoneNumberWidget class is a likely culprit. I would suggest returning [] instead of None.