1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import logging
29 logger = logging.getLogger('camelot.view.elixir_admin')
30
31 import sqlalchemy.sql.expression
32
33 from camelot.admin.object_admin import ObjectAdmin
34 from camelot.view.model_thread import post, model_function, gui_function
35 from camelot.core.utils import ugettext_lazy
36 from camelot.admin.validator.entity_validator import EntityValidator
40 """Admin class specific for classes that are mapped by sqlalchemy.
41 This allows for much more introspection than the standard ObjectAdmin.
42 """
43
44 validator = EntityValidator
45
46 @model_function
48 """:return: an sqlalchemy query for all the objects that should be
49 displayed in the table or the selection view. Overwrite this method to
50 change the default query, which selects all rows in the database.
51 """
52 return self.entity.query
53
54 @model_function
56 """Get the admin class for an entity that is a subclass of this admin's
57 entity or this admin's entity itself.
58 """
59 for subclass_admin in self.get_subclasses():
60 if subclass_admin.entity == entity:
61 return subclass_admin
62 return self
63
64 @model_function
66 """Returns admin objects for the subclasses of the Entity represented
67 by this admin object.
68 """
69 if not self._subclasses:
70 from elixir import entities
71 self._subclasses = [
72 e.Admin(self.app_admin, e)
73 for e in entities
74 if (
75 issubclass(e, (self.entity,))
76 and hasattr(e, 'Admin')
77 and e != self.entity
78 )
79 ]
80 return self._subclasses
81
82 @model_function
84 if hasattr(obj, 'id') and obj.id:
85 if hasattr(obj, '__unicode__'):
86 return u'%s %s : %s' % (
87 unicode(self.get_verbose_name()),
88 unicode(obj.id),
89 unicode(obj)
90 )
91 else:
92 return u'%s %s' % (
93 self.get_verbose_name(),
94 unicode(obj.id)
95 )
96 else:
97 return self.get_verbose_name()
98
99 @model_function
101 """Get the attributes needed to visualize the field field_name
102 :param field_name: the name of the field
103
104 :return: a dictionary of attributes needed to visualize the field,
105 those attributes can be:
106 * python_type : the corresponding python type of the object
107 * editable : bool specifying wether the user can edit this field
108 * widget : which widget to be used to render the field
109 * ...
110 """
111 try:
112 return self._field_attributes[field_name]
113 except KeyError:
114
115 def create_default_getter(field_name):
116 return lambda o:getattr(o, field_name)
117
118 from camelot.view.controls import delegates
119
120
121
122 attributes = dict(
123 python_type = str,
124 getter = create_default_getter(field_name),
125 length = None,
126 tooltip = None,
127 background_color = None,
128 minimal_column_width = 12,
129 editable = False,
130 nullable = True,
131 widget = 'str',
132 blank = True,
133 delegate = delegates.PlainTextDelegate,
134 validator_list = [],
135 name = ugettext_lazy(field_name.replace('_', ' ').capitalize())
136 )
137
138
139
140
141 forced_attributes = {}
142 try:
143 forced_attributes = self.field_attributes[field_name]
144 except KeyError:
145 pass
146
147 def get_entity_admin(target):
148 """Helper function that instantiated an Admin object for a
149 target entity class.
150
151 :param target: an entity class for which an Admin object is
152 needed.
153 """
154 try:
155 fa = self.field_attributes[field_name]
156 target = fa.get('target', target)
157 admin_class = fa['admin']
158 return admin_class(self.app_admin, target)
159 except KeyError:
160 return self.get_related_entity_admin(target)
161
162
163
164
165 from sqlalchemy import orm
166 from sqlalchemy.exceptions import InvalidRequestError
167 from sqlalchemy.orm.exc import UnmappedClassError
168 from field_attributes import _sqlalchemy_to_python_type_
169 try:
170 mapper = orm.class_mapper(self.entity)
171 except UnmappedClassError, exception:
172 from elixir import entities
173 mapped_entities = [str(e) for e in entities]
174 logger.error(u'%s is not a mapped class, mapped classes include %s'%(self.entity, u','.join([unicode(me) for me in mapped_entities])),
175 exc_info=exception)
176 raise exception
177 try:
178 property = mapper.get_property(
179 field_name,
180 resolve_synonyms=True
181 )
182 if isinstance(property, orm.properties.ColumnProperty):
183 type = property.columns[0].type
184 python_type = _sqlalchemy_to_python_type_.get(
185 type.__class__,
186 None
187 )
188 if python_type:
189 attributes.update(python_type(type))
190 if not isinstance(
191 property.columns[0],
192 sqlalchemy.sql.expression._Label
193 ):
194 attributes['nullable'] = property.columns[0].nullable
195 attributes['default'] = property.columns[0].default
196 elif isinstance(property, orm.properties.PropertyLoader):
197 target = property._get_target().class_
198 foreign_keys = list(property._foreign_keys)
199 if property.direction == orm.interfaces.ONETOMANY:
200 attributes.update(
201 python_type = list,
202 editable = True,
203 nullable = True,
204 delegate = delegates.One2ManyDelegate,
205 target = target,
206 create_inline = False,
207 backref = property.backref.key,
208 direction = property.direction,
209 admin = get_entity_admin(target)
210 )
211 elif property.direction == orm.interfaces.MANYTOONE:
212 attributes.update(
213 python_type = str,
214 editable = True,
215 delegate = delegates.Many2OneDelegate,
216 target = target,
217
218
219
220
221 nullable = foreign_keys[0].nullable,
222 direction = property.direction,
223 admin = get_entity_admin(target)
224 )
225 elif property.direction == orm.interfaces.MANYTOMANY:
226 attributes.update(
227 python_type = list,
228 editable = True,
229 target = target,
230 nullable = True,
231 create_inline = False,
232 direction = property.direction,
233 delegate = delegates.ManyToManyDelegate,
234 admin = get_entity_admin(target)
235 )
236 else:
237 raise Exception('PropertyLoader has unknown direction')
238 except InvalidRequestError:
239
240
241
242
243 pass
244
245 if 'choices' in forced_attributes:
246 attributes['delegate'] = delegates.ComboBoxDelegate
247 attributes['editable'] = True
248
249
250
251
252 attributes.update(forced_attributes)
253
254
255
256
257
258 if 'target' in attributes:
259 attributes['admin'] = get_entity_admin(attributes['target'])
260
261 self._field_attributes[field_name] = attributes
262 return attributes
263
264 @model_function
267
268 @model_function
280
281 return list(filter_generator())
282
283 @model_function
284 - def set_defaults(self, entity_instance, include_nullable_fields=True):
285 """Set the defaults of an object
286 :param include_nullable_fields: also set defaults for nullable fields, depending
287 on the context, this should be set to False to allow the user to set the field
288 to None
289 """
290 from sqlalchemy.schema import ColumnDefault
291 for field, attributes in self.get_fields():
292 has_default = False
293 try:
294 default = attributes['default']
295 has_default = True
296 except KeyError:
297 pass
298 if has_default:
299
300
301
302
303 value = attributes['getter'](entity_instance)
304 if value:
305 continue
306 if isinstance(default, ColumnDefault):
307 default_value = default.execute()
308 elif callable(default):
309 import inspect
310 args, _varargs, _kwargs, _defs = \
311 inspect.getargspec(default)
312 if len(args):
313 default_value = default(entity_instance)
314 else:
315 default_value = default()
316 else:
317 default_value = default
318 logger.debug(
319 'set default for %s to %s' % (
320 field,
321 unicode(default_value)
322 )
323 )
324 try:
325 setattr(entity_instance, field, default_value)
326 except AttributeError, e:
327 logger.error(
328 'Programming Error : could not set'
329 ' attribute %s to %s on %s' % (
330 field,
331 default_value,
332 entity_instance.__class__.__name__
333 ),
334 exc_info=e
335 )
336
337
338 @gui_function
340 """Returns a Qt widget that can be used to select an element from a
341 query
342
343 :param query: sqlalchemy query object
344
345 :param parent: the widget that will contain this select view, the
346 returned widget has an entity_selected_signal signal that will be fired
347 when a entity has been selected.
348 """
349 from controls.tableview import TableView
350 from art import Icon
351 from proxy.queryproxy import QueryTableProxy
352 from PyQt4.QtCore import SIGNAL
353
354 class SelectQueryTableProxy(QueryTableProxy):
355 header_icon = Icon('tango/16x16/emblems/emblem-symbolic-link.png')
356
357 class SelectView(TableView):
358
359 table_model = SelectQueryTableProxy
360 title_format = 'Select %s'
361
362 def __init__(self, admin, parent):
363 TableView.__init__(
364 self, admin,
365 search_text=search_text, parent=parent
366 )
367 self.entity_selected_signal = SIGNAL('entity_selected')
368 self.connect(self, SIGNAL('row_selected'), self.sectionClicked)
369 self.setUpdatesEnabled(True)
370
371 def emit_and_close(self, instance_getter):
372 self.emit(self.entity_selected_signal, instance_getter)
373 from camelot.view.workspace import get_workspace
374 for window in get_workspace().subWindowList():
375 if hasattr(window, 'widget') and window.widget() == self:
376 window.close()
377
378 def sectionClicked(self, index):
379
380
381 if self._table_model:
382
383 def create_constant_getter(cst):
384 return lambda:cst
385
386 def create_instance_getter():
387 entity = self._table_model._get_object(index)
388 return create_constant_getter(entity)
389
390 post(create_instance_getter, self.emit_and_close)
391
392 widget = SelectView(admin, parent)
393 widget.setUpdatesEnabled(True)
394 widget.setMinimumSize(admin.list_size[0], admin.list_size[1])
395 widget.update()
396 return widget
397
398 @gui_function
431
432 return openForm
433
434 tableview.connect(
435 tableview,
436 SIGNAL('row_selected'),
437 createOpenForm(self, tableview)
438 )
439
440 return tableview
441
442 @model_function
443 - def delete(self, entity_instance):
472
473 @model_function
474 - def flush(self, entity_instance):
475 """Flush the pending changes of this entity instance to the backend"""
476 from sqlalchemy.orm.session import Session
477 session = Session.object_session( entity_instance )
478 if not session:
479 logger.error('Programming Error : entity %s cannot be flushed because it has no session'%(unicode(entity_instance)))
480 session.flush( [entity_instance] )
481
482
483 @model_function
484 - def copy(self, entity_instance):
485 """Duplicate this entity instance"""
486 new_entity_instance = entity_instance.__class__()
487 new_entity_instance.from_dict( entity_instance.to_dict(exclude=['id']) )
488 return new_entity_instance
489