Removed 1-form limit for new instance forms.
[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                 
124                 for field_name in self.data_columns:
125                         column = {
126                                 'dataIndex': field_name,
127                                 'sortable': False,
128                                 'editable': False,
129                         }
130                         header, attr = label_for_field(field_name, self.model, model_admin=self, return_attr=True)
131                         column['header'] = capfirst(header)
132                         if (field_name in self.sortable_fields) or (getattr(attr, 'admin_order_field', None) in self.sortable_fields):
133                                 column['sortable'] = True
134                         if field_name in self.data_editable_columns:
135                                 column['editable'] = True
136                         if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
137                                 column['xtype'] = 'foreignkeycolumn'
138                         spec.append(column)
139                 return spec
140         
141         @property
142         def data_columns_spec_json(self):
143                 return json.dumps(self.data_columns_spec)
144         
145         @property
146         def icon_names(self):
147                 return super(ModelAdmin, self).icon_names + [
148                         self.icon_name
149                 ]
150         
151         @ext_method
152         def has_permission(self, request):
153                 return self.has_read_permission(request) or self.has_add_permission(request)
154         
155         @ext_method
156         def has_read_permission(self, request):
157                 return self.has_change_permission(request)
158         
159         @ext_method
160         def has_add_permission(self, request):
161                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
162         
163         @ext_method
164         def has_change_permission(self, request):
165                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
166         
167         @ext_method
168         def has_delete_permission(self, request):
169                 return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
170         
171         @ext_method
172         def all(self, request):
173                 if not self.has_read_permission(request):
174                         raise PermissionDenied
175                 return self.model._default_manager.all()
176         
177         def queryset(self, request):
178                 return self.model._default_manager.get_query_set()
179         
180         @ext_method
181         def filter(self, request, **kwargs):
182                 if not self.has_read_permission(request):
183                         raise PermissionDenied
184                 return self.queryset(request).filter(**kwargs)
185         
186         @ext_method
187         def get(self, request, **kwargs):
188                 if not self.has_read_permission(request):
189                         raise PermissionDenied
190                 return self.queryset(request).values().get(**kwargs)
191         
192         @property
193         def form_class(self):
194                 return modelform_factory(self.model, form=self.form)
195         
196         @ext_method
197         def get_form(self, request, **kwargs):
198                 if len(kwargs) > 0:
199                         instance = self.model._default_manager.all().get(**kwargs)
200                 else:
201                         if not self.has_add_permission(request):
202                                 raise PermissionDenied
203                         instance = None
204                 
205                 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
206                         raise PermissionDenied
207                 
208                 return self.form_class(instance=instance).as_extdirect()
209         
210         @ext_method(form_handler=True)
211         def save_form(self, request):
212                 if 'pk' in request.POST:
213                         try:
214                                 instance = self.model._default_manager.all().get(pk=request.POST['pk'])
215                         except ObjectDoesNotExist:
216                                 instance = None
217                 else:
218                         instance = None
219                 
220                 if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
221                         raise PermissionDenied
222                 
223                 form = self.form_class(request.POST, request.FILES, instance=instance)
224                 
225                 if form.is_valid():
226                         saved = form.save()
227                         return True, None, saved.pk
228                 else:
229                         return False, form.errors
230         
231         def data_serialize_object(self, obj):
232                 row = {}
233                 for field_name in self.data_fields:
234                         result = None
235                         try:
236                                 field, attr, value = lookup_field(field_name, obj, self)
237                         except (AttributeError, ObjectDoesNotExist):
238                                 pass
239                         else:
240                                 result = self.data_serialize_field_value(field, value)
241                         row[field_name] = result
242                 return row
243         
244         @property
245         def data_metadata(self):
246                 return {
247                         'idProperty': 'pk',
248                         'root': 'root',
249                         'totalProperty': 'total',
250                         'successProperty': 'success',
251                         'fields': self.data_fields_spec,
252                 }
253         
254         def data_serialize_queryset(self, queryset, params=None):
255                 serialized = {
256                         'metaData': self.data_metadata,
257                         'root': [],
258                         'total': queryset.count(),
259                         'success': True,
260                 }
261                 
262                 if params is not None:
263                         if 'sort' in params:
264                                 order_by = params['sort']
265                                 if order_by in self.data_fields:
266                                         if order_by not in self.sortable_fields:
267                                                 try:
268                                                         if hasattr(self, order_by):
269                                                                 attr = getattr(self, order_by)
270                                                         else:
271                                                                 attr = getattr(self.model, order_by)
272                                                         order_by = attr.admin_order_field
273                                                 except AttributeError:
274                                                         order_by = None
275                                         if order_by is not None:
276                                                 if params.get('dir', 'ASC') == 'DESC':
277                                                         order_by = '-' + order_by
278                                                 serialized['metaData']['sortInfo'] = {
279                                                         'field': params['sort'],
280                                                         'direction': params.get('dir', 'ASC'),
281                                                 }
282                                                 queryset = queryset.order_by(order_by)
283                         if 'start' in params:
284                                 start = params['start']
285                                 serialized['metaData']['start'] = start
286                                 if 'limit' in params:
287                                         limit = params['limit']
288                                         serialized['metaData']['limit'] = limit
289                                         queryset = queryset[start:(start+limit)]
290                                 else:
291                                         queryset = queryset[start:]
292                 
293                 for obj in queryset:
294                         serialized['root'].append(self.data_serialize_object(obj))
295                 
296                 return serialized
297         
298         @ext_method
299         def data_read(self, request, **params):
300                 if not self.has_read_permission(request):
301                         raise PermissionDenied
302                 
303                 queryset = self.model._default_manager.all()
304                 query = params.pop('query', None)
305                 filters = params.pop('filters', None)
306                 
307                 if filters:
308                         if isinstance(filters, Q):
309                                 queryset = queryset.filter(filters)
310                         elif isinstance(filters, dict):
311                                 queryset = queryset.filter(**filters)
312                         else:
313                                 raise TypeError('Invalid filters parameter')
314                 
315                 def construct_search(field_name):
316                         if field_name.startswith('^'):
317                                 return "%s__istartswith" % field_name[1:]
318                         elif field_name.startswith('='):
319                                 return "%s__iexact" % field_name[1:]
320                         elif field_name.startswith('@'):
321                                 return "%s__search" % field_name[1:]
322                         else:
323                                 return "%s__icontains" % field_name
324                 
325                 if self.search_fields and query:
326                         for word in query.split():
327                                 or_queries = [Q(**{construct_search(str(field_name)): word}) for field_name in self.search_fields]
328                                 queryset = queryset.filter(reduce(operator.or_, or_queries))
329                         for field_name in self.search_fields:
330                                 if '__' in field_name:
331                                         queryset = queryset.distinct()
332                                         break
333                 
334                 return self.data_serialize_queryset(queryset, params)
335         
336         @ext_method
337         def data_create(self, request, **kwargs):
338                 if not self.has_add_permission(request):
339                         raise PermissionDenied
340                 
341                 return kwargs
342         
343         @ext_method
344         def data_update(self, request, **kwargs):
345                 if not self.has_change_permission(request):
346                         raise PermissionDenied
347                 
348                 return kwargs
349         
350         @ext_method
351         def data_destroy(self, request, **params):
352                 if not self.has_delete_permission(request):
353                         raise PermissionDenied
354                 
355                 pks = params['root']
356                 
357                 if type(pks) is not list:
358                         pks = [pks]
359                 
360                 for pk in pks:
361                         if type(pk) is dict:
362                                 pk = pk['pk']
363                         obj = self.model._default_manager.all().get(pk=pk)
364                         obj.delete()
365                 
366                 return {
367                         'metaData': self.data_metadata,
368                         'success': True,
369                         'root': list(),
370                 }
371         
372         @ext_method
373         def data_destroy_consequences(self, request, pks):
374                 if not self.has_delete_permission(request):
375                         raise PermissionDenied
376                 
377                 if type(pks) is not list:
378                         pks = [pks]
379                 objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
380                 
381                 collector = NestedObjects()
382                 
383                 for obj in objs:
384                         obj._collect_sub_objects(collector)
385                 
386                 return collector.nested(self.data_serialize_model_instance)