Created
May 9, 2011 19:45
-
-
Save ruiwen/963239 to your computer and use it in GitHub Desktop.
UtilMixIn with JSON renderer for Django models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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