Skip to content

Instantly share code, notes, and snippets.

@ruiwen
Created May 9, 2011 19:45
Show Gist options
  • Save ruiwen/963239 to your computer and use it in GitHub Desktop.
Save ruiwen/963239 to your computer and use it in GitHub Desktop.
UtilMixIn with JSON renderer for Django models
# Utility classes
class UtilMixIn():
def __json_render_field(self, f, only_fields=[], mapped_fields={}):
field = self._meta.get_field_by_name(f)[0]
if isinstance(field, models.fields.DateTimeField):
return time.mktime(getattr(self, f).timetuple())
elif isinstance(field, models.fields.related.ForeignKey):
return getattr(self, "%s_id" % f)
elif isinstance(field, (models.related.RelatedObject,models.related.RelatedField)):
# Retrieve only() the fields specified in secondary
items = getattr(self, f).only(*only_fields).all()
return [i.json(as_dict=True, fields=mapped_fields) for i in items]
elif isinstance(field, models.fields.files.ImageFieldFile):
return getattr(self, f).name
elif isinstance(field, models.fields.DecimalField):
return getattr(self, f).to_eng_string()
else:
return getattr(self, f)
def json(self, **kwargs):
'''
Returns a JSON representation of a Model
@param fields list / dict
List of fields to return
Defaults to fields defined on model.
Use this to limit the set of fields returned
Using the dict form of the 'fields' parameter implies that you'd like to perform
custom remapping of field names.
For example, if you wanted a 'name' attribute on a User Model, that was obtained
from the get_full_name() method, you would do this:
>>> # u is a User instance
>>> u.json(fields={'name': 'get_full_name'})
As it stands, you can only use attribute names that do not already exist on the
model. Using an existing name will simply cause the remapping to be ignored
The value in the dictionary corresponding to the custom field name (as the key)
can also be a callable, like so (in combination with the above):
>>> def say_hello():
>>> return "Hello"
>>>
>>> u.json(fields={'name': 'get_full_name', 'greeting': say_hello})
'{"name": "Test User", "greeting": "hello"}'
To use field name remapping on related fields, eg. ForeignKeys or their reverse
relation, use the expanded tuple form:
>>> u.json(fields=[
>>> ('answers' , ('response', 'updated'),
>>> {'resp': 'response', 'up': 'updated'}
>>> )
>>> ])
In this form, the reverse relation 'answers' is followed on the User model,
retrieving related Answers.
The second tuple tells the JSON renderer which fields are to be retrieved from
the database. This defaults to all model fields.
The last parameter in the tuple, the dictionary, defines the remapping for field
names in the format {'new_name': 'orig_name'}
In plain English, the above call means:
- On this User instance
- Retrieve only the 'response' and the 'updated' fields from the database
- Remap the field 'response' to 'resp', and the field 'updated' to 'up'
The result of the above call to .json() returns something similar to the following:
'{"answers": [
{"resp": "Answer, User 2, Wks 1, Qn 1", "up": 1304446496.0},
{"resp": "Answer, User 2, Wks 1, Qn 2", "up": 1302859482.0},
...
]}'
@param exclude list
List of fields NOT to return.
Defaults to an empty list
You can only exclude fields that would have been in 'fields', default or otherwise
@param as_dict bool
If True, returns a dict, a JSON-formatted string otherwise
Defaults to False
'''
if kwargs.has_key('fields') and len(kwargs['fields']) > 0:
fields = kwargs['fields']
else:
fields = [f.name for f in self._meta.fields]
if kwargs.has_key('exclude'):
exclude = kwargs['exclude']
else:
exclude = []
out = {}
for f in fields:
# Pre-processing
only_fields = []
mapped_fields = {}
if isinstance(f, (tuple, list)):
if len(f) > 3:
raise SyntaxError("Field tuple should have at most 3 values, not %s" % (len(f))) # Take max of 3 values
else:
try:
only_fields = tuple(f[1])
try:
mapped_fields = f[2]
except IndexError:
pass
except IndexError:
only_fields = []
f = f[0] # Original 'f' tuple now destroyed
# Moving swiftly along
if f in exclude:
continue
try:
out[f] = self.__json_render_field(f, only_fields, mapped_fields)
except models.fields.FieldDoesNotExist: # Sneaky exception hiding in django.db.fields
field = fields[f]
# If the secondary parameter is a straight-up callable..
if hasattr(field, '__call__'):
out[f] = field() # .. call it.
# Or is it a callable on self?
elif hasattr(getattr(self, field), '__call__'):
out[f] = getattr(self, field)()
# Or is it just a field by another name?
elif hasattr(self, field):
out[f] = self.__json_render_field(field)
else:
raise SyntaxError("Second parameter in field tuple, %s, must be a callable if first parameter, %s, doesn't exist on object: %s" % (field, f, self) )
if kwargs.has_key('as_dict') and kwargs['as_dict'] is True:
return out # Return a dict
else:
return json.dumps(out) # Return a string
# Mix UtilMixIn into Django's User
User.__bases__ += (UtilMixIn,)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment