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 import router
5 from django.db.models import Q
6 from django.db.models.fields.related import ManyToOneRel
7 from django.db.models.fields.files import FieldFile, ImageFieldFile, FileField
8 from django.forms.models import ModelForm, modelform_factory
9 from django.template.defaultfilters import capfirst
10 from django.utils import simplejson as json
11 from django.utils.encoding import smart_unicode
12 from .base import Plugin
13 from ..extdirect import ext_action, ext_method
17 @ext_action(name='models')
20 Plugin providing model-related UI and functionality on the client
26 def index_js_urls(self):
27 return super(Models, self).index_js_urls + [
28 settings.STATIC_URL + 'gilbert/extjs/examples/ux/SearchField.js',
29 settings.STATIC_URL + 'gilbert/plugins/models.js',
34 return super(Models, self).icon_names + [
47 class ModelAdmin(Plugin):
49 Default ModelAdmin class used by Sites to expose a model-centric API
57 data_columns = ('__unicode__',)
58 data_editable_columns = ()
60 def __init__(self, site, model):
61 super(ModelAdmin, self).__init__(site)
63 self.model_meta = model._meta
66 def data_serialize_model_instance(cls, obj):
68 'app_label': obj._meta.app_label,
69 'name': obj._meta.module_name,
71 '__unicode__': unicode(obj),
75 def data_serialize_field_value(cls, field, value):
77 #return smart_unicode(value)
79 if isinstance(field.rel, ManyToOneRel):
81 return cls.data_serialize_model_instance(value)
82 elif isinstance(value, FieldFile):
88 if isinstance(value, ImageFieldFile):
91 'height': value.height,
97 def sortable_fields(self):
98 return [field.name for field in self.model_meta.fields]
101 def data_fields(self):
102 fields = ['pk', '__unicode__']
103 fields.extend(self.data_columns)
104 fields.extend(field.name for field in self.model_meta.fields)
105 return tuple(set(fields))
108 def data_fields_spec(self):
110 for field_name in self.data_fields:
114 if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
115 field_spec['type'] = 'gilbertmodelforeignkey'
116 elif field_name in [field.name for field in self.model_meta.fields if isinstance(field, FileField)]:
117 field_spec['type'] = 'gilbertmodelfilefield'
118 spec.append(field_spec)
122 def data_columns_spec(self):
125 for field_name in self.data_columns:
127 'dataIndex': field_name,
131 header, attr = label_for_field(field_name, self.model, model_admin=self, return_attr=True)
132 column['header'] = capfirst(header)
133 if (field_name in self.sortable_fields) or (getattr(attr, 'admin_order_field', None) in self.sortable_fields):
134 column['sortable'] = True
135 if field_name in self.data_editable_columns:
136 column['editable'] = True
137 if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
138 column['xtype'] = 'foreignkeycolumn'
143 def data_columns_spec_json(self):
144 return json.dumps(self.data_columns_spec)
147 def icon_names(self):
148 return super(ModelAdmin, self).icon_names + [
153 def has_permission(self, request):
154 return self.has_read_permission(request) or self.has_add_permission(request)
157 def has_read_permission(self, request):
158 return self.has_change_permission(request)
161 def has_add_permission(self, request):
162 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
165 def has_change_permission(self, request):
166 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
169 def has_delete_permission(self, request):
170 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
173 def all(self, request):
174 if not self.has_read_permission(request):
175 raise PermissionDenied
176 return self.model._default_manager.all()
178 def queryset(self, request):
179 return self.model._default_manager.get_query_set()
182 def filter(self, request, **kwargs):
183 if not self.has_read_permission(request):
184 raise PermissionDenied
185 return self.queryset(request).filter(**kwargs)
188 def get(self, request, **kwargs):
189 if not self.has_read_permission(request):
190 raise PermissionDenied
191 return self.queryset(request).values().get(**kwargs)
194 def form_class(self):
195 return modelform_factory(self.model, form=self.form)
198 def get_form(self, request, **kwargs):
200 instance = self.model._default_manager.all().get(**kwargs)
202 if not self.has_add_permission(request):
203 raise PermissionDenied
206 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
207 raise PermissionDenied
209 return self.form_class(instance=instance).as_extdirect()
211 @ext_method(form_handler=True)
212 def save_form(self, request):
213 if 'pk' in request.POST:
215 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
216 except ObjectDoesNotExist:
221 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
222 raise PermissionDenied
224 form = self.form_class(request.POST, request.FILES, instance=instance)
228 return True, None, saved.pk
230 return False, form.errors
232 def data_serialize_object(self, obj):
234 for field_name in self.data_fields:
237 field, attr, value = lookup_field(field_name, obj, self)
238 except (AttributeError, ObjectDoesNotExist):
241 result = self.data_serialize_field_value(field, value)
242 row[field_name] = result
246 def data_metadata(self):
250 'totalProperty': 'total',
251 'successProperty': 'success',
252 'fields': self.data_fields_spec,
255 def data_serialize_queryset(self, queryset, params=None):
257 'metaData': self.data_metadata,
259 'total': queryset.count(),
263 if params is not None:
265 order_by = params['sort']
266 if order_by in self.data_fields:
267 if order_by not in self.sortable_fields:
269 if hasattr(self, order_by):
270 attr = getattr(self, order_by)
272 attr = getattr(self.model, order_by)
273 order_by = attr.admin_order_field
274 except AttributeError:
276 if order_by is not None:
277 if params.get('dir', 'ASC') == 'DESC':
278 order_by = '-' + order_by
279 serialized['metaData']['sortInfo'] = {
280 'field': params['sort'],
281 'direction': params.get('dir', 'ASC'),
283 queryset = queryset.order_by(order_by)
284 if 'start' in params:
285 start = params['start']
286 serialized['metaData']['start'] = start
287 if 'limit' in params:
288 limit = params['limit']
289 serialized['metaData']['limit'] = limit
290 queryset = queryset[start:(start+limit)]
292 queryset = queryset[start:]
295 serialized['root'].append(self.data_serialize_object(obj))
300 def data_read(self, request, **params):
301 if not self.has_read_permission(request):
302 raise PermissionDenied
304 queryset = self.model._default_manager.all()
305 query = params.pop('query', None)
306 filters = params.pop('filters', None)
309 if isinstance(filters, Q):
310 queryset = queryset.filter(filters)
311 elif isinstance(filters, dict):
312 queryset = queryset.filter(**filters)
314 raise TypeError('Invalid filters parameter')
316 def construct_search(field_name):
317 if field_name.startswith('^'):
318 return "%s__istartswith" % field_name[1:]
319 elif field_name.startswith('='):
320 return "%s__iexact" % field_name[1:]
321 elif field_name.startswith('@'):
322 return "%s__search" % field_name[1:]
324 return "%s__icontains" % field_name
326 if self.search_fields and query:
327 for word in query.split():
328 or_queries = [Q(**{construct_search(str(field_name)): word}) for field_name in self.search_fields]
329 queryset = queryset.filter(reduce(operator.or_, or_queries))
330 for field_name in self.search_fields:
331 if '__' in field_name:
332 queryset = queryset.distinct()
335 return self.data_serialize_queryset(queryset, params)
338 def data_create(self, request, **kwargs):
339 if not self.has_add_permission(request):
340 raise PermissionDenied
345 def data_update(self, request, **kwargs):
346 if not self.has_change_permission(request):
347 raise PermissionDenied
352 def data_destroy(self, request, **params):
353 if not self.has_delete_permission(request):
354 raise PermissionDenied
358 if type(pks) is not list:
364 obj = self.model._default_manager.all().get(pk=pk)
368 'metaData': self.data_metadata,
374 def data_destroy_consequences(self, request, pks):
375 if not self.has_delete_permission(request):
376 raise PermissionDenied
378 if type(pks) is not list:
380 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
382 using = router.db_for_write(self.model)
383 collector = NestedObjects(using=using)
384 collector.collect(objs)
386 return collector.nested(self.data_serialize_model_instance)