All of my work from commits: dd4a194, 692644a, 4a60203, 5de46bc, 152042d, 64a2d4e...
[philo.git] / contrib / gilbert / plugins / models.py
1 import operator
2 from django.forms.models import ModelForm, modelform_factory
3 from django.db.models import Q
4 from django.db.models.fields.related import ManyToOneRel
5 from django.db.models.fields.files import FieldFile, ImageFieldFile, FileField
6 from django.contrib.admin.util import lookup_field, label_for_field, display_for_field, NestedObjects
7 from django.utils.encoding import smart_unicode
8 import staticmedia
9 from .base import Plugin
10 from ..extdirect import ext_action, ext_method
11 from django.core.exceptions import PermissionDenied
12 from django.utils import simplejson as json
13
14
15 @ext_action(name='models')
16 class Models(Plugin):
17         """
18         Plugin providing model-related UI and functionality on the client
19         side.
20         
21         """
22         
23         @property
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'),
28                 ]
29         
30         @property
31         def icon_names(self):
32                 return super(Models, self).icon_names + [
33                         'databases',
34                         'database',
35                         'plus',
36                         'minus',
37                         'gear',
38                         'pencil',
39                         'database-import',
40                         'block',
41                 ]
42
43
44 @ext_action
45 class ModelAdmin(Plugin):
46         """
47         Default ModelAdmin class used by Sites to expose a model-centric API
48         on the client side.
49         
50         """
51         
52         form = ModelForm
53         icon_name = 'block'
54         search_fields = ()
55         data_columns = ('__unicode__',)
56         data_editable_columns = ()
57         
58         def __init__(self, site, model):
59                 super(ModelAdmin, self).__init__(site)
60                 self.model = model
61                 self.model_meta = model._meta
62         
63         @classmethod
64         def data_serialize_model_instance(cls, obj):
65                 return {
66                         'app_label': obj._meta.app_label,
67                         'name': obj._meta.object_name,
68                         'pk': obj.pk,
69                         '__unicode__': unicode(obj),
70                 }
71         
72         @classmethod
73         def data_serialize_field_value(cls, field, value):
74                 if field is None:
75                         #return smart_unicode(value)
76                         return value
77                 if isinstance(field.rel, ManyToOneRel):
78                         if value is not None:
79                                 return cls.data_serialize_model_instance(value)
80                 elif isinstance(value, FieldFile):
81                         new_value = {
82                                 'path': value.path,
83                                 'url': value.url,
84                                 'size': value.size,
85                         }
86                         if isinstance(value, ImageFieldFile):
87                                 new_value.update({
88                                         'width': value.width,
89                                         'height': value.height,
90                                 })
91                 else:
92                         return value
93         
94         @property
95         def sortable_fields(self):
96                 return [field.name for field in self.model_meta.fields]
97         
98         @property
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))
104         
105         @property
106         def data_fields_spec(self):
107                 spec = []
108                 for field_name in self.data_fields:
109                         field_spec = {
110                                 'name': field_name,
111                         }
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)
117                 return spec
118         
119         @property
120         def data_columns_spec(self):
121                 spec = []
122                 for field_name in self.data_columns:
123                         column = {
124                                 'dataIndex': field_name,
125                                 'sortable': False,
126                                 'editable': False,
127                         }
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
134                         spec.append(column)
135                 return spec
136         
137         @property
138         def data_columns_spec_json(self):
139                 return json.dumps(self.data_columns_spec)
140         
141         @property
142         def icon_names(self):
143                 return super(ModelAdmin, self).icon_names + [
144                         self.icon_name
145                 ]
146         
147         @ext_method
148         def has_permission(self, request):
149                 return self.has_read_permission(request) or self.has_add_permission(request)
150         
151         @ext_method
152         def has_read_permission(self, request):
153                 return self.has_change_permission(request)
154         
155         @ext_method
156         def has_add_permission(self, request):
157                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
158         
159         @ext_method
160         def has_change_permission(self, request):
161                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
162         
163         @ext_method
164         def has_delete_permission(self, request):
165                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
166         
167         @ext_method
168         def all(self, request):
169                 if not self.has_read_permission(request):
170                         raise PermissionDenied
171                 return self.model._default_manager.all()
172         
173         @ext_method
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)
178         
179         @ext_method
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)
184         
185         @property
186         def form_class(self):
187                 return modelform_factory(self.model, form=self.form)
188         
189         @ext_method
190         def get_form(self, request, **kwargs):
191                 if len(kwargs) > 0:
192                         instance = self.model._default_manager.all().get(**kwargs)
193                 else:
194                         if not self.has_add_permission(request):
195                                 raise PermissionDenied
196                         instance = None
197                 
198                 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
199                         raise PermissionDenied
200                 
201                 return self.form_class(instance=instance).as_extdirect()
202         
203         @ext_method(form_handler=True)
204         def save_form(self, request):
205                 if 'pk' in request.POST:
206                         try:
207                                 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
208                         except ObjectDoesNotExist:
209                                 instance = None
210                 else:
211                         instance = None
212                 
213                 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
214                         raise PermissionDenied
215                 
216                 form = self.form_class(request.POST, request.FILES, instance=instance)
217                 
218                 try:
219                         saved = form.save()
220                         return True, None, saved.pk
221                 except ValueError:
222                         return False, form.errors
223         
224         def data_serialize_object(self, obj):
225                 row = {}
226                 for field_name in self.data_fields:
227                         result = None
228                         try:
229                                 field, attr, value = lookup_field(field_name, obj, self)
230                         except (AttributeError, ObjectDoesNotExist):
231                                 pass
232                         else:
233                                 result = self.data_serialize_field_value(field, value)
234                         row[field_name] = result
235                 return row
236         
237         @property
238         def data_metadata(self):
239                 return {
240                         'idProperty': 'pk',
241                         'root': 'root',
242                         'totalProperty': 'total',
243                         'successProperty': 'success',
244                         'fields': self.data_fields_spec,
245                 }
246         
247         def data_serialize_queryset(self, queryset, params=None):
248                 serialized = {
249                         'metaData': self.data_metadata,
250                         'root': [],
251                         'total': queryset.count(),
252                         'success': True,
253                 }
254                 
255                 if params is not None:
256                         if 'sort' in params:
257                                 order_by = params['sort']
258                                 if order_by in self.data_fields:
259                                         if order_by not in self.sortable_fields:
260                                                 try:
261                                                         if hasattr(self, order_by):
262                                                                 attr = getattr(self, order_by)
263                                                         else:
264                                                                 attr = getattr(self.model, order_by)
265                                                         order_by = attr.admin_order_field
266                                                 except AttributeError:
267                                                         order_by = None
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'),
274                                                 }
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)]
283                                 else:
284                                         queryset = queryset[start:]
285                 
286                 for obj in queryset:
287                         serialized['root'].append(self.data_serialize_object(obj))
288                 
289                 return serialized
290         
291         @ext_method
292         def data_read(self, request, **params):
293                 if not self.has_read_permission(request):
294                         raise PermissionDenied
295                 
296                 queryset = self.model._default_manager.all()
297                 query = params.pop('query', None)
298                 filters = params.pop('filters', None)
299                 
300                 if filters:
301                         if isinstance(filters, Q):
302                                 queryset = queryset.filter(filters)
303                         elif isinstance(filters, dict):
304                                 queryset = queryset.filter(**filters)
305                         else:
306                                 raise TypeError('Invalid filters parameter')
307                 
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:]
315                         else:
316                                 return "%s__icontains" % field_name
317                 
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()
325                                         break
326                 
327                 return self.data_serialize_queryset(queryset, params)
328         
329         @ext_method
330         def data_create(self, request, **kwargs):
331                 if not self.has_add_permission(request):
332                         raise PermissionDenied
333                 
334                 return kwargs
335         
336         @ext_method
337         def data_update(self, request, **kwargs):
338                 if not self.has_change_permission(request):
339                         raise PermissionDenied
340                 
341                 return kwargs
342         
343         @ext_method
344         def data_destroy(self, request, **params):
345                 if not self.has_delete_permission(request):
346                         raise PermissionDenied
347                 
348                 pks = params['root']
349                 
350                 if type(pks) is not list:
351                         pks = [pks]
352                 
353                 for pk in pks:
354                         if type(pk) is dict:
355                                 pk = pk['pk']
356                         obj = self.model._default_manager.all().get(pk=pk)
357                         obj.delete()
358                 
359                 return {
360                         'metaData': self.data_metadata,
361                         'success': True,
362                         'root': list(),
363                 }
364         
365         @ext_method
366         def data_destroy_consequences(self, request, pks):
367                 if not self.has_delete_permission(request):
368                         raise PermissionDenied
369                 
370                 if type(pks) is not list:
371                         pks = [pks]
372                 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
373                 
374                 collector = NestedObjects()
375                 
376                 for obj in objs:
377                         obj._collect_sub_objects(collector)
378                 
379                 return collector.nested(self.data_serialize_model_instance)