Merge branch 'gilbert-ext4-murano' of git://github.com/lapilofu/philo into gilbert...
[philo.git] / philo / contrib / gilbert / plugins / models.py
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
14 import operator
15
16
17 @ext_action(name='models')
18 class Models(Plugin):
19         """
20         Plugin providing model-related UI and functionality on the client
21         side.
22         
23         """
24         
25         @property
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',
30                 ]
31         
32         @property
33         def icon_names(self):
34                 return super(Models, self).icon_names + [
35                         'databases',
36                         'database',
37                         'plus',
38                         'minus',
39                         'gear',
40                         'pencil',
41                         'database-import',
42                         'block',
43                 ]
44
45
46 @ext_action
47 class ModelAdmin(Plugin):
48         """
49         Default ModelAdmin class used by Sites to expose a model-centric API
50         on the client side.
51         
52         """
53         
54         form = ModelForm
55         icon_name = 'block'
56         search_fields = ()
57         data_columns = ('__unicode__',)
58         data_editable_columns = ()
59         
60         def __init__(self, site, model):
61                 super(ModelAdmin, self).__init__(site)
62                 self.model = model
63                 self.model_meta = model._meta
64         
65         @classmethod
66         def data_serialize_model_instance(cls, obj):
67                 return {
68                         'app_label': obj._meta.app_label,
69                         'name': obj._meta.module_name,
70                         'pk': obj.pk,
71                         '__unicode__': unicode(obj),
72                 }
73         
74         @classmethod
75         def data_serialize_field_value(cls, field, value):
76                 if field is None:
77                         #return smart_unicode(value)
78                         return value
79                 if isinstance(field.rel, ManyToOneRel):
80                         if value is not None:
81                                 return cls.data_serialize_model_instance(value)
82                 elif isinstance(value, FieldFile):
83                         new_value = {
84                                 'path': value.path,
85                                 'url': value.url,
86                                 'size': value.size,
87                         }
88                         if isinstance(value, ImageFieldFile):
89                                 new_value.update({
90                                         'width': value.width,
91                                         'height': value.height,
92                                 })
93                 else:
94                         return value
95         
96         @property
97         def sortable_fields(self):
98                 return [field.name for field in self.model_meta.fields]
99         
100         @property
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))
106         
107         @property
108         def data_fields_spec(self):
109                 spec = []
110                 for field_name in self.data_fields:
111                         field_spec = {
112                                 'name': field_name,
113                         }
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)
119                 return spec
120         
121         @property
122         def data_columns_spec(self):
123                 spec = []
124                 
125                 for field_name in self.data_columns:
126                         column = {
127                                 'dataIndex': field_name,
128                                 'sortable': False,
129                                 'editable': False,
130                         }
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'
139                         spec.append(column)
140                 return spec
141         
142         @property
143         def data_columns_spec_json(self):
144                 return json.dumps(self.data_columns_spec)
145         
146         @property
147         def icon_names(self):
148                 return super(ModelAdmin, self).icon_names + [
149                         self.icon_name
150                 ]
151         
152         @ext_method
153         def has_permission(self, request):
154                 return self.has_read_permission(request) or self.has_add_permission(request)
155         
156         @ext_method
157         def has_read_permission(self, request):
158                 return self.has_change_permission(request)
159         
160         @ext_method
161         def has_add_permission(self, request):
162                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
163         
164         @ext_method
165         def has_change_permission(self, request):
166                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
167         
168         @ext_method
169         def has_delete_permission(self, request):
170                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
171         
172         @ext_method
173         def all(self, request):
174                 if not self.has_read_permission(request):
175                         raise PermissionDenied
176                 return self.model._default_manager.all()
177         
178         def queryset(self, request):
179                 return self.model._default_manager.get_query_set()
180         
181         @ext_method
182         def filter(self, request, **kwargs):
183                 if not self.has_read_permission(request):
184                         raise PermissionDenied
185                 return self.queryset(request).filter(**kwargs)
186         
187         @ext_method
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)
192         
193         @property
194         def form_class(self):
195                 return modelform_factory(self.model, form=self.form)
196         
197         @ext_method
198         def get_form(self, request, **kwargs):
199                 if len(kwargs) > 0:
200                         instance = self.model._default_manager.all().get(**kwargs)
201                 else:
202                         if not self.has_add_permission(request):
203                                 raise PermissionDenied
204                         instance = None
205                 
206                 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
207                         raise PermissionDenied
208                 
209                 return self.form_class(instance=instance).as_extdirect()
210         
211         @ext_method(form_handler=True)
212         def save_form(self, request):
213                 if 'pk' in request.POST:
214                         try:
215                                 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
216                         except ObjectDoesNotExist:
217                                 instance = None
218                 else:
219                         instance = None
220                 
221                 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
222                         raise PermissionDenied
223                 
224                 form = self.form_class(request.POST, request.FILES, instance=instance)
225                 
226                 if form.is_valid():
227                         saved = form.save()
228                         return True, None, saved.pk
229                 else:
230                         return False, form.errors
231         
232         def data_serialize_object(self, obj):
233                 row = {}
234                 for field_name in self.data_fields:
235                         result = None
236                         try:
237                                 field, attr, value = lookup_field(field_name, obj, self)
238                         except (AttributeError, ObjectDoesNotExist):
239                                 pass
240                         else:
241                                 result = self.data_serialize_field_value(field, value)
242                         row[field_name] = result
243                 return row
244         
245         @property
246         def data_metadata(self):
247                 return {
248                         'idProperty': 'pk',
249                         'root': 'root',
250                         'totalProperty': 'total',
251                         'successProperty': 'success',
252                         'fields': self.data_fields_spec,
253                 }
254         
255         def data_serialize_queryset(self, queryset, params=None):
256                 serialized = {
257                         'metaData': self.data_metadata,
258                         'root': [],
259                         'total': queryset.count(),
260                         'success': True,
261                 }
262                 
263                 if params is not None:
264                         if 'sort' in params:
265                                 order_by = params['sort']
266                                 if order_by in self.data_fields:
267                                         if order_by not in self.sortable_fields:
268                                                 try:
269                                                         if hasattr(self, order_by):
270                                                                 attr = getattr(self, order_by)
271                                                         else:
272                                                                 attr = getattr(self.model, order_by)
273                                                         order_by = attr.admin_order_field
274                                                 except AttributeError:
275                                                         order_by = None
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'),
282                                                 }
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)]
291                                 else:
292                                         queryset = queryset[start:]
293                 
294                 for obj in queryset:
295                         serialized['root'].append(self.data_serialize_object(obj))
296                 
297                 return serialized
298         
299         @ext_method
300         def data_read(self, request, **params):
301                 if not self.has_read_permission(request):
302                         raise PermissionDenied
303                 
304                 queryset = self.model._default_manager.all()
305                 query = params.pop('query', None)
306                 filters = params.pop('filters', None)
307                 
308                 if filters:
309                         if isinstance(filters, Q):
310                                 queryset = queryset.filter(filters)
311                         elif isinstance(filters, dict):
312                                 queryset = queryset.filter(**filters)
313                         else:
314                                 raise TypeError('Invalid filters parameter')
315                 
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:]
323                         else:
324                                 return "%s__icontains" % field_name
325                 
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()
333                                         break
334                 
335                 return self.data_serialize_queryset(queryset, params)
336         
337         @ext_method
338         def data_create(self, request, **kwargs):
339                 if not self.has_add_permission(request):
340                         raise PermissionDenied
341                 
342                 return kwargs
343         
344         @ext_method
345         def data_update(self, request, **kwargs):
346                 if not self.has_change_permission(request):
347                         raise PermissionDenied
348                 
349                 return kwargs
350         
351         @ext_method
352         def data_destroy(self, request, **params):
353                 if not self.has_delete_permission(request):
354                         raise PermissionDenied
355                 
356                 pks = params['root']
357                 
358                 if type(pks) is not list:
359                         pks = [pks]
360                 
361                 for pk in pks:
362                         if type(pk) is dict:
363                                 pk = pk['pk']
364                         obj = self.model._default_manager.all().get(pk=pk)
365                         obj.delete()
366                 
367                 return {
368                         'metaData': self.data_metadata,
369                         'success': True,
370                         'root': list(),
371                 }
372         
373         @ext_method
374         def data_destroy_consequences(self, request, pks):
375                 if not self.has_delete_permission(request):
376                         raise PermissionDenied
377                 
378                 if type(pks) is not list:
379                         pks = [pks]
380                 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
381                 
382                 using = router.db_for_write(self.model)
383                 collector = NestedObjects(using=using)
384                 collector.collect(objs)
385                 
386                 return collector.nested(self.data_serialize_model_instance)