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.template.defaultfilters import capfirst
9 from django.utils import simplejson as json
10 from django.utils.encoding import smart_unicode
11 from .base import Plugin
12 from ..extdirect import ext_action, ext_method
16 @ext_action(name='models')
19 Plugin providing model-related UI and functionality on the client
25 def index_js_urls(self):
26 return super(Models, self).index_js_urls + [
27 settings.STATIC_URL + 'gilbert/extjs/examples/ux/SearchField.js',
28 settings.STATIC_URL + 'gilbert/plugins/models.js',
33 return super(Models, self).icon_names + [
46 class ModelAdmin(Plugin):
48 Default ModelAdmin class used by Sites to expose a model-centric API
56 data_columns = ('__unicode__',)
57 data_editable_columns = ()
59 def __init__(self, site, model):
60 super(ModelAdmin, self).__init__(site)
62 self.model_meta = model._meta
65 def data_serialize_model_instance(cls, obj):
67 'app_label': obj._meta.app_label,
68 'name': obj._meta.module_name,
70 '__unicode__': unicode(obj),
74 def data_serialize_field_value(cls, field, value):
76 #return smart_unicode(value)
78 if isinstance(field.rel, ManyToOneRel):
80 return cls.data_serialize_model_instance(value)
81 elif isinstance(value, FieldFile):
87 if isinstance(value, ImageFieldFile):
90 'height': value.height,
96 def sortable_fields(self):
97 return [field.name for field in self.model_meta.fields]
100 def data_fields(self):
101 fields = ['pk', '__unicode__']
102 fields.extend(self.data_columns)
103 fields.extend(field.name for field in self.model_meta.fields)
104 return tuple(set(fields))
107 def data_fields_spec(self):
109 for field_name in self.data_fields:
113 if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
114 field_spec['type'] = 'gilbertmodelforeignkey'
115 elif field_name in [field.name for field in self.model_meta.fields if isinstance(field, FileField)]:
116 field_spec['type'] = 'gilbertmodelfilefield'
117 spec.append(field_spec)
121 def data_columns_spec(self):
124 for field_name in self.data_columns:
126 'dataIndex': field_name,
130 header, attr = label_for_field(field_name, self.model, model_admin=self, return_attr=True)
131 column['header'] = capfirst(header)
132 if (field_name in self.sortable_fields) or (getattr(attr, 'admin_order_field', None) in self.sortable_fields):
133 column['sortable'] = True
134 if field_name in self.data_editable_columns:
135 column['editable'] = True
136 if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
137 column['xtype'] = 'foreignkeycolumn'
142 def data_columns_spec_json(self):
143 return json.dumps(self.data_columns_spec)
146 def icon_names(self):
147 return super(ModelAdmin, self).icon_names + [
152 def has_permission(self, request):
153 return self.has_read_permission(request) or self.has_add_permission(request)
156 def has_read_permission(self, request):
157 return self.has_change_permission(request)
160 def has_add_permission(self, request):
161 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
164 def has_change_permission(self, request):
165 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
168 def has_delete_permission(self, request):
169 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
172 def all(self, request):
173 if not self.has_read_permission(request):
174 raise PermissionDenied
175 return self.model._default_manager.all()
177 def queryset(self, request):
178 return self.model._default_manager.get_query_set()
181 def filter(self, request, **kwargs):
182 if not self.has_read_permission(request):
183 raise PermissionDenied
184 return self.queryset(request).filter(**kwargs)
187 def get(self, request, **kwargs):
188 if not self.has_read_permission(request):
189 raise PermissionDenied
190 return self.queryset(request).values().get(**kwargs)
193 def form_class(self):
194 return modelform_factory(self.model, form=self.form)
197 def get_form(self, request, **kwargs):
199 instance = self.model._default_manager.all().get(**kwargs)
201 if not self.has_add_permission(request):
202 raise PermissionDenied
205 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
206 raise PermissionDenied
208 return self.form_class(instance=instance).as_extdirect()
210 @ext_method(form_handler=True)
211 def save_form(self, request):
212 if 'pk' in request.POST:
214 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
215 except ObjectDoesNotExist:
220 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
221 raise PermissionDenied
223 form = self.form_class(request.POST, request.FILES, instance=instance)
227 return True, None, saved.pk
229 return False, form.errors
231 def data_serialize_object(self, obj):
233 for field_name in self.data_fields:
236 field, attr, value = lookup_field(field_name, obj, self)
237 except (AttributeError, ObjectDoesNotExist):
240 result = self.data_serialize_field_value(field, value)
241 row[field_name] = result
245 def data_metadata(self):
249 'totalProperty': 'total',
250 'successProperty': 'success',
251 'fields': self.data_fields_spec,
254 def data_serialize_queryset(self, queryset, params=None):
256 'metaData': self.data_metadata,
258 'total': queryset.count(),
262 if params is not None:
264 order_by = params['sort']
265 if order_by in self.data_fields:
266 if order_by not in self.sortable_fields:
268 if hasattr(self, order_by):
269 attr = getattr(self, order_by)
271 attr = getattr(self.model, order_by)
272 order_by = attr.admin_order_field
273 except AttributeError:
275 if order_by is not None:
276 if params.get('dir', 'ASC') == 'DESC':
277 order_by = '-' + order_by
278 serialized['metaData']['sortInfo'] = {
279 'field': params['sort'],
280 'direction': params.get('dir', 'ASC'),
282 queryset = queryset.order_by(order_by)
283 if 'start' in params:
284 start = params['start']
285 serialized['metaData']['start'] = start
286 if 'limit' in params:
287 limit = params['limit']
288 serialized['metaData']['limit'] = limit
289 queryset = queryset[start:(start+limit)]
291 queryset = queryset[start:]
294 serialized['root'].append(self.data_serialize_object(obj))
299 def data_read(self, request, **params):
300 if not self.has_read_permission(request):
301 raise PermissionDenied
303 queryset = self.model._default_manager.all()
304 query = params.pop('query', None)
305 filters = params.pop('filters', None)
308 if isinstance(filters, Q):
309 queryset = queryset.filter(filters)
310 elif isinstance(filters, dict):
311 queryset = queryset.filter(**filters)
313 raise TypeError('Invalid filters parameter')
315 def construct_search(field_name):
316 if field_name.startswith('^'):
317 return "%s__istartswith" % field_name[1:]
318 elif field_name.startswith('='):
319 return "%s__iexact" % field_name[1:]
320 elif field_name.startswith('@'):
321 return "%s__search" % field_name[1:]
323 return "%s__icontains" % field_name
325 if self.search_fields and query:
326 for word in query.split():
327 or_queries = [Q(**{construct_search(str(field_name)): word}) for field_name in self.search_fields]
328 queryset = queryset.filter(reduce(operator.or_, or_queries))
329 for field_name in self.search_fields:
330 if '__' in field_name:
331 queryset = queryset.distinct()
334 return self.data_serialize_queryset(queryset, params)
337 def data_create(self, request, **kwargs):
338 if not self.has_add_permission(request):
339 raise PermissionDenied
344 def data_update(self, request, **kwargs):
345 if not self.has_change_permission(request):
346 raise PermissionDenied
351 def data_destroy(self, request, **params):
352 if not self.has_delete_permission(request):
353 raise PermissionDenied
357 if type(pks) is not list:
363 obj = self.model._default_manager.all().get(pk=pk)
367 'metaData': self.data_metadata,
373 def data_destroy_consequences(self, request, pks):
374 if not self.has_delete_permission(request):
375 raise PermissionDenied
377 if type(pks) is not list:
379 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
381 collector = NestedObjects()
384 obj._collect_sub_objects(collector)
386 return collector.nested(self.data_serialize_model_instance)