It’s been a while that i didn’t write anything. Well i’ve been too busy with work&studies. But today i’ve developed a nice and painless way to resolve relationships between models in django such that i can serialize the relationship using one field only getting the most significant one. This approach is very useful in ajax/javascript (extjs actually scenarios).
Let’s assume that we have an application in django and i want it to send data to a extjs client application (yep extjs based javascript is a program by itself IMHO). The problem in this scenario is that we have to emit django models as json representations such that extjs can read them. I’ve been googling for a while and i found that the used approach is to get the dictionary-like representation of the instance and send it. But this approach has many drawbacks: you end up having a lot of field_id fields which are exactly the relations and a lot of fields starting with _ which are private python memebers.
My solution was to add a .flat() method such that when called it serializes as python dict the instance, expanding relationships using the most significant fields. Who is the one which sets that significant fields ? It’s you, straight in the model definition. In this way the configuration is distributed within models and you obtain exactly what you wanna have for that relationships. You can also get more than one field for each relation and just join them using a separator. But let’s look at the code:
class CommonObj(object):
def __repr__(self):
return str(self.__dict__)
def __str__(self):
return self.__repr__()
def flat(self, mapping={}, func=lambda x:x, separator=' '):
'''
Emit a dictionary representing the class. It's able to expand the relationship between classes
using the special __config__ class you can define in your models. When representation will occour
it will expand the relations and will return a single field value which is the result of the join
using the fields_separator of the main_fields list defined in __config__ for each related class.
If the __config__ is missed it will try to use the name of the relation as the value for the emitted
dictionary.
mapping defines how to map the results. it's a dictionary composed by {existent_key:rep_key ...} which
changes all the existent methods of the object (the existent_key) with the rep_key
'''
def get_wanted_rep(key):
if key in mapping:
return mapping[key]
return key
res = dict()
iter = dict(self.__dict__)
for k,v in iter.iteritems():
if k[0] == '_':
continue
elif k[-3:] == '_id' and k != 'id':
ist = getattr(self, k[:-3])
if ist:
try:
attrs = ist.__class__.__config__.main_fields
except AttributeError:
attrs = [k[:-3]]
fields = list()
for attr in attrs:
fields.append(func(unicode(getattr(ist, attr))))
try:
sep = ist.__class__.__config__.fields_separator
if sep:
separator = sep
except AttributeError:
pass
res[get_wanted_rep(k[:-3])] = separator.join(fields)
else:
try:
res[get_wanted_rep(k)] = func(v)
except AttributeError:
#needed to print numbers
res[get_wanted_rep(k)] = v
return res
class Persona(CommonObj, models.Model):
nome = models.CharField(max_length=30)
cognome = models.CharField(max_length=30)
secondo_cognome = models.CharField(max_length=30, blank=True, null=True)
indirizzo = models.CharField(max_length=100, blank=True, null=True)
citta = models.CharField(max_length=15, blank=True, null=True)
provincia = models.CharField(max_length=30, blank=True, null=True)
cap = models.IntegerField(blank=True, null=True)
telefono = models.CharField(max_length=15, blank=True, null=True)
cellulare = models.CharField(max_length=10, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
cliente = models.BooleanField()
problemi_udito = models.BooleanField()
porta_apparecchio = models.BooleanField()
venuto = models.BooleanField()
spontaneo = models.BooleanField()
perdita_media = models.CharField(max_length=5, blank=True, null=True, choices=perdita_media_choices)
tipo_apparecchio = models.CharField(max_length=30, blank=True, null=True)
lato_apparecchio = models.IntegerField(blank=True, null=True, choices=lato_choices)
pila = models.IntegerField(blank=True, null=True, choices=pila_choices)
note = models.TextField(blank=True, null=True)
preventivo = models.TextField(blank=True, null=True)
We have defined a model which inherits from the CommonObj class which adds the .flat() method. It’s a smart and configurable serializer that expands relations trying to guess the value (it gets the value which has the same name of the relation) it’s able to perform operations thanks to the lambda function parameter to the returned values. Here is an example of this powerful serializer.
goshawk@earth:~/Projects/cacerp/cacerp$ python manage.py shell
Python 2.6.6 (r266:84292, Sep 15 2010, 16:22:56)
Type "copyright", "credits" or "license" for more information.
IPython 0.10 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object'. ?object also works, ?? prints more.
In [1]: from callcenter.models import Persona
In [2]: p = Persona.objects.get(pk=1)
In [4]: p.flat()
Out[4]:
{'cap': 20151L,
'cellulare': u'',
'citta': u'MILANO',
'cliente': False,
'cognome': u'XXXXX',
'email': u'',
'id': 1L,
'indirizzo': u'VIA MARIO BORSA 24',
'lato_apparecchio': None,
'nome': u'XXXXXXX',
'note': u'AS ',
'perdita_media': None,
'pila': None,
'porta_apparecchio': False,
'preventivo': u'',
'problemi_udito': False,
'provincia': u'MI',
'secondo_cognome': u'',
'spontaneo': False,
'telefono': u'XXXXXXX',
'tipo_apparecchio': u'',
'venuto': False}
It’s possible do define a mapping between the model names and the wanted result. Let’s assume we want CAP instead of cap as result and we want city instead of citta. It’s very easy to accomplish it:
In [9]: p.flat(mapping={'cap':'CAP', 'citta':'city'})
Out[9]:
{'CAP': 20151L,
'cellulare': u'',
'city': u'MILANO',
'cliente': False,
'cognome': u'XXXXX',
'email': u'',
'id': 1L,
'indirizzo': u'VIA MARIO BORSA 24',
'lato_apparecchio': None,
'nome': u'XXXXXX',
'note': u'AS ',
'perdita_media': None,
'pila': None,
'porta_apparecchio': False,
'preventivo': u'',
'problemi_udito': False,
'provincia': u'MI',
'secondo_cognome': u'',
'spontaneo': False,
'telefono': u'XXXXXX',
'tipo_apparecchio': u'',
'venuto': False}
As you can see it’s changed as our defined mapping. Another powerful feature is to do an action on the values thanks to the func function parameter. Here is what we can do:
In [30]: p.flat(func=lambda x: x.capitalize())
Out[30]:
{'cap': 20151L,
'cellulare': u'',
'citta': u'Milano',
'cliente': False,
'cognome': u'Xxxxxx',
'email': u'',
'id': 1L,
'indirizzo': u'Via mario borsa 24',
'lato_apparecchio': None,
'nome': u'Xxxxx',
'note': u'As ',
'perdita_media': None,
'pila': None,
'porta_apparecchio': False,
'preventivo': u'',
'problemi_udito': False,
'provincia': u'Mi',
'secondo_cognome': u'',
'spontaneo': False,
'telefono': u'xxxxxxxx',
'tipo_apparecchio': u'',
'venuto': False}
As you can verify, the values have been capitalized (first character with maiusc on and with maiusc off all the others).
Hope you like it and helps!
Like this:
Like Loading...