Ver código fonte

Merge branch 't46'

Roland Haroutiounian 9 anos atrás
pai
commit
9a99941696
1 arquivos alterados com 433 adições e 137 exclusões
  1. 433
    137
      EditorialModel/fieldtypes.py

+ 433
- 137
EditorialModel/fieldtypes.py Ver arquivo

@@ -1,53 +1,341 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3
-from Lodel.utils.mlstring import MlString
3
+from django.db import models
4
+import re
4 5
 import datetime
5
-
6
-## get_field_type (Function)
7
-#
8
-# returns an instance of the corresponding EmFieldType child class for a given name
9
-#
10
-# @param name str: name of the field type
11
-# @return EmFieldType
12
-def get_field_type(name):
13
-    class_name = 'EmField_' + name
14
-    constructor = globals()[class_name]
15
-    instance = constructor()
16
-    return instance
6
+import json
7
+import importlib
8
+import copy
9
+import EditorialModel
10
+from Lodel.utils.mlstring import MlString
17 11
 
18 12
 
19
-## EmFieldType (Class)
13
+## @brief Characterise fields for LeObject and EmComponent
14
+# This class handles values rules for LeObject and EmComponents.
20 15
 #
21
-# Generic field type
22
-class EmFieldType():
23
-
24
-    def __init__(self, name):
25
-        self.name = name
26
-        self.value = None
27
-
28
-    def from_python(self, value):
29
-        self.value = value
30
-
31
-
32
-## EmField_integer (Class)
16
+# It allows to EmFieldValue classes family to have rules to cast, validate and save values.
33 17
 #
34
-# Integer field type
35
-class EmField_integer(EmFieldType):
36
-
37
-    def __init__(self):
38
-        super(EmField_integer, self).__init__('integer')
39
-
18
+# There is exposed methods that allows LeObject and EmType to run a save sequence. This methods are :
19
+# - EmFieldType.to_value() That cast a value to an EmFieldType internal storage
20
+# - EmFieldType.is_valid() That indicates wether or not a value is valid
21
+# - EmFieldType.pre_save() That returns weighted SQL request that must be run before any value save @ref EditorialModel::types::EmType.save_values()
22
+# - EmFieldType.to_sql() That returns a value that can be inserted in database.
23
+# - EmFieldType.post_save() That returns weighted SQL request that must be run after any value save @ref EditorialModel::types::EmType.save_values()
24
+#
25
+class EmFieldType(object):
26
+
27
+    ## Stores options and default value for EmFieldType
28
+    # options list :
29
+    # - type (str|None) : if None will make an 'abstract' fieldtype (with no value assignement possible)
30
+    # - nullable (bool) : tell whether or not a fieldType accept NULL value
31
+    # - default (mixed) : The default value for a FieldType
32
+    # - primarykey (bool) : If true the fieldType represent a PK
33
+    # - autoincrement (bool) : If true the fieldType will be autoincremented
34
+    # - index (bool) : If true the columns will be indexed in Db
35
+    # - name (str) : FieldType name
36
+    # - doc (str) : FieldType documentation
37
+    # - onupdate (callable) : A callback to call on_update
38
+    # - valueobject (EmFieldValue or childs) : An object that represent values for this fieldType
39
+    # - type_* (mixed) : Use to construct an options dictinnary for a type (exemple : type_length => nnewColumn(lenght= [type_lenght VALUE] )) @ref EmFieldSQLType
40
+    # - validators (list) : List of validator functions to use
41
+    _opt = {
42
+        'name': None,
43
+        'type': None,
44
+        'nullable': True,
45
+        'default': None,
46
+        'primarykey': False,
47
+        'autoincrement': False,
48
+        'uniq': False,
49
+        'index': False,
50
+        'doc': None,
51
+        'onupdate': None,
52
+        'valueobject': None,
53
+        'validators': None
54
+    }
55
+
56
+    ## Instanciate an EmFieldType
57
+    # For arguments see @ref EmFieldType::_opt
58
+    # @see EmFieldType::_opt
59
+    def __init__(self, **kwargs):
60
+        self.__init(kwargs)
61
+        self.type_options = dict()
62
+
63
+        # stores all col_* arguments into the self.type_options dictionary
64
+        args = kwargs.copy()
65
+        for optname in args:
66
+            type_opt = re.sub(r'^type_', '', optname)
67
+            if type_opt != optname:
68
+                self.type_options[type_opt] = args[optname]
69
+                del kwargs[optname]
70
+
71
+        # checks if the other arguments are valid
72
+        if len(set(kwargs.keys()) - set(self.__class__._opt.keys())) > 0:
73
+            badargs = ""
74
+            for bad in set(kwargs.keys()) - set(self.__class__._opt.keys()):
75
+                badargs += " " + bad
76
+            raise TypeError("Unexpected arguments : %s" % badargs)
77
+
78
+        # stores other arguments as instance attribute
79
+        for opt_name in self.__class__._opt:
80
+            setattr(self, opt_name, (kwargs[opt_name] if opt_name in kwargs else self.__class__._opt[opt_name]))
81
+
82
+        # checks type's options valididty
83
+        if self.type != None:
84
+            try:
85
+                EmFieldSQLType.sqlType(self.type, **self.type_options)
86
+            except TypeError as e:
87
+                raise e
88
+
89
+        # Default value for name
90
+        if self.name == None:
91
+            if  self.__class__ == EmFieldType:
92
+                self.name = 'generic'
93
+            else:
94
+                self.name = self.__class__.__name__
95
+
96
+        # Default value for doc
97
+        if self.doc == None:
98
+            if self.__class__ == EmFieldType:
99
+                self.doc = 'Abstract generic EmFieldType'
100
+            else:
101
+                self.doc = self.__class__.__name__
102
+
103
+    ## MUST be called first in each constructor
104
+    # @todo this solution is not good (look at the __init__ for EmFieldType childs)
105
+    def __init(self, kwargs):
106
+        try:
107
+            self.args_copy
108
+        except AttributeError:
109
+            self.args_copy = kwargs.copy()
110
+
111
+    @property
112
+    ## A predicate that indicates whether or not an EmFieldType is "abstract"
113
+    def is_abstract(self):
114
+        return (self.type == None)
115
+
116
+    ## Returns a copy of the current EmFieldType
117
+    def copy(self):
118
+        args = self.args_copy.copy()
119
+        return self.__class__(**args)
120
+
121
+    def dump_opt(self):
122
+        return json.dumps(self.args_copy)
123
+
124
+    @staticmethod
125
+    ## Return an instance from a classname and options from dump_opt
126
+    # @param classname str: The EmFieldType class name
127
+    # @param json_opt str: getted from dump_opt
128
+    def restore(colname, classname, json_opt):
129
+        field_type_class = getattr(EditorialModel.fieldtypes, classname)
130
+        init_opt = json.loads(json_opt)
131
+        init_opt['name'] = colname
132
+        return field_type_class(**init_opt)
133
+
134
+    ## Return a value object 'driven' by this EmFieldType
135
+    # @param name str: The column name associated with the value
136
+    # @param *init_val mixed: If given this will be the initialisation value
137
+    # @return a EmFieldValue instance
138
+    # @todo better default value (and bad values) handling
139
+    def valueObject(self, name, *init_val):
140
+        if self.valueObject == None:
141
+            return EmFieldValue(name, self, *init_val)
142
+        return self.valueObject(name, self, *init_val)
143
+
144
+    ## Cast to the correct value
145
+    # @param v mixed : the value to cast
146
+    # @return A gently casted value
147
+    # @throw ValueError if v is innapropriate
148
+    # @throw NotImplementedError if self is an abstract EmFieldType
149
+    def to_value(self, v):
150
+        if self.type == None:
151
+            raise NotImplemented("This EmFieldType is abstract")
152
+        if v == None and not self.nullable:
153
+            raise TypeError("Field not nullable")
154
+        return v
155
+
156
+    ## to_value alias
157
+    # @param value mixed : the value to cast
158
+    # @return A casted value
40 159
     def from_string(self, value):
41
-        if value == '':
42
-            self.value = 0
160
+        return self.to_value(value)
161
+
162
+    ## Returns a gently sql forged value
163
+    # @return A sql forged value
164
+    # @warning It assumes that the value is correct and comes from an EmFieldValue object
165
+    def to_sql(self, value):
166
+        if self.is_abstract:
167
+            raise NotImplementedError("This EmFieldType is abstract")
168
+        return True
169
+
170
+    ## Returns whether or not a value is valid for this EmFieldType
171
+    # @note This function always returns True and is here for being overloaded  by child objects
172
+    # @return A boolean, True if valid else False
173
+    def is_valid(self, value):
174
+        if self.is_abstract:
175
+            raise NotImplementedError("This EmFieldType is abstract")
176
+        return True
177
+
178
+    ## Pre-save actions
179
+    def pre_save(self):
180
+        if self.is_abstract:
181
+            raise NotImplementedError("This EmFieldType is abstract")
182
+        return []
183
+
184
+    ## Post-save actions
185
+    def post_save(self):
186
+        if self.is_abstract:
187
+            raise NotImplementedError("This EmFieldType is abstract")
188
+        return []
189
+
190
+    @classmethod
191
+    ## Function designed to be called by child class to enforce a type
192
+    # @param args dict: The kwargs argument of __init__
193
+    # @param typename str: The typename to enforce
194
+    # @return The new kwargs to be used
195
+    # @throw TypeError if type is present is args
196
+    def _setType(cl, args, typename):
197
+        return cl._argEnforce(args, 'type', typename, True)
198
+
199
+    @classmethod
200
+    ## Function designed to be called by child's constructo to enforce an argument
201
+    # @param args dict: The constructor's kwargs
202
+    # @param argname str: The name of the argument to enforce
203
+    # @param argval mixed: The value we want to enforce
204
+    # @param Return a new kwargs
205
+    # @throw TypeError if type is present is args and exception argument is True
206
+    def _argEnforce(cl, args, argname, argval, exception=True):
207
+        if exception and argname in args:
208
+            raise TypeError("Invalid argument '"+argname+"' for "+cl.__class__.__name__+" __init__")
209
+        args[argname] = argval
210
+        return args
211
+
212
+    @classmethod
213
+    ## Function designed to be called by child's constructor to set a default value
214
+    # @param args dict: The constructor's kwargs
215
+    # @param argname str: The name of the argument with default value
216
+    # @param argval mixed : The default value
217
+    # @return a new kwargs dict
218
+    def _argDefault(cl, args, argname, argval):
219
+        if argname not in args:
220
+            args[argname] = argval
221
+        return args
222
+
223
+class EmFieldValue(object):
224
+
225
+    ## Instanciates a EmFieldValue
226
+    # @param name str : The column name associated with this value
227
+    # @param fieldtype EmFieldType: The EmFieldType defining the value
228
+    # @param *value *list: This argument allow to pass a value to set (even None) and to detect if no value given to set to EmFieldType's default value
229
+    # @throw TypeError if more than 2 arguments given
230
+    # @throw TypeError if fieldtype is not an EmFieldType
231
+    # @throw TypeError if fieldtype is an abstract EmFieldType
232
+    def __init__(self, name, fieldtype, *value):
233
+        if not isinstance(fieldtype, EmFieldType):
234
+            raise TypeError("Expected <class EmFieldType> for 'fieldtype' argument, but got : %s instead" % str(type(fieldtype)))
235
+        if fieldtype.is_abstract:
236
+            raise TypeError("The given fieldtype in argument is abstract.")
237
+
238
+        # This copy ensures that the fieldtype will not change during the value lifecycle
239
+        super(EmFieldValue, self).__setattr__('fieldtype', fieldtype.copy())
240
+
241
+        if len(value) > 1:
242
+            raise TypeError("Accept only 2 positionnal parameters. %s given." % str(len(value)+1))
243
+        elif len(value) == 1:
244
+            self.value = value[0]
245
+        else:
246
+            self.value = fieldtype.default
247
+
248
+        # Use this to set value in the constructor
249
+        setv = super(EmFieldValue, self).__setattr__
250
+        # This copy makes column attributes accessible easily
251
+        for attrname in self.fieldtype.__dict__:
252
+            setv(attrname, getattr(self.fieldtype, attrname))
253
+
254
+        # Assign some EmFieldType methods to the value
255
+        setv('from_python', self.fieldtype.to_value)
256
+        setv('from_string', self.fieldtype.to_value)
257
+        #setv('sqlCol', self.fieldtype.sqlCol)
258
+
259
+    ## The only writable attribute of EmFieldValue is the value
260
+    # @param name str: Have to be value
261
+    # @param value mixed: The value to set
262
+    # @throw AtrributeError if another attribute than value is to be set
263
+    # @throw ValueError if self.to_value raises it
264
+    # @see EmFieldType::to_value()
265
+    def __setattr__(self, name, value):
266
+        if name != "value":
267
+            raise AttributeError("EmFieldValue has only the value property settable")
268
+        super(EmFieldValue,self).__setattr__('value', self.fieldtype.to_value(value))
269
+
270
+        ##
271
+    # @warning When getting the fieldtype you actually get a copy of it to prevent any modifications !
272
+    def __getattr__(self, name):
273
+        if name == 'fieldtype':
274
+            return self.fieldtype.copy()
275
+        return super(EmFieldValue, self).__getattribute__(name)
276
+
277
+    ## @brief Return a valid SQL value
278
+    #
279
+    # Can be used to convert any value (giving one positionnal argument) or to return the current value
280
+    # @param *value list: If a positionnal argument is given return it and not the instance value
281
+    # @return A value suitable for sql
282
+    def to_sql(self, *value):
283
+        if len(value) > 1:
284
+            raise TypeError("Excepted 0 or 1 positional argument but got "+str(len(value)))
285
+        elif len(value) == 1:
286
+            return self.fieldtype.to_sql(value[0])
287
+        return self.fieldtype.to_sql(self.value)
288
+
289
+class EmFieldSQLType(object):
290
+    _integer = {'sql': models.IntegerField}
291
+    _bigint = {'sql': models.BigIntegerField}
292
+    _smallint = {'sql': models.SmallIntegerField}
293
+    _boolean = {'sql': models.BooleanField}
294
+    _nullableboolean = {'sql': models.NullBooleanField}
295
+    _float = {'sql': models.FloatField}
296
+    _varchar = {'sql': models.CharField}
297
+    _text = {'sql': models.TextField}
298
+    _time = {'sql': models.TimeField}
299
+    _date = {'sql': models.DateField}
300
+    _datetime = {'sql': models.DateTimeField}
301
+
302
+    _names = {
303
+        'int': _integer,
304
+        'integer': _integer,
305
+        'bigint': _bigint,
306
+        'smallint': _smallint,
307
+        'boolean': _boolean,
308
+        'bool': _boolean,
309
+        'float': _float,
310
+        'char': _varchar,
311
+        'varchar': _varchar,
312
+        'text': _text,
313
+        'time': _time,
314
+        'date': _date,
315
+        'datetime': _datetime,
316
+    }
317
+
318
+    @classmethod
319
+    def sqlType(cls, name, **kwargs):
320
+        if not isinstance(name, str):
321
+            raise TypeError("Expect <class str>, <class int>|None but got : %s %s" % (str(type(name)), str(type(size))))
322
+        name = name.lower()
323
+        if name not in cls._names:
324
+            raise ValueError("Unknown type '%s'" % name)
325
+
326
+        if name in ['boolean','bool'] and kwargs['nullable'] in [1,'true']:
327
+            sqlclass = _nullableboolean
43 328
         else:
44
-            self.value = int(value)
329
+            sqlclass = cls._names[name]
45 330
 
46
-    def to_sql(self, value=None):
47
-        if value is None:
48
-            value = self.value
49
-        return value
331
+        if len(kwargs) == 0:
332
+            return sqlclass['sql']
50 333
 
334
+        return sqlclass['sql'](**kwargs)
335
+
336
+
337
+## @brief Represents values with common arithmetic operations
338
+class EmFieldValue_int(EmFieldValue):
51 339
     def __int__(self):
52 340
         return self.value
53 341
 
@@ -82,116 +370,124 @@ class EmField_integer(EmFieldType):
82 370
         self.value = int(self.value / other)
83 371
         return self
84 372
 
85
-    def sql_column(self):
86
-        return "int(11) NOT NULL"
87
-
88
-
89
-## EmField_boolean (Class)
90
-#
91
-# Boolean field type
92
-class EmField_boolean(EmFieldType):
93 373
 
94
-    def __init__(self):
95
-        super(EmField_boolean, self).__init__('boolean')
96
-
97
-    def from_string(self, value):
98
-        if value and value != "0":
99
-            self.value = True
100
-        else:
101
-            self.value = False
102
-
103
-    def to_sql(self, value=None):
104
-        if value is None:
105
-            value = self.value
106
-        return 1 if value else 0
107
-
108
-    def sql_column(self):
109
-        return "tinyint(1) DEFAULT NULL"
110
-
111
-
112
-## EmField_char (Class)
113
-#
114
-# Varchar field type
115
-class EmField_char(EmFieldType):
116
-
117
-    def __init__(self):
118
-        super(EmField_char, self).__init__('char')
119
-
120
-    def from_string(self, value):
121
-        if value:
122
-            self.value = value
123
-        else:
124
-            self.value = ''
374
+## @brief Handles integer fields
375
+# @note Enforcing type to be int
376
+# @note Default name is 'integer' and default 'valueobject' is EmFieldValue_int
377
+class EmField_integer(EmFieldType):
125 378
 
126
-    def to_sql(self, value=None):
127
-        if value is None:
128
-            value = self.value
379
+    def __init__(self, **kwargs):
380
+        self._init(kwargs)
381
+        # Default name
382
+        kwargs = self.__class__._argDefault(kwargs, 'name', 'integer')
383
+        # Default value object
384
+        kwargs = self.__class__._argDefault(kwargs, 'valueobject', EmFieldValue_int)
385
+        # Type enforcing
386
+        kwargs = self.__class__._setType(kwargs, 'int')
387
+        super(EmField_integer, self).__init__(**kwargs)
388
+
389
+    ##
390
+    # @todo catch cast error ?
391
+    def to_sql(self, value):
129 392
         return value
130 393
 
131
-    def sql_column(self):
132
-        return "varchar(250) DEFAULT NULL"
133
-
134
-
135
-## EmField_date (Class)
136
-#
137
-# Date Field type
138
-class EmField_date(EmFieldType):
139
-
140
-    def __init__(self):
141
-        super(EmField_date, self).__init__('date')
142
-
143
-    def from_string(self, value):
144
-        if value:
145
-            self.value = value
146
-        else:
147
-            self.value = ''
148
-
149
-    def to_sql(self, value=None):
150
-        if value is None:
151
-            value = self.value
152
-        if not value:
153
-            return datetime.datetime.now()
154
-        return value
394
+    def to_value(self, value):
395
+        if value == None:
396
+            return super(EmField_integer, self).to_value(value)
397
+        return int(value)
155 398
 
156
-    def sql_column(self):
157
-        return "varchar(250) DEFAULT NULL"
158 399
 
400
+## @brief Handles boolean fields
401
+# @note Enforce type to be 'boolean'
402
+# @note Default name is 'boolean'
403
+class EmField_boolean(EmFieldType):
404
+    def __init__(self, **kwargs):
405
+        self._init(kwargs)
406
+        #Default name
407
+        kwargs = self.__class__._argDefault(kwargs, 'name', 'boolean')
408
+        #Type enforcing
409
+        kwargs = self.__class__._setType(kwargs, 'boolean')
410
+        super(EmField_boolean, self).__init__(**kwargs)
411
+        pass
159 412
 
160
-## EmField_mlstring (Class)
161
-#
162
-# mlstring Field type
163
-class EmField_mlstring(EmFieldType):
413
+    def to_sql(self, value):
414
+        return 1 if super(EmField_boolean, self).to_sql(value) else 0
164 415
 
165
-    def __init__(self):
166
-        super(EmField_mlstring, self).__init__('mlstring')
416
+    def to_value(self, value):
417
+        if value == None:
418
+            return super(EmField_boolean, self).to_value(value)
419
+        self.value = bool(value)
420
+        return self.value
167 421
 
168
-    def from_string(self, value):
169
-        self.value = MlString.load(value)
170 422
 
171
-    def to_sql(self, value=None):
172
-        if value is None:
173
-            value = self.value
423
+## @brief Handles string fields
424
+# @note Enforce type to be (varchar)
425
+# @note Default 'name' is 'char'
426
+# @note Default 'type_length' is 76
427
+class EmField_char(EmFieldType):
428
+    default_length = 76
429
+    def __init__(self, **kwargs):
430
+        self._init(kwargs)
431
+        kwargs = self.__class__._argDefault(kwargs, 'type_length', self.__class__.default_length)
432
+        kwargs = self.__class__._argDefault(kwargs, 'name', 'char')
433
+        #Type enforcing
434
+        kwargs = self.__class__._setType(kwargs, 'varchar')
435
+        super(EmField_char, self).__init__(**kwargs)
436
+    def to_sql(self, value):
174 437
         return str(value)
175 438
 
176
-    def sql_column(self):
177
-        return "varchar(250) DEFAULT NULL"
178
-
179 439
 
180
-class EmField_icon(EmFieldType):
181
-    
182
-    def __init__(self):
183
-        super(EmField_icon, self).__init__('icon')
184
-        pass
185
-
186
-    def from_string(self,value):
187
-        self.value = value
188
-
189
-    def to_sql(self, value=None):
190
-        if value != None:
191
-            value = str(value)
192
-        return value
193
-
194
-    def sql_column(self):
440
+## @brief Handles date fields
441
+# @note Enforce type to be 'datetime'
442
+# @todo rename to EmField_datetime
443
+# @todo timezones support
444
+class EmField_date(EmFieldType):
445
+    def __init__(self, **kwargs):
446
+        self._init(kwargs)
447
+        kwargs = self.__class__._argDefault(kwargs, 'name', 'date')
448
+        #Type enforcing
449
+        kwargs = self.__class__._setType(kwargs, 'datetime')
450
+        super(EmField_date, self).__init__(**kwargs)
451
+
452
+    def to_sql(self, value):
453
+        return value #thanks to sqlalchemy
454
+
455
+    def to_value(self, value):
456
+        if value == None:
457
+            return super(EmField_date, self).to_value(value)
458
+        if isinstance(value, int):
459
+            #assume its a timestamp
460
+            return datetime.fromtimestamp(value)
461
+        if isinstance(value, datetime.datetime):
462
+            return value
463
+
464
+
465
+## @brief Handles strings with translations
466
+class EmField_mlstring(EmField_char):
467
+
468
+    def __init__(self, **kwargs):
469
+        self._init(kwargs)
470
+        kwargs = self.__class__._argDefault(kwargs, 'name', 'mlstr')
471
+        super(EmField_mlstring, self).__init__(**kwargs)
472
+
473
+    def to_sql(self, value):
474
+        return value.__str__()
475
+
476
+    def to_value(self, value):
477
+        if value == None:
478
+            return super(EmField_mlstring, self).to_value(value)
479
+        if isinstance(value, str):
480
+            return MlString.load(value)
481
+        elif isinstance(value, MlString):
482
+            return value
483
+        raise TypeError("<class str> or <class MlString> excepted. But got "+str(type(value)))
484
+
485
+
486
+## @brief Handles lodel uid fields
487
+class EmField_uid(EmField_integer):
488
+    def __init__(self, **kwargs):
489
+        self._init(kwargs)
490
+        kwargs = self.__class__._argEnforce(kwargs, 'primarykey', True)
491
+        super(EmField_uid, self).__init__(**kwargs)
195 492
         pass
196 493
 
197
-

Carregando…
Cancelar
Salvar