Django MultiWidget Phone Number Field - python

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.

Related

Nested data in attribute is not accessible

I have two classes (Student and Course). I'm trying to write a method for the Course class that will remove a given student from a course. However, there's a problem when I run
self.students.remove(student) in the method. The error tells me that student is not in the students list. Printing the students list I don't actually see the values, but instead I see a reference to it:
print(self.students)
> [<data.Student object at 0x7fc9980334f0>, <data.Student object at 0x7fc998033580>, <data.Student object at 0x7fc9980428b0>, <data.Student object at 0x7fc998042a00>]
However, if I select a specific student at an index then I'm able to see the actual data.
print(self.students[0])
> 2020411:King,Maha
Why is this happening when trying to print the students attribute?
Code if needed:
from copy import deepcopy
class Student:
def __init__(self, sid, last, first):
self.sid = sid
self.last = last
self.first = first
def __str__(self):
return '{}:{},{}'.format(self.sid, self.last, self.first)
def __repr__(self):
return '{}:{},{}'.format(self.sid, self.last, self.first)
class Course:
def __init__(self, crn, students):
self.crn = crn
self.students = deepcopy(students)
def key(self):
return self.crn
def is_empty(self):
return len(self.students) == 0
def get_student(self, student_key):
for student in self.students:
if student.key() == student_key:
return deepcopy(student)
return None
def __contains__(self, student):
for i in self.students:
if student.key() == i.key():
return True
break
return False
def register(self, student):
if student not in self:
self.students.append(deepcopy(student))
return
def drop(self, student):
s = None
if student in self:
s = deepcopy(student)
self.students.remove(student)
return s
student1 = Student(2020411, 'King', 'Maha')
student2 = Student(2019399, 'Hess', 'Alvin')
student3 = Student(2020301, 'Chin', 'Yu')
student4 = Student(2019111, 'Hay', 'Ria')
student_list = [student1, student2, student3]
course1 = Course('CP104', student_list)
removed_student = course1.drop(student2)
The issue with deepcopy() is that it creates an entirely new object that has the same attributes as the original one, yet they are not equal. For list.remove(), this compares the reference to check if the actual object exists. In your case, you are trying to remove an object that is not in the list.
Instead of removing it, if you want to return the student, use list.pop().
def drop(self, student):
for i, s in enumerate(self.students):
if s.sid == student.sid :
return self.students.pop(i)
As a side note, it will be easier to do operations if Course.students is a dictionary such that:
self.students = {
`sid1`: student1,
`sid2`: student2,
# etc
}
EDIT: Alternatively, implement __eq__() in Student so that list.remove() will work.
def __eq__(self, other):
return self.sid == other.sid and self.first == other.first and self.last == other.last

getattr and setattr on nested subobjects / chained properties?

I have an object (Person) that has multiple subobjects (Pet, Residence) as properties. I want to be able to dynamically set the properties of these subobjects like so:
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
class Residence(object):
def __init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
if __name__=='__main__':
p=Person()
setattr(p,'pet.name','Sparky')
setattr(p,'residence.type','Apartment')
print p.__dict__
Currently I get the wrong output: {'pet': <__main__.Pet object at 0x10c5ec050>, 'residence': <__main__.Residence object at 0x10c5ec0d0>, 'pet.name': 'Sparky', 'residence.type': 'Apartment'}
As you can see, instead of setting the name attribute on the Pet subobject of the Person, a new attribute pet.name is created on the Person.
I cannot specify person.pet to setattr() because different sub-objects will be set by the same method, which parses some text and fills in the object attributes if/when a relevant key is found.
Is there a easy/builtin way to accomplish this?
Or perhaps I need to write a recursive function to parse the string and call getattr() multiple times until the necessary subobject is found and then call setattr() on that found subobject?
You could use functools.reduce:
import functools
def rsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
# using wonder's beautiful simplification: https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
return getattr(obj, attr, *args)
return functools.reduce(_getattr, [obj] + attr.split('.'))
rgetattr and rsetattr are drop-in replacements for getattr and setattr,
which can also handle dotted attr strings.
import functools
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
class Residence(object):
def __init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
def rsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
return getattr(obj, attr, *args)
return functools.reduce(_getattr, [obj] + attr.split('.'))
if __name__=='__main__':
p = Person()
print(rgetattr(p, 'pet.favorite.color', 'calico'))
# 'calico'
try:
# Without a default argument, `rgetattr`, like `getattr`, raises
# AttributeError when the dotted attribute is missing
print(rgetattr(p, 'pet.favorite.color'))
except AttributeError as err:
print(err)
# 'Pet' object has no attribute 'favorite'
rsetattr(p, 'pet.name', 'Sparky')
rsetattr(p, 'residence.type', 'Apartment')
print(p.__dict__)
print(p.pet.name)
# Sparky
print(p.residence.type)
# Apartment
For an out of the box solution, you can use operator.attrgetter:
from operator import attrgetter
attrgetter(dotted_path)(obj)
For one parent and one child:
if __name__=='__main__':
p = Person()
parent, child = 'pet.name'.split('.')
setattr(getattr(p, parent), child, 'Sparky')
parent, child = 'residence.type'.split('.')
setattr(getattr(p, parent), child, 'Sparky')
print p.__dict__
This is simpler than the other answers for this particular use case.
unutbu's answer (https://stackoverflow.com/a/31174427/2683842) has a "bug". After getattr() fails and is replaced by default, it continues calling getattr on default.
Example: rgetattr(object(), "nothing.imag", 1) should equal 1 in my opinion, but it returns 0:
getattr(object(), 'nothing', 1) == 1.
getattr(1, 'imag', 1) == 0 (since 1 is real and has no complex component).
Solution
I modified rgetattr to return default at the first missing attribute:
import functools
DELIMITER = "."
def rgetattr(obj, path: str, *default):
"""
:param obj: Object
:param path: 'attr1.attr2.etc'
:param default: Optional default value, at any point in the path
:return: obj.attr1.attr2.etc
"""
attrs = path.split(DELIMITER)
try:
return functools.reduce(getattr, attrs, obj)
except AttributeError:
if default:
return default[0]
raise
This should be a
def getNestedAttr(obj,nestedParam):
next = obj
for p in nestedParam.split('.'):
next = getattr(next,p)
return next
class Issue : pass
issue = Issue()
issue.status = Issue()
issue.status.name = "Hello"
getattr(issue,'status.name')
'''
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Issue' object has no attribute 'status.name'
'''
getNestedAttr(issue,'status.name')
#'Hello'
simple solution
I made a simple version based on ubntu's answer called magicattr that also works on attrs, lists, and dicts by parsing and walking the ast.
For example, with this class:
class Person:
settings = {
'autosave': True,
'style': {
'height': 30,
'width': 200
},
'themes': ['light', 'dark']
}
def __init__(self, name, age, friends):
self.name = name
self.age = age
self.friends = friends
bob = Person(name="Bob", age=31, friends=[])
jill = Person(name="Jill", age=29, friends=[bob])
jack = Person(name="Jack", age=28, friends=[bob, jill])
You can do this
# Nothing new
assert magicattr.get(bob, 'age') == 31
# Lists
assert magicattr.get(jill, 'friends[0].name') == 'Bob'
assert magicattr.get(jack, 'friends[-1].age') == 29
# Dict lookups
assert magicattr.get(jack, 'settings["style"]["width"]') == 200
# Combination of lookups
assert magicattr.get(jack, 'settings["themes"][-2]') == 'light'
assert magicattr.get(jack, 'friends[-1].settings["themes"][1]') == 'dark'
# Setattr
magicattr.set(bob, 'settings["style"]["width"]', 400)
assert magicattr.get(bob, 'settings["style"]["width"]') == 400
# Nested objects
magicattr.set(bob, 'friends', [jack, jill])
assert magicattr.get(jack, 'friends[0].friends[0]') == jack
magicattr.set(jill, 'friends[0].age', 32)
assert bob.age == 32
It also won't let you/someone call functions or assign a value since it doesn't use eval or allow Assign/Call nodes.
with pytest.raises(ValueError) as e:
magicattr.get(bob, 'friends = [1,1]')
# Nice try, function calls are not allowed
with pytest.raises(ValueError):
magicattr.get(bob, 'friends.pop(0)')
And a easy to understand three-liner based on jimbo1qaz's answer, reduced to the very limit:
def rgetattr(obj, path, default):
try:
return functools.reduce(getattr, path.split(), obj)
except AttributeError:
return default
Usage:
>>> class O(object):
... pass
... o = O()
... o.first = O()
... o.first.second = O()
... o.first.second.third = 42
... rgetattr(o, 'first second third', None)
42
Just keep in mind that "space" is not a typical delimiter for this use case.
Thanks for the accepted answer above. It was helpful.
In case anyone wants to extend the use for hasattr use the code below:
def rhasattr(obj, attr):
_nested_attrs = attr.split(".")
_curr_obj = obj
for _a in _nested_attrs[:-1]:
if hasattr(_curr_obj, _a):
_curr_obj = getattr(_curr_obj, _a)
else:
return False
return hasattr(_curr_obj, _nested_attrs[-1])
Ok so while typing the question I had an idea of how to do this and it seems to work fine. Here is what I came up with:
def set_attribute(obj, path_string, new_value):
parts = path_string.split('.')
final_attribute_index = len(parts)-1
current_attribute = obj
i = 0
for part in parts:
new_attr = getattr(current_attribute, part, None)
if current_attribute is None:
print 'Error %s not found in %s' % (part, current_attribute)
break
if i == final_attribute_index:
setattr(current_attribute, part, new_value)
current_attribute = new_attr
i+=1
def get_attribute(obj, path_string):
parts = path_string.split('.')
final_attribute_index = len(parts)-1
current_attribute = obj
i = 0
for part in parts:
new_attr = getattr(current_attribute, part, None)
if current_attribute is None:
print 'Error %s not found in %s' % (part, current_attribute)
return None
if i == final_attribute_index:
return getattr(current_attribute, part)
current_attribute = new_attr
i += 1
I guess this solves my question, but I am still curious if there is a better way to do this?
I feel like this has to be something pretty common in OOP and python, so I'm surprised gatattr and setattr do not support this natively.
Here's something similar to ChaimG's answer, but it works with an arbitrary number of cases. However, it only supports get attributes, not setting them.
requested_attr = 'pet.name'
parent = Person()
sub_names = requested_attr.split('.')
sub = None
for sub_name in sub_names:
try:
sub = parent.__getattribute__(sub_name)
parent = sub
except AttributeError:
raise Exception("The panel doesn't have an attribute that matches your request!")
pets_name = sub
I just love recursive functions
def rgetattr(obj,attr):
_this_func = rgetattr
sp = attr.split('.',1)
if len(sp)==1:
l,r = sp[0],''
else:
l,r = sp
obj = getattr(obj,l)
if r:
obj = _this_func(obj,r)
return obj
I know this post is pretty old but below code might help some one.
def getNestedObjectValue(obj={}, attr=""):
splittedFields = attr.split(".")
nestedValue = ""
previousValue = ""
for field in splittedFields:
previousValue = nestedValue
nestedValue = (
obj.get(field) if previousValue == "" else previousValue.get(field)
)
return nestedValue
print(
getNestedObjectValue(
obj={
"name": "ADASDASD",
"properties": {"somefield": {"value": "zxczxcxczxcxzc"}},
},
attr="properties.somefield.value",
)
)
Output
PS C:\myprograms\samples> python .\sample.py
zxczxcxczxcxzc

GDB pretty-printing: returning string from a children()'s iterator, but displayed as a char[]

I have a handy class that I use to allow me to easily add a set of "summariser" functions to a GDB pretty printer (for example, a Rect class could have an [Area] field, computed by Python). it then prints all the existing children as well, so you can see everything at once.
class SummaryAndFieldIterator:
"""
Iterator to first go through a list of summariser functions,
then display all the fields in the object in order
"""
def __init__ (self, obj, summaries):
self.count = 0
self.obj = obj;
self.summaries = summaries;
self.keys = sorted(obj.type.iterkeys())
def __iter__(self):
return self
def __next__(self):
if (self.count >= len(self.keys) + len(self.summaries)):
raise StopIteration
elif self.count < len(self.summaries):
name, retVal = self.summaries[self.count](self.obj)
# FIXME: this doesn't seem to work when a string is returned
# in retVal?
result = "[%s]" % name, retVal
else:
field = self.count - len(self.summaries)
result = self.keys[field], self.obj[self.keys[field]]
self.count += 1
return result
next = __next__
class MyObjectPrinter:
def __init__(self, val):
self.val = val
def get_int(self):
return "meaning", 42
def get_string(self):
return "hoopiness", "Forty-two"
def children(self):
return SummaryAndFieldIterator(self.val, [self.get_string])
This works very well for the summarisers which return numeric values, but for strings, it ends up displaying as an array, so that I get
NAME VALUE
myobj {..}
|-->[meaning] 42
|-->[hoopiness]
|-->[0] 'F'
|-->[1] 'o'
.....
|-->real_field 34234
This is presumably becuase the string that comes from
name, retVal = self.summaries[self.count](self.obj)
does not generate a sufficiently "stringy" gdb.Value object when it is returned by SummaryAndFieldIterator's __next__ method. Adjusting the display_hint() method of MyObjectPrinter doesn't seem to have any effect (but I doubt it would, as this is the child, not the object).
Anyone know how to return a string from the children() iterator and get it to display as a string?
Okay, apparently this may be a bug related to the way that GDB/MI communicates with pretty-printers, Bugzilla created here : https://sourceware.org/bugzilla/show_bug.cgi?id=18282

Serialize and De-serialize a Django Enum field to accept Numerical and Text representation

I'm trying to create an enum field in Django that, upon a GET request will return the text representation of the enum and upon a POST or PATCH request will convert the text representation to the corresponding integer before saving.
The
transform_<field>()
method works nicely for converting the integer enum value to its corresponding string, but I can't figure out a better way of converting the string into it's corresponding integer other than hacking the
validate_<field>()
method.
Is there a better way of doing this? Please see code below
Models file
class Status(enum.Enum):
RUNNING = 0
COMPLETED = 1
labels = {
RUNNING: 'Running',
COMPLETED: 'Completed'
}
translation = {v: k for k, v in labels.iteritems()}
class Job(models.Model):
status = enum.EnumField(Status)
Serializer
class JobSeralizer(serializers.ModelSerailzer):
status = seralizers.CharField(max_length=32, default=Status.QUEUED)
def transform_status(self, obj, value):
return JobStatus.labels[value]
def validate_status(self, attrs, source):
"""Allow status to take numeric or character representation of status
"""
status = attrs[source]
if status in JobStatus.translation:
attrs[source] = JobStatus.translation[status]
elif status.isdigit():
attrs[source] = int(status)
else:
raise serializers.ValidationError("'%s' not a valid status" % status)
return attrs
As OP stated, you can do this easily using custom fields in drf v3.x. Here's a quick example of a generic custom field used to convert values <-> labels (e.g. enum values <-> textual representation):
class KeyValueField(serializers.Field):
""" A field that takes a field's value as the key and returns
the associated value for serialization """
labels = {}
inverted_labels = {}
def __init__(self, labels, *args, **kwargs):
self.labels = labels
# Check to make sure the labels dict is reversible, otherwise
# deserialization may produce unpredictable results
inverted = {}
for k, v in labels.iteritems():
if v in inverted:
raise ValueError(
'The field is not deserializable with the given labels.'
' Please ensure that labels map 1:1 with values'
)
inverted[v] = k
self.inverted_labels = inverted
return super(KeyValueField, self).__init__(*args, **kwargs)
def to_representation(self, obj):
if type(obj) is list:
return [self.labels.get(o, None) for o in obj]
else:
return self.labels.get(obj, None)
def to_internal_value(self, data):
if type(data) is list:
return [self.inverted_labels.get(o, None) for o in data]
else:
return self.inverted_labels.get(data, None)
The field initialization would look something like this:
class MySerializer(serializers.Serializer):
afield = KeyValueField(labels={0:'enum text 0', 1:'enum text 1'})

Need to store bytestring using Django

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)

Categories

Resources