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