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
13 import operator, staticmedia
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 staticmedia.url('gilbert/extjs/examples/ux/SearchField.js'),
28 staticmedia.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):
123 for field_name in self.data_columns:
125 'dataIndex': field_name,
129 header, attr = label_for_field(field_name, self.model, model_admin=self, return_attr=True)
130 column['header'] = capfirst(header)
131 if (field_name in self.sortable_fields) or (getattr(attr, 'admin_order_field', None) in self.sortable_fields):
132 column['sortable'] = True
133 if field_name in self.data_editable_columns:
134 column['editable'] = True
139 def data_columns_spec_json(self):
140 return json.dumps(self.data_columns_spec)
143 def icon_names(self):
144 return super(ModelAdmin, self).icon_names + [
149 def has_permission(self, request):
150 return self.has_read_permission(request) or self.has_add_permission(request)
153 def has_read_permission(self, request):
154 return self.has_change_permission(request)
157 def has_add_permission(self, request):
158 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
161 def has_change_permission(self, request):
162 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
165 def has_delete_permission(self, request):
166 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
169 def all(self, request):
170 if not self.has_read_permission(request):
171 raise PermissionDenied
172 return self.model._default_manager.all()
174 def queryset(self, request):
175 return self.model._default_manager.get_query_set()
178 def filter(self, request, **kwargs):
179 if not self.has_read_permission(request):
180 raise PermissionDenied
181 return self.queryset(request).filter(**kwargs)
184 def get(self, request, **kwargs):
185 if not self.has_read_permission(request):
186 raise PermissionDenied
187 return self.queryset(request).values().get(**kwargs)
190 def form_class(self):
191 return modelform_factory(self.model, form=self.form)
194 def get_form(self, request, **kwargs):
196 instance = self.model._default_manager.all().get(**kwargs)
198 if not self.has_add_permission(request):
199 raise PermissionDenied
202 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
203 raise PermissionDenied
205 return self.form_class(instance=instance).as_extdirect()
207 @ext_method(form_handler=True)
208 def save_form(self, request):
209 if 'pk' in request.POST:
211 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
212 except ObjectDoesNotExist:
217 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
218 raise PermissionDenied
220 form = self.form_class(request.POST, request.FILES, instance=instance)
224 return True, None, saved.pk
226 return False, form.errors
228 def data_serialize_object(self, obj):
230 for field_name in self.data_fields:
233 field, attr, value = lookup_field(field_name, obj, self)
234 except (AttributeError, ObjectDoesNotExist):
237 result = self.data_serialize_field_value(field, value)
238 row[field_name] = result
242 def data_metadata(self):
246 'totalProperty': 'total',
247 'successProperty': 'success',
248 'fields': self.data_fields_spec,
251 def data_serialize_queryset(self, queryset, params=None):
253 'metaData': self.data_metadata,
255 'total': queryset.count(),
259 if params is not None:
261 order_by = params['sort']
262 if order_by in self.data_fields:
263 if order_by not in self.sortable_fields:
265 if hasattr(self, order_by):
266 attr = getattr(self, order_by)
268 attr = getattr(self.model, order_by)
269 order_by = attr.admin_order_field
270 except AttributeError:
272 if order_by is not None:
273 if params.get('dir', 'ASC') == 'DESC':
274 order_by = '-' + order_by
275 serialized['metaData']['sortInfo'] = {
276 'field': params['sort'],
277 'direction': params.get('dir', 'ASC'),
279 queryset = queryset.order_by(order_by)
280 if 'start' in params:
281 start = params['start']
282 serialized['metaData']['start'] = start
283 if 'limit' in params:
284 limit = params['limit']
285 serialized['metaData']['limit'] = limit
286 queryset = queryset[start:(start+limit)]
288 queryset = queryset[start:]
291 serialized['root'].append(self.data_serialize_object(obj))
296 def data_read(self, request, **params):
297 if not self.has_read_permission(request):
298 raise PermissionDenied
300 queryset = self.model._default_manager.all()
301 query = params.pop('query', None)
302 filters = params.pop('filters', None)
305 if isinstance(filters, Q):
306 queryset = queryset.filter(filters)
307 elif isinstance(filters, dict):
308 queryset = queryset.filter(**filters)
310 raise TypeError('Invalid filters parameter')
312 def construct_search(field_name):
313 if field_name.startswith('^'):
314 return "%s__istartswith" % field_name[1:]
315 elif field_name.startswith('='):
316 return "%s__iexact" % field_name[1:]
317 elif field_name.startswith('@'):
318 return "%s__search" % field_name[1:]
320 return "%s__icontains" % field_name
322 if self.search_fields and query:
323 for word in query.split():
324 or_queries = [Q(**{construct_search(str(field_name)): word}) for field_name in self.search_fields]
325 queryset = queryset.filter(reduce(operator.or_, or_queries))
326 for field_name in self.search_fields:
327 if '__' in field_name:
328 queryset = queryset.distinct()
331 return self.data_serialize_queryset(queryset, params)
334 def data_create(self, request, **kwargs):
335 if not self.has_add_permission(request):
336 raise PermissionDenied
341 def data_update(self, request, **kwargs):
342 if not self.has_change_permission(request):
343 raise PermissionDenied
348 def data_destroy(self, request, **params):
349 if not self.has_delete_permission(request):
350 raise PermissionDenied
354 if type(pks) is not list:
360 obj = self.model._default_manager.all().get(pk=pk)
364 'metaData': self.data_metadata,
370 def data_destroy_consequences(self, request, pks):
371 if not self.has_delete_permission(request):
372 raise PermissionDenied
374 if type(pks) is not list:
376 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
378 collector = NestedObjects()
381 obj._collect_sub_objects(collector)
383 return collector.nested(self.data_serialize_model_instance)