Created
August 28, 2011 13:59
-
-
Save ruiwen/1176697 to your computer and use it in GitHub Desktop.
Utility methods for Django
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
# Various utility functions | |
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden | |
from django.template import RequestContext, Context, loader | |
from django.conf import settings | |
from django.contrib.auth.forms import AuthenticationForm | |
from django.contrib.auth import authenticate, login as django_login | |
from django.db import models | |
import json | |
def render(request, template, dictionary={}, context_instance=None, mimetype='text/html', status=200): | |
t = loader.get_template(template) | |
c = RequestContext(request, dictionary) | |
return HttpResponse(t.render(c), mimetype=mimetype, status=status) | |
def login(request, template_name='registration/login.html', redirect_field_name=settings.LOGIN_REDIRECT_URL, authentication_form=AuthenticationForm): | |
'''Custom login function, with ability to handle both Ajax and non-Ajax logins''' | |
if request.method == 'POST': | |
form = authentication_form(data=request.POST) | |
if form.is_valid(): | |
u = form.get_user() | |
django_login(request, u) # It's valid, so log the User in | |
if request.is_ajax(): | |
return HttpResponse(json.dumps({ | |
'id': u.id, | |
'username': u.username, | |
'first_name': u.first_name, | |
'last_name': u.last_name, | |
'email': u.email | |
})) | |
else: | |
return HttpResponseRedirect(redirect_field_name) | |
else: | |
if request.is_ajax(): | |
return HttpResponseForbidden() | |
else: | |
form = authentication_form() | |
return render(request, template_name, {'next': redirect_field_name, 'form': form}) | |
# Utility classes | |
class UtilMixIn(): | |
def __json_render_field(self, f, field, only_fields=[], mapped_fields={}): | |
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 | |
else: | |
try: | |
return getattr(self, f) | |
except AttributeError: | |
return getattr(self, field) | |
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 = User.objects.get(username='testuser') | |
>>> u.json(fields={'name': 'get_full_name'}) | |
'{"name": "Test User"}' | |
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'} | |
... ) | |
... ]) | |
'{"answers": [{"resp": "Answer, User 2, Wks 1, Qn 1", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 1, Qn 2", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 1, Qn 3", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 2, Qn 1", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 2, Qn 2", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 2, Qn 3", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 3, Qn 1", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 3, Qn 2", "up": 1302859482.0}, {"resp": "Answer, User 2, Wks 3, Qn 3", "up": 1302859482.0}]}' | |
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: | |
field = self._meta.get_field_by_name(f)[0] | |
out[f] = self.__json_render_field(f, field, only_fields, mapped_fields) | |
except models.fields.FieldDoesNotExist: # Sneaky exception hiding in django.db.fields | |
if isinstance(fields, (tuple,list)): | |
field = fields[fields.index(f)] | |
elif isinstance(fields, dict): | |
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): | |
# Try getting the field from the model again | |
try: | |
real_field = self._meta.get_field_by_name(field)[0] | |
out[f] = self.__json_render_field(field, real_field) | |
except models.fields.FieldDoesNotExist: | |
# If we get this exception, chances are, this 'attribute' is a @property method | |
# So let's just assign it straight | |
out[f] = getattr(self, 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment