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 """
29 Camelot extends the SQLAlchemy field types with a number of its own field
30 types. Those field types are automatically mapped to a specific delegate taking
31 care of the visualisation.
32
33 Those fields are stored in the :mod:`camelot.types` module.
34 """
35
36 import logging
37 logger = logging.getLogger('camelot.types')
38
39 from sqlalchemy import types
40
41 from camelot.core.files.storage import StoredFile, StoredImage, Storage
42
44 """A single field that can be used to enter phone numbers, fax numbers, email
45 addresses, im addresses. The editor provides soft validation of the data
46 entered. The address or number is stored as a string in the database.
47
48 This column type accepts and returns tuples of strings, the first string is
49 the :attr:`virtual_address_type`, and the second the address itself.
50
51 eg: ``('email','project-camelot@conceptive.be')`` is stored as
52 ``mail://project-camelot@conceptive.be``
53
54 .. image:: ../_static/virtualaddress_editor.png
55 """
56
57 impl = types.Unicode
58 virtual_address_types = ['phone', 'fax', 'mobile', 'email', 'im', 'pager',]
59
61
62 impl_processor = self.impl.bind_processor(dialect)
63 if not impl_processor:
64 impl_processor = lambda x:x
65
66 def processor(value):
67 if value is not None:
68 if value[1]:
69 value = u'://'.join(value)
70 else:
71 value = None
72 return impl_processor(value)
73
74 return processor
75
77
78 impl_processor = self.impl.result_processor(dialect)
79 if not impl_processor:
80 impl_processor = lambda x:x
81
82 def processor(value):
83
84 if value:
85 split = value.split('://')
86 if len(split)>1:
87 return tuple(split)
88 return (u'phone',u'')
89
90 return processor
91
92
93 -class Code(types.TypeDecorator):
94 """SQLAlchemy column type to store codes. Where a code is a list of strings
95 on which a regular expression can be enforced.
96
97 This column type accepts and returns a list of strings and stores them as a
98 string joined with points.
99
100 eg: ``['08', 'AB']`` is stored as ``08.AB``
101
102 .. image:: ../_static/editors/CodeEditor_editable.png
103 """
104
105 impl = types.Unicode
106
107 - def __init__(self, parts, separator=u'.', **kwargs):
108 """
109 :param parts: a list of input masks specifying the mask for each part,
110 eg ``['99', 'AA']``. For valid input masks, see
111 `QLineEdit <http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qlineedit.html>`_
112 """
113 import string
114 translator = string.maketrans('', '')
115 self.parts = parts
116 self.separator = separator
117 max_length = sum(len(part.translate(translator, '<>!')) for part in parts) + len(parts)*len(self.separator)
118 types.TypeDecorator.__init__(self, length=max_length, **kwargs)
119
121
122 impl_processor = self.impl.bind_processor(dialect)
123 if not impl_processor:
124 impl_processor = lambda x:x
125
126 def processor(value):
127 if value is not None:
128 value = self.separator.join(value)
129 return impl_processor(value)
130
131 return processor
132
134
135 impl_processor = self.impl.result_processor(dialect)
136 if not impl_processor:
137 impl_processor = lambda x:x
138
139 def processor(value):
140
141 if value:
142 return value.split(self.separator)
143 return ['' for _p in self.parts]
144
145 return processor
146
150
151 -class Rating(types.TypeDecorator):
152 """The rating field is an integer field that is visualized as a number of stars that
153 can be selected::
154
155 class Movie(Entity):
156 title = Field(Unicode(60), required=True)
157 rating = Field(camelot.types.Rating())
158
159 .. image:: ../_static/editors/StarEditor_editable.png
160 """
161
162 impl = types.Integer
163
164 -class RichText(types.TypeDecorator):
165 """RichText fields are unlimited text fields which contain html. The html will be
166 rendered in a rich text editor.
167
168 .. image:: ../_static/editors/RichTextEditor_editable.png
169 """
170
171 impl = types.UnicodeText
172
197
198 -class Color(types.TypeDecorator):
199 """The Color field returns and accepts tuples of the form (r,g,b,a) where
200 r,g,b,a are integers between 0 and 255. The color is stored as an hexadecimal
201 string of the form AARRGGBB into the database, where AA is the transparency,
202 RR is red, GG is green BB is blue::
203
204 class MovieType(Entity):
205 color = Field(camelot.types.Color())
206
207 .. image:: ../_static/editors/ColorEditor_editable.png
208
209 The colors are stored in the database as strings
210 """
211
212 impl = types.Unicode
213
216
218
219 impl_processor = self.impl.bind_processor(dialect)
220 if not impl_processor:
221 impl_processor = lambda x:x
222
223 def processor(value):
224 if value is not None:
225 assert len(value) == 4
226 for i in range(4):
227 assert value[i] >= 0
228 assert value[i] <= 255
229 return '%02X%02X%02X%02X'%(value[3], value[0], value[1], value[2])
230 return impl_processor(value)
231
232 return processor
233
235
236 impl_processor = self.impl.result_processor(dialect)
237 if not impl_processor:
238 impl_processor = lambda x:x
239
240 def processor(value):
241
242 if value:
243 return (int(value[2:4],16), int(value[4:6],16), int(value[6:8],16), int(value[0:2],16))
244
245 return processor
246
248 """The enumeration field stores integers in the database, but represents them as
249 strings. This allows efficient storage and querying while preserving readable code.
250
251 Typical use of this field would be a status field.
252
253 Enumeration fields are visualized as a combo box, where the labels in the combo
254 box are the capitalized strings::
255
256 class Movie(Entity):
257 title = Field(Unicode(60), required=True)
258 state = Field(camelot.types.Enumeration([(1,'planned'), (2,'recording'), (3,'finished'), (4,'canceled')]),
259 index=True, required=True, default='planning')
260
261 .. image:: ../_static/editors/ChoicesEditor_editable.png
262 """
263
264 impl = types.Integer
265
266 - def __init__(self, choices=[], **kwargs):
267 """
268 @param param: choices is a list of tuples. each tuple contains an integer and its
269 associated string. eg. : choices = [(1,'draft'), (2,'approved')]
270 """
271 types.TypeDecorator.__init__(self, **kwargs)
272 self._int_to_string = dict(choices)
273 self._string_to_int = dict((v,k) for (k,v) in choices)
274 self.choices = [v for (k,v) in choices]
275
277
278 impl_processor = self.impl.bind_processor(dialect)
279 if not impl_processor:
280 impl_processor = lambda x:x
281
282 def processor(value):
283 if value is not None:
284 try:
285 value = self._string_to_int[value]
286 return impl_processor(value)
287 except KeyError, e:
288 logger.error('could not process enumeration value %s, possible values are %s'%(value, u', '.join(list(self._string_to_int.keys()))), exc_info=e)
289
290 return processor
291
293
294 impl_processor = self.impl.result_processor(dialect)
295 if not impl_processor:
296 impl_processor = lambda x:x
297
298 def processor(value):
299 if value is not None:
300 value = impl_processor(value)
301 try:
302 return self._int_to_string[value]
303 except KeyError, e:
304 logger.error('could not process %s'%value, exc_info=e)
305
306 return processor
307
308 -class File(types.TypeDecorator):
309 """Sqlalchemy column type to store files. Only the location of the file is stored
310
311 This column type accepts and returns a StoredFile, and stores them in the directory
312 specified by settings.MEDIA_ROOT. The name of the file is stored as a string in
313 the database. A subdirectory upload_to can be specified::
314
315 class Movie(Entity):
316 script = Field(camelot.types.File(upload_to='script'))
317
318 .. image:: ../_static/editors/FileEditor_editable.png
319 """
320
321 impl = types.Unicode
322 stored_file_implementation = StoredFile
323
324 - def __init__(self, max_length=100, upload_to='', storage=Storage, **kwargs):
325 self.max_length = max_length
326 self.storage = storage(upload_to, self.stored_file_implementation)
327 types.TypeDecorator.__init__(self, length=max_length, **kwargs)
328
330
331 impl_processor = self.impl.bind_processor(dialect)
332 if not impl_processor:
333 impl_processor = lambda x:x
334
335 def processor(value):
336 if value is not None:
337 assert isinstance(value, (self.stored_file_implementation))
338 return impl_processor(value.name)
339 return impl_processor(value)
340
341 return processor
342
344
345 impl_processor = self.impl.result_processor(dialect)
346 if not impl_processor:
347 impl_processor = lambda x:x
348
349 def processor(value):
350
351 if value:
352 value = impl_processor(value)
353 return self.stored_file_implementation(self.storage, value)
354
355 return processor
356
358 """Sqlalchemy column type to store images
359
360 This column type accepts and returns a StoredImage, and stores them in the directory
361 specified by settings.MEDIA_ROOT. The name of the file is stored as a string in
362 the database.
363
364 The Image field type provides the same functionallity as the File field type, but
365 the files stored should be images.
366
367 .. image:: ../_static/editors/ImageEditor_editable.png
368 """
369
370 stored_file_implementation = StoredImage
371