1 from django.conf import settings
2 from django.contrib.admin.util import lookup_field, label_for_field, display_for_field, NestedObjects
3 from django.core.exceptions import PermissionDenied
4 from django.db.models import Q
5 from django.db.models.fields.related import ManyToOneRel
6 from django.db.models.fields.files import FieldFile, ImageFieldFile, FileField
7 from django.forms.models import ModelForm, modelform_factory
8 from django.utils import simplejson as json
9 from django.utils.encoding import smart_unicode
10 from .base import Plugin
11 from ..extdirect import ext_action, ext_method
12 import operator, staticmedia
15 @ext_action(name='models')
18 Plugin providing model-related UI and functionality on the client
24 def index_js_urls(self):
25 return super(Models, self).index_js_urls + [
26 staticmedia.url('gilbert/extjs/examples/ux/SearchField.js'),
27 staticmedia.url('gilbert/plugins/models.js'),
32 return super(Models, self).icon_names + [
45 class ModelAdmin(Plugin):
47 Default ModelAdmin class used by Sites to expose a model-centric API
55 data_columns = ('__unicode__',)
56 data_editable_columns = ()
58 def __init__(self, site, model):
59 super(ModelAdmin, self).__init__(site)
61 self.model_meta = model._meta
64 def data_serialize_model_instance(cls, obj):
66 'app_label': obj._meta.app_label,
67 'name': obj._meta.object_name,
69 '__unicode__': unicode(obj),
73 def data_serialize_field_value(cls, field, value):
75 #return smart_unicode(value)
77 if isinstance(field.rel, ManyToOneRel):
79 return cls.data_serialize_model_instance(value)
80 elif isinstance(value, FieldFile):
86 if isinstance(value, ImageFieldFile):
89 'height': value.height,
95 def sortable_fields(self):
96 return [field.name for field in self.model_meta.fields]
99 def data_fields(self):
100 fields = ['pk', '__unicode__']
101 fields.extend(self.data_columns)
102 fields.extend(field.name for field in self.model_meta.fields)
103 return tuple(set(fields))
106 def data_fields_spec(self):
108 for field_name in self.data_fields:
112 if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
113 field_spec['type'] = 'gilbertmodelforeignkey'
114 elif field_name in [field.name for field in self.model_meta.fields if isinstance(field, FileField)]:
115 field_spec['type'] = 'gilbertmodelfilefield'
116 spec.append(field_spec)
120 def data_columns_spec(self):
122 for field_name in self.data_columns:
124 'dataIndex': field_name,
128 header, attr = label_for_field(field_name, self.model, model_admin=self, return_attr=True)
129 column['header'] = header
130 if (field_name in self.sortable_fields) or (getattr(attr, 'admin_order_field', None) in self.sortable_fields):
131 column['sortable'] = True
132 if field_name in self.data_editable_columns:
133 column['editable'] = True
138 def data_columns_spec_json(self):
139 return json.dumps(self.data_columns_spec)
142 def icon_names(self):
143 return super(ModelAdmin, self).icon_names + [
148 def has_permission(self, request):
149 return self.has_read_permission(request) or self.has_add_permission(request)
152 def has_read_permission(self, request):
153 return self.has_change_permission(request)
156 def has_add_permission(self, request):
157 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
160 def has_change_permission(self, request):
161 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
164 def has_delete_permission(self, request):
165 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
168 def all(self, request):
169 if not self.has_read_permission(request):
170 raise PermissionDenied
171 return self.model._default_manager.all()
174 def filter(self, request, **kwargs):
175 if not self.has_read_permission(request):
176 raise PermissionDenied
177 return self.model._default_manager.all().filter(**kwargs)
180 def get(self, request, **kwargs):
181 if not self.has_read_permission(request):
182 raise PermissionDenied
183 return self.model._default_manager.all().values().get(**kwargs)
186 def form_class(self):
187 return modelform_factory(self.model, form=self.form)
190 def get_form(self, request, **kwargs):
192 instance = self.model._default_manager.all().get(**kwargs)
194 if not self.has_add_permission(request):
195 raise PermissionDenied
198 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
199 raise PermissionDenied
201 return self.form_class(instance=instance).as_extdirect()
203 @ext_method(form_handler=True)
204 def save_form(self, request):
205 if 'pk' in request.POST:
207 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
208 except ObjectDoesNotExist:
213 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
214 raise PermissionDenied
216 form = self.form_class(request.POST, request.FILES, instance=instance)
220 return True, None, saved.pk
222 return False, form.errors
224 def data_serialize_object(self, obj):
226 for field_name in self.data_fields:
229 field, attr, value = lookup_field(field_name, obj, self)
230 except (AttributeError, ObjectDoesNotExist):
233 result = self.data_serialize_field_value(field, value)
234 row[field_name] = result
238 def data_metadata(self):
242 'totalProperty': 'total',
243 'successProperty': 'success',
244 'fields': self.data_fields_spec,
247 def data_serialize_queryset(self, queryset, params=None):
249 'metaData': self.data_metadata,
251 'total': queryset.count(),
255 if params is not None:
257 order_by = params['sort']
258 if order_by in self.data_fields:
259 if order_by not in self.sortable_fields:
261 if hasattr(self, order_by):
262 attr = getattr(self, order_by)
264 attr = getattr(self.model, order_by)
265 order_by = attr.admin_order_field
266 except AttributeError:
268 if order_by is not None:
269 if params.get('dir', 'ASC') == 'DESC':
270 order_by = '-' + order_by
271 serialized['metaData']['sortInfo'] = {
272 'field': params['sort'],
273 'direction': params.get('dir', 'ASC'),
275 queryset = queryset.order_by(order_by)
276 if 'start' in params:
277 start = params['start']
278 serialized['metaData']['start'] = start
279 if 'limit' in params:
280 limit = params['limit']
281 serialized['metaData']['limit'] = limit
282 queryset = queryset[start:(start+limit)]
284 queryset = queryset[start:]
287 serialized['root'].append(self.data_serialize_object(obj))
292 def data_read(self, request, **params):
293 if not self.has_read_permission(request):
294 raise PermissionDenied
296 queryset = self.model._default_manager.all()
297 query = params.pop('query', None)
298 filters = params.pop('filters', None)
301 if isinstance(filters, Q):
302 queryset = queryset.filter(filters)
303 elif isinstance(filters, dict):
304 queryset = queryset.filter(**filters)
306 raise TypeError('Invalid filters parameter')
308 def construct_search(field_name):
309 if field_name.startswith('^'):
310 return "%s__istartswith" % field_name[1:]
311 elif field_name.startswith('='):
312 return "%s__iexact" % field_name[1:]
313 elif field_name.startswith('@'):
314 return "%s__search" % field_name[1:]
316 return "%s__icontains" % field_name
318 if self.search_fields and query:
319 for word in query.split():
320 or_queries = [Q(**{construct_search(str(field_name)): word}) for field_name in self.search_fields]
321 queryset = queryset.filter(reduce(operator.or_, or_queries))
322 for field_name in self.search_fields:
323 if '__' in field_name:
324 queryset = queryset.distinct()
327 return self.data_serialize_queryset(queryset, params)
330 def data_create(self, request, **kwargs):
331 if not self.has_add_permission(request):
332 raise PermissionDenied
337 def data_update(self, request, **kwargs):
338 if not self.has_change_permission(request):
339 raise PermissionDenied
344 def data_destroy(self, request, **params):
345 if not self.has_delete_permission(request):
346 raise PermissionDenied
350 if type(pks) is not list:
356 obj = self.model._default_manager.all().get(pk=pk)
360 'metaData': self.data_metadata,
366 def data_destroy_consequences(self, request, pks):
367 if not self.has_delete_permission(request):
368 raise PermissionDenied
370 if type(pks) is not list:
372 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
374 collector = NestedObjects()
377 obj._collect_sub_objects(collector)
379 return collector.nested(self.data_serialize_model_instance)