Browse Source

Merge branch 'fieldtypes' with the new EmField processing

Conflicts:
	EditorialModel/model.py
	EditorialModel/test/me.json
Yann Weber 9 years ago
parent
commit
38395e09a5

+ 2
- 1
.gitignore View File

@@ -4,4 +4,5 @@
4 4
 .idea
5 5
 Lodel/settings/locale.py
6 6
 Lodel/settings/local.py
7
-doc
7
+doc
8
+.*.swp

+ 5
- 2
EditorialModel/backend/graphviz.py View File

@@ -1,6 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3 3
 import datetime
4
+import EditorialModel
4 5
 from EditorialModel.classtypes import EmClassType
5 6
 from EditorialModel.fieldgroups import EmFieldGroup
6 7
 from EditorialModel.types import EmType
@@ -88,7 +89,8 @@ class EmBackendGraphviz(object):
88 89
             for f in c.fields():
89 90
                 if ((cn == 'EmType' and f.optional) or (cn == 'EmFieldGroup' and not f.optional)) and f.rel_field_id is None:
90 91
                     
91
-                    if not (f.rel_to_type_id is None):
92
+                    #if not (f.rel_to_type_id is None):
93
+                    if isinstance(f, EditorialModel.fieldtypes.rel2type.EmFieldRel2Type):
92 94
                         rel_node_id = '%s%s'%(EmBackendGraphviz._component_id(c), EmBackendGraphviz._component_id(em.component(f.rel_to_type_id)))
93 95
 
94 96
                         rel_node = '\t%s [ label="rel_to_type'%rel_node_id
@@ -114,7 +116,8 @@ class EmBackendGraphviz(object):
114 116
                     if first:
115 117
                         ret += ' { '
116 118
                         first = False
117
-                    if not (f.rel_to_type_id is None):
119
+                    #if not (f.rel_to_type_id is None):
120
+                    if isinstance(f, EditorialModel.fieldtypes.rel2type.EmFieldRel2Type):
118 121
                         ret += '<f%d> '%cntref
119 122
                         cntref += 1
120 123
                     ret += f.name

+ 31
- 17
EditorialModel/backend/json_backend.py View File

@@ -9,17 +9,23 @@
9 9
 import json
10 10
 import datetime
11 11
 from Lodel.utils.mlstring import MlString
12
+import EditorialModel
12 13
 
13 14
 
14 15
 def date_cast(date):
15 16
     if len(date):
16
-        return datetime.datetime(date)
17
+        return datetime.datetime.strptime(date, '%c')
17 18
     else:
18 19
         return None
19 20
 
20 21
 
22
+## @brief dirty obsolote cast function
21 23
 def int_or_none(i):
22
-    if len(i):
24
+    if isinstance(i, int):
25
+        return i
26
+    elif i is None:
27
+        return None
28
+    elif len(i):
23 29
         return int(i)
24 30
     else:
25 31
         return None
@@ -47,27 +53,35 @@ class EmBackendJson(object):
47 53
     #
48 54
     # @param json_file str: path to the json_file used as data source
49 55
     def __init__(self, json_file):
50
-        with open(json_file) as json_data:
51
-            self.data = json.loads(json_data.read())
56
+        self.json_file = json_file
57
+        pass
58
+
59
+    @staticmethod
60
+    ## @brief Used by json.dumps to serialize date and datetime
61
+    def date_handler(obj):
62
+        return obj.strftime('%c') if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date) else None
63
+
52 64
 
53 65
     ## Loads the data in the data source json file
54 66
     #
55 67
     # @return list
56 68
     def load(self):
57 69
         data = {}
58
-        for index, component in self.data.items():
59
-            attributes = {}
60
-            for attr_name, attr_value in component.items():
61
-                if attr_name in EmBackendJson.cast_methods:
62
-                    attributes[attr_name] = EmBackendJson.cast_methods[attr_name](attr_value)
63
-                else:
64
-                    attributes[attr_name] = attr_value
65
-            data[int(index)] = attributes
70
+        with open(self.json_file) as json_data:
71
+            rdata = json.loads(json_data.read())
72
+            for index, component in rdata.items():
73
+                attributes = {}
74
+                for attr_name, attr_value in component.items():
75
+                    if attr_name in EmBackendJson.cast_methods:
76
+                        attributes[attr_name] = EmBackendJson.cast_methods[attr_name](attr_value)
77
+                    else:
78
+                        attributes[attr_name] = attr_value
79
+                data[int(index)] = attributes
66 80
         return data
67 81
 
68 82
     ## Saves the data in the data source json file
69
-    #
70
-    # @return bool
71
-    # @todo à implémenter
72
-    def save(self, em):
73
-        return True
83
+    # @param filename str : The filename to save the EM in (if None use self.json_file provided at init )
84
+    def save(self, em, filename = None):
85
+        with open(self.json_file if filename is None else filename, 'w') as fp:
86
+            fp.write(json.dumps({ component.uid : component.dumps() for component in em.components() }, default=self.date_handler))
87
+

+ 23
- 5
EditorialModel/components.py View File

@@ -10,7 +10,7 @@ import logging
10 10
 from collections import OrderedDict
11 11
 import hashlib
12 12
 
13
-import EditorialModel.fieldtypes as ftypes
13
+import EditorialModel
14 14
 from EditorialModel.exceptions import *
15 15
 from Lodel.utils.mlstring import MlString
16 16
 
@@ -56,7 +56,7 @@ class EmComponent(object):
56 56
     ## @brief Return a dict with attributes name as key and attributes value as value
57 57
     # @note Used at creation and deletion to call the migration handler
58 58
     def attr_dump(self):
59
-        return {fname: fval for fname, fval in self.__dict__.items() if not (fname.startswith('__') or (fname == 'uid'))}
59
+        return {fname: fval for fname, fval in self.__dict__.items() if not (fname.startswith('_') or (fname == 'uid'))}
60 60
 
61 61
     @property
62 62
     ## @brief Provide a uniq name
@@ -64,12 +64,30 @@ class EmComponent(object):
64 64
     # Identify a component with his type and name
65 65
     def uniq_name(self):
66 66
         uname = self.__class__.__name__
67
-        try:
68
-            uname += '_'+self.em_class.name
69
-        except AttributeError: pass
67
+        if not isinstance(self, EditorialModel.fields.EmField):
68
+            try:
69
+                uname += '_'+self.em_class.name
70
+            except AttributeError: pass
70 71
         uname += '_'+self.name
71 72
         return uname
72 73
         
74
+    ## @brief dumps attr for serialization
75
+    def dumps(self):
76
+        #attr =  {fname: fval for fname, fval in self.__dict__.items() if not (fname.startswith('_'))}
77
+        attr = self.attr_dump
78
+        del(attr['model'])
79
+        for attr_f in attr:
80
+            if isinstance(attr[attr_f], EmComponent):
81
+                attr[attr_f] = attr[attr_f].uid
82
+            elif isinstance(attr[attr_f], MlString):
83
+                attr[attr_f] = attr[attr_f].__str__()
84
+        if isinstance(self, EditorialModel.fields.EmField):
85
+            attr['component'] = 'EmField'
86
+            attr['type'] = self.fieldtype
87
+        else:
88
+            attr['component'] = self.__class__.__name__
89
+        return attr
90
+
73 91
     ## @brief This function has to be called after the instanciation, checks, and init manipulations are done
74 92
     # @note Create a new attribute _inited that allow __setattr__ to know if it has or not to call the migration handler
75 93
     def init_ended(self):

+ 50
- 34
EditorialModel/fields.py View File

@@ -1,8 +1,12 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3
+import importlib
4
+import warnings
5
+
3 6
 from EditorialModel.components import EmComponent
4 7
 from EditorialModel.exceptions import EmComponentCheckError
5 8
 import EditorialModel
9
+import EditorialModel.fieldtypes
6 10
 from django.db import models
7 11
 
8 12
 ## EmField (Class)
@@ -12,42 +16,68 @@ class EmField(EmComponent):
12 16
 
13 17
     ranked_in = 'fieldgroup_id'
14 18
 
15
-    fieldtypes = {
16
-        'int': models.IntegerField,
17
-        'integer': models.IntegerField,
18
-        'bigint': models.BigIntegerField,
19
-        'smallint': models.SmallIntegerField,
20
-        'boolean': models.BooleanField,
21
-        'bool': models.BooleanField,
22
-        'float': models.FloatField,
23
-        'char': models.CharField,
24
-        'varchar': models.CharField,
25
-        'text': models.TextField,
26
-        'time': models.TimeField,
27
-        'date': models.DateField,
28
-        'datetime': models.DateTimeField,
29
-    }
19
+    ftype = None
20
+    help = 'Default help text'
30 21
 
31 22
     ## Instanciate a new EmField
32 23
     # @todo define and test type for icon and fieldtype
33
-    def __init__(self, model, uid, name, fieldgroup_id, fieldtype, optional=False, internal=False, rel_to_type_id=None, rel_field_id=None, icon='0', string=None, help_text=None, date_update=None, date_create=None, rank=None, **kwargs):
24
+    # @warning nullable == True by default
25
+    def __init__(self, model, uid, name, fieldgroup_id, fieldtype, optional=False, internal=False, rel_field_id=None, icon='0', string=None, help_text=None, date_update=None, date_create=None, rank=None, nullable = True, default = None, uniq = False, **kwargs):
26
+
27
+        if self.ftype == None:
28
+            raise NotImplementedError("Trying to instanciate an EmField and not one of the fieldtypes child classes")
34 29
 
35 30
         self.fieldgroup_id = fieldgroup_id
36 31
         self.check_type('fieldgroup_id', int)
37
-        self.fieldtype = fieldtype
38 32
         self.optional = optional
39 33
         self.check_type('optional', bool)
40 34
         self.internal = internal
41 35
         self.check_type('internal', bool)
42
-        self.rel_to_type_id = rel_to_type_id
43
-        self.check_type('rel_to_type_id', (int, type(None)))
44 36
         self.rel_field_id = rel_field_id
45 37
         self.check_type('rel_field_id', (int, type(None)))
46 38
         self.icon = icon
47
-        self.options = kwargs
39
+
40
+
41
+        #Field type elements
42
+        self.fieldtype = fieldtype
43
+        self.nullable = nullable
44
+        self.default = default
45
+        self.uniq = uniq
46
+
47
+        if len(kwargs) > 0:
48
+            for kwargs_f in kwargs:
49
+                warnings.warn("Argument '%s' not used and will be invalid for EmField __init__"%kwargs_f,SyntaxWarning)
50
+            
48 51
 
49 52
         super(EmField, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
50 53
 
54
+    @staticmethod
55
+    ## @brief Return an EmField subclass given a wanted field type
56
+    # @return An EmField subclass
57
+    # @throw When not found
58
+    # @see EmField::fieldtypes()
59
+    def get_field_class(ftype, **kwargs):
60
+        ftype_module = importlib.import_module('EditorialModel.fieldtypes.%s'%ftype)
61
+        return ftype_module.fclass
62
+
63
+    @staticmethod
64
+    ## @brief Return the list of allowed field type
65
+    def fieldtypes_list():
66
+        return [ f for f in EditorialModel.fieldtypes.__all__ if f != '__init__' ]
67
+
68
+    ## @brief Abstract method that should return a validation function
69
+    # @param raise_e Exception : if not valid raise this exception
70
+    # @param ret_valid : if valid return this value
71
+    # @param ret_invalid : if not valid return this value
72
+    def validation_function(self, raise_e = None, ret_valid = None, ret_invalid = None):
73
+        if self.__class__ == EmField:
74
+            raise NotImplementedError("Abstract method")
75
+        if raise_e is None and ret_valid is None:
76
+            raise AttributeError("Behavior doesn't allows to return a valid validation function")
77
+
78
+        return False
79
+            
80
+
51 81
     ## @brief Return the list of relation fields for a rel_to_type
52 82
     # @return None if the field is not a rel_to_type else return a list of EmField
53 83
     def rel_to_type_fields(self):
@@ -72,17 +102,3 @@ class EmField(EmComponent):
72 102
     def delete_check(self):
73 103
         return True
74 104
 
75
-    def to_django(self):
76
-        if self.fieldtype in ('varchar', 'char'):
77
-            max_length = None if 'max_length' not in self.options else self.options['max_length']
78
-            return self.fieldtypes[self.fieldtype](max_length=max_length, **self.options)
79
-
80
-        if self.fieldtype in ('time', 'datetime', 'date'):
81
-            auto_now = False if 'auto_now' not in self.options else self.options['auto_now']
82
-            auto_now_add = False if 'auto_now_add' not in self.options else self.options['auto_now_add']
83
-            return self.fieldtypes[self.fieldtype](auto_now=auto_now, auto_now_add=auto_now_add, **self.options)
84
-
85
-        if self.fieldtype == 'boolean' and ('nullable' in self.options and self.options['nullable'] == 1):
86
-            return models.NullBooleanField(**self.options)
87
-
88
-        return self.fieldtypes[self.fieldtype](**self.options)

+ 0
- 494
EditorialModel/fieldtypes.py View File

@@ -1,494 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from django.db import models
4
-import re
5
-import datetime
6
-import json
7
-import importlib
8
-import copy
9
-import EditorialModel
10
-from Lodel.utils.mlstring import MlString
11
-
12
-
13
-## @brief Characterise fields for LeObject and EmComponent
14
-# This class handles values rules for LeObject and EmComponents.
15
-#
16
-# It allows to EmFieldValue classes family to have rules to cast, validate and save values.
17
-#
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
159
-    def from_string(self, value):
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
328
-        else:
329
-            sqlclass = cls._names[name]
330
-
331
-        if len(kwargs) == 0:
332
-            return sqlclass['sql']
333
-
334
-        return sqlclass['sql'](**kwargs)
335
-
336
-
337
-## @brief Represents values with common arithmetic operations
338
-class EmFieldValue_int(EmFieldValue):
339
-    def __int__(self):
340
-        return self.value
341
-
342
-    def __add__(self, other):
343
-        return self.value + other
344
-
345
-    def __sub__(self, other):
346
-        return self.value - other
347
-
348
-    def __mul__(self, other):
349
-        return self.value * other
350
-
351
-    def __div__(self, other):
352
-        return self.value / other
353
-
354
-    def __mod__(self, other):
355
-        return self.value % other
356
-
357
-    def __iadd__(self, other):
358
-        self.value = int(self.value + other)
359
-        return self
360
-
361
-    def __isub__(self, other):
362
-        self.value = int(self.value - other)
363
-        return self
364
-
365
-    def __imul__(self, other):
366
-        self.value = int(self.value * other)
367
-        return self
368
-
369
-    def __idiv__(self, other):
370
-        self.value = int(self.value / other)
371
-        return self
372
-
373
-
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):
378
-
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):
392
-    #    return value
393
-
394
-    def to_value(self, value):
395
-        if value == None:
396
-            return super(EmField_integer, self).to_value(value)
397
-        return int(value)
398
-
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
-
412
-    #def to_sql(self, value):
413
-    #    return 1 if super(EmField_boolean, self).to_sql(value) else 0
414
-
415
-    def to_value(self, value):
416
-        if value == None:
417
-            return super(EmField_boolean, self).to_value(value)
418
-        self.value = bool(value)
419
-        return self.value
420
-
421
-
422
-## @brief Handles string fields
423
-# @note Enforce type to be (varchar)
424
-# @note Default 'name' is 'char'
425
-# @note Default 'type_length' is 76
426
-class EmField_char(EmFieldType):
427
-
428
-    default_length = 76
429
-
430
-    def __init__(self, **kwargs):
431
-        self._init(kwargs)
432
-        kwargs = self.__class__._argDefault(kwargs, 'type_length', self.__class__.default_length)
433
-        kwargs = self.__class__._argDefault(kwargs, 'name', 'char')
434
-        #Type enforcing
435
-        kwargs = self.__class__._setType(kwargs, 'varchar')
436
-        super(EmField_char, self).__init__(**kwargs)
437
-
438
-    #def to_sql(self, value):
439
-    #    return str(value)
440
-
441
-
442
-## @brief Handles date fields
443
-# @note Enforce type to be 'datetime'
444
-# @todo rename to EmField_datetime
445
-# @todo timezones support
446
-class EmField_date(EmFieldType):
447
-    def __init__(self, **kwargs):
448
-        self._init(kwargs)
449
-        kwargs = self.__class__._argDefault(kwargs, 'name', 'date')
450
-        #Type enforcing
451
-        kwargs = self.__class__._setType(kwargs, 'datetime')
452
-        super(EmField_date, self).__init__(**kwargs)
453
-
454
-    #def to_sql(self, value):
455
-    #    return value #thanks to sqlalchemy
456
-
457
-    def to_value(self, value):
458
-        if value == None:
459
-            return super(EmField_date, self).to_value(value)
460
-        if isinstance(value, int):
461
-            #assume its a timestamp
462
-            return datetime.fromtimestamp(value)
463
-        if isinstance(value, datetime.datetime):
464
-            return value
465
-
466
-
467
-## @brief Handles strings with translations
468
-class EmField_mlstring(EmField_char):
469
-
470
-    def __init__(self, **kwargs):
471
-        self._init(kwargs)
472
-        kwargs = self.__class__._argDefault(kwargs, 'name', 'mlstr')
473
-        super(EmField_mlstring, self).__init__(**kwargs)
474
-
475
-    #def to_sql(self, value):
476
-    #    return value.__str__()
477
-
478
-    def to_value(self, value):
479
-        if value == None:
480
-            return super(EmField_mlstring, self).to_value(value)
481
-        if isinstance(value, str):
482
-            return MlString.load(value)
483
-        elif isinstance(value, MlString):
484
-            return value
485
-        raise TypeError("<class str> or <class MlString> excepted. But got "+str(type(value)))
486
-
487
-
488
-## @brief Handles lodel uid fields
489
-class EmField_uid(EmField_integer):
490
-
491
-    def __init__(self, **kwargs):
492
-        self._init(kwargs)
493
-        kwargs = self.__class__._argEnforce(kwargs, 'primarykey', True)
494
-        super(EmField_uid, self).__init__(**kwargs)

+ 5
- 0
EditorialModel/fieldtypes/__init__.py View File

@@ -0,0 +1,5 @@
1
+from os.path import dirname, basename, isfile
2
+import glob
3
+modules = glob.glob(dirname(__file__)+"/*.py")
4
+__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and basename(f) != '__init__.py']
5
+

+ 15
- 0
EditorialModel/fieldtypes/bool.py View File

@@ -0,0 +1,15 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+class EmFieldBool(EmField):
6
+    
7
+    ftype = 'bool'
8
+    help = 'A basic boolean field'
9
+    
10
+    ## @brief A char field
11
+    # @brief max_length int : The maximum length of this field
12
+    def __init__(self, **kwargs):
13
+        super(EmFieldChar, self).__init__(**kwargs)
14
+
15
+fclass=EmFieldBool

+ 16
- 0
EditorialModel/fieldtypes/char.py View File

@@ -0,0 +1,16 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+class EmFieldChar(EmField):
6
+    
7
+    ftype = 'char'
8
+    help = 'Basic string (varchar) field. Take max_length=64 as option'
9
+    
10
+    ## @brief A char field
11
+    # @brief max_length int : The maximum length of this field
12
+    def __init__(self, max_length=64, **kwargs):
13
+        self.max_length = max_length
14
+        super(EmFieldChar, self).__init__(**kwargs)
15
+
16
+fclass = EmFieldChar

+ 19
- 0
EditorialModel/fieldtypes/datetime.py View File

@@ -0,0 +1,19 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+class EmFieldDatetime(EmField):
6
+    
7
+    ftype = 'datetime'
8
+
9
+    help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
10
+
11
+    ## @brief A datetime field
12
+    # @param now_on_update bool : If true the date is set to NOW on update
13
+    # @param now_on_create bool : If true the date is set to NEW on creation
14
+    def __init__(self, now_on_update = False, now_on_create = False, **kwargs):
15
+        self.now_on_update = now_on_update
16
+        self.now_on_create = now_on_create
17
+        super(EmFieldDatetime, self).__init__(**kwargs)
18
+
19
+fclass = EmFieldDatetime

+ 16
- 0
EditorialModel/fieldtypes/file.py View File

@@ -0,0 +1,16 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+class EmFieldFile(EmField):
6
+    
7
+    ftype = 'file'
8
+    help = 'A file field. With one options upload_path'
9
+    
10
+    ## @brief A char field
11
+    # @brief max_length int : The maximum length of this field
12
+    def __init__(self, upload_path = None,**kwargs):
13
+        self.upload_path = upload_path
14
+        super(EmFieldChar, self).__init__(**kwargs)
15
+
16
+fclass=EmFieldFile

+ 15
- 0
EditorialModel/fieldtypes/int.py View File

@@ -0,0 +1,15 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+
6
+class EmFieldInt(EmField):
7
+    
8
+    ftype = 'int'
9
+
10
+    help = 'Basic integer field'
11
+
12
+    def __init__(self, **kwargs):
13
+        super(EmFieldChar, self).__init__(**kwargs)
14
+
15
+fclass=EmFieldInt

+ 34
- 0
EditorialModel/fieldtypes/regexchar.py View File

@@ -0,0 +1,34 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import re
4
+from EditorialModel.fieldtypes.char import EmFieldChar
5
+
6
+class EmFieldCharRegex(EmFieldChar):
7
+
8
+    help = 'String field validated with a regex. Take two optionss : max_length and regex'
9
+    
10
+    ## @brief A char field validated with a regex
11
+    # @param regex str : a regex string (passed as argument to re.compile() )
12
+    # @param max_length int : the maximum length for this field
13
+    def __init__(self, regex = '', **kwargs):
14
+        self.regex = regex
15
+        v_re = re.compile(regex) #trigger an error if invalid regex
16
+
17
+        super(EmFieldCharRegex, self).__init__(**kwargs)
18
+
19
+    def validation_function(self, raise_e = None, ret_valid = None, ret_invalid = None):
20
+        super(EmFieldChar, self).validation_function(raise_e, ret_valid, ret_invalid)
21
+
22
+        if not raise_e is None:
23
+            def v_fun(value):
24
+                if not re.match(self.regex):
25
+                    raise raise_e
26
+        else:
27
+            def v_fun(value):
28
+                if not re.match(self.regex):
29
+                    return ret_invalid
30
+                else:
31
+                    return ret_valid
32
+        return v_fun
33
+ 
34
+fclass = EmFieldCharRegex

+ 22
- 0
EditorialModel/fieldtypes/rel2type.py View File

@@ -0,0 +1,22 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+class EmFieldRel2Type(EmField):
6
+
7
+    ftype= 'rel2type'
8
+
9
+    help = 'Relationnal field (relation2type). Take rel_to_type_id as option (an EmType uid)'
10
+
11
+    def __init__(self, rel_to_type_id, **kwargs):
12
+        self.rel_to_type_id = rel_to_type_id
13
+        super(EmFieldRel2Type, self).__init__(**kwargs)
14
+
15
+    def get_related_type(self):
16
+        return self.model.component(self.rel_to_type_id)
17
+
18
+    def get_related_fields(self):
19
+        return [ f for f in self.model.components(EmField) if f.rel_field_id == self.uid ]
20
+        
21
+
22
+fclass = EmFieldRel2Type

+ 13
- 0
EditorialModel/fieldtypes/text.py View File

@@ -0,0 +1,13 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from EditorialModel.fields import EmField
4
+
5
+class EmFieldText(EmField):
6
+    
7
+    ftype = 'text'
8
+    help = 'A text field (big string)'
9
+
10
+    def __init__(self, **kwargs):
11
+        super(EmFieldText, self).__init__(**kwargs)
12
+
13
+fclass = EmFieldText

+ 82
- 16
EditorialModel/migrationhandler/django.py View File

@@ -1,16 +1,15 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3
-#from django.conf import settings
4
-#settings.configure(DEBUG=True)
5 3
 import os
6 4
 import sys
7
-from django.db import models
8
-import django
9 5
 
6
+import django
7
+from django.db import models
10 8
 from django.db.models.loading import cache as django_cache
9
+from django.core.exceptions import ValidationError
10
+
11 11
 from EditorialModel.exceptions import *
12 12
 
13
-#django.conf.settings.configure(DEBUG=True)
14 13
 
15 14
 
16 15
 ## @brief Create a django model
@@ -22,7 +21,7 @@ from EditorialModel.exceptions import *
22 21
 # @param admin_opts dict : Dict of options for admin part of this model
23 22
 # @param parent_class str : Parent class name
24 23
 # @return A dynamically created django model
25
-# @source https://code.djangoproject.com/wiki/DynamicModels
24
+# @note Source : https://code.djangoproject.com/wiki/DynamicModels
26 25
 #
27 26
 def create_model(name, fields=None, app_label='', module='', options=None, admin_opts=None, parent_class=None):
28 27
     class Meta:
@@ -67,11 +66,11 @@ def create_model(name, fields=None, app_label='', module='', options=None, admin
67 66
 
68 67
 class DjangoMigrationHandler(object):
69 68
 
70
-    ##
69
+    ## @brief Instanciate a new DjangoMigrationHandler
71 70
     # @param app_name str : The django application name for models generation
72 71
     # @param debug bool : Set to True to be in debug mode
72
+    # @param dryrun bool : If true don't do any migration, only simulate them
73 73
     def __init__(self, app_name, debug=False, dryrun=False):
74
-        self.models = {}
75 74
         self.debug = debug
76 75
         self.app_name = app_name
77 76
         self.dryrun = dryrun
@@ -114,6 +113,9 @@ class DjangoMigrationHandler(object):
114 113
         return True
115 114
 
116 115
     ## @brief Print a debug message representing a migration
116
+    # @param uid int : The EmComponent uid
117
+    # @param initial_state dict | None : dict representing the fields that are changing
118
+    # @param new_state dict | None : dict represnting the new fields states
117 119
     def dump_migration(self, uid, initial_state, new_state):
118 120
         if self.debug:
119 121
             print("\n##############")
@@ -203,7 +205,7 @@ class DjangoMigrationHandler(object):
203 205
     # @note There is a problem with the related_name for superiors fk : The related name cannot be subordinates, it has to be the subordinates em_type name
204 206
     def em_to_models(self, edMod):
205 207
         
206
-        module_name = self.app_name+'models'
208
+        module_name = self.app_name+'.models'
207 209
 
208 210
         #Purging django models cache
209 211
         if self.app_name in django_cache.all_models:
@@ -211,9 +213,6 @@ class DjangoMigrationHandler(object):
211 213
                 del(django_cache.all_models[self.app_name][modname])
212 214
             #del(django_cache.all_models[self.app_name])
213 215
 
214
-        #This cache at instance level seems to be useless...
215
-        del(self.models)
216
-        self.models = {}
217 216
 
218 217
         app_name = self.app_name
219 218
         #Creating the document model
@@ -247,9 +246,10 @@ class DjangoMigrationHandler(object):
247 246
                 if not emfield.optional:
248 247
                     # !!! Replace with fieldtype 2 django converter
249 248
                     #emclass_fields[emfield.uniq_name] = models.CharField(max_length=56, default=emfield.uniq_name)
250
-                    emclass_fields[emfield.uniq_name] = emfield.to_django()
249
+                    emclass_fields[emfield.uniq_name] = self.field_to_django(emfield, emclass)
251 250
             #print("Model for class %s created with fields : "%emclass.uniq_name, emclass_fields)
252
-            print("Model for class %s created"%emclass.uniq_name)
251
+            if self.debug:
252
+                print("Model for class %s created"%emclass.uniq_name)
253 253
             django_models['classes'][emclass.uniq_name] = create_model(emclass.uniq_name, emclass_fields, self.app_name, module_name, parent_class=django_models['doc'])
254 254
             
255 255
             #Creating the EmTypes models with EmClass inherithance
@@ -260,7 +260,7 @@ class DjangoMigrationHandler(object):
260 260
                 #Adding selected optionnal fields
261 261
                 for emfield in emtype.selected_fields():
262 262
                     #emtype_fields[emfield.uniq_name] = models.CharField(max_length=56, default=emfield.uniq_name)
263
-                    emtype_fields[emfield.uniq_name] = emfield.to_django()
263
+                    emtype_fields[emfield.uniq_name] = self.field_to_django(emfield, emtype)
264 264
                 #Adding superiors foreign key
265 265
                 for nature, superior in emtype.superiors().items():
266 266
                     emtype_fields[nature] = models.ForeignKey(superior.uniq_name, related_name=emtype.uniq_name, null=True)
@@ -269,7 +269,73 @@ class DjangoMigrationHandler(object):
269 269
                     print("Model for type %s created"%emtype.uniq_name)
270 270
                 django_models['types'][emtype.uniq_name] = create_model(emtype.uniq_name, emtype_fields, self.app_name, module_name, parent_class=django_models['classes'][emclass.uniq_name])
271 271
 
272
-        self.models=django_models
273 272
         pass
274 273
 
274
+    ## @brief Return a good django field type given a field
275
+    # @param f EmField : an EmField object
276
+    # @param assoc_comp EmComponent : The associated component (type or class)
277
+    # @return A django field instance
278
+    # @note The manytomany fields created with the rel2type field has no related_name associated to it
279
+    def field_to_django(self, f, assoc_comp):
280
+
281
+        #Building the args dictionnary for django field instanciation
282
+        args = dict()
283
+        args['null'] = f.nullable
284
+        if not (f.default is None):
285
+            args['default'] = f.default
286
+        v_fun = f.validation_function(raise_e = ValidationError)
287
+        if v_fun:
288
+            args['validators'] = [v_fun]
289
+        if f.uniq:
290
+            args['unique'] = True
291
+        
292
+        # Field instanciation
293
+        if f.ftype == 'char': #varchar field
294
+            args['max_length'] = f.max_length
295
+            return models.CharField(**args)
296
+        elif f.ftype == 'int': #integer field
297
+            return models.IntegerField(**args)
298
+        elif f.ftype == 'text': #text field
299
+            return models.TextField(**args)
300
+        elif f.ftype == 'datetime': #Datetime field
301
+            args['auto_now'] = f.now_on_update
302
+            args['auto_now_add'] = f.now_on_create
303
+            return models.DateTimeField(**args)
304
+        elif f.ftype == 'bool': #Boolean field
305
+            if args['null']:
306
+                return models.NullBooleanField(**args)
307
+            del(args['null'])
308
+            return models.BooleanField(**args)
309
+        elif f.ftype == 'rel2type': #Relation to type
310
+
311
+            if assoc_comp == None:
312
+                raise RuntimeError("Rel2type field in a rel2type table is not allowed")
313
+            #create first a throught model if there is data field associated with the relation
314
+            kwargs = dict()
315
+
316
+            relf_l = f.get_related_fields()
317
+            if len(relf_l) > 0:
318
+                through_fields = {}
319
+                
320
+                #The two FK of the through model
321
+                through_fields[assoc_comp.name] = models.ForeignKey(assoc_comp.uniq_name)
322
+                rtype = f.get_related_type()
323
+                through_fields[rtype.name] = models.ForeignKey(rtype.uniq_name)
324
+
325
+                for relf in relf_l:
326
+                    through_fields[relf.name] = self.field_to_django(relf, None)
327
+
328
+                #through_model_name = f.uniq_name+assoc_comp.uniq_name+'to'+rtype.uniq_name
329
+                through_model_name = f.name+assoc_comp.name+'to'+rtype.name
330
+                module_name = self.app_name+'.models'
331
+                #model created
332
+                through_model = create_model(through_model_name, through_fields, self.app_name, module_name)
333
+                kwargs['through'] = through_model_name
334
+            
335
+            return models.ManyToManyField(f.get_related_type().uniq_name, **kwargs)
336
+        else: #Unknow data type
337
+            raise NotImplemented("The conversion to django fields is not yet implemented for %s field type"%f.ftype)
338
+
339
+
340
+        
275 341
 

+ 35
- 9
EditorialModel/model.py View File

@@ -3,6 +3,7 @@
3 3
 ## @file editorialmodel.py
4 4
 # Manage instance of an editorial model
5 5
 
6
+import EditorialModel
6 7
 from EditorialModel.migrationhandler.dummy import DummyMigrationHandler
7 8
 from EditorialModel.backend.dummy_backend import EmBackendDummy
8 9
 from EditorialModel.classes import EmClass
@@ -54,6 +55,9 @@ class Model(object):
54 55
     # @return A class name as string or False if cls is not an EmComponent child class
55 56
     def name_from_emclass(em_class):
56 57
         if em_class not in Model.components_class:
58
+            spl = em_class.__module__.split('.')
59
+            if spl[1] == 'fieldtypes':
60
+                return 'EmField'
57 61
             return False
58 62
         return em_class.__name__
59 63
 
@@ -68,11 +72,21 @@ class Model(object):
68 72
             #Store and delete the EmComponent class name from datas
69 73
             cls_name = kwargs['component']
70 74
             del kwargs['component']
71
-            cls = self.emclass_from_name(cls_name)
75
+            
76
+            if cls_name == 'EmField':
77
+                #Special EmField process because of fieldtypes
78
+                if not 'type' in kwargs:
79
+                    raise AttributeError("Missing 'type' from EmField instanciation")
80
+                cls = EditorialModel.fields.EmField.get_field_class(kwargs['type'])
81
+                kwargs['fieldtype'] = kwargs['type']
82
+                del(kwargs['type'])
83
+            else:
84
+                cls = self.emclass_from_name(cls_name)
85
+
72 86
             if cls:
73 87
                 kwargs['uid'] = uid
74 88
                 # create a dict for the component and one indexed by uids, store instanciated component in it
75
-                self._components['uids'][uid] = cls(self, **kwargs)
89
+                self._components['uids'][uid] = cls(model=self, **kwargs)
76 90
                 self._components[cls_name].append(self._components['uids'][uid])
77 91
             else:
78 92
                 raise ValueError("Unknow EmComponent class : '" + cls_name + "'")
@@ -91,13 +105,16 @@ class Model(object):
91 105
             component.init_ended()
92 106
 
93 107
     ## Saves data using the current backend
94
-    def save(self):
95
-        return self.backend.save(self)
108
+    # @param filename str | None : if None use the current backend file (provided at backend instanciation)
109
+    def save(self, filename = None):
110
+        return self.backend.save(self, filename)
96 111
 
97 112
     ## Given a EmComponent child class return a list of instances
98 113
     # @param cls EmComponent : A python class
99 114
     # @return a list of instances or False if the class is not an EmComponent child
100
-    def components(self, cls):
115
+    def components(self, cls=None):
116
+        if cls is None:
117
+            return [ self.component(uid) for uid in self._components['uids'] ]
101 118
         key_name = self.name_from_emclass(cls)
102 119
         return False if key_name is False else self._components[key_name]
103 120
 
@@ -110,9 +127,10 @@ class Model(object):
110 127
     ## Sort components by rank in Model::_components
111 128
     # @param emclass pythonClass : The type of components to sort
112 129
     # @throw AttributeError if emclass is not valid
130
+    # @warning disabled the test on component_class because of EmField new way of working
113 131
     def sort_components(self, component_class):
114
-        if component_class not in self.components_class:
115
-            raise AttributeError("Bad argument emclass : '" + component_class + "', excpeting one of " + str(self.components_class))
132
+        #if component_class not in self.components_class:
133
+        #    raise AttributeError("Bad argument emclass : '" + str(component_class) + "', excpeting one of " + str(self.components_class))
116 134
 
117 135
         self._components[self.name_from_emclass(component_class)] = sorted(self.components(component_class), key=lambda comp: comp.rank)
118 136
 
@@ -130,9 +148,17 @@ class Model(object):
130 148
     # @param datas dict : the options needed by the component creation
131 149
     # @throw ValueError if datas['rank'] is not valid (too big or too small, not an integer nor 'last' or 'first' )
132 150
     # @todo Handle a raise from the migration handler
151
+    # @todo Transform the datas arg in **datas ?
133 152
     def create_component(self, component_type, datas, uid=None):
134
-
135
-        em_obj = self.emclass_from_name(component_type)
153
+        
154
+        if component_type == 'EmField':
155
+            #special process for EmField
156
+            if not 'type' in datas:
157
+                raise AttributeError("Missing 'type' from EmField instanciation")
158
+            em_obj = EditorialModel.fields.EmField.get_field_class(datas['type'])
159
+            del(datas['type'])
160
+        else:
161
+            em_obj = self.emclass_from_name(component_type)
136 162
 
137 163
         rank = 'last'
138 164
         if 'rank' in datas:

+ 7
- 7
EditorialModel/test/me.json View File

@@ -9,7 +9,7 @@
9 9
     "component":"EmFieldGroup", "name":"info", "string":"{\"fre\":\"Info\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"1"
10 10
   },
11 11
   "4" : {
12
-    "component":"EmField", "name":"titre", "string":"{\"fre\":\"Titre\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"3", "rel_to_type_id":"", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
12
+    "component":"EmField", "name":"titre", "string":"{\"fre\":\"Titre\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "type":"char", "fieldgroup_id":"3", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
13 13
   },
14 14
   "5" : {
15 15
     "component":"EmType", "name":"article", "string":"{\"fre\":\"Article\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"1", "icon":"0", "sortcolumn":"rank", "fields_list":[7], "superiors_list":{"parent":14}
@@ -18,22 +18,22 @@
18 18
     "component":"EmType", "name":"personne", "string":"{\"fre\":\"Personne\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"2", "icon":"0", "sortcolumn":"rank", "fields_list":[10]
19 19
   },
20 20
   "7" : {
21
-    "component":"EmField", "name":"soustitre", "string":"{\"fre\":\"Sous-titre\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"3", "rel_to_type_id":"", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
21
+    "component":"EmField", "name":"soustitre", "string":"{\"fre\":\"Sous-titre\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "type":"char", "fieldgroup_id":"3", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
22 22
   },
23 23
   "8" : {
24 24
     "component":"EmFieldGroup", "name":"civilité", "string":"{\"fre\":\"Civilité\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"2"
25 25
   },
26 26
   "9" : {
27
-    "component":"EmField", "name":"nom", "string":"{\"fre\":\"Nom\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"8", "rel_to_type_id":"", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
27
+    "component":"EmField", "name":"nom", "string":"{\"fre\":\"Nom\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "type":"char", "fieldgroup_id":"8", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
28 28
   },
29 29
   "10" : {
30
-    "component":"EmField", "name":"prenom", "string":"{\"fre\":\"Preom\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"8", "rel_to_type_id":"", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
30
+    "component":"EmField", "name":"prenom", "string":"{\"fre\":\"Preom\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "type":"char", "fieldgroup_id":"8", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
31 31
   },
32 32
   "11" : {
33
-    "component":"EmField", "name":"auteur", "string":"{\"fre\":\"Auteur\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"17", "rel_to_type_id":"6", "rel_field_id":"", "optional":1, "internal":"", "icon":"0"
33
+    "component":"EmField", "name":"auteur", "string":"{\"fre\":\"Auteur\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "type":"rel2type", "fieldgroup_id":"17", "rel_to_type_id":"6", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
34 34
   },
35 35
   "12" : {
36
-    "component":"EmField", "name":"adresse", "string":"{\"fre\":\"Adresse\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"17", "rel_to_type_id":"", "rel_field_id":"11", "optional":0, "internal":"", "icon":"0"
36
+    "component":"EmField", "name":"adresse", "string":"{\"fre\":\"Adresse\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "type":"char", "fieldgroup_id":"17", "rel_field_id":"11", "optional":0, "internal":"", "icon":"0"
37 37
   },
38 38
   "13" : {
39 39
     "component":"EmClass", "name":"publication", "string":"{\"fre\":\"Publication\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "classtype":"entity", "icon":"0", "sortcolumn":"rank"
@@ -45,7 +45,7 @@
45 45
     "component":"EmFieldGroup", "name":"info", "string":"{\"fre\":\"Info\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "class_id":"13"
46 46
   },
47 47
   "16" : {
48
-    "component":"EmField", "name":"titre", "string":"{\"fre\":\"Titre\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "fieldtype":"varchar", "fieldgroup_id":"15", "rel_to_type_id":"", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
48
+    "component":"EmField", "name":"titre", "string":"{\"fre\":\"Titre\"}", "help_text":"{}", "rank":"1", "date_update":"", "date_create":"", "type":"char", "fieldgroup_id":"15", "rel_field_id":"", "optional":0, "internal":"", "icon":"0"
49 49
   },
50 50
   "17" : {
51 51
     "component":"EmFieldGroup", "name":"gens", "string":"{\"fre\":\"Gens\"}", "help_text":"{}", "rank":"2", "date_update":"", "date_create":"", "class_id":"1"

+ 0
- 0
Lodel/management/__init__.py View File


+ 0
- 0
Lodel/management/commands/__init__.py View File


+ 24
- 0
Lodel/management/commands/listfieldtypes.py View File

@@ -0,0 +1,24 @@
1
+from django.core.management.base import BaseCommand, CommandError
2
+from optparse import make_option
3
+from EditorialModel.fields import EmField
4
+
5
+class Command(BaseCommand):
6
+    option_list = BaseCommand.option_list + (
7
+        make_option('--silent',
8
+            action='store_true',
9
+            dest='silent',
10
+            default=False,
11
+            help='Only display field types name and hide the help text'),
12
+    )
13
+    help = 'List all the possible field types'
14
+
15
+    def handle(self, *args, **options):
16
+        ftl = EmField.fieldtypes_list()
17
+        ftl.sort()
18
+        if options['silent']:
19
+            for ftype in ftl:
20
+                self.stdout.write("%s\n"%ftype)
21
+        else:
22
+            self.stdout.write("Field types list : ")
23
+            for ftype in ftl:
24
+                self.stdout.write("\t{0:15} {1}".format(ftype, EmField.get_field_class(ftype).help))

+ 1
- 0
Lodel/settings/defaults.py View File

@@ -59,6 +59,7 @@ INSTALLED_APPS = (
59 59
     'django.contrib.messages',
60 60
     'django.contrib.staticfiles',
61 61
     'LodelTestInstance',
62
+    'Lodel',
62 63
 )
63 64
 
64 65
 MIDDLEWARE_CLASSES = (

Loading…
Cancel
Save