/
__init__.py
399 lines (285 loc) · 13.8 KB
/
__init__.py
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# ============================================================================
#
# Copyright (C) 2007-2012 Conceptive Engineering bvba. All rights reserved.
# www.conceptive.be / project-camelot@conceptive.be
#
# This file is part of the Camelot Library.
#
# This file may be used under the terms of the GNU General Public
# License version 2.0 as published by the Free Software Foundation
# and appearing in the file license.txt included in the packaging of
# this file. Please review this information to ensure GNU
# General Public Licensing requirements will be met.
#
# If you are unsure which license is appropriate for your use, please
# visit www.python-camelot.com or contact project-camelot@conceptive.be
#
# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# For use of this library in commercial applications, please contact
# project-camelot@conceptive.be
#
# ============================================================================
"""
Camelot extends the SQLAlchemy column types with a number of its own column
types. Those field types are automatically mapped to a specific delegate taking
care of the visualisation.
Those fields are stored in the :mod:`camelot.types` module.
"""
import logging
logger = logging.getLogger('camelot.types')
from sqlalchemy import types
from camelot.core.files.storage import StoredFile, StoredImage, Storage
class VirtualAddress(types.TypeDecorator):
"""A single field that can be used to enter phone numbers, fax numbers, email
addresses, im addresses. The editor provides soft validation of the data
entered. The address or number is stored as a string in the database.
This column type accepts and returns tuples of strings, the first string is
the :attr:`virtual_address_type`, and the second the address itself.
eg: ``('email','project-camelot@conceptive.be')`` is stored as
``email://project-camelot@conceptive.be``
.. image:: /_static/virtualaddress_editor.png
"""
impl = types.Unicode
virtual_address_types = ['phone', 'fax', 'mobile', 'email', 'im', 'pager',]
def bind_processor(self, dialect):
impl_processor = self.impl.bind_processor(dialect)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value is not None:
if value[1]:
value = u'://'.join(value)
else:
value = None
return impl_processor(value)
return processor
def result_processor(self, dialect, coltype=None):
impl_processor = self.impl.result_processor(dialect, coltype)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value:
split = value.split('://')
if len(split)>1:
return tuple(split)
return (u'phone',u'')
return processor
class Code(types.TypeDecorator):
"""SQLAlchemy column type to store codes. Where a code is a list of strings
on which a regular expression can be enforced.
This column type accepts and returns a list of strings and stores them as a
string joined with points.
eg: ``['08', 'AB']`` is stored as ``08.AB``
.. image:: /_static/editors/CodeEditor_editable.png
:param parts: a list of input masks specifying the mask for each part,
eg ``['99', 'AA']``. For valid input masks, see
`QLineEdit <http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qlineedit.html>`_
:param separator: a string that will be used to separate the different parts
in the GUI and in the database
:param length: the size of the underlying string field in the database, if no
length is specified, it will be calculated from the parts
"""
impl = types.Unicode
def __init__(self, parts, separator=u'.', length = None, **kwargs):
import string
translator = string.maketrans('', '')
self.parts = parts
self.separator = separator
max_length = sum(len(part.translate(translator, '<>!')) for part in parts) + len(parts)*len(self.separator)
types.TypeDecorator.__init__( self, length = length or max_length, **kwargs )
def bind_processor(self, dialect):
impl_processor = self.impl.bind_processor(dialect)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value is not None:
value = self.separator.join(value)
return impl_processor(value)
return processor
def result_processor(self, dialect, coltype=None):
impl_processor = self.impl.result_processor(dialect, coltype)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value:
return value.split(self.separator)
return ['' for _p in self.parts]
return processor
class IPAddress(Code):
def __init__(self, **kwargs):
super(IPAddress, self).__init__(parts=['900','900','900','900'])
class Rating(types.TypeDecorator):
"""The rating field is an integer field that is visualized as a number of stars that
can be selected::
class Movie( Entity ):
title = Column( Unicode(60), nullable = False )
rating = Column( camelot.types.Rating() )
.. image:: /_static/editors/StarEditor_editable.png
"""
impl = types.Integer
class RichText(types.TypeDecorator):
"""RichText fields are unlimited text fields which contain html. The html will be
rendered in a rich text editor.
.. image:: /_static/editors/RichTextEditor_editable.png
"""
impl = types.UnicodeText
class Language(types.TypeDecorator):
"""The languages are stored as a string in the database of
the form *language*(_*country*), where :
* *language* is a lowercase, two-letter, ISO 639 language code,
* *territory* is an uppercase, two-letter, ISO 3166 country code
This used to be implemented using babel, but this was too slow and
used too much memory, so now it's implemented using QT.
"""
impl = types.Unicode
def __init__(self):
types.TypeDecorator.__init__(self, length=20)
class Color(types.TypeDecorator):
"""The Color field returns and accepts tuples of the form (r,g,b,a) where
r,g,b,a are integers between 0 and 255. The color is stored as an hexadecimal
string of the form AARRGGBB into the database, where AA is the transparency,
RR is red, GG is green BB is blue::
class MovieType( Entity ):
color = Column( camelot.types.Color() )
.. image:: /_static/editors/ColorEditor_editable.png
The colors are stored in the database as strings.
Use::
QColor(*color)
to convert a color tuple to a QColor.
"""
impl = types.Unicode
def __init__(self):
types.TypeDecorator.__init__(self, length=8)
def bind_processor(self, dialect):
impl_processor = self.impl.bind_processor(dialect)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value is not None:
assert len(value) == 4
for i in range(4):
assert value[i] >= 0
assert value[i] <= 255
return '%02X%02X%02X%02X'%(value[3], value[0], value[1], value[2])
return impl_processor(value)
return processor
def result_processor(self, dialect, coltype=None):
impl_processor = self.impl.result_processor(dialect, coltype)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value:
return (int(value[2:4],16), int(value[4:6],16), int(value[6:8],16), int(value[0:2],16))
return processor
class Enumeration(types.TypeDecorator):
"""The enumeration field stores integers in the database, but represents them as
strings. This allows efficient storage and querying while preserving readable code.
Typical use of this field would be a status field.
Enumeration fields are visualized as a combo box, where the labels in the combo
box are the capitalized strings::
class Movie(Entity):
title = Column( Unicode(60), nullable = False )
state = Column( camelot.types.Enumeration([(1,'planned'), (2,'recording'), (3,'finished'), (4,'canceled')]),
index = True, nullable = False, default = 'planning' )
.. image:: /_static/editors/ChoicesEditor_editable.png
If None should be a possible value of the enumeration, add (None, None) to the list of
possible enumerations. None will be presented as empty in the GUI.
:param choices: is a list of tuples. each tuple contains an integer and its
associated string. such as ::
choices = [(1,'draft'), (2,'approved')]
"""
impl = types.Integer
def __init__(self, choices=[], **kwargs):
types.TypeDecorator.__init__(self, **kwargs)
self._int_to_string = dict(choices)
self._string_to_int = dict((v,k) for (k,v) in choices)
self.choices = [v for (k,v) in choices]
def bind_processor(self, dialect):
impl_processor = self.impl.bind_processor(dialect)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value is not None:
try:
value = self._string_to_int[value]
return impl_processor(value)
except KeyError, e:
logger.error('could not process enumeration value %s, possible values are %s'%(value, u', '.join(list(self._string_to_int.keys()))), exc_info=e)
raise
else:
impl_processor(value)
return processor
def result_processor(self, dialect, coltype=None):
impl_processor = self.impl.result_processor(dialect, coltype)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value is not None:
value = impl_processor(value)
try:
return self._int_to_string[value]
except KeyError, e:
logger.error('could not process %s'%value, exc_info=e)
raise
return processor
class File(types.TypeDecorator):
"""Sqlalchemy column type to store files. Only the location of the file is stored
This column type accepts and returns a StoredFile. The name of the file is
stored as a string in the database. A subdirectory upload_to can be specified::
class Movie( Entity ):
script = Column( camelot.types.File( upload_to = 'script' ) )
.. image:: /_static/editors/FileEditor_editable.png
Retrieving the actual storage from a File field can be a little cumbersome.
The easy way is taking it from the field attributes, in which it will be
put by default. If no field attributes are available at the location where
the storage is needed, eg in some function doing document processing, one
needs to go through SQLAlchemy to retrieve it.
For an 'task' object with a File field named 'document', the
storage can be retrieved::
from sqlalchemy import orm
task_mapper = orm.object_mapper( task )
document_property = task_mapper.get_property('document')
storage = document_property.columns[0].type.storage
:param max_length: the maximum length of the name of the file that will
be saved in the database.
:param upload_to: a subdirectory in the Storage, in which the the file
should be stored.
:param storage: an alternative storage to use for this field.
"""
impl = types.Unicode
stored_file_implementation = StoredFile
def __init__(self, max_length=100, upload_to=u'', storage=Storage, **kwargs):
self.max_length = max_length
self.storage = storage(upload_to, self.stored_file_implementation)
types.TypeDecorator.__init__(self, length=max_length, **kwargs)
def bind_processor(self, dialect):
impl_processor = self.impl.bind_processor(dialect)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value is not None:
assert isinstance(value, (self.stored_file_implementation))
return impl_processor(value.name)
return impl_processor(value)
return processor
def result_processor(self, dialect, coltype=None):
impl_processor = self.impl.result_processor(dialect, coltype)
if not impl_processor:
impl_processor = lambda x:x
def processor(value):
if value:
value = impl_processor(value)
return self.stored_file_implementation(self.storage, value)
return processor
class Image(File):
"""Sqlalchemy column type to store images
This column type accepts and returns a StoredImage, and stores them in the directory
specified by settings.CAMELOT_MEDIA_ROOT. The name of the file is stored as a string in
the database.
The Image field type provides the same functionallity as the File field type, but
the files stored should be images.
.. image:: /_static/editors/ImageEditor_editable.png
"""
stored_file_implementation = StoredImage