1+ import django .contrib .admin
2+ from django .conf .urls .defaults import patterns , url
3+ from django .contrib .admin import *
4+ from django .contrib .admin .util import unquote
5+ from django .core .exceptions import PermissionDenied , ObjectDoesNotExist
6+ from django .core .serializers import serialize
7+ from django .core .serializers .json import Serializer , DjangoJSONEncoder
8+ from django .http import HttpResponse , Http404
9+ from django .utils import simplejson as json
10+ from django .utils .encoding import smart_unicode
11+ from django .utils .functional import update_wrapper
12+ from django .utils .html import escape
13+ from django .db .models import Field , ForeignKey
14+
15+ from reversion .admin import VersionAdmin
16+
17+ from netnod .utils import filter_from_queryargs
18+
19+ class AdminJsonSerializer (Serializer ):
20+ """
21+ Serializes a QuerySet to Json, with selectable object expansion.
22+ The representation is different from that of the builtin Json
23+ serializer in that there is no separate "model", "pk" and "fields"
24+ entries for each object, instead only the "fields" dictionary is
25+ serialized, and the model is the key of a top-level dictionary
26+ entry which encloses the table serialization:
27+ {
28+ "app.model": {
29+ "1": {
30+ "foo": "1",
31+ "bar": 42,
32+ }
33+ }
34+ }
35+ """
36+
37+ internal_use_only = False
38+
39+ def end_object (self , obj ):
40+ expansions = [ n .split ("__" )[0 ] for n in self .options ["expand" ] if n ]
41+ serialize = self .__class__ ().serialize
42+ for name in expansions :
43+ try :
44+ field = getattr (obj , name )
45+ #self._current["_"+name] = smart_unicode(field)
46+ if not isinstance (field , Field ):
47+ options = self .options .copy ()
48+ options ["expand" ] = [ v [len (name )+ 2 :] for v in options ["expand" ] if v .startswith (name + "__" ) ]
49+ if hasattr (field , "all" ):
50+ if options ["expand" ]:
51+ self ._current [name ] = dict ([ (rel .pk , self .expand_related (rel , name )) for rel in field .all ().select_related () ])
52+ else :
53+ self ._current [name ] = dict ([ (rel .pk , self .expand_related (rel , name )) for rel in field .all () ])
54+ elif callable (field ):
55+ self ._current [name ] = dict ([ (rel .pk , self .expand_related (rel , name )) for rel in field () ])
56+ except ObjectDoesNotExist :
57+ pass
58+ except AttributeError :
59+ names = obj ._meta .get_all_field_names () + [ f .get_accessor_name () for f in obj ._meta .get_all_related_objects ()]
60+ raise Http404 ("Cannot resolve keyword '%s' into field. "
61+ "Choices are: %s" % (name , ", " .join (names )))
62+ self .objects .append (self ._current )
63+ self ._current = None
64+
65+ def end_serialization (self ):
66+ self .options .pop ('stream' , None )
67+ self .options .pop ('fields' , None )
68+ self .options .pop ('expand' , None )
69+ json .dump (self .objects , self .stream , cls = DjangoJSONEncoder , ** self .options )
70+
71+ def expand_related (self , related , name ):
72+ options = self .options .copy ()
73+ options ["expand" ] = [ v [len (name )+ 2 :] for v in options ["expand" ] if v .startswith (name + "__" ) ]
74+ return json .loads (self .__class__ ().serialize ([ related ], ** options ))[0 ]
75+
76+ def handle_fk_field (self , obj , field ):
77+ try :
78+ related = getattr (obj , field .name )
79+ except ObjectDoesNotExist :
80+ related = None
81+ if related is not None :
82+ if field .name in self .options ["expand" ]:
83+ related = self .expand_related (related , field .name )
84+ elif self .use_natural_keys and hasattr (related , 'natural_key' ):
85+ related = related .natural_key ()
86+ elif field .rel .field_name == related ._meta .pk .name :
87+ # Related to remote object via primary key
88+ related = smart_unicode (related ._get_pk_val (), strings_only = True )
89+ else :
90+ # Related to remote object via other field
91+ related = smart_unicode (getattr (related , field .rel .field_name ), strings_only = True )
92+ self ._current [field .name ] = related
93+
94+ def handle_m2m_field (self , obj , field ):
95+ if field .rel .through ._meta .auto_created :
96+ if field .name in self .options ["expand" ]:
97+ m2m_value = lambda value : self .expand_related (value , field .name )
98+ elif self .use_natural_keys and hasattr (field .rel .to , 'natural_key' ):
99+ m2m_value = lambda value : value .natural_key ()
100+ else :
101+ m2m_value = lambda value : smart_unicode (value ._get_pk_val (), strings_only = True )
102+ self ._current [field .name ] = [m2m_value (related )
103+ for related in getattr (obj , field .name ).iterator ()]
104+
105+
106+
107+ class ModelAdmin (VersionAdmin ):
108+ def get_urls (self ):
109+ def wrap (view ):
110+ def wrapper (* args , ** kwargs ):
111+ return self .admin_site .admin_view (view )(* args , ** kwargs )
112+ return update_wrapper (wrapper , view )
113+
114+ info = self .model ._meta .app_label , self .model ._meta .module_name
115+
116+ urlpatterns = super (ModelAdmin , self ).get_urls ()
117+ urlpatterns = patterns ('' ,
118+ url (r'^json/$' ,
119+ wrap (self .json_listview ),
120+ name = '%s_%s_jsonlist' % info ),
121+ url (r'^(.+)/json/$' ,
122+ wrap (self .json_view ),
123+ name = '%s_%s_json' % info ),
124+ ) + urlpatterns
125+ return urlpatterns
126+
127+ def json_listview (self , request ):
128+ "The json admin list view for this model."
129+ if not self .has_change_permission (request , None ):
130+ raise PermissionDenied
131+ filter , exclude = filter_from_queryargs (request )
132+ for k in filter .keys ():
133+ if k .startswith ("_" ):
134+ del filter [k ]
135+ key = request .REQUEST .get ("_key" , "pk" )
136+ exp = request .REQUEST .get ("_expand" , "" ).split ("," )
137+ type = request .REQUEST .get ("_type" , 'application/json' )
138+ qs = self .queryset (request ).filter (** filter ).exclude (** exclude )
139+ if exp :
140+ qs = qs .select_related ()
141+ qd = dict ( ( (getattr (o , key ), json .loads (AdminJsonSerializer ().serialize ([o ], expand = exp ))[0 ] ) for o in qs ) )
142+ return HttpResponse (json .dumps ({smart_unicode (self .model ._meta ): qd }, sort_keys = True , indent = 3 ), content_type = type )
143+
144+ def json_view (self , request , object_id , extra_context = None ):
145+ "The json view for an object of this model."
146+ try :
147+ obj = self .queryset (request ).get (pk = unquote (object_id ))
148+ except self .model .DoesNotExist :
149+ # Don't raise Http404 just yet, because we haven't checked
150+ # permissions yet. We don't want an unauthenticated user to be able
151+ # to determine whether a given object exists.
152+ obj = None
153+
154+ if not self .has_change_permission (request , obj ):
155+ raise PermissionDenied
156+
157+ if obj is None :
158+ raise Http404 (_ ('%(name)s object with primary key %(key)r does not exist.' ) % {'name' : force_unicode (self .model ._meta .verbose_name ), 'key' : escape (object_id )})
159+
160+ return HttpResponse (serialize ("json" , [ obj ], sort_keys = True , indent = 3 )[2 :- 2 ], content_type = 'application/json' )
161+
0 commit comments