Browse Source

Cleaning the folder hierarchy

Roland Haroutiounian 8 years ago
parent
commit
5cae70382a
100 changed files with 0 additions and 20472 deletions
  1. 0
    0
      DataSource/MySQL/__init__.py
  2. 0
    88
      DataSource/MySQL/fieldtypes.py
  3. 0
    420
      DataSource/MySQL/leapidatasource.py
  4. 0
    659
      DataSource/MySQL/migrationhandler.py
  5. 0
    156
      DataSource/MySQL/migrationhandler__future__.py
  6. 0
    0
      DataSource/MySQL/test/__init__.py
  7. 0
    279
      DataSource/MySQL/test/test_datasource.py
  8. 0
    89
      DataSource/MySQL/utils.py
  9. 0
    0
      DataSource/__init__.py
  10. 0
    0
      DataSource/dummy/__init__.py
  11. 0
    62
      DataSource/dummy/leapidatasource.py
  12. 0
    50
      DataSource/dummy/migrationhandler.py
  13. 0
    2403
      Doxyfile
  14. 0
    2
      EditorialModel/__init__.py
  15. 0
    2
      EditorialModel/backend/__init__.py
  16. 0
    17
      EditorialModel/backend/dummy_backend.py
  17. 0
    129
      EditorialModel/backend/graphviz.py
  18. 0
    99
      EditorialModel/backend/json_backend.py
  19. 0
    247
      EditorialModel/backend/lodel1_backend.py
  20. 0
    142
      EditorialModel/classes.py
  21. 0
    217
      EditorialModel/classtypes.py
  22. 0
    218
      EditorialModel/components.py
  23. 0
    27
      EditorialModel/exceptions.py
  24. 0
    151
      EditorialModel/fields.py
  25. 0
    1
      EditorialModel/fieldtypes/__init__.py
  26. 0
    25
      EditorialModel/fieldtypes/bool.py
  27. 0
    15
      EditorialModel/fieldtypes/char.py
  28. 0
    17
      EditorialModel/fieldtypes/datetime.py
  29. 0
    15
      EditorialModel/fieldtypes/dictionary.py
  30. 0
    39
      EditorialModel/fieldtypes/emuid.py
  31. 0
    16
      EditorialModel/fieldtypes/file.py
  32. 0
    25
      EditorialModel/fieldtypes/format.py
  33. 0
    293
      EditorialModel/fieldtypes/generic.py
  34. 0
    45
      EditorialModel/fieldtypes/i18n.py
  35. 0
    21
      EditorialModel/fieldtypes/integer.py
  36. 0
    26
      EditorialModel/fieldtypes/join.py
  37. 0
    68
      EditorialModel/fieldtypes/leo.py
  38. 0
    24
      EditorialModel/fieldtypes/namerelation.py
  39. 0
    48
      EditorialModel/fieldtypes/naturerelation.py
  40. 0
    26
      EditorialModel/fieldtypes/pk.py
  41. 0
    27
      EditorialModel/fieldtypes/rank.py
  42. 0
    27
      EditorialModel/fieldtypes/regexchar.py
  43. 0
    21
      EditorialModel/fieldtypes/rel2type.py
  44. 0
    14
      EditorialModel/fieldtypes/text.py
  45. 0
    330
      EditorialModel/model.py
  46. 0
    193
      EditorialModel/randomem.py
  47. 0
    0
      EditorialModel/test/__init__.py
  48. 0
    603
      EditorialModel/test/me.json
  49. 0
    8026
      EditorialModel/test/model-lodel-1.0.xml
  50. 0
    194
      EditorialModel/test/test_classes.py
  51. 0
    113
      EditorialModel/test/test_component.py
  52. 0
    115
      EditorialModel/test/test_field.py
  53. 0
    314
      EditorialModel/test/test_model.py
  54. 0
    115
      EditorialModel/test/test_types.py
  55. 0
    357
      EditorialModel/types.py
  56. 0
    91
      Lodel/__init__.py
  57. 0
    87
      Lodel/hooks.py
  58. 0
    113
      Lodel/logger.py
  59. 0
    87
      Lodel/plugins.py
  60. 0
    144
      Lodel/settings.py
  61. 0
    26
      Lodel/settings_format.py
  62. 0
    0
      Lodel/test/__init__.py
  63. 0
    163
      Lodel/test/tests_hooks.py
  64. 0
    155
      Lodel/test/tests_user.py
  65. 0
    282
      Lodel/user.py
  66. 0
    0
      Lodel/utils/__init__.py
  67. 0
    115
      Lodel/utils/mlstring.py
  68. 0
    40
      Lodel/utils/mosql.py
  69. 0
    48
      Makefile
  70. 0
    56
      README.md
  71. 0
    1
      Router/__init__.py
  72. 0
    4
      Router/urls.py
  73. 0
    56
      Template/Loader.py
  74. 0
    0
      Template/__init__.py
  75. 0
    0
      Template/api/__init__.py
  76. 0
    3
      Template/api/api_lodel_templates.py
  77. 0
    7
      Template/exceptions/NotAllowedCustomAPIKeyError.py
  78. 0
    1
      Template/exceptions/__init__.py
  79. 0
    14
      doc/img/graphviz/Makefile
  80. 0
    13
      doc/img/graphviz/em_components.dot
  81. 0
    26
      doc/img/graphviz/em_relations.dot
  82. 0
    77
      doc/img/graphviz/em_types_hierarch.dot
  83. 0
    39
      doc/img/graphviz/example_em_graph.dot
  84. 0
    19
      doc/img/graphviz/lodel2_ui.dot
  85. BIN
      doc/img/openedition_logo.png
  86. 0
    26
      install/Makefile
  87. 0
    11
      install/README.txt
  88. 0
    1
      install/__init__.py
  89. 0
    171
      install/dynleapi.py
  90. 0
    603
      install/em.json
  91. 0
    26
      install/instance_settings.py
  92. 0
    38
      install/loader.py
  93. 0
    88
      install/netipy.py
  94. 0
    22
      install/netipy_loader.py
  95. 0
    91
      install/utils.py
  96. 0
    1
      leapi/__init__.py
  97. 0
    46
      leapi/leclass.py
  98. 0
    740
      leapi/lecrud.py
  99. 0
    331
      leapi/lefactory.py
  100. 0
    0
      leapi/leobject.py

+ 0
- 0
DataSource/MySQL/__init__.py View File


+ 0
- 88
DataSource/MySQL/fieldtypes.py View File

@@ -1,88 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @package DataSource.MySQL.fieldtypes 
4
-# 
5
-# Defines usefull function to handle fieldtype in MySQL datasource
6
-
7
-import EditorialModel
8
-from EditorialModel import fieldtypes as ed_fieldtypes
9
-import EditorialModel.fieldtypes.generic
10
-from EditorialModel.fieldtypes.generic import SingleValueFieldType, MultiValueFieldType, ReferenceFieldType
11
-import EditorialModel.fieldtypes.integer
12
-import EditorialModel.fieldtypes.char
13
-import EditorialModel.fieldtypes.bool
14
-import EditorialModel.fieldtypes.text
15
-import EditorialModel.fieldtypes.rel2type
16
-import EditorialModel.fieldtypes.leo
17
-
18
-## @brief Returns column specs from fieldtype
19
-# @param emfieldtype EmFieldType : An EmFieldType insance
20
-# @param noauto_inc bool : if True don't set the autoincrement parameter
21
-# @todo escape default value
22
-def singlevaluefieldtype_db_init_specs(emfieldtype, noauto_inc = False):
23
-    colspec = ''
24
-    if not emfieldtype.nullable:
25
-        colspec = 'NOT NULL'
26
-    if hasattr(emfieldtype, 'default'):
27
-        colspec += ' DEFAULT '
28
-        if emfieldtype.default is None:
29
-            colspec += 'NULL '
30
-        else:
31
-            colspec += emfieldtype.default  # ESCAPE VALUE HERE !!!!
32
-
33
-    if emfieldtype.name == 'pk' and not noauto_inc:
34
-        colspec += ' AUTO_INCREMENT'
35
-
36
-    return colspec
37
-
38
-## @brief Given a fieldtype return instructions to be executed by the migration handler
39
-#
40
-# The returned value in a tuple of len = 3
41
-#
42
-# The first items gives instruction type. Possible values are :
43
-# - 'column' : add a column
44
-#  - the second tuple item is the SQL type of the new column
45
-#  - the third tuple item is the SQL specs (constraints like default value, nullable, unique , auto_increment etc.)
46
-# - 'table' : add a column in another table and make a fk to the current table
47
-#  - the second tuple item is a tuple(key_name, key_value)
48
-#  - the third tuple item is a tuple(column_type, column_spec)
49
-# @param fieldtype GenericFieldType : A FieldType instance
50
-# @param noauto_inc bool : if True don't set the autoincrement parameter
51
-# @return a tuple (instruction_type, infos)
52
-def fieldtype_db_init(fieldtype, noauto_inc = False):
53
-    if isinstance(fieldtype, EditorialModel.fieldtypes.rel2type.EmFieldType):
54
-        return (None, None, None)
55
-    elif isinstance(fieldtype, SingleValueFieldType):
56
-        res = [ 'column', None, singlevaluefieldtype_db_init_specs(fieldtype, noauto_inc) ]
57
-        # We will create a column
58
-        if isinstance(fieldtype, EditorialModel.fieldtypes.integer.EmFieldType):
59
-            res[1] = 'INT'
60
-        elif isinstance(fieldtype, EditorialModel.fieldtypes.char.EmFieldType):
61
-            res[1] = 'VARCHAR(%d)' % fieldtype.max_length
62
-        elif isinstance(fieldtype, EditorialModel.fieldtypes.text.EmFieldType):
63
-            res[1] = 'TEXT'
64
-        elif isinstance(fieldtype, EditorialModel.fieldtypes.bool.EmFieldType):
65
-            res[1] = 'BOOL'
66
-        elif isinstance(fieldtype, EditorialModel.fieldtypes.datetime.EmFieldType):
67
-            res[1] = 'DATETIME'
68
-        elif isinstance(fieldtype, EditorialModel.fieldtypes.generic.ReferenceFieldType):
69
-            res[1] = 'INT'
70
-        else:
71
-            raise RuntimeError("Unsupported fieldtype : ", fieldtype)
72
-    elif isinstance(fieldtype, MultiValueFieldType):
73
-        res = [ 'table', None, None ]
74
-        res[1] = (fieldtype.keyname, fieldtype.key_fieldtype)
75
-        res[2] = fieldtype.value_fieldtype
76
-    else:
77
-        raise NotImplementedError("Not yet implemented")
78
-    return tuple(res)
79
-
80
-## @brief Cast a value given a fieldtype
81
-# @param fieldtype EmFieldType : a fieldtype instance
82
-# @param value : mixed value
83
-# @return The value in a way usable by the datasource
84
-def fieldtype_cast(fieldtype, value):
85
-    if isinstance(fieldtype, EditorialModel.fieldtypes.leo.EmFieldType):
86
-        return value.uidget()
87
-    return value
88
-

+ 0
- 420
DataSource/MySQL/leapidatasource.py View File

@@ -1,420 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import pymysql
4
-import copy
5
-
6
-import leapi
7
-from leapi.leobject import REL_SUB, REL_SUP
8
-
9
-from leapi.lecrud import _LeCrud, REL_SUP, REL_SUB
10
-
11
-from mosql.db import Database, all_to_dicts, one_to_dict
12
-from mosql.query import select, insert, update, delete, join, left_join
13
-from mosql.util import raw, or_
14
-import mosql.mysql
15
-
16
-from DataSource.dummy.leapidatasource import DummyDatasource
17
-from DataSource.MySQL import utils
18
-from EditorialModel.classtypes import EmNature
19
-from EditorialModel.fieldtypes.generic import MultiValueFieldType
20
-
21
-from Lodel.settings import Settings
22
-from .fieldtypes import fieldtype_cast
23
-
24
-## MySQL DataSource for LeObject
25
-class LeDataSourceSQL(DummyDatasource):
26
-
27
-    RELATIONS_POSITIONS_FIELDS = {REL_SUP: 'superior_id', REL_SUB: 'subordinate_id'}
28
-
29
-    def __init__(self, module=pymysql, conn_args=None):
30
-        super(LeDataSourceSQL, self).__init__()
31
-        self.module = module
32
-        if conn_args is None:
33
-            conn_args = copy.copy(Settings.get('datasource')['default'])
34
-            self.module = conn_args['module']
35
-            del conn_args['module']
36
-        self.connection = Database(self.module, **conn_args)
37
-
38
-    ## @brief select lodel editorial components using given filters
39
-    # @param target_cls LeCrud(class): The component class concerned by the select (a LeCrud child class (not instance !) )
40
-    # @param field_list list: List of field to fetch
41
-    # @param filters list: List of filters (see @ref api_user_side)
42
-    # @param rel_filters list: List of relational filters (see @ref api_user_side)
43
-    # @param group list of tupple: List of column to group together.  group = [('titre', 'ASC'), ]
44
-    # @param order list of tupple : List of column to order.  order = [('titre', 'ASC'), ]
45
-    # @param offset int : Used with limit to choose the start row
46
-    # @param limit int : Number of row to be returned
47
-    # @param instanciate bool : If True return an instance, else return a dict
48
-    # @return a list of LeCrud child classes
49
-    # @todo this only works with LeObject.get(), LeClass.get() and LeType.get()
50
-    # @todo for speed get rid of all_to_dicts
51
-    # @todo filters: all use cases are not implemented
52
-    # @todo group: mosql does not permit direction in group_by clause, it should, so for now we don't use direction in group clause
53
-    def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=None, instanciate=True):
54
-
55
-        joins = []
56
-        mandatory_fields = []
57
-        class_table = False
58
-
59
-        # it is a LeObject, query only on main table
60
-        if target_cls.__name__ == 'LeObject':
61
-            main_table = utils.common_tables['object']
62
-            fields = [(main_table, target_cls.fieldlist())]
63
-
64
-        # it is a LeType or a LeClass, query on main table left join class table on lodel_id
65
-        elif target_cls.is_letype() or target_cls.is_leclass():
66
-            # find main table
67
-            main_table = utils.common_tables['object']
68
-            main_class = target_cls.leo_class()
69
-            # find class table
70
-            class_table = utils.object_table_name(main_class.__name__)
71
-            class_fk = main_class.uidname()
72
-            main_lodel_id = utils.column_prefix(main_table, main_class.uidname())
73
-            class_lodel_id = utils.column_prefix(class_table, main_class.uidname())
74
-            # do the join
75
-            joins = [left_join(class_table, {main_lodel_id:class_lodel_id})]
76
-
77
-            mandatory_fields = [class_fk, 'type_id']
78
-
79
-            fields = [(main_table, target_cls.name2class('LeObject').fieldlist()), (class_table, main_class.fieldlist())]
80
-
81
-        elif target_cls.is_lehierarch():
82
-            main_table = utils.common_tables['relation']
83
-            fields = [(main_table, target_cls.name2class('LeRelation').fieldlist())]
84
-        elif target_cls.is_lerel2type():
85
-            # find main table
86
-            main_table = utils.common_tables['relation']
87
-            # find relational table
88
-            class_table = utils.r2t_table_name(target_cls._superior_cls.__name__, target_cls._subordinate_cls.__name__)
89
-            class_fk = target_cls.name2class('LeRelation').uidname()
90
-            main_relation_id = utils.column_prefix(main_table, class_fk)
91
-            class_relation_id = utils.column_prefix(class_table, class_fk)
92
-            # do the joins
93
-            lodel_id = target_cls.name2class('LeObject').uidname()
94
-            joins = [
95
-                left_join(class_table, {main_relation_id:class_relation_id}),
96
-                # join with the object table to retrieve class and type of superior and subordinate
97
-                left_join(utils.common_tables['object'] + ' as sup_obj', {'sup_obj.'+lodel_id:target_cls._superior_field_name}),
98
-                left_join(utils.common_tables['object'] + ' as sub_obj', {'sub_obj.'+lodel_id:target_cls._subordinate_field_name})
99
-            ]
100
-
101
-            mandatory_fields = [class_fk, 'class_id', 'type_id']
102
-
103
-            fields = [
104
-                (main_table, target_cls.name2class('LeRelation').fieldlist()),
105
-                (class_table, target_cls.fieldlist()),
106
-                ('sup_obj', ['class_id']),
107
-                ('sub_obj', ['type_id'])
108
-            ]
109
-
110
-        else:
111
-            raise AttributeError("Target class '%s' in get() is not a Lodel Editorial Object !" % target_cls)
112
-
113
-
114
-        # extract mutltivalued field from field_list
115
-        if class_table:
116
-            multivalue_fields = self.get_multivalue_fields(target_cls)
117
-            for field_names in multivalue_fields.values():
118
-                for field_name in field_names:
119
-                    try:
120
-                        field_list.remove(field_name)
121
-                    except ValueError:
122
-                        pass  # field_name is not in field_list
123
-        else:
124
-            multivalue_fields = False
125
-
126
-        # add mandatory fields to field_list
127
-        for mandatory_field in mandatory_fields:
128
-            if mandatory_field not in field_list:
129
-                field_list.append(mandatory_field)
130
-
131
-        # prefix column name in fields list
132
-        prefixed_field_list = [utils.find_prefix(name, fields) for name in field_list]
133
-
134
-        kwargs = {}
135
-        if group:
136
-            kwargs['group_by'] = (utils.find_prefix(column, fields) for column, direction in group)
137
-        if order:
138
-            kwargs['order_by'] = (utils.find_prefix(column, fields) + ' ' + direction for column, direction in order)
139
-        if limit:
140
-            kwargs['limit'] = limit
141
-        if offset:
142
-            kwargs['offset'] = offset
143
-
144
-        # relational filters
145
-        # @todo this only works with hierarchy relations
146
-        if rel_filters:
147
-            le_relation = target_cls.name2class('LeRelation')
148
-            rel_cpt = 0
149
-            for rel_filter in rel_filters:
150
-                rel_cpt += 1
151
-                rel_name = 'rel' + str(rel_cpt)
152
-                name, op, value = rel_filter
153
-                direction, nature = name
154
-                if direction == REL_SUP:
155
-                    join_column, filter_column = (le_relation._subordinate_field_name, le_relation._superior_field_name)
156
-                else:
157
-                    join_column, filter_column = (le_relation._superior_field_name, le_relation._subordinate_field_name)
158
-                rel_join = left_join(utils.common_tables['relation'] + ' as ' + rel_name, {utils.column_prefix(main_table, main_class.uidname()):utils.column_prefix(rel_name, join_column)})
159
-                filters.append((utils.column_prefix(rel_name, 'nature'), '=', nature))
160
-                filters.append((utils.column_prefix(rel_name, filter_column), op, value))
161
-                joins.append(rel_join)
162
-
163
-        # prefix filters'' column names, and prepare dict for mosql where {(fieldname, op): value}
164
-        # TODO: this will not work with special filters
165
-        wheres = {(utils.find_prefix(name, fields), op):fieldtype_cast(target_cls.fieldtypes()[name], value) for name,op,value in filters}
166
-        query = select(main_table, select=prefixed_field_list, where=wheres, joins=joins, **kwargs)
167
-
168
-        # Executing the query
169
-        cur = utils.query(self.connection, query)
170
-        results = all_to_dicts(cur)
171
-        #print(results)
172
-
173
-        # query multivalued tables, inject result in main result
174
-        if multivalue_fields:
175
-            for result in results:
176
-                for key_name, fields in multivalue_fields.items():
177
-                    query_fields = [key_name]
178
-                    query_fields.extend(fields)
179
-                    table_name = utils.multivalue_table_name(class_table, key_name)
180
-                    sql = select(table_name, select=query_fields, where={(class_fk, '='):result[class_fk]})
181
-
182
-                    multi = {name:{} for name in fields}
183
-                    cur = utils.query(self.connection, sql)
184
-
185
-                    multi_results = all_to_dicts(cur)
186
-                    for multi_result in multi_results:
187
-                        for field in fields:
188
-                            multi[field][multi_result[key_name]] = multi_result[field]
189
-                    result.update(multi)
190
-
191
-        # instanciate each row to editorial components
192
-        if instanciate:
193
-            results = [target_cls.object_from_data(datas) for datas in results]
194
-            #print('results', results)
195
-
196
-        return results
197
-
198
-    ## @brief delete lodel editorial components given filters
199
-    # @param target_cls LeCrud(class): The component class concerned by the delete (a LeCrud child class (not instance !) )
200
-    # @param uid int : A uniq ID to identify the object we want to delete
201
-    # @return the number of deleted components
202
-    def delete(self, target_cls, uid):
203
-        if target_cls.implements_leobject():
204
-            tablename = utils.common_tables['object']
205
-        elif target_cls.implements_lerelation():
206
-            tablename = utils.common_tables['relation']
207
-        else:
208
-            raise AttributeError("'%s' is not a LeObject nor a LeRelation, it's not possible to delete it")
209
-        uidname = target_cls.uidname()
210
-        sql = delete(tablename, ((uidname, uid),))
211
-        utils.query(self.connection, sql) #No way to check the result ?
212
-        return True
213
-
214
-    ## @brief update ONE existing lodel editorial component
215
-    # @param target_cls LeCrud(class) : Instance of the object concerned by the update
216
-    # @param lodel_id : id of the component
217
-    # @param **datas : Datas in kwargs
218
-    # @return the number of updated components
219
-    def update(self, target_cls, lodel_id, **datas):
220
-
221
-        # it is a LeType
222
-        if target_cls.is_letype():
223
-            # find main table and main table datas
224
-            main_table = utils.common_tables['object']
225
-            fk_name = target_cls.uidname()
226
-            main_datas = {fk_name: raw(fk_name)} #  be sure to have one SET clause
227
-            main_fields = target_cls.name2class('LeObject').fieldlist()
228
-            class_table = utils.object_table_name(target_cls.leo_class().__name__)
229
-        elif target_cls.is_lerel2type():
230
-            main_table = utils.common_tables['relation']
231
-            le_relation = target_cls.name2class('LeRelation')
232
-            fk_name = le_relation.uidname()
233
-            main_datas = {fk_name: raw(fk_name)} #  be sure to have one SET clause
234
-            main_fields = le_relation.fieldlist()
235
-
236
-            class_table = utils.r2t_table_name(target_cls._superior_cls.__name__, target_cls._subordinate_cls.__name__)
237
-        else:
238
-            raise AttributeError("'%s' is not a LeType nor a LeRelation, it's impossible to update it" % target_cls)
239
-
240
-
241
-        datas = { fname: fieldtype_cast(target_cls.fieldtypes()[fname], datas[fname]) for fname in datas }
242
-
243
-        for main_column_name in main_fields:
244
-            if main_column_name in datas:
245
-                main_datas[main_column_name] = datas[main_column_name]
246
-                del(datas[main_column_name])
247
-
248
-        # extract multivalued field from class_table datas
249
-        multivalued_datas = self.create_multivalued_datas(target_cls, datas)
250
-
251
-        where = {fk_name: lodel_id}
252
-        sql = update(main_table, where, main_datas)
253
-        utils.query(self.connection, sql)
254
-
255
-        # update on class table
256
-        if datas:
257
-            sql = update(class_table, where, datas)
258
-            utils.query(self.connection, sql)
259
-
260
-        # do multivalued insert
261
-        # first delete old values, then insert new ones
262
-        for key_name, lines in multivalued_datas.items():
263
-            table_name = utils.multivalue_table_name(class_table, key_name)
264
-            sql = delete(table_name, where)
265
-            utils.query(self.connection, sql)
266
-            for key_value, line_datas in lines.items():
267
-                line_datas[key_name] = key_value
268
-                line_datas[fk_name] = lodel_id
269
-                sql = insert(table_name, line_datas)
270
-                utils.query(self.connection, sql)
271
-
272
-
273
-        return True
274
-
275
-    ## @brief inserts a new lodel editorial component
276
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
277
-    # @param **datas : The datas to insert
278
-    # @return The inserted component's id
279
-    # @todo should work with LeType, LeClass, and Relations
280
-    def insert(self, target_cls, **datas):
281
-        class_table = False
282
-
283
-        # it is a LeType
284
-        if target_cls.is_letype():
285
-            main_table = utils.common_tables['object']
286
-            main_datas = {'class_id':target_cls.leo_class()._class_id, 'type_id':target_cls._type_id}
287
-            main_fields = target_cls.name2class('LeObject').fieldlist()
288
-
289
-            class_table = utils.object_table_name(target_cls.leo_class().__name__)
290
-            fk_name = target_cls.uidname()
291
-        # it is a hierarchy
292
-        elif target_cls.is_lehierarch():
293
-            main_table = utils.common_tables['relation']
294
-            main_datas = {
295
-                utils.column_name(target_cls._superior_field_name): datas[target_cls._superior_field_name].lodel_id,
296
-                utils.column_name(target_cls._subordinate_field_name): datas[target_cls._subordinate_field_name].lodel_id
297
-            }
298
-            main_fields = target_cls.name2class('LeRelation').fieldlist()
299
-        # it is a relation
300
-        elif target_cls.is_lerel2type():
301
-            main_table = utils.common_tables['relation']
302
-            main_datas = {
303
-                utils.column_name(target_cls._superior_field_name): datas[target_cls._superior_field_name].lodel_id,
304
-                utils.column_name(target_cls._subordinate_field_name): datas[target_cls._subordinate_field_name].lodel_id
305
-            }
306
-            main_fields = target_cls.name2class('LeRelation').fieldlist()
307
-
308
-            superior_class = datas['superior'].leo_class()
309
-            class_table = utils.r2t_table_name(superior_class.__name__, datas['subordinate'].__class__.__name__)
310
-            fk_name = superior_class.name2class('LeRelation').uidname()
311
-        else:
312
-            raise AttributeError("'%s' is not a LeType nor a LeRelation, it's impossible to insert it" % target_cls)
313
-
314
-        # cast datas
315
-        datas = { fname: fieldtype_cast(target_cls.fieldtypes()[fname], datas[fname]) for fname in datas }
316
-
317
-        # extract main table datas from datas
318
-        for main_column_name in main_fields:
319
-            if main_column_name in datas:
320
-                if main_column_name not in main_datas:
321
-                    main_datas[main_column_name] = datas[main_column_name]
322
-                del(datas[main_column_name])
323
-
324
-        # extract multivalued field from class_table datas
325
-        if class_table:
326
-            multivalued_datas = self.create_multivalued_datas(target_cls, datas)
327
-
328
-        sql = insert(main_table, main_datas)
329
-        cur = utils.query(self.connection, sql)
330
-        lodel_id = cur.lastrowid
331
-
332
-        if class_table:
333
-            # insert in class_table
334
-            datas[fk_name] = lodel_id
335
-            sql = insert(class_table, datas)
336
-            utils.query(self.connection, sql)
337
-
338
-            # do multivalued inserts
339
-            for key_name, lines in multivalued_datas.items():
340
-                table_name = utils.multivalue_table_name(class_table, key_name)
341
-                for key_value, line_datas in lines.items():
342
-                    line_datas[key_name] = key_value
343
-                    line_datas[fk_name] = lodel_id
344
-                    sql = insert(table_name, line_datas)
345
-                    utils.query(self.connection, sql)
346
-
347
-        return lodel_id
348
-
349
-    # extract multivalued field from datas, prepare multivalued data list
350
-    def create_multivalued_datas(self, target_cls, datas):
351
-            multivalue_fields = self.get_multivalue_fields(target_cls)
352
-
353
-            if multivalue_fields:
354
-                # construct multivalued datas
355
-                multivalued_datas = {key:{} for key in multivalue_fields}
356
-                for key_name, names in multivalue_fields.items():
357
-                    for field_name in names:
358
-                        try:
359
-                            for key, value in datas[field_name].items():
360
-                                if key not in multivalued_datas[key_name]:
361
-                                    multivalued_datas[key_name][key] = {}
362
-                                multivalued_datas[key_name][key][field_name] = value
363
-                            del(datas[field_name])
364
-                        except KeyError:
365
-                            pass  # field_name is not in datas
366
-                return multivalued_datas
367
-            else:
368
-                return {}
369
-
370
-    # return multivalue fields of a class
371
-    def get_multivalue_fields(self, target_cls):
372
-        multivalue_fields = {}
373
-        # scan fieldtypes to get mutltivalued field
374
-        for field_name, fieldtype in target_cls.fieldtypes(complete=False).items():
375
-                if isinstance(fieldtype, MultiValueFieldType):
376
-                    if  fieldtype.keyname in multivalue_fields:
377
-                        multivalue_fields[fieldtype.keyname].append(field_name)
378
-                    else:
379
-                        multivalue_fields[fieldtype.keyname] = [field_name]
380
-
381
-        return multivalue_fields
382
-
383
-
384
-    ## @brief insert multiple editorial component
385
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
386
-    # @param datas_list list : A list of dict representing the datas to insert
387
-    # @return int the number of inserted component
388
-    def insert_multi(self, target_cls, datas_list):
389
-        res = list()
390
-        for data in datas_list:
391
-            res.append(self.insert(target_cls, data))
392
-        return len(res)
393
-
394
-    ## @brief Sets a new rank on a relation
395
-    # @param le_relation LeRelation
396
-    # @param rank int: integer representing the absolute new rank
397
-    # @return True if success, False if failure
398
-    # TODO Conserver cette méthode dans le datasource du fait des requêtes SQL. Elle est appelée par le set_rank de LeRelation
399
-    def update_rank(self, le_relation, rank):
400
-
401
-        lesup = le_relation.id_sup
402
-        lesub = le_relation.id_sub
403
-        current_rank = le_relation.rank
404
-
405
-        relations = le_relation.__class__.get(query_filters=[('id_sup', '=', lesup)], order=[('rank', 'ASC')])
406
-        # relations = self.get_related(lesup, lesub.__class__, get_sub=True)
407
-
408
-        # insert the relation at the right position considering its new rank
409
-        our_relation = relations.pop(current_rank)
410
-        relations.insert(our_relation, rank)
411
-
412
-        # rebuild now the list of relations from the resorted list and recalculating the ranks
413
-        rdatas = [(attrs['relation_id'], new_rank+1) for new_rank, (sup, sub, attrs) in enumerate(relations)]
414
-
415
-        sql = insert(MySQL.relations_table_name, columns=(MySQL.relations_pkname, 'rank'), values=rdatas, on_duplicate_key_update={'rank', mosql.util.raw('VALUES(`rank`)')})
416
-        with self.connection as cur:
417
-            if cur.execute(sql) != 1:
418
-                return False
419
-            else:
420
-                return True

+ 0
- 659
DataSource/MySQL/migrationhandler.py View File

@@ -1,659 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-import copy
4
-import pymysql
5
-
6
-from Lodel.settings import Settings
7
-
8
-import EditorialModel
9
-import EditorialModel.classtypes
10
-import EditorialModel.fieldtypes
11
-import EditorialModel.fieldtypes.generic
12
-from EditorialModel.fieldtypes.generic import MultiValueFieldType
13
-
14
-from DataSource.MySQL import fieldtypes as fieldtypes_utils
15
-from DataSource.MySQL import utils
16
-from DataSource.dummy.migrationhandler import DummyMigrationHandler
17
-
18
-# The global MH algorithm is as follow :
19
-# A create_table(table_name, pk_name, pk_opt) method that create a table
20
-# with one pk field
21
-# An add_column(table_name, field_name, field_opt) method that add a column to a table
22
-#
23
-# The create_default_table method will call both methods to create the object and relation tables
24
-#
25
-# Supported operations :
26
-# - EmClass creation
27
-# - EmClass deletion
28
-# - EmField creation
29
-# - EmField deletion
30
-# - rel2type attribute creation
31
-# - rel2type attribute deletion
32
-#
33
-# Unsupported operations :
34
-# - EmClass rename
35
-# - EmField rename
36
-# - rel2type field rename
37
-# - rel2type attribute rename
38
-# - EmFieldType changes
39
-#
40
-# @todo Unified datasources and migration handlers via utils functions
41
-
42
-
43
-## @brief Modify a MySQL database given editorial model changes
44
-class MysqlMigrationHandler(DummyMigrationHandler):
45
-
46
-    ## @brief Construct a MysqlMigrationHandler
47
-    # @param module : sql module
48
-    # @param conn_args dict : A dict containing connection options
49
-    # @param db_engine str : Name of a MySQL db engine (default is InnoDB, don't change it ! ;) ) 
50
-    # @param **kwargs : arguement given to the module.connect() method
51
-    def __init__(self, module=pymysql, conn_args=None, db_engine='InnoDB', **kwargs):
52
-        # Database connection
53
-        self._dbmodule = module
54
-        if conn_args is None:
55
-            conn_args = copy.copy(Settings.get('datasource')['default'])
56
-            self._dbmodule = conn_args['module']
57
-            del conn_args['module']
58
-        self.db_conn = self._dbmodule.connect(**conn_args)
59
-        # Fetch options
60
-        mh_settings = Settings.migration_options
61
-        self.debug = kwargs['debug'] if 'debug' in kwargs else Settings.debug_sql
62
-        self.dryrun = kwargs['dryrun'] if 'dryrun' in kwargs else mh_settings['dryrun']
63
-        self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else mh_settings['foreign_keys']
64
-        self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_if_exists' in kwargs else mh_settings['drop_if_exists']
65
-        self.db_engine = db_engine
66
-        #Create default tables
67
-        self._create_default_tables(self.drop_if_exists)
68
-
69
-    ## @brief Modify the db given an EM change
70
-    #
71
-    # @note Here we don't care about the relation parameter of _add_column() method because the
72
-    # only case in wich we want to add a field that is linked with the relation table is for rel2type
73
-    # attr creation. The relation parameter is set to True in the add_relationnal_field() method
74
-    # 
75
-    # @param em model : The EditorialModel.model object to provide the global context
76
-    # @param uid int : The uid of the change EmComponent
77
-    # @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
78
-    # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
79
-    # @param engine str : Mysql db engine, should be "InnoDB"
80
-    # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
81
-    def register_change(self, em, uid, initial_state, new_state, engine=None):
82
-        if engine is None:
83
-            engine = self.db_engine
84
-        if isinstance(em.component(uid), EditorialModel.classes.EmClass):
85
-            if initial_state is None:
86
-                #EmClass creation
87
-                self.create_emclass_table(em, uid, engine)
88
-            elif new_state is None:
89
-                #EmClass deletion
90
-                self.delete_emclass_table(em, uid)
91
-        elif isinstance(em.component(uid), EditorialModel.fields.EmField):
92
-            emfield = em.component(uid)
93
-            if emfield.rel_field_id is None:
94
-                #non relationnal field
95
-                if initial_state is None:
96
-                    #non relationnal EmField creation
97
-                    if emfield.name not in EditorialModel.classtypes.common_fields.keys():
98
-                        self.add_col_from_emfield(em, uid)
99
-                elif new_state is None:
100
-                    #non relationnal EmField deletion
101
-                    if emfield.name not in EditorialModel.classtypes.common_fields.keys():
102
-                        self.delete_col_from_emfield(em, uid)
103
-            else:
104
-                #relationnal field
105
-                if initial_state is None:
106
-                    #Rel2type attr creation
107
-                    self.add_relationnal_field(em, uid)
108
-                elif new_state is None:
109
-                    #Rel2type attr deletion
110
-                    self.del_relationnal_field(em, uid)
111
-
112
-    ## @brief dumdumdummy
113
-    # @note implemented to avoid the log message of EditorialModel.migrationhandler.dummy.DummyMigrationHandler
114
-    def register_model_state(self, em, state_hash):
115
-        pass
116
-
117
-    ## @brief Add a relationnal field
118
-    # Add a rel2type attribute
119
-    # @note this function handles the table creation
120
-    # @param edmod Model : EditorialModel.model.Model instance
121
-    # @param rfuid int : Relationnal field uid
122
-    def add_relationnal_field(self, edmod, rfuid):
123
-        emfield = edmod.component(rfuid)
124
-        if not isinstance(emfield, EditorialModel.fields.EmField):
125
-            raise ValueError("The given uid is not an EmField uid")
126
-
127
-        r2tf = edmod.component(emfield.rel_field_id)
128
-        tname = self._r2t2table_name(edmod, r2tf)
129
-        pkname, pkftype = self._relation_pk
130
-
131
-        #If not exists create a relational table
132
-        self._create_table(
133
-                            tname,
134
-                            pkname,
135
-                            pkftype,
136
-                            self.db_engine,
137
-                            if_exists='nothing',
138
-                            noauto_inc = True,
139
-                        )
140
-        #Add a foreign key if wanted
141
-        if self.foreign_keys:
142
-            self._add_fk(tname, utils.common_tables['relation'], pkname, pkname)
143
-        #Add the column
144
-        self._add_column(tname, emfield.name, emfield.fieldtype_instance(), relation=True)
145
-        #Update table triggers
146
-        self._generate_triggers(tname, self._r2type2cols(edmod, r2tf))
147
-
148
-    ## @brief Delete a rel2type attribute
149
-    #
150
-    # Delete a rel2type attribute
151
-    # @note this method handles the table deletion
152
-    # @param edmod Model : EditorialModel.model.Model instance
153
-    # @param rfuid int : Relationnal field uid
154
-    def del_relationnal_field(self, edmod, rfuid):
155
-        emfield = edmod.component(rfuid)
156
-        if not isinstance(emfield, EditorialModel.fields.EmField):
157
-            raise ValueError("The given uid is not an EmField uid")
158
-
159
-        r2tf = edmod.component(emfield.rel_field_id)
160
-        tname = self._r2t2table_name(edmod, r2tf)
161
-
162
-        if len(self._r2type2cols(edmod, r2tf)) == 1:
163
-            #The table can be deleted (no more attribute for this rel2type)
164
-            self._query("""DROP TABLE {table_name}""".format(table_name=tname))
165
-        else:
166
-            self._del_column(tname, emfield.name)
167
-            #Update table triggers
168
-            self._generate_triggers(tname, self._r2type2cols(edmod, r2tf))
169
-
170
-    ## @brief Given an EmField uid add a column to the corresponding table
171
-    # @param edmod Model : A Model instance
172
-    # @param uid int : An EmField uid
173
-    def add_col_from_emfield(self, edmod, uid):
174
-        emfield = edmod.component(uid)
175
-        if not isinstance(emfield, EditorialModel.fields.EmField):
176
-            raise ValueError("The given uid is not an EmField uid")
177
-
178
-        emclass = emfield.em_class
179
-        tname = utils.object_table_name(emclass.name)
180
-        self._add_column(tname, emfield.name, emfield.fieldtype_instance())
181
-        # Refresh the table triggers
182
-        cols_l = self._class2cols(emclass)
183
-        self._generate_triggers(tname, cols_l)
184
-
185
-    ## @brief Given a class uid create the coressponding table
186
-    # @param edmod Model : A Model instance
187
-    # @param uid int : An EmField uid
188
-    # @param engine str : Db engine (should be "InnoDB")
189
-    def create_emclass_table(self, edmod, uid, engine):
190
-        emclass = edmod.component(uid)
191
-        if not isinstance(emclass, EditorialModel.classes.EmClass):
192
-            raise ValueError("The given uid is not an EmClass uid")
193
-        pkname, pktype = self._object_pk
194
-        table_name = utils.object_table_name(emclass.name)
195
-        self._create_table(
196
-                            table_name,
197
-                            pkname,
198
-                            pktype,
199
-                            engine=engine,
200
-                            noauto_inc = True
201
-        )
202
-        if self.foreign_keys:
203
-            self._add_fk(table_name, utils.common_tables['object'], pkname, pkname)
204
-
205
-    ## @brief Given an EmClass uid delete the corresponding table
206
-    # @param edmod Model : A Model instance
207
-    # @param uid int : An EmField uid
208
-    def delete_emclass_table(self, edmod, uid):
209
-        emclass = edmod.component(uid)
210
-        if not isinstance(emclass, EditorialModel.classes.EmClass):
211
-            raise ValueError("The give uid is not an EmClass uid")
212
-        tname = utils.object_table_name(emclass.name)
213
-        # Delete the table triggers to prevent errors
214
-        self._generate_triggers(tname, dict())
215
-
216
-        tname = utils.escape_idname(tname)
217
-
218
-        self._query("""DROP TABLE {table_name};""".format(table_name=tname))
219
-
220
-    ## @brief Given an EmField delete the corresponding column
221
-    # @param edmod Model : an @ref EditorialModel.model.Model instance
222
-    # @param uid int : an EmField uid
223
-    def delete_col_from_emfield(self, edmod, uid):
224
-        emfield = edmod.component(uid)
225
-        if not isinstance(emfield, EditorialModel.fields.EmField):
226
-            raise ValueError("The given uid is not an EmField uid")
227
-
228
-        if isinstance(emfield.fieldtype_instance(), MultiValueFieldType):
229
-            return self._del_column_multivalue(emfield)
230
-
231
-        emclass = emfield.em_class
232
-        tname = utils.object_table_name(emclass.name)
233
-        # Delete the table triggers to prevent errors
234
-        self._generate_triggers(tname, dict())
235
-
236
-        self._del_column(tname, emfield.name)
237
-        # Refresh the table triggers
238
-        cols_l = self._class2cols(emclass)
239
-        self._generate_triggers(tname, cols_l)
240
-
241
-    ## @brief Delete a column from a table
242
-    # @param tname str : The table name
243
-    # @param fname str : The column name
244
-    def _del_column(self, tname, fname):
245
-        tname = utils.escape_idname(tname)
246
-        fname = utils.escape_idname(fname)
247
-
248
-        self._query("""ALTER TABLE {table_name} DROP COLUMN {col_name};""".format(table_name=tname, col_name=fname))
249
-
250
-    ## @brief Construct a table name given a rela2type EmField instance
251
-    # @param edmod Model : A Model instance
252
-    # @param emfield EmField : An EmField instance
253
-    # @return a table name
254
-    def _r2t2table_name(self, edmod, emfield):
255
-        emclass = emfield.em_class
256
-        emtype = edmod.component(emfield.rel_to_type_id)
257
-        return utils.r2t_table_name(emclass.name, emtype.name)
258
-
259
-    ## @brief Generate a columns_fieldtype dict given a rel2type EmField
260
-    # @param edmod Model : an @ref EditorialModel.model.Model instance
261
-    # @param emfield EmField : and @ref EditorialModel.fields.EmField instance
262
-    def _r2type2cols(self, edmod, emfield):
263
-        return {f.name: f.fieldtype_instance() for f in edmod.components('EmField') if f.rel_field_id == emfield.uid}
264
-
265
-    ## @brief Generate a columns_fieldtype dict given an EmClass
266
-    # @param emclass EmClass : An EmClass instance
267
-    # @return A dict with column name as key and EmFieldType instance as value
268
-    def _class2cols(self, emclass):
269
-        if not isinstance(emclass, EditorialModel.classes.EmClass):
270
-            raise ValueError("The given uid is not an EmClass uid")
271
-        return {f.name: f.fieldtype_instance() for f in emclass.fields() if f.name not in EditorialModel.classtypes.common_fields.keys()}
272
-
273
-    ## @brief Create object and relations tables
274
-    # @param drop_if_exist bool : If true drop tables if exists
275
-    def _create_default_tables(self, drop_if_exist=False):
276
-        if_exists = 'drop' if drop_if_exist else 'nothing'
277
-        #Object table
278
-        tname = utils.common_tables['object']
279
-        pk_name, pk_ftype = self._object_pk
280
-        self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
281
-        #Adding columns
282
-        cols = {fname: self._common_field_to_ftype(fname) for fname in EditorialModel.classtypes.common_fields}
283
-        for fname, ftype in cols.items():
284
-            if fname != pk_name:
285
-                self._add_column(tname, fname, ftype, relation=False)
286
-        #Creating triggers
287
-        self._generate_triggers(tname, cols)
288
-        object_tname = tname
289
-
290
-        #Relation table
291
-        tname = utils.common_tables['relation']
292
-        pk_name, pk_ftype = self._relation_pk
293
-        self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
294
-        #Adding columns
295
-        for fname, ftype in self._relation_cols.items():
296
-            self._add_column(tname, fname, ftype, relation=True)
297
-        #Creating triggers
298
-        self._generate_triggers(tname, self._relation_cols)
299
-
300
-        # Creating foreign keys between relation and object table
301
-        sup_cname, sub_cname = self.get_sup_and_sub_cols()
302
-
303
-    ## @brief Returns the fieldname for superior and subordinate in relation table
304
-    # @return a tuple (superior_name, subordinate_name)
305
-    @classmethod
306
-    def get_sup_and_sub_cols(cls):
307
-        sup = None
308
-        sub = None
309
-        for fname, finfo in EditorialModel.classtypes.relations_common_fields.items():
310
-            if finfo['fieldtype'] == 'leo':
311
-                if finfo['superior']:
312
-                    sup = fname
313
-                else:
314
-                    sub = fname
315
-        return utils.column_name(sup), utils.column_name(sub)
316
-
317
-    ## @brief Create a table with primary key
318
-    # @param table_name str : table name
319
-    # @param pk_name str | tuple : pk column name (give tuple for multi pk)
320
-    # @param pk_ftype fieldtype | tuple : pk fieldtype (give a tuple for multi pk)
321
-    # @param engine str : The engine to use with this table
322
-    # @param charset str : The charset of this table
323
-    # @param if_exists str : takes values in ['nothing', 'drop']
324
-    # @param noauto_inc bool : if True forbids autoincrement on PK
325
-    def _create_table(self, table_name, pk_name, pk_ftype, engine, charset='utf8', if_exists='nothing', noauto_inc = False):
326
-        #Escaped table name
327
-        etname = utils.escape_idname(table_name)
328
-        if not isinstance(pk_name, tuple):
329
-            pk_name = tuple([pk_name])
330
-            pk_ftype = tuple([pk_ftype])
331
-
332
-        if len(pk_name) != len(pk_ftype):
333
-            raise ValueError("You have to give as many pk_name as pk_ftype")
334
-        
335
-        pk_instr_cols = ''
336
-        pk_format = "{pk_name} {pk_type} {pk_specs},\n"
337
-        for i in range(len(pk_name)):
338
-            instr_type, pk_type, pk_specs = fieldtypes_utils.fieldtype_db_init(pk_ftype[i], noauto_inc)
339
-            if instr_type != 'column':
340
-                raise ValueError("Migration handler doesn't support MultiValueFieldType as primary keys")
341
-            pk_instr_cols += pk_format.format(
342
-                                                pk_name = utils.escape_idname(pk_name[i]),
343
-                                                pk_type = pk_type,
344
-                                                pk_specs = pk_specs
345
-                                            )
346
-        pk_instr_cols += "PRIMARY KEY("+(','.join([utils.escape_idname(pkn) for pkn in pk_name]))+')'
347
-
348
-        if if_exists == 'drop':
349
-            self._query("""DROP TABLE IF EXISTS {table_name};""".format(table_name=etname))
350
-
351
-        qres = """CREATE TABLE IF NOT EXISTS {table_name} (
352
-{pk_cols}
353
-) ENGINE={engine} DEFAULT CHARSET={charset};""".format(
354
-                                                        table_name = table_name,
355
-                                                        pk_cols = pk_instr_cols,
356
-                                                        engine = engine,
357
-                                                        charset = charset
358
-        )
359
-        self._query(qres)
360
-
361
-    ## @brief Add a column to a table
362
-    # @param table_name str : The table name
363
-    # @param col_name str : The columns name
364
-    # @param col_fieldtype EmFieldype the fieldtype
365
-    # @param relation bool | None : a flag to indicate if we add a column in a table linked with an bject or with a relation (used only when the column is MultiValueFieldType )
366
-    # @param drop_if_exists bool : if True delete the column before re-adding it
367
-    # @return True if the column was added else return False
368
-    def _add_column(self, table_name, col_name, col_fieldtype, drop_if_exists=False, relation=False):
369
-        instr, col_type, col_specs = fieldtypes_utils.fieldtype_db_init(col_fieldtype)
370
-
371
-        if instr == 'table':
372
-            # multivalue field. We are not going to add a column in this table
373
-            # but in corresponding multivalue table
374
-            self._add_column_multivalue(
375
-                                            ref_table_name = table_name,
376
-                                            key_infos = col_type,
377
-                                            column_infos = (col_name, col_specs),
378
-                                            relation = relation
379
-                                        )
380
-            return True
381
-
382
-        col_name = utils.column_name(col_name)
383
-
384
-        add_col = """ALTER TABLE {table_name}
385
-ADD COLUMN {col_name} {col_type} {col_specs};"""
386
-
387
-        etname = utils.escape_idname(table_name)
388
-        ecname = utils.escape_idname(col_name)
389
-
390
-        if instr is None:
391
-            return True
392
-        if instr != "column":
393
-            raise RuntimeError("Bad implementation")
394
-
395
-        add_col = add_col.format(
396
-            table_name=etname,
397
-            col_name=ecname,
398
-            col_type=col_type,
399
-            col_specs=col_specs,
400
-        )
401
-        try:
402
-            self._query(add_col)
403
-        except self._dbmodule.err.InternalError:
404
-            if drop_if_exists:
405
-                self._del_column(table_name, col_name)
406
-                self._add_column(table_name, col_name, col_fieldtype, drop_if_exists)
407
-            else:
408
-                #LOG
409
-                print("Aborded, column `%s` exists" % col_name)
410
-                return False
411
-
412
-        if isinstance(col_fieldtype, EditorialModel.fieldtypes.generic.ReferenceFieldType):
413
-            # We have to create a FK !
414
-            if col_fieldtype.reference == 'object':
415
-                dst_table_name = utils.common_tables['object']
416
-                dst_col_name, _ = self._object_pk
417
-            elif col_fieldtypes.reference == 'relation':
418
-                dst_table_name = utils.common_tables['relation']
419
-                dst_col_name, _ = self._relation_pk
420
-            
421
-            fk_name = 'fk_%s-%s_%s-%s' % (
422
-                                            table_name,
423
-                                            col_name,
424
-                                            dst_table_name,
425
-                                            dst_col_name,
426
-                                        )
427
-                
428
-            self._add_fk(
429
-                            src_table_name = table_name,
430
-                            dst_table_name = dst_table_name,
431
-                            src_col_name = col_name,
432
-                            dst_col_name = dst_col_name,
433
-                            fk_name = fk_name
434
-                        )
435
-
436
-        return True
437
-
438
-    ## @brief Add a column to a multivalue table
439
-    #
440
-    # Add a column (and create a table if not existing) for storing multivalue
441
-    # datas. (typically i18n)
442
-    # @param ref_table_name str : Referenced table name
443
-    # @param key_infos tuple : tuple(key_name, key_fieldtype)
444
-    # @param column_infos tuple : tuple(col_name, col_fieldtype)
445
-    # @param relation bool : pass True if concern a LeRelation or False of concern a LeObject
446
-    def _add_column_multivalue(self, ref_table_name, key_infos, column_infos, relation):
447
-        key_name, key_ftype = key_infos
448
-        col_name, col_ftype = column_infos
449
-        table_name = utils.multivalue_table_name(ref_table_name, key_name)
450
-        if relation:
451
-            pk_infos = self._relation_pk
452
-        else:
453
-            pk_infos = self._object_pk
454
-        # table creation
455
-        self._create_table(
456
-                            table_name = table_name,
457
-                            pk_name = (key_name, pk_infos[0]),
458
-                            pk_ftype = (key_ftype, pk_infos[1]),
459
-                            engine = self.db_engine,
460
-                            if_exists = 'nothing',
461
-                            noauto_inc = True
462
-        )
463
-        # with FK
464
-        self._add_fk(table_name, ref_table_name, pk_infos[0], pk_infos[0])
465
-        # adding the column
466
-        self._add_column(table_name, col_name, col_ftype)
467
-
468
-    ## @brief Delete a multivalue column
469
-    # @param emfield EmField : EmField instance
470
-    # @note untested
471
-    def _del_column_multivalue(self, emfield):
472
-        ftype = emfield.fieldtype_instance()
473
-        if not isinstance(ftype, MultiValueFieldType):
474
-            raise ValueError("Except an emfield with multivalue fieldtype")
475
-        tname = utils.object_table_name(emfield.em_class.name)
476
-        tname = utils.multivalue_table_name(tname, ftype.keyname)
477
-        self._del_column(tname, emfield.name)
478
-        if len([ f for f in emfield.em_class.fields() if isinstance(f.fieldtype_instance(), MultiValueFieldType)]) == 0:
479
-            try:
480
-                self._query("DROP TABLE %s;" % utils.escape_idname(tname))
481
-            except self._dbmodule.err.InternalError as expt:
482
-                print(expt)
483
-
484
-
485
-    ## @brief Add a foreign key
486
-    # @param src_table_name str : The name of the table where we will add the FK
487
-    # @param dst_table_name str : The name of the table the FK will point on
488
-    # @param src_col_name str : The name of the concerned column in the src_table
489
-    # @param dst_col_name str : The name of the concerned column in the dst_table
490
-    # @param fk_name str|None : The foreign key name, if None the name will be generated (not a good idea)
491
-    def _add_fk(self, src_table_name, dst_table_name, src_col_name, dst_col_name, fk_name=None):
492
-        stname = utils.escape_idname(src_table_name)
493
-        dtname = utils.escape_idname(dst_table_name)
494
-        scname = utils.escape_idname(src_col_name)
495
-        dcname = utils.escape_idname(dst_col_name)
496
-
497
-        if fk_name is None:
498
-            fk_name = utils.get_fk_name(src_table_name, dst_table_name)
499
-
500
-        self._del_fk(src_table_name, dst_table_name, fk_name)
501
-
502
-        self._query("""ALTER TABLE {src_table}
503
-ADD CONSTRAINT {fk_name}
504
-FOREIGN KEY ({src_col}) references {dst_table}({dst_col})
505
-ON DELETE CASCADE
506
-ON UPDATE CASCADE;""".format(
507
-    fk_name=utils.escape_idname(fk_name),
508
-    src_table=stname,
509
-    src_col=scname,
510
-    dst_table=dtname,
511
-    dst_col=dcname
512
-))
513
-
514
-    ## @brief Given a source and a destination table, delete the corresponding FK
515
-    # @param src_table_name str : The name of the table where the FK is
516
-    # @param dst_table_name str : The name of the table the FK point on
517
-    # @param fk_name str|None : the foreign key name, if None try to guess it
518
-    # @warning fails silently
519
-    def _del_fk(self, src_table_name, dst_table_name, fk_name=None):
520
-        if fk_name is None:
521
-            fk_name = utils.get_fk_name(src_table_name, dst_table_name)
522
-        fk_name = utils.escape_idname(fk_name)
523
-        try:
524
-            self._query("""ALTER TABLE {src_table}
525
-DROP FOREIGN KEY {fk_name}""".format(
526
-    src_table=utils.escape_idname(src_table_name),
527
-    fk_name=fk_name
528
-))
529
-        except self._dbmodule.err.InternalError:
530
-            # If the FK don't exists we do not care
531
-            pass
532
-
533
-    ## @brief Generate triggers given a table_name and its columns fieldtypes
534
-    # @param table_name str : Table name
535
-    # @param cols_ftype dict : with col name as key and column fieldtype as value
536
-    def _generate_triggers(self, table_name, cols_ftype):
537
-        colval_l_upd = dict()  # param for update trigger
538
-        colval_l_ins = dict()  # param for insert trigger
539
-
540
-        for cname, cftype in cols_ftype.items():
541
-            if isinstance(cftype, EditorialModel.fieldtypes.datetime.EmFieldType):
542
-                if cftype.now_on_update:
543
-                    colval_l_upd[cname] = 'NOW()'
544
-                if cftype.now_on_create:
545
-                    colval_l_ins[cname] = 'NOW()'
546
-
547
-        self._table_trigger(table_name, 'UPDATE', colval_l_upd)
548
-        self._table_trigger(table_name, 'INSERT', colval_l_ins)
549
-
550
-    ## @brief Create trigger for a table
551
-    #
552
-    # Primarly designed to create trigger for DATETIME types
553
-    # The method generates triggers of the form
554
-    # <pre>
555
-    # CREATE TRIGGER BEFORE &lt;moment&gt; ON &lt;table_name&gt;
556
-    # FOR EACH ROW SET lt;for colname, colval in &lt;cols_val&gt;
557
-    # NEW.lt;colname> = lt;colval&gt;,
558
-    # lt;endfor&gt;;
559
-    # </pre>
560
-    # @param table_name str : The table name
561
-    # @param moment str : can be 'update' or 'insert'
562
-    # @param cols_val dict : Dict with column name as key and column value as value
563
-    def _table_trigger(self, table_name, moment, cols_val):
564
-        trigger_name = utils.escape_idname("%s_%s_trig" % (table_name, moment))
565
-        #Try to delete the trigger
566
-        drop_trig = """DROP TRIGGER IF EXISTS {trigger_name};""".format(trigger_name=trigger_name)
567
-        self._query(drop_trig)
568
-
569
-        col_val_l = ', '.join(["NEW.%s = %s" % (utils.escape_idname(utils.column_name(cname)), cval)for cname, cval in cols_val.items()])
570
-        #Create a trigger if needed
571
-        if len(col_val_l) > 0:
572
-            trig_q = """CREATE TRIGGER {trigger_name} BEFORE {moment} ON {table_name}
573
-FOR EACH ROW SET {col_val_list};""".format(
574
-    trigger_name=trigger_name,
575
-    table_name=utils.escape_idname(table_name),
576
-    moment=moment, col_val_list=col_val_l
577
-)
578
-            self._query(trig_q)
579
-
580
-    ## @brief Delete all table created by the MH
581
-    # @param model Model : the Editorial model
582
-    def __purge_db(self, model):
583
-        for uid in [
584
-                    field
585
-                    for field in model.components('EmField')
586
-                    if isinstance(field.fieldtype_instance(), MultiValueFieldType)
587
-        ]:
588
-            self._del_column_multivalue(field)
589
-
590
-        for uid in [c.uid for c in model.components('EmClass')]:
591
-            try:
592
-                self.delete_emclass_table(model, uid)
593
-            except self._dbmodule.err.InternalError as expt:
594
-                print(expt)
595
-
596
-        for tname in [utils.r2t_table_name(f.em_class.name, model.component(f.rel_to_type_id).name) for f in model.components('EmField') if f.fieldtype == 'rel2type']:
597
-            try:
598
-                self._query("DROP TABLE %s;" % tname)
599
-            except self._dbmodule.err.InternalError as expt:
600
-                print(expt)
601
-
602
-        for tname in [utils.common_tables['relation'], utils.common_tables['relation']]:
603
-            try:
604
-                self._query("DROP TABLE %s;" % tname)
605
-            except self._dbmodule.err.InternalError as expt:
606
-                print(expt)
607
-
608
-    ## @brief Return primary key name & fieldtype for relation or object
609
-    @classmethod
610
-    def extract_pk(cls, common_fields):
611
-        for fname, finfo in common_fields.items():
612
-            if finfo['fieldtype'] == 'pk':
613
-                fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name('pk')
614
-                finfo_cp = copy.copy(finfo)
615
-                del(finfo_cp['fieldtype'])
616
-                return (utils.column_name(fname), fto(**finfo_cp))
617
-        raise RuntimeError("No primary key found in common fields : %s" % common_fields)
618
-
619
-    ## @brief Exec a query
620
-    # @param query str : SQL query
621
-    def _query(self, query):
622
-        if self.debug:
623
-            print(query + "\n")
624
-        if not self.dryrun:
625
-            with self.db_conn.cursor() as cur:
626
-                cur.execute(query)
627
-        self.db_conn.commit()  # autocommit
628
-
629
-    ## @brief Given a common field name return an EmFieldType instance
630
-    # @param cls
631
-    # @param cname str : Common field name
632
-    # @return An EmFieldType instance
633
-    @classmethod
634
-    def _common_field_to_ftype(cls, cname):
635
-        fta = copy.copy(EditorialModel.classtypes.common_fields[cname])
636
-        fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fta['fieldtype'])
637
-        del fta['fieldtype']
638
-        return fto(**fta)
639
-
640
-    ## @brief Returns a tuple (pkname, pk_ftype)
641
-    @property
642
-    def _object_pk(self):
643
-        return self.extract_pk(EditorialModel.classtypes.common_fields)
644
-
645
-    ## @brief Returns a tuple (rel_pkname, rel_ftype)
646
-    @property
647
-    def _relation_pk(self):
648
-        return self.extract_pk(EditorialModel.classtypes.relations_common_fields)
649
-
650
-    ## @brief Returns a dict { colname:fieldtype } of relation table columns
651
-    @property
652
-    def _relation_cols(self):
653
-        res = dict()
654
-        for fieldname, fieldinfo in EditorialModel.classtypes.relations_common_fields.items():
655
-            finfo = copy.copy(fieldinfo)
656
-            fieldtype_name = finfo['fieldtype']
657
-            del(finfo['fieldtype'])
658
-            res[fieldname] = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fieldtype_name)(**finfo)
659
-        return res

+ 0
- 156
DataSource/MySQL/migrationhandler__future__.py View File

@@ -1,156 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.migrationhandler.sql
4
-# @brief A dummy migration handler
5
-#
6
-# According to it every modifications are possible
7
-#
8
-
9
-import EditorialModel
10
-from DataSource.dummy.migrationhandler import DummyMigrationHandler
11
-from EditorialModel.fieldtypes.generic import GenericFieldType
12
-from EditorialModel.model import Model
13
-from mosql.db import Database
14
-from Lodel.utils.mosql import create, alter_add
15
-
16
-
17
-## Manage Model changes
18
-class SQLMigrationHandler(DummyMigrationHandler):
19
-
20
-    fieldtype_to_sql = {
21
-        'char': "CHAR(255)",
22
-        'integer': 'INT'
23
-    }
24
-
25
-    def __init__(self, module=None, *conn_args, **conn_kargs):
26
-        super(SQLMigrationHandler, self).__init__(False)
27
-
28
-        self.db = Database(module, *conn_args, **conn_kargs)
29
-        self._pk_column = (EditorialModel.classtypes.pk_name(), 'INTEGER PRIMARY KEY AUTO_INCREMENT')
30
-        self._main_table_name = 'object'
31
-        self._relation_table_name = 'relation'
32
-
33
-        self._install_tables()
34
-
35
-    ## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
36
-    # @note The states ( initial_state and new_state ) contains only fields that changes
37
-    # @param model model : The EditorialModel.model object to provide the global context
38
-    # @param uid int : The uid of the change EmComponent
39
-    # @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
40
-    # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
41
-    # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
42
-    def register_change(self, model, uid, initial_state, new_state):
43
-        # find type of component change
44
-        if initial_state is None:
45
-            state_change = 'new'
46
-        elif new_state is None:
47
-            state_change = 'del'
48
-        else:
49
-            state_change = 'upgrade'
50
-
51
-        # call method to handle the database change
52
-        component_name = Model.name_from_emclass(type(model.component(uid)))
53
-        handler_func = component_name.lower() + '_' + state_change
54
-        if hasattr(self, handler_func):
55
-            getattr(self, handler_func)(model, uid, initial_state, new_state)
56
-
57
-    # New Class, a table must be created
58
-    def emclass_new(self, model, uid, initial_state, new_state):
59
-        class_table_name = self._class_table_name(new_state['name'])
60
-        self._query_bd(
61
-            create(table=class_table_name, column=[self._pk_column])
62
-        )
63
-
64
-    # New Field, must create a column in Class table or in Class_Type relational attribute table
65
-    # @todo common fields creation does not allow to add new common fields. It should
66
-    def emfield_new(self, model, uid, initial_state, new_state):
67
-
68
-        # field is of type rel2type, create the relational class_type table and return
69
-        if new_state['fieldtype'] == 'rel2type':
70
-            # find relational_type name, and class name of the field
71
-            class_name = self._class_table_name_from_field(model, new_state)
72
-            type_name = model.component(new_state['rel_to_type_id']).name
73
-            table_name = self._relational_table_name(class_name, type_name)
74
-            self._query_bd(
75
-                create(table=table_name, column=[self._pk_column]),
76
-            )
77
-            return
78
-
79
-        # Column creation
80
-        #
81
-        # field is internal, create a column in the objects table
82
-        if new_state['internal']:
83
-            if new_state['fieldtype'] == 'pk':  # this column has already beeen created by self._install_tables()
84
-                return
85
-            if new_state['name'] in EditorialModel.classtypes.common_fields:  # this column has already beeen created by self._install_tables()
86
-                return
87
-
88
-        # field is relational (rel_field_id), create a column in the class_type table
89
-        elif new_state['rel_field_id']:
90
-            class_name = self._class_table_name_from_field(model, new_state)
91
-            rel_type_id = model.component(new_state['rel_field_id']).rel_to_type_id
92
-            type_name = model.component(rel_type_id).name
93
-            table_name = self._relational_table_name(class_name, type_name)
94
-
95
-        # else create a column in the class table
96
-        else:
97
-            table_name = self._class_table_name_from_field(model, new_state)
98
-
99
-        field_definition = self._fieldtype_definition(new_state['fieldtype'], new_state)
100
-        self._query_bd(
101
-            alter_add(table=table_name, column=[(new_state['name'],field_definition)])
102
-        )
103
-
104
-    ## convert fieldtype name to SQL definition
105
-    def _fieldtype_definition(self, fieldtype, options):
106
-        basic_type = GenericFieldType.from_name(fieldtype).ftype
107
-        if basic_type == 'int':
108
-            return 'INT'
109
-        elif basic_type == 'char':
110
-            max_length = options['max_length'] if 'max_length' in options else 255
111
-            return 'CHAR(%s)' % max_length
112
-        elif basic_type == 'text':
113
-            return 'TEXT'
114
-        elif basic_type == 'bool':
115
-            return 'BOOLEAN'
116
-        elif basic_type == 'datetime':
117
-            definition = 'DATETIME'
118
-            if 'now_on_create' in options and options['now_on_create']:
119
-                definition += ' DEFAULT CURRENT_TIMESTAMP'
120
-            #if 'now_on_update' in options and options['now_on_update']:
121
-                #definition += ' ON UPDATE CURRENT_TIMESTAMP'
122
-            return definition
123
-
124
-        raise EditorialModel.exceptions.MigrationHandlerChangeError("Basic type '%s' of fieldtype '%s' is not compatible with SQL migration Handler" % basic_type, fieldtype)
125
-
126
-    ## Test if internal tables must be created, create it if it must
127
-    def _install_tables(self):
128
-        # create common fields definition
129
-        common_fields = [self._pk_column]
130
-        for name, options in EditorialModel.classtypes.common_fields.items():
131
-            if options['fieldtype'] != 'pk':
132
-                common_fields.append((name, self._fieldtype_definition(options['fieldtype'], options)))
133
-
134
-        # create common tables
135
-        self._query_bd(
136
-            create(table=self._main_table_name, column=common_fields),
137
-            create(table=self._relation_table_name, column=[('relation_id','INTEGER PRIMARY KEY AUTOINCREMENT'), ('superior_id','INT'), ('subdordinate_id','INT'), ('nature','CHAR(255)'), ('depth','INT'), ('rank','INT')])
138
-        )
139
-
140
-    def _query_bd(self, *queries):
141
-        with self.db as cur:
142
-            for query in queries:
143
-                print(query)
144
-                cur.execute(query)
145
-
146
-    def _class_table_name(self, class_name):
147
-        return 'class_' + class_name
148
-
149
-    def _relational_table_name(self, class_name, type_name):
150
-        return 'r2t_' + class_name + '_' + type_name
151
-
152
-    def _class_table_name_from_field(self, model, field):
153
-        class_id = field['class_id']
154
-        class_name = model.component(class_id).name
155
-        class_table_name = self._class_table_name(class_name)
156
-        return class_table_name

+ 0
- 0
DataSource/MySQL/test/__init__.py View File


+ 0
- 279
DataSource/MySQL/test/test_datasource.py View File

@@ -1,279 +0,0 @@
1
-"""
2
-    Tests for MySQL Datasource
3
-"""
4
-
5
-import random
6
-import copy
7
-
8
-import unittest
9
-import unittest.mock as mock
10
-from unittest import TestCase
11
-from unittest.mock import patch, Mock, call
12
-
13
-import mosql
14
-import mosql.db
15
-from mosql.util import Query as mosql_Query
16
-import pymysql
17
-
18
-import leapi.test.utils #Code generation functions
19
-
20
-from Lodel.settings import Settings
21
-import DataSource.MySQL
22
-from DataSource.MySQL.leapidatasource import LeDataSourceSQL as DataSource
23
-import DataSource.MySQL.utils as db_utils
24
-from EditorialModel.classtypes import common_fields, relations_common_fields
25
-from EditorialModel.fieldtypes.generic import MultiValueFieldType
26
-
27
-class DataSourceTestCase(TestCase):
28
-    #Dynamic code generation & import
29
-    @classmethod
30
-    def setUpClass(cls):
31
-        """ Write the generated code in a temporary directory and import it """
32
-        cls.tmpdir = leapi.test.utils.tmp_load_factory_code()
33
-    @classmethod
34
-    def tearDownClass(cls):
35
-        """ Remove the temporary directory created at class setup """
36
-        leapi.test.utils.cleanup(cls.tmpdir)
37
-
38
-    def test_init(self):
39
-        """ Test __init__ for datasource """
40
-        with patch.object(mosql.db.Database, '__init__', return_value=None) as mock_db:
41
-            #Test __init__ without arguments
42
-            DataSource()
43
-            conn_args = Settings.get('datasource')['default']
44
-            db_module = conn_args['module']
45
-            del(conn_args['module'])
46
-            mock_db.assert_called_once_with(db_module, **conn_args)
47
-
48
-            mock_db.reset_mock()
49
-            #test with arguments
50
-            conn_args = { 'hello': 'world', 'answer': 42 }
51
-            DataSource(mosql, conn_args)
52
-            mock_db.assert_called_once_with(mosql, **conn_args)
53
-
54
-            mock_db.reset_mock()
55
-
56
-            DataSource(conn_args = conn_args)
57
-            mock_db.assert_called_once_with(pymysql, **conn_args)
58
-    
59
-    def test_insert_leobject(self):
60
-        """ Test the insert method on LeObjects """
61
-        from dyncode import Article, Personne, Rubrique
62
-
63
-        for letype in [Article, Personne, Rubrique]:
64
-            lodel_id = random.randint(0,4096) # Choose a random lodel_id
65
-            
66
-            # Mock the cursor to superseed the lastrowid property
67
-            cursor_mock = Mock()
68
-            cursor_mock.lastrowid = lodel_id
69
-            # Mock the cursor() call on connection
70
-            cursor_call_mock = Mock(return_value=cursor_mock)
71
-            # Mock the connection to set the cursor() call mock
72
-            connection_mock = Mock()
73
-            connection_mock.cursor = cursor_call_mock
74
-            # Mock the connect() call on dbmodule to bla bla
75
-            connect_call_mock = Mock(return_value=connection_mock)
76
-            # Mock the db module to set the connection mock (on connect() call)
77
-            dbmodule_mock = Mock()
78
-            dbmodule_mock.connect = connect_call_mock
79
-
80
-            datasource = DataSource(module=dbmodule_mock, conn_args = {})
81
-
82
-            sql_query = 'SELECT wow FROM splendid_table'
83
-
84
-            class_table_datas = {
85
-                fname: random.randint(42,1337)
86
-                for fname in letype.fieldlist(complete = False)
87
-                if not isinstance(letype.fieldtypes()[fname], MultiValueFieldType)
88
-            }
89
-            i18n = dict()
90
-            for fname in letype.fieldlist():
91
-                if isinstance(letype.fieldtypes()[fname], MultiValueFieldType):
92
-                    i18n[fname] = {'fre': 'bonjour', 'eng': 'hello'}
93
-                    class_table_datas[fname] = i18n[fname]
94
-            object_table_datas = { 'string': random.randint(-42,42) }
95
-            
96
-            # build the insert datas argument
97
-            insert_datas = copy.copy(object_table_datas)
98
-            insert_datas.update(class_table_datas)
99
-
100
-            with patch.object(mosql_Query, '__call__', return_value=sql_query) as mock_insert:
101
-                with patch.object(db_utils, 'query', return_value=cursor_mock) as mock_utils_query:
102
-                    # call the insert() method
103
-                    datasource.insert(letype, **insert_datas)
104
-
105
-                    # construct expected datas for object table insert
106
-                    object_table_datas['class_id'] = letype._class_id
107
-                    object_table_datas['type_id'] = letype._type_id
108
-                    # construct expected datas used in class table insert
109
-                    class_table_datas['lodel_id'] = lodel_id
110
-                    for fname in i18n:
111
-                        del(class_table_datas[fname])
112
-
113
-                    expected_calls = [
114
-                        # insert in object table call
115
-                        call(
116
-                            db_utils.common_tables['object'],
117
-                            object_table_datas
118
-                        ),
119
-                        # insert in class specific table call
120
-                        call(
121
-                            db_utils.object_table_name(letype._leclass.__name__),
122
-                            class_table_datas
123
-                        ),
124
-                    ]
125
-                    i18n_calls = dict()
126
-                    for fname, fval in i18n.items():
127
-                        keyname = letype.fieldtypes()[fname].keyname
128
-                        if not keyname in i18n_calls:
129
-                            i18n_calls[keyname] = dict()
130
-                        for lang, val in fval.items():
131
-                            if not lang in i18n_calls[keyname]:
132
-                                i18n_calls[keyname][lang] = dict()
133
-                            i18n_calls[keyname][lang][fname] = val
134
-                    real_i18n_calls = []
135
-                    for keyname, kval in i18n_calls.items():
136
-                        table_name = db_utils.multivalue_table_name(
137
-                            db_utils.object_table_name(letype._leclass.__name__),
138
-                            keyname
139
-                        )
140
-                        for lang, value in kval.items():
141
-                            value[keyname] = lang
142
-                            value[letype.uidname()] = lodel_id
143
-                            expected_calls.append(call(
144
-                                    table_name,
145
-                                    value
146
-                                )
147
-                            )
148
-                    expected_utils_query_calls = [
149
-                        call(datasource.connection, sql_query),
150
-                        call(datasource.connection, sql_query),
151
-                    ]
152
-
153
-                    mock_insert.assert_has_calls(expected_calls, any_order = False)
154
-                    mock_utils_query.assert_has_calls(expected_utils_query_calls)
155
-
156
-    def test_select_leobject(self):
157
-        """ Test select method on leobject without relational filters """
158
-        from dyncode import LeObject, Article, Textes
159
-        
160
-        # Utils var and stuff to make tests queries write easier
161
-        
162
-        # lodel_id field name
163
-        lodel_id = LeObject.uidname()
164
-        # pre-fetch tables name to make the tests_queries
165
-        table_names = {
166
-            LeObject: db_utils.common_tables['object'],
167
-            Article: db_utils.object_table_name(Article._leclass.__name__),
168
-            Textes: db_utils.object_table_name(Textes.__name__),
169
-        }
170
-        # lodel_id name to be use in joins (leobject side)
171
-        join_lodel_id = db_utils.column_prefix(table_names[LeObject], lodel_id)
172
-        # lodel_id name to be use in joins (leclass side)
173
-        def cls_lodel_id(cls): return db_utils.column_prefix(table_names[cls], lodel_id)
174
-
175
-        tests_queries = [
176
-            # call leobject.select(fields = ['lodel_id', 'string'], filters = [])
177
-            (
178
-                #field_list details
179
-                {
180
-                    'leobject': [lodel_id, 'string'],
181
-                    'leclass': [],
182
-                },
183
-                #Select args
184
-                {
185
-                    'target_cls': LeObject,
186
-                    'filters': [],
187
-                    'rel_filters': [],
188
-                },
189
-                # expt args
190
-                {
191
-                    'where':{},
192
-                    'joins':None, #Expected call on Query.__call__ (called when we call left_join)
193
-                }
194
-            ),
195
-            # call leobject.select(fields = ['lodel_id', 'string'], filters = ['lodel_id = 42', 'string = "hello"', 'rank > 1'])
196
-            (
197
-                {
198
-                    'leobject': [lodel_id, 'string'],
199
-                    'leclass': [],
200
-                },
201
-                {
202
-                    'target_cls': LeObject,
203
-                    'filters': [
204
-                        (lodel_id,'=', 42),
205
-                        ('string', '=', 'Hello'),
206
-                        ('modification_date', '>', 1),
207
-                    ],
208
-                    'rel_filters': [],
209
-                },
210
-                {
211
-                    'where':{
212
-                        (
213
-                            db_utils.column_prefix(table_names[LeObject], lodel_id),
214
-                            '='
215
-                        ): 42,
216
-                        (
217
-                            db_utils.column_prefix(table_names[LeObject], 'string'),
218
-                            '='
219
-                        ): 'Hello',
220
-                        (
221
-                            db_utils.column_prefix(table_names[LeObject], 'modification_date'),
222
-                            '>'
223
-                        ): 1
224
-                    },
225
-                    'joins':None, #Expected call on Query.__call__ (called when we call left_join)
226
-                }
227
-            ),
228
-        ]
229
-
230
-        # mock the database module to avoid connection tries
231
-        dbmodule_mock = Mock()
232
-
233
-        datasource = DataSource(module=dbmodule_mock, conn_args = {})
234
-        sql_query = 'SELECT foo FROM LeObject' # Fake return of mosql.select()
235
-
236
-        for fields_details, select_args, expt_args in tests_queries:
237
-            mosql_query_return_value = sql_query
238
-            with patch.object(mosql_Query, '__call__', return_value=mosql_query_return_value) as mock_select:
239
-                with patch.object(db_utils, 'query') as mock_utils_query:
240
-                    # building the field_list argument
241
-                    select_args['field_list'] = copy.copy(fields_details['leobject'])
242
-                    select_args['field_list'] += fields_details['leclass']
243
-                    # call
244
-                    datasource.select(**select_args)
245
-
246
-                    # building the select expected argument
247
-                    select_arg = [ db_utils.column_prefix(db_utils.common_tables['object'], fname) for fname in fields_details['leobject'] ]
248
-                    if len(fields_details['leclass']) > 0:
249
-                        leclass = select_args['target_cls']
250
-                        leclass = leclass._leclass if leclass.is_letype() else leclass
251
-                        table_name = db_utils.object_table_name(leclass.__name__)
252
-                        select_arg += [ db_utils.column_prefix(table_name, field_name) for field_name in fields_details['leclass'] ]
253
-                    
254
-                    if expt_args['joins'] is None:
255
-                        # If the call is made with LeObject as target class no joins call is made
256
-                        mock_select.assert_called_once_with(
257
-                            db_utils.common_tables['object'],
258
-                            select= select_arg,
259
-                            where=expt_args['where'],
260
-                            joins=[]
261
-                        )
262
-                    else:
263
-                        # Else there has to be 2 calls, 1 for the join another for the select
264
-                        expected_calls = [
265
-                            expt_args['joins'],
266
-                            call(
267
-                                db_utils.common_tables['object'],
268
-                                select= select_arg,
269
-                                where=expt_args['where'],
270
-                                joins=[mosql_query_return_value]
271
-                            )
272
-                        ]
273
-                        mock_select.assert_has_calls(expected_calls, any_order=False)
274
-                    
275
-                    # Tests that the query is really sent to the query method
276
-                    mock_utils_query.assert_called_once_with(
277
-                        datasource.connection,
278
-                        sql_query
279
-                    )

+ 0
- 89
DataSource/MySQL/utils.py View File

@@ -1,89 +0,0 @@
1
-# -*- coding: utf8 -*-
2
-
3
-from Lodel.settings import Settings
4
-
5
-common_tables = {
6
-    'relation': 'relation',
7
-    'object': 'object'
8
-}
9
-table_preffix = {
10
-    'relation': 'rel_',
11
-    'object': 'class_',
12
-}
13
-
14
-## @brief indicates if we want ON DELETE CASCADE on foreign keys
15
-# @todo implementation in migration handler
16
-fk_on_delete_cascade = False
17
-## @brief Lodel_id for the hierachy root
18
-leroot_lodel_id = 0
19
-
20
-
21
-## @brief Return a table name given a EmClass or LeClass name
22
-# @param class_name str : The class name
23
-# @return a table name
24
-def object_table_name(class_name):
25
-    return ("%s%s" % (table_preffix['object'], class_name)).lower()
26
-
27
-
28
-## @brief Return a table name given a class name and a type name
29
-# @param class_name str : The (Em|Le)Class name
30
-# @param type_name str : The (Em|Le)Type name
31
-# @return a table name
32
-def r2t_table_name(class_name, type_name):
33
-    return ("%s%s_%s" % (table_preffix['relation'], class_name, type_name)).lower()
34
-
35
-def multivalue_table_name(referenced_table_name, key_name):
36
-    return ("%s%s" % (key_name, referenced_table_name))
37
-
38
-## @brief Return a column name given a field name
39
-# @param field_name : The EmField or LeObject field name
40
-# @return A column name
41
-def column_name(field_name):
42
-    return field_name.lower()
43
-
44
-
45
-## @brief gets the fk name between two tables
46
-# @param src_table_name str
47
-# @param dst_table_name str
48
-# @return str
49
-def get_fk_name(src_table_name, dst_table_name):
50
-    return ("fk_%s_%s" % (src_table_name, dst_table_name)).lower()
51
-
52
-
53
-## @brief Exec a query
54
-# @param connection : Db connection (has returned by dbmodule.connect())
55
-# @param query_string str : SQL query
56
-def query(connection, query_string):
57
-    if Settings.debug_sql:
58
-        print("SQL : ", query_string)
59
-    with connection as cur:
60
-        try:
61
-            cur.execute(query_string)
62
-        except Exception as err:
63
-            raise err
64
-        return cur
65
-
66
-
67
-## @brief Identifier escaping
68
-# @param idname str : An SQL identifier
69
-def escape_idname(idname):
70
-    if '`' in idname:
71
-        raise ValueError("Invalid name : '%s'" % idname)
72
-    return '`%s`' % idname
73
-
74
-
75
-## Brief add table prefix to a column name
76
-# @param name string: column name to prefix
77
-# @param prefixes dict(prefix:list(name,))
78
-# @return prefixed_name string: the name prefixed
79
-# find the same name in some list of names, prepend the key of the dict to the name
80
-def find_prefix(name, prefixes):
81
-    for prefix, names in prefixes:
82
-        if name in names:
83
-            return column_prefix(prefix, name)
84
-    return name
85
-
86
-
87
-## prefix a column name with the table name
88
-def column_prefix(table, column):
89
-    return '%s.%s' % (table, column)

+ 0
- 0
DataSource/__init__.py View File


+ 0
- 0
DataSource/dummy/__init__.py View File


+ 0
- 62
DataSource/dummy/leapidatasource.py View File

@@ -1,62 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @brief Dummy datasource for LeObject
4
-#
5
-# This class has to be extended to apply to a real datasource
6
-# But it can be used as an empty and debug datasource
7
-class DummyDatasource(object):
8
-
9
-    def __init__(self, module=None, *conn_args, **conn_kargs):
10
-        self.module = module
11
-        self.conn_args = conn_args
12
-        self.conn_kargs = conn_kargs
13
-
14
-    ## @brief select lodel editorial components given filters
15
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
16
-    # @param field_list list : List of field names we want in the returned value or instance
17
-    # @param filters list : List of filters (see @ref leobject_filters )
18
-    # @param rel_filters list : List of relationnal filters (see @ref leobject_filters )
19
-    # @param group list of tupple: List of column to group together.  group = [('titre', 'ASC'), ]
20
-    # @param order list of tupple : List of column to order.  order = [('titre', 'ASC'), ]
21
-    # @param limit int : Number of row to be returned
22
-    # @param offset int : Used with limit to choose the start row
23
-    # @param instanciate bool : If True return an instance, else return a dict
24
-    # @return a list of LeCrud child classes
25
-    def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0, instanciate=True):
26
-        pass
27
-
28
-    ## @brief delete lodel editorial components given filters
29
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
30
-    # @param leo_id int : The component ID (lodel_id or relation_id)
31
-    # @return the number of deleted components
32
-    def delete(self, target_cls, leo_id):
33
-        pass
34
-
35
-    ## @brief update an existing lodel editorial component
36
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
37
-    # @param leo_id int : The uniq ID of the object we want to update
38
-    # @param **datas : Datas in kwargs
39
-    # @return The number of updated components
40
-    def update(self, target_cls, leo_id, **datas):
41
-        pass
42
-    
43
-    ## @brief insert a new lodel editorial component
44
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
45
-    # @param **datas : The datas to insert
46
-    # @return The inserted component's id
47
-    def insert(self, target_cls, **datas):
48
-        pass
49
-    
50
-    ## @brief insert multiple editorial component
51
-    # @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
52
-    # @param datas_list list : A list of dict representing the datas to insert
53
-    # @return int the number of inserted component
54
-    def insert_multi(self, target_cls, datas_list):
55
-        pass
56
-    
57
-    ## @brief Update a rank for a relation (and shift properly every concerned relations)
58
-    # @param le_relation LeRelation : A LeRelation instance
59
-    # @param new_rank int : An integer representing the absolute new rank
60
-    # @return ???
61
-    def update_rank(self, le_relation, new_rank):
62
-        pass

+ 0
- 50
DataSource/dummy/migrationhandler.py View File

@@ -1,50 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.migrationhandler.dummy
4
-# @brief A dummy migration handler
5
-#
6
-# According to it every modifications are possible
7
-#
8
-
9
-
10
-## Manage Model changes
11
-class DummyMigrationHandler(object):
12
-
13
-    def __init__(self, debug=False):
14
-        self.debug = debug
15
-
16
-    ## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
17
-    # @note The states ( initial_state and new_state ) contains only fields that changes
18
-    # @param em model : The EditorialModel.model object to provide the global context
19
-    # @param uid int : The uid of the change EmComponent
20
-    # @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
21
-    # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
22
-    # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
23
-    def register_change(self, em, uid, initial_state, new_state):
24
-        if self.debug:
25
-            print("\n##############")
26
-            print("DummyMigrationHandler debug. Changes for component with uid %d :" % uid)
27
-            if initial_state is None:
28
-                print("Component creation (uid = %d): \n\t" % uid, new_state)
29
-            elif new_state is None:
30
-                print("Component deletion (uid = %d): \n\t" % uid, initial_state)
31
-            else:
32
-                field_list = set(initial_state.keys()).union(set(new_state.keys()))
33
-                for field_name in field_list:
34
-                    str_chg = "\t%s " % field_name
35
-                    if field_name in initial_state:
36
-                        str_chg += "'" + str(initial_state[field_name]) + "'"
37
-                    else:
38
-                        str_chg += " creating "
39
-                    str_chg += " => "
40
-                    if field_name in new_state:
41
-                        str_chg += "'" + str(new_state[field_name]) + "'"
42
-                    else:
43
-                        str_chg += " deletion "
44
-                    print(str_chg)
45
-            print("##############\n")
46
-
47
-    ## @brief Not usefull for the moment
48
-    def register_model_state(self, em, state_hash):
49
-        if self.debug:
50
-            print("New EditorialModel state registered : '%s'" % state_hash)

+ 0
- 2403
Doxyfile
File diff suppressed because it is too large
View File


+ 0
- 2
EditorialModel/__init__.py View File

@@ -1,2 +0,0 @@
1
-## @package EditorialModel
2
-# @brief Group all objects usefull for editorial model management

+ 0
- 2
EditorialModel/backend/__init__.py View File

@@ -1,2 +0,0 @@
1
-## @package EditorialModel.backend
2
-# @brief Backend that allows the EM to handle different data sources

+ 0
- 17
EditorialModel/backend/dummy_backend.py View File

@@ -1,17 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.backend.dummy_backend
4
-# @brief Gives an empty backend
5
-#
6
-# Allows an editorial model to use an empty backend
7
-# Mostly for migration and test purpose
8
-
9
-## Manages a Json file based backend structure
10
-class EmBackendDummy(object):
11
-
12
-    def load(self):
13
-        data = {}
14
-        return data
15
-
16
-    def save(self):
17
-        return True

+ 0
- 129
EditorialModel/backend/graphviz.py View File

@@ -1,129 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-import datetime
4
-import EditorialModel
5
-from EditorialModel.classtypes import EmClassType
6
-from EditorialModel.types import EmType
7
-from EditorialModel.fields import EmField
8
-from Lodel.utils.mlstring import MlString
9
-from EditorialModel.backend.dummy_backend import EmBackendDummy
10
-
11
-class EmBackendGraphviz(EmBackendDummy):
12
-
13
-    ## @brief Constructor
14
-    # @param dot_fname str : The filename where we want to save the dot repr of the EM
15
-    def __init__(self, dot_fname):
16
-        self.edges = ""
17
-        self.dot_fname = dot_fname
18
-        #with open(dot_file, 'w+') as dot_fp:
19
-    
20
-    ## @brief Not implementend
21
-    # @warning Not implemented
22
-    def load(self):
23
-        raise NotImplementedError(self.__class__.__name__+' cannot load an EM')
24
-
25
-    ## @brief Save an EM in a dot file
26
-    # @param em model : The EM to save
27
-    # @warning hardcoded classtype
28
-    def save(self, em):
29
-        self.edges = ""
30
-        with open(self.dot_fname, 'w') as dotfp:
31
-            dotfp.write("digraph G {\n\trankdir = BT\n")
32
-            
33
-            dotfp.write('subgraph cluster_classtype {\nstyle="invis"\n')
34
-            for ct in [ 'entity', 'entry', 'person' ]:
35
-                dotfp.write('\n\nct%s [ label="classtype %s" shape="tripleoctagon" ]\n'%(ct, ct))
36
-            dotfp.write("}\n")
37
-
38
-            
39
-            dotfp.write('subgraph cluster_class {\nstyle="invis"\n')
40
-            for c in em.classes():
41
-
42
-                dotfp.write(self._component_node(c, em))
43
-                cn = c.__class__.__name__
44
-                cid = self._component_id(c)
45
-                self.edges += cid+' -> ct%s [ style="dashed" ]\n'%c.classtype
46
-            dotfp.write("}\n")
47
-
48
-            #dotfp.write('subgraph cluster_type {\nstyle="invis"\n')
49
-            for c in em.components(EmType):
50
-                dotfp.write(self._component_node(c, em))
51
-                cn = c.__class__.__name__
52
-                cid = self._component_id(c)
53
-                self.edges += cid+' -> '+self._component_id(c.em_class)+' [ style="dotted" ]\n'
54
-                for nat, sups in c.superiors().items():
55
-                    for sup in sups:
56
-                        self.edges += cid+' -> '+self._component_id(sup)+' [ label="%s" color="green" ]\n'%nat
57
-            #dotfp.write("}\n")
58
-            
59
-            """
60
-            for rf in [ f for f in em.components(EmField) if f.fieldtype == 'rel2type']:
61
-                dotfp.write(self._component_node(rf, em))
62
-                cid = self._component_id(rf)
63
-                self.edges += cid+' -> '+self._component_id(rf.em_class)+'\n'
64
-                self.edges += cid+' -> '+self._component_id(em.component(rf.rel_to_type_id))+'\n'
65
-            """
66
-
67
-            dotfp.write(self.edges)
68
-
69
-            dotfp.write("\n}")
70
-        pass
71
-
72
-    @staticmethod
73
-    def _component_id(c):
74
-        return 'emcomp%d'%c.uid
75
-
76
-    def _component_node(self, c, em):
77
-        ret = "\t"+EmBackendGraphviz._component_id(c)
78
-        cn = c.__class__.__name__
79
-        rel_field = ""
80
-        if cn == 'EmClass':
81
-            ret += '[ label="EmClass %s", shape="%s" ]'%(c.name.replace('"','\\"'), 'doubleoctagon')
82
-        elif cn == 'EmField':
83
-            str_def = '[ label="Rel2Type {fname}|{{{records}}}", shape="record"]'
84
-            records = ' | '.join([f.name for f in em.components('EmField') if f.rel_field_id == c.uid])
85
-            ret += str_def.format(fname=c.name.replace('"','\\"'), records=records)
86
-        elif cn == 'EmType':
87
-            ret += '[ label="%s %s '%(cn, c.name.replace('"','\\"'))
88
-
89
-            cntref = 0
90
-            first = True
91
-            for f in [ f for f in c.fields() if f.name not in c.em_class.default_fields_list().keys()]:
92
-                if f.rel_field_id is None:
93
-                    if f.fieldtype == 'rel2type':
94
-                        rel_node_id = '%s%s%s'%(EmBackendGraphviz._component_id(c), EmBackendGraphviz._component_id(em.component(f.rel_to_type_id)), f.uid)
95
-
96
-                        rel_node = '\t%s [ label="rel2type %s'% (rel_node_id, f.name)
97
-
98
-                        if len(f.rel_to_type_fields()) > 0:
99
-                            #rel_node += '| {'
100
-                            first = True
101
-                            for rf in f.rel_to_type_fields():
102
-                                rel_node += ' | '
103
-                                if first:
104
-                                    rel_node += '{ '
105
-                                    first = False
106
-                                rel_node += rf.name
107
-                        rel_node += '}' if len(f.rel_to_type_fields()) > 0 else ''
108
-                        rel_node += '" shape="record"]\n'
109
-
110
-                        rel_field += rel_node
111
-
112
-                        ref_node = EmBackendGraphviz._component_id(em.component(f.rel_to_type_id))
113
-                        self.edges += '%s:f%d -> %s [ color="purple" dir="both" ]\n'%(EmBackendGraphviz._component_id(c), cntref, rel_node_id)
114
-                        self.edges += '%s -> %s [color="purple" dir="both" ]\n'%(rel_node_id, ref_node)
115
-
116
-                    ret += '|'
117
-                    if first:
118
-                        ret += ' { '
119
-                        first = False
120
-                    if f.fieldtype == 'rel2type':
121
-                        ret += '<f%d> '%cntref
122
-                        cntref += 1
123
-                    ret += f.name
124
-            ret += '}" shape="record" color="%s" ]'%('blue' if cn == 'EmType' else 'red')
125
-        else:
126
-            return ""
127
-        ret +="\n"+rel_field
128
-        return ret
129
-            

+ 0
- 99
EditorialModel/backend/json_backend.py View File

@@ -1,99 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.backend.json_backend
4
-# @brief Handle json files
5
-#
6
-# Allows an editorial model to be loaded/saved in
7
-# json format
8
-
9
-import json
10
-import datetime
11
-from Lodel.utils.mlstring import MlString
12
-from EditorialModel.backend.dummy_backend import EmBackendDummy
13
-
14
-
15
-def date_cast(date):
16
-    if len(date):
17
-        return datetime.datetime.strptime(date, '%c')
18
-    else:
19
-        return None
20
-
21
-
22
-## @brief dirty obsolote cast function
23
-def int_or_none(i):
24
-    if isinstance(i, int):
25
-        return i
26
-    elif i is None:
27
-        return None
28
-    elif len(i):
29
-        return int(i)
30
-    else:
31
-        return None
32
-
33
-
34
-## Manages a Json file based backend structure
35
-class EmBackendJson(EmBackendDummy):
36
-
37
-    cast_methods = {
38
-        'uid': int,
39
-        'rank': int,
40
-        'class_id': int,
41
-        'fieldgroup_id': int,
42
-        'rel_to_type_id': int_or_none,
43
-        'rel_field_id': int_or_none,
44
-        'optional': bool,
45
-        'string': MlString.load,
46
-        'help_text': MlString.load,
47
-        'date_create': date_cast,
48
-        'date_update': date_cast,
49
-    }
50
-
51
-    ## Constructor
52
-    #
53
-    # @param json_file str: path to the json_file used as data source
54
-    # @param json_string str: json_data used as data source
55
-    def __init__(self, json_file=None, json_string=None):
56
-        if (not json_file and not json_string) or (json_file and json_string):
57
-            raise AttributeError
58
-        self._json_file = json_file
59
-        self._json_string = json_string
60
-
61
-    @staticmethod
62
-    ## @brief Used by json.dumps to serialize date and datetime
63
-    def date_handler(obj):
64
-        return obj.strftime('%c') if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date) else None
65
-
66
-    ## Loads the data from given file or string
67
-    #
68
-    # @return list
69
-    def load(self):
70
-        data = {}
71
-        json_string = self._load_from_file() if self._json_file else self._json_string
72
-        json_data = json.loads(json_string)
73
-        for index, component in json_data.items():
74
-            attributes = {}
75
-            for attr_name, attr_value in component.items():
76
-                if attr_name in EmBackendJson.cast_methods:
77
-                    attributes[attr_name] = EmBackendJson.cast_methods[attr_name](attr_value)
78
-                else:
79
-                    attributes[attr_name] = attr_value
80
-            data[int(index)] = attributes
81
-
82
-        return data
83
-
84
-    def _load_from_file(self):
85
-        with open(self._json_file) as content:
86
-            data = content.read()
87
-        return data
88
-
89
-    ## Saves the data in the data source json file
90
-    # @param model Model : The editorial model
91
-    # @param filename str|None : The filename to save the EM in (if None use self.json_file provided at init )
92
-    # @return json string
93
-    def save(self, model, filename=None):
94
-        json_dump = json.dumps({component.uid: component.attr_flat() for component in model.components()}, default=self.date_handler, indent=True)
95
-        if self._json_file:
96
-            with open(self._json_file if filename is None else filename, 'w') as json_file:
97
-                json_file.write(json_dump)
98
-        else:
99
-            return json_dump

+ 0
- 247
EditorialModel/backend/lodel1_backend.py View File

@@ -1,247 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.backend.lodel1_backend
4
-# @brief Handle convertion of lodel 1.0 model.xml
5
-#
6
-
7
-import xml.etree.ElementTree as ET
8
-import datetime
9
-import re
10
-from Lodel.utils.mlstring import MlString
11
-from EditorialModel.backend.dummy_backend import EmBackendDummy
12
-
13
-
14
-## Manages a Json file based backend structure
15
-class EmBackendLodel1(EmBackendDummy):
16
-    def __init__(self, xml_file=None, xml_string=None):
17
-        if (not xml_file and not xml_string) or (xml_file and xml_string):
18
-            raise AttributeError
19
-        self._xml_file = xml_file
20
-        self._xml_string = xml_string
21
-        self.lodel2_components = self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': [], 'EmFieldGroup': []}
22
-        self._uids = []
23
-        self._fieldgroups_map = {}
24
-
25
-    ## Loads the data from given file or string
26
-    #
27
-    # @return list
28
-    def load(self):
29
-        xml_string = self._load_from_file() if self._xml_file else self._xml_string
30
-
31
-        root_element = ET.fromstring(xml_string)
32
-        self._import_components(root_element)
33
-
34
-        # change uid of EmField and EmFieldGroup DONE
35
-        # take care of fieldgroup_id 0 !!
36
-
37
-        # add relational fields and rel_to_type_id
38
-
39
-        # add superiors_list in types
40
-
41
-        # create dict with all components DONE
42
-
43
-        #print (self.lodel2_components)
44
-        return self.lodel2_components['uids']
45
-
46
-    ## Import the four basic components
47
-    def _import_components(self, root_element):
48
-        # component name and xpath to find them in the xml model
49
-        # the order is very important
50
-        # class and type first, they put their uid in self._uids
51
-        # fieldgroups must change their uid, it will be used by fields later
52
-        to_import = [
53
-            ('EmClass', "./table[@name='#_TP_classes']/datas/row"),
54
-            ('EmType', "./table[@name='##_TP_types']/datas/row"),
55
-            ('EmFieldGroup', "./table[@name='#_TP_tablefieldgroups']/datas/row"),
56
-            ('EmField', "./table[@name='#_TP_tablefields']/datas/row")
57
-        ]
58
-        for comp in to_import:
59
-            component_name, xpath = comp
60
-            #print (component_name, xpath)
61
-            components = root_element.findall(xpath)
62
-            for lodel1_component in components:
63
-                cols = lodel1_component.findall('*')
64
-                fields = self._dom_elements_to_dict(cols)
65
-                #print(fields)
66
-                lodel2_component = self._map_component(component_name, fields)
67
-                lodel2_component['component'] = component_name
68
-                #print(lodel2_component)
69
-                uid = lodel2_component['uid']
70
-                self.lodel2_components[component_name].append(lodel2_component)
71
-                #if component_name in ['EmClass', 'EmType']:
72
-                self._uids.append(uid)
73
-                #del lodel2_component['uid']
74
-                self.lodel2_components['uids'][uid] = lodel2_component
75
-                #print ('————')
76
-
77
-    def _get_uid(self):
78
-        uid = 1
79
-        while True:
80
-            if uid not in self._uids:
81
-                return uid
82
-            uid += 1
83
-
84
-    ## map lodel1 values to lodel2 values
85
-    def _map_component(self, component, fields):
86
-        new_dic = {}
87
-        for mapping in ONE_TO_TWO[component]:
88
-            lodel1_fieldname, lodel2_fieldname = mapping[0:2]
89
-            if len(mapping) == 3:
90
-                cast_function = mapping[2]
91
-                if callable(cast_function):
92
-                    value = cast_function(fields[lodel1_fieldname])
93
-                    values = {lodel2_fieldname: value}
94
-                else:
95
-                    values = getattr(self, cast_function)(lodel2_fieldname, fields[lodel1_fieldname], fields)
96
-            else:
97
-                values = {lodel2_fieldname: fields[lodel1_fieldname]}
98
-            if values:
99
-                for name, value in values.items():
100
-                    new_dic[name] = value
101
-            #print (lodel1_fieldname, lodel2_fieldname, value)
102
-        return new_dic
103
-
104
-    ## convert collection of dom element to a dict
105
-    # \<col name="id"\>252\</col\> => {'id':'252'}
106
-    def _dom_elements_to_dict(self, elements):
107
-        fields = {}
108
-        for element in elements:
109
-            if 'name' in element.attrib:
110
-                fields[element.attrib['name']] = element.text if element.text is not None else ''
111
-        return fields
112
-
113
-    def _load_from_file(self):
114
-        with open(self._xml_file) as content:
115
-            data = content.read()
116
-        return data
117
-
118
-    def save(self, model, filename=None):
119
-        pass
120
-
121
-    # Map methods lodel1 to lodel2
122
-
123
-    ## combine title and altertitle into one MlString
124
-    def title_to_mlstring(self, name, value, fields):
125
-        title = MlString({'fre': value})
126
-        if 'altertitle' in fields:
127
-            langs = re.findall('lang="(..)">([^<]*)', fields['altertitle'])
128
-            for string in langs:
129
-                title.set(string[0], string[1])
130
-        return {name: title}
131
-
132
-    ## set a new unused uid for EmFieldGroup
133
-    # save the oldid to apply to fields
134
-    def new_fieldgroup_id(self, name, value, fields):
135
-        uid = self._get_uid()
136
-        print(value)
137
-        self._fieldgroups_map[int(value)] = uid
138
-        return {name: uid}
139
-
140
-    # give a new unused uid
141
-    def new_uid(self, name, value, fields):
142
-        uid = self._get_uid()
143
-        return {name: uid}
144
-
145
-    # return the new fieldgroup_id given the old one
146
-    def fieldgroup_id(self, name, value, fields):
147
-        old_id = int(value)
148
-        try:
149
-            new_id = self._fieldgroups_map[old_id]
150
-        except KeyError:
151
-            print(old_id, fields)
152
-            return False
153
-        return {name: new_id}
154
-
155
-    def mlstring_cast(self, name, value, fields):
156
-        return {name: MlString({'fre': value})}
157
-
158
-    def to_classid(self, name, value, fields):
159
-        for em_class in self.lodel2_components['EmClass']:
160
-            if em_class['name'] == value:
161
-                return {name: em_class['uid']}
162
-        return False
163
-
164
-    def date_cast(self, name, value, fields):
165
-        date = None
166
-        if len(value):
167
-            try:  # 2015-09-14 14:20:28
168
-                date = datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
169
-            except ValueError:
170
-                pass
171
-        return {name: date}
172
-
173
-    def classtype_cast(self, name, value, fields):
174
-        classtype_map = {'entries': 'entry', 'entities': 'entity', 'persons': 'person'}
175
-        if value in classtype_map:
176
-            return {name: classtype_map[value]}
177
-        return False
178
-
179
-    def map_fieldtypes(self, name, value, fields):
180
-        fieldtypes = {
181
-            'longtext': 'text',
182
-            'date': 'datetime',
183
-            'tinytext': 'text',
184
-            'lang': 'char',
185
-            'boolean': 'bool',
186
-            'email': 'char',
187
-            'url': 'char',
188
-            'mltext': 'text',
189
-            'image': 'text',
190
-            'number': 'int',
191
-            #'persons': 'rel2type',
192
-            #'entries': 'rel2type',
193
-            #'entities': 'rel2type',
194
-            'persons': 'text',
195
-            'entries': 'text',
196
-            'entities': 'text'
197
-        }
198
-        if value in fieldtypes:
199
-            return {name: fieldtypes[value]}
200
-        return {name: value}
201
-
202
-
203
-ONE_TO_TWO = {
204
-    'EmClass': [
205
-        ("id", "uid", int),
206
-        ("icon", "icon"),
207
-        ("class", "name"),
208
-        ("title", "string", 'title_to_mlstring'),
209
-        ("classtype", "classtype", 'classtype_cast'),
210
-        ("comment", "help_text", 'mlstring_cast'),
211
-        #("status",""),
212
-        ("rank", "rank", int),
213
-        ("upd", "date_update", 'date_cast')
214
-    ],
215
-    'EmFieldGroup': [
216
-        ("id", "uid", 'new_fieldgroup_id'),
217
-        ("name", "name"),
218
-        ("class", "class_id", 'to_classid'),
219
-        ("title", "string", 'title_to_mlstring'),
220
-        ("comment", "help_text", 'mlstring_cast'),
221
-        #("status",""),
222
-        ("rank", "rank", int),
223
-        ("upd", "date_update", 'date_cast')
224
-    ],
225
-    'EmType': [
226
-        ("id", "uid", int),
227
-        ("icon", "icon"),
228
-        ("type", "name"),
229
-        ("title", "string", 'title_to_mlstring'),
230
-        ("class", "class_id", 'to_classid'),
231
-        ("comment", "help_text", 'mlstring_cast'),
232
-        #("status",""),
233
-        ("rank", "rank", int),
234
-        ("upd", "date_update", 'date_cast')
235
-    ],
236
-    'EmField': [
237
-        ("id", "uid", 'new_uid'),
238
-        ("name", "name"),
239
-        ("idgroup", "fieldgroup_id", 'fieldgroup_id'),
240
-        ("type", "fieldtype", 'map_fieldtypes'),
241
-        ("title", "string", 'title_to_mlstring'),
242
-        ("comment", "help_text", 'mlstring_cast'),
243
-        #("status",""),
244
-        ("rank", "rank", int),
245
-        ("upd", "date_update", 'date_cast')
246
-    ]
247
-}

+ 0
- 142
EditorialModel/classes.py View File

@@ -1,142 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @file classes.py
4
-# @see EditorialModel::classes::EmClass
5
-
6
-import copy
7
-
8
-# imports used in classtypes <-> actual model checks
9
-import difflib
10
-import warnings
11
-
12
-import EditorialModel
13
-from EditorialModel.components import EmComponent
14
-from EditorialModel.classtypes import EmClassType
15
-
16
-
17
-## @brief Manipulate Classes of the Editorial Model
18
-# Create classes of object.
19
-# @see EmClass, EditorialModel.types.EmType, EditorialModel.fieldgroups.EmFieldGroup, EmField
20
-# @todo sortcolumn handling
21
-class EmClass(EmComponent):
22
-
23
-    ranked_in = 'classtype'
24
-
25
-    ## EmClass instanciation
26
-    # @todo Classtype initialisation and test is not good EmClassType should give an answer or something like that
27
-    # @todo defines types check for icon and sortcolumn
28
-    def __init__(self, model, uid, name, classtype, icon='0', sortcolumn='rank', string=None, help_text=None, date_update=None, date_create=None, rank=None):
29
-
30
-        if EmClassType.get(classtype) is None:
31
-            raise AttributeError("Unknown classtype '%s'" % classtype)
32
-
33
-        self.classtype = classtype
34
-        self.icon = icon
35
-        self.sortcolumn = sortcolumn  # 'rank'
36
-        super(EmClass, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
37
-
38
-    ## @brief Check if the EmComponent is valid
39
-    # 
40
-    # This function add default and common fields to the EmClass if they are not yet created (and raises warning if existing common fields are outdated given the one describe in EditorialModel.classtypes
41
-    # @throw EmComponentCheckError if fails
42
-    # 
43
-    # @warning If default parameters of EmField constructor changes this method can be broken
44
-    def check(self):
45
-        # Checks that this class is up to date given the common_fields described in EditorialModel.classtypes
46
-        # if not just print a warning, don't raise an Exception
47
-        for field in self.fields():
48
-            if field.name in EditorialModel.classtypes.common_fields:
49
-                # Building fieltypes options to match the ones stored in EditorialModel.classtypes
50
-                ftype_opts = field.fieldtype_options()
51
-                ftype_opts['fieldtype'] = field.fieldtype
52
-                ftype_opts['string'] = field.string
53
-
54
-                ctype_opts = EditorialModel.classtypes.common_fields[field.name]
55
-                #Adding default value for options nullable, uniq and internal to fieldtypes options stored in classtypes
56
-                defaults = { 'nullable': False, 'uniq': False, 'internal': False}
57
-                for opt_name, opt_val in defaults.items():
58
-                    if opt_name not in ctype_opts:
59
-                        ctype_opts[opt_name] = opt_val
60
-
61
-                if ftype_opts != ctype_opts:
62
-                    field.set_fieldtype_options(**ctype_opts)
63
-                    # If options mismatch produce a diff and display a warning
64
-                    ctype_opts = [ "%s: %s\n"%(repr(k), repr(ctype_opts[k])) for k in sorted(ctype_opts.keys())]
65
-                    ftype_opts = [ "%s: %s\n"%(repr(k), repr(ftype_opts[k])) for k in sorted(ftype_opts.keys())]
66
-
67
-                    diff_list = difflib.unified_diff(
68
-                        ctype_opts,
69
-                        ftype_opts, 
70
-                        fromfile="Classtypes.%s" % field.name,
71
-                        tofile="CurrentModel_%s.%s" % (self.name, field.name)
72
-                    )
73
-                    diff = ''.join(diff_list)
74
-                    warnings.warn("LOADED MODEL IS OUTDATED !!! The common_fields defined in classtypes differs from the fields in this model.\nHere is a diff : \n%s" % diff)
75
-            
76
-        for fname in self.default_fields_list().keys():
77
-            if fname not in [f.name for f in self.fields()]:
78
-                self.model.add_default_class_fields(self.uid)
79
-        super(EmClass, self).check()
80
-
81
-    ## @brief Return the default fields list for this EmClass
82
-    # @return a dict with key = fieldname and value is a dict to pass to the EditorialModel::model::Model::creat_component() method
83
-    def default_fields_list(self):
84
-        ctype = EditorialModel.classtypes.EmClassType.get(self.classtype)
85
-        res = ctype['default_fields']
86
-        for k, v in EditorialModel.classtypes.common_fields.items():
87
-            res[k] = copy.copy(v)
88
-        return res
89
-
90
-    ## @brief Delete a class if it's ''empty''
91
-    # If a class has no fieldgroups delete it
92
-    # @return bool : True if deleted False if deletion aborded
93
-    def delete_check(self):
94
-        for emtype in self.model.components(EditorialModel.types.EmType):
95
-            if emtype.class_id == self.uid:
96
-                return False
97
-        #If the class contains EmField that are not added by default, you cannot delete the EmClass
98
-        if len([f for f in self.fields() if f.name not in self.default_fields_list().keys()]) > 0:
99
-            return False
100
-        return True
101
-
102
-    ## Retrieve list of the field_groups of this class
103
-    # @return A list of fieldgroups instance
104
-    def _fieldgroups(self):
105
-        ret = []
106
-        for fieldgroup in self.model.components(EditorialModel.fieldgroups.EmFieldGroup):
107
-            if fieldgroup.class_id == self.uid:
108
-                ret.append(fieldgroup)
109
-        return ret
110
-
111
-    ## Retrieve list of fields
112
-    # @return fields [EmField]:
113
-    def fields(self, relational=True):
114
-        if relational:
115
-            return [f for f in self.model.components('EmField') if f.class_id == self.uid]
116
-        else:
117
-            return [f for f in self.model.components('EmField') if f.class_id == self.uid and f.fieldtype != 'rel2type' and f.rel_field_id is None]
118
-
119
-    ## Retrieve list of type of this class
120
-    # @return types [EditorialModel.types.EmType]:
121
-    def types(self):
122
-        ret = []
123
-        for emtype in self.model.components(EditorialModel.types.EmType):
124
-            if emtype.class_id == self.uid:
125
-                ret.append(emtype)
126
-        return ret
127
-
128
-    ## Add a new EditorialModel.types.EmType that can ben linked to this class
129
-    # @param  em_type EditorialModel.types.EmType: type to link
130
-    # @return success bool: done or not
131
-    # @deprecated To do this add a rel2type field to any fieldtype of this EmClass
132
-    def link_type(self, em_type):
133
-        pass
134
-
135
-    ## Retrieve list of EditorialModel.types.EmType that are linked to this class
136
-    #  @return types [EditorialModel.types.EmType]:
137
-    def linked_types(self):
138
-        res = list()
139
-        for field in self.fields():
140
-            if field.fieldtype_instance().name == 'rel2type':
141
-                res.append(self.model.component(field.rel_to_type_id))
142
-        return res

+ 0
- 217
EditorialModel/classtypes.py View File

@@ -1,217 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-object_uid = 'lodel_id'
4
-object_em_class_id = 'class_id'
5
-object_em_type_id = 'type_id'
6
-
7
-relation_uid = 'id_relation'
8
-relation_superior = 'superior'
9
-relation_subordinate = 'subordinate'
10
-relation_name = 'relation_name'
11
-
12
-
13
-common_fields = {
14
-    object_uid: {
15
-        'fieldtype': 'pk',
16
-        'internal': 'autosql',
17
-        'immutable' : True,
18
-        'string': '{"___": "", "fre": "identifiant lodel", "eng": "lodel identifier"}'
19
-    },
20
-    object_em_class_id : {
21
-        'fieldtype': 'emuid',
22
-        'is_id_class': True,
23
-        'internal': 'automatic',
24
-        'immutable' : True,
25
-        'string': '{"___": "", "fre": "identifiant de la classe", "eng": "class identifier"}'
26
-    },
27
-    object_em_type_id : {
28
-        'fieldtype': 'emuid',
29
-        'is_id_class': False,
30
-        'internal': 'automatic',
31
-        'immutable' : True,
32
-        'string': '{"___": "", "fre": "identifiant de la type", "eng": "type identifier"}'
33
-    },
34
-    'string': {
35
-        'fieldtype': 'char',
36
-        'max_length': 128,
37
-        'internal': 'automatic',
38
-        'nullable': True,
39
-        'immutable' : False,
40
-        'string': '{"___": "", "fre": "Représentation textuel", "eng": "String representation"}',
41
-    },
42
-    'creation_date': {
43
-        'fieldtype': 'datetime',
44
-        'now_on_create': True,
45
-        'internal': 'autosql',
46
-        'immutable' : True,
47
-        'string': '{"___": "", "fre": "Date de création", "eng": "Creation date"}',
48
-    },
49
-    'modification_date': {
50
-        'fieldtype': 'datetime',
51
-        'now_on_create': True,
52
-        'now_on_update': True,
53
-        'internal': 'autosql',
54
-        'immutable' : True,
55
-        'string': '{"___": "", "fre": "Date de modification", "eng": "Modification date"}',
56
-    }
57
-}
58
-
59
-relations_common_fields = {
60
-    relation_uid: {
61
-        'fieldtype': 'pk',
62
-        'internal': 'autosql',
63
-        'immutable' : True,
64
-    },
65
-    'nature': {
66
-        'fieldtype': 'naturerelation',
67
-        'immutable' : True,
68
-    },
69
-    'depth': {
70
-        'fieldtype': 'integer',
71
-        'internal': 'automatic',
72
-        'immutable' : True,
73
-    },
74
-    'rank': {
75
-        'fieldtype': 'rank',
76
-        'internal': 'automatic',
77
-        'immutable' : True,
78
-    },
79
-    relation_superior : {
80
-        'fieldtype': 'leo',
81
-        'superior': True,
82
-        'immutable' : True,
83
-    },
84
-    relation_subordinate: {
85
-        'fieldtype': 'leo',
86
-        'superior': False,
87
-        'immutable' : True,
88
-    },
89
-    relation_name: {
90
-        'fieldtype': 'namerelation',
91
-        'max_length': 128,
92
-        'immutable' : True,
93
-    }
94
-}
95
-
96
-
97
-def pk_name():
98
-    for name, option in common_fields.items():
99
-        if option['fieldtype'] == 'pk':
100
-            return name
101
-
102
-
103
-## EmNature (Class)
104
-#
105
-# constant name for the nature of type hierarchy
106
-class EmNature(object):
107
-    PARENT = 'parent'
108
-    TRANSLATION = 'translation'
109
-    IDENTITY = 'identity'
110
-
111
-    @classmethod
112
-    def getall(cls):
113
-        return [cls.PARENT, cls.TRANSLATION, cls.IDENTITY]
114
-
115
-
116
-## EmClassType (Class)
117
-#
118
-# Representation of the classTypes
119
-#
120
-#    Defines 3 generic classtype : entity, entry and person
121
-#        - entity : to define editorial content
122
-#        - entry  : to define keywords
123
-#        - person : to define people (in the real world, this classtype will only have one class and one type)
124
-#
125
-#    The hierarchy dict fixes the possible hierarchical links the types of each classtype can have :
126
-#       - 'attach' : what type of superior a type can have
127
-#            - 'classtype' a type can have superiors of the same classtype
128
-#            - 'type'      a type can only have superiors of the same type
129
-#       - automatic : possible superiors
130
-#            - False : possible superiors must be defined
131
-#            - True  : possible superiors can not be defined, they will be enforced by the ME automatically
132
-#       - maxdepth : maximum depth
133
-#       - maxchildren : maximum children
134
-#
135
-#   Classtypes contains default internal fields too
136
-#
137
-class EmClassType(object):
138
-
139
-    entity = {
140
-        'name': 'entity',
141
-        'hierarchy': {
142
-            EmNature.PARENT: {
143
-                'attach': 'classtype',
144
-                'automatic': False,
145
-                'maxdepth': -1,
146
-                'maxchildren': -1
147
-            },
148
-            EmNature.TRANSLATION: {
149
-                'attach': 'type',
150
-                'automatic': False,
151
-                'maxdepth': 1,
152
-                'maxchildren': -1
153
-            },
154
-        },
155
-        'default_fields': {}
156
-    }
157
-
158
-    entry = {
159
-        'name': 'entry',
160
-        'hierarchy': {
161
-            EmNature.PARENT: {
162
-                'attach': 'type',
163
-                'automatic': False,
164
-            },
165
-            EmNature.TRANSLATION: {
166
-                'attach': 'type',
167
-                'automatic': False,
168
-                'maxdepth': 1,
169
-                'maxchildren': -1
170
-            },
171
-        },
172
-        'default_fields': {}
173
-    }
174
-
175
-    person = {
176
-        'name': 'person',
177
-        'hierarchy': {
178
-            EmNature.IDENTITY: {
179
-                'attach': 'classtype',
180
-                'automatic': True,
181
-                'maxdepth': -1,
182
-                'maxchildren': 1
183
-            },
184
-        },
185
-        'default_fields': {}
186
-    }
187
-
188
-    ## @brief return a classtype from its name
189
-    # @param cls
190
-    # @param classtype str : A classtype name
191
-    # @return None if no classtype with this name, else return a dict containing classtype informations
192
-    @classmethod
193
-    def get(cls, classtype):
194
-        try:
195
-            return getattr(cls, classtype.lower())
196
-        except AttributeError:
197
-            return None
198
-
199
-    ## @brief Get all the classtype
200
-    # @return A list of dict representing classtypes
201
-    @classmethod
202
-    def getall(cls):
203
-        return [cls.entity, cls.entry, cls.person]
204
-
205
-    ## @brief Return possible nature of relations for a classtype name
206
-    #
207
-    # @param classtype_name str: The classtype name
208
-    # @return A list of EmNature names (list of str)
209
-    @staticmethod
210
-    def natures(classtype_name):
211
-        if not isinstance(classtype_name, str):
212
-            raise TypeError("Excepted <class str> but got %s" % str(type(classtype_name)))
213
-        try:
214
-            classtype = getattr(EmClassType, classtype_name)
215
-        except AttributeError:
216
-            raise AttributeError("Unknown classtype : '%s'" % classtype_name)
217
-        return classtype['hierarchy'].keys()

+ 0
- 218
EditorialModel/components.py View File

@@ -1,218 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.components
4
-# @brief Base objects for all EditorialModel components
5
-#
6
-# Defines the EditorialModel::components::EmComponent class
7
-
8
-import datetime
9
-import hashlib
10
-import EditorialModel
11
-from Lodel.utils.mlstring import MlString
12
-
13
-
14
-## @brief This class is the mother class of all editorial model objects
15
-#
16
-# It gather all the properties and mechanism that are common to every editorial model objects
17
-# @see EditorialModel::classes::EmClass, EditorialModel::types::EmType, EditorialModel::fieldgroups::EmFieldGroup, EditorialModel::fields::EmField
18
-class EmComponent(object):
19
-
20
-    ## Used by EmComponent::modify_rank
21
-    ranked_in = None
22
-
23
-    def __init__(self, model, uid, name, string=None, help_text=None, date_update=None, date_create=None, rank=None):
24
-        if type(self) == EmComponent:
25
-            raise NotImplementedError('Abstract class')
26
-        if model.__class__.__name__ != 'Model':
27
-            raise TypeError("Excepted type for 'model' arg is <class 'Model'> but got {} instead".format(type(model)))
28
-
29
-        self.model = model
30
-
31
-        self.uid = uid
32
-        self.check_type('uid', int)
33
-        self.name = name
34
-        self.check_type('name', str)
35
-        self.string = string
36
-        self.help_text = MlString() if help_text is None else help_text
37
-        self.check_type('help_text', MlString)
38
-        self.date_update = datetime.datetime.now() if date_update is None else date_update  # WARNING timezone !
39
-        self.check_type('date_update', datetime.datetime)
40
-        self.date_create = datetime.datetime.now() if date_create is None else date_create  # WARNING timezone !
41
-        self.check_type('date_create', datetime.datetime)
42
-        self._inited = False
43
-
44
-        #Handling specials ranks for component creation
45
-        self.rank = rank
46
-
47
-    ## @brief Return a dict with attributes name as key and attributes value as value
48
-    # @note Used at creation and deletion to call the migration handler
49
-    def attr_dump(self):
50
-        return {fname: fval for fname, fval in self.__dict__.items() if not (fname.startswith('_') or (fname == 'uid') or (fname == 'model'))}
51
-
52
-    ## @brief Return a dict with attributes name as key and attributes value flattened
53
-    def attr_flat(self):
54
-        attributes_dump = self.attr_dump()
55
-        for attr_name in list(attributes_dump.keys()):
56
-            if isinstance(attributes_dump[attr_name], EmComponent):
57
-                attributes_dump[attr_name] = attributes_dump[attr_name].uid
58
-            elif isinstance(attributes_dump[attr_name], MlString):
59
-                attributes_dump[attr_name] = attributes_dump[attr_name].json_dumps()
60
-        attributes_dump['component'] = self.__class__.__name__
61
-
62
-        return attributes_dump
63
-
64
-    @property
65
-    ## @brief Provide a uniq name
66
-    #
67
-    # Identify a component with his type and name
68
-    def uniq_name(self):
69
-        uname = self.__class__.__name__
70
-        if not isinstance(self, EditorialModel.fields.EmField):  # WARNING this could crash with fieldtypes
71
-            try:
72
-                uname += '_' + self.em_class.name  # TODO Ajouter la propriété
73
-            except AttributeError:
74
-                pass
75
-        uname += '_' + self.name
76
-        return uname
77
-
78
-    ## @brief This function has to be called after the instanciation, checks, and init manipulations are done
79
-    # @note Create a new attribute _inited that allow __setattr__ to know if it has or not to call the migration handler
80
-    def init_ended(self):
81
-        self._inited = True
82
-
83
-    ## @brief Reimplementation for calling the migration handler to register the change
84
-    def __setattr__(self, attr_name, value):
85
-        if attr_name == 'string':
86
-            if not(isinstance(value, MlString)):
87
-                value = MlString(value)
88
-            super().__setattr__('string', value)
89
-        inited = '_inited' in self.__dict__ and self.__dict__['_inited']
90
-        if inited:
91
-            # if fails raise MigrationHandlerChangeError
92
-            self.model.migration_handler.register_change(self.model, self.uid, {attr_name: getattr(self, attr_name)}, {attr_name: value})
93
-        super(EmComponent, self).__setattr__(attr_name, value)
94
-        if inited:
95
-            self.model.migration_handler.register_model_state(self.model, hash(self.model))
96
-
97
-    ## Check the type of attribute named var_name
98
-    # @param var_name str : the attribute name
99
-    # @param excepted_type tuple|type : Tuple of type or a type
100
-    # @throw AttributeError if wrong type detected
101
-    def check_type(self, var_name, excepted_type):
102
-        var = getattr(self, var_name)
103
-
104
-        if not isinstance(var, excepted_type):
105
-            raise AttributeError("Excepted %s to be an %s but got %s instead" % (var_name, str(excepted_type), str(type(var))))
106
-
107
-    ## @brief Hash function that allows to compare two EmComponent
108
-    # @return EmComponent+ClassName+uid
109
-    def __hash__(self):
110
-        # flatten list of attributes of the component to an ordered list
111
-        # so every time we have the same string representation
112
-        attributes_flat = self.attr_flat()
113
-        ordered_attributes = sorted(list(attributes_flat.keys()))
114
-
115
-        component_dump = []
116
-        for attr_name in ordered_attributes:
117
-            if isinstance(attributes_flat[attr_name], datetime.datetime):  # drop date values
118
-                continue
119
-            component_dump.append((attr_name, attributes_flat[attr_name]))
120
-
121
-        return int(hashlib.md5(str(component_dump).encode('utf-8')).hexdigest(), 16)
122
-
123
-    ## @brief Test if two EmComponent are "equals"
124
-    # @return True or False
125
-    def __eq__(self, other):
126
-        return hash(self) == hash(other)
127
-
128
-    ## Check if the EmComponent is valid
129
-    # This function has to check that rank are correct and continuous other checks are made in childs classes
130
-    # @warning Hardcoded minimum rank
131
-    # @warning Rank modified by _fields['rank'].value
132
-    # @throw EmComponentCheckError if fails
133
-    def check(self):
134
-        self.model.sort_components(self.__class__)
135
-        if self.get_max_rank() != len(self.same_rank_group()) or self.rank <= 0:
136
-            #Non continuous ranks
137
-            for i, component in enumerate(self.same_rank_group()):
138
-                component.rank = i + 1
139
-        # No need to sort again here
140
-
141
-    ## @brief Delete predicate. Indicates if a component can be deleted
142
-    # @return True if deletion OK else return False
143
-    def delete_check(self):
144
-        raise NotImplementedError("Virtual method")
145
-
146
-    ## @brief Get the maximum rank given an EmComponent child class and a ranked_in filter
147
-    # @return The max rank is the rank group or 0 if no components in that group
148
-    def get_max_rank(self):
149
-        same_rgroup = self.same_rank_group()
150
-        return max([comp.rank for comp in same_rgroup]) if len(same_rgroup) > 0 else 0
151
-
152
-    ## Return an array of instances that are concerned by the same rank
153
-    # @return An array of instances that are concerned by the same rank
154
-    def same_rank_group(self):
155
-        components = self.model.components(self.__class__)
156
-        ranked_in = self.__class__.ranked_in
157
-        return [c for c in components if getattr(c, ranked_in) == getattr(self, ranked_in)]
158
-
159
-    ## Set a new rank for this component
160
-    # @note This function assume that ranks are properly set from 1 to x with no gap
161
-    #
162
-    # @warning Hardcoded minimum rank
163
-    # @warning Rank modified by _fields['rank'].value
164
-    #
165
-    # @param new_rank int: The new rank
166
-    #
167
-    # @throw TypeError If bad argument type
168
-    # @throw ValueError if out of bound value
169
-    def set_rank(self, new_rank):
170
-        if not isinstance(new_rank, int):
171
-            raise TypeError("Excepted <class int> but got " + str(type(new_rank)))
172
-        if new_rank <= 0 or (new_rank > 1 and new_rank > self.get_max_rank()):
173
-            raise ValueError("Invalid new rank : " + str(new_rank))
174
-
175
-        mod = new_rank - self.rank  # Indicates the "direction" of the "move"
176
-
177
-        if mod == 0:
178
-            return True
179
-
180
-        limits = [self.rank + (1 if mod > 0 else -1), new_rank]  # The range of modified ranks
181
-        limits.sort()
182
-
183
-        for component in [c for c in self.same_rank_group() if c.rank >= limits[0] and c.rank <= limits[1]]:
184
-            component.rank = component.rank + (-1 if mod > 0 else 1)
185
-
186
-        self.rank = new_rank
187
-
188
-        self.model.sort_components(self.__class__)
189
-
190
-    ## Modify a rank given an integer modifier
191
-    # @note this method always tries to make the modification : if modifier is too big put
192
-    # the component in last position, if modifier is to small put the component
193
-    # in first position
194
-    # @param rank_mod int : can be a negative positive or zero integer
195
-    # @return True if the modification was made as wanted else return false
196
-    # @throw TypeError if rank_mod is not an integer
197
-    def modify_rank(self, rank_mod):
198
-        if not isinstance(rank_mod, int):
199
-            raise TypeError("Excepted <class int>. But got %s" % str(type(rank_mod)))
200
-        ret = True
201
-        new_rank = self.rank + rank_mod
202
-        if new_rank < 1:
203
-            ret = False
204
-            new_rank = 1
205
-        elif new_rank > self.get_max_rank():
206
-            ret = False
207
-            new_rank = self.get_max_rank()
208
-
209
-        self.set_rank(new_rank)
210
-        return ret
211
-
212
-    ## @brief Return a string representation of the component
213
-    # @return A string representation of the component
214
-    def __repr__(self):
215
-        if self.name is None:
216
-            return "<%s #%s, 'non populated'>" % (type(self).__name__, self.uid)
217
-        else:
218
-            return "<%s #%s, '%s'>" % (type(self).__name__, self.uid, self.name)

+ 0
- 27
EditorialModel/exceptions.py View File

@@ -1,27 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-
4
-## @brief An exception class to tell that a component don't exist
5
-class EmComponentNotExistError(Exception):
6
-    pass
7
-
8
-
9
-## @brief Raised on uniq constraint error at creation
10
-# This exception class is dedicated to be raised when create() method is called
11
-# if an EmComponent with this name but different parameters allready exist
12
-class EmComponentExistError(Exception):
13
-    pass
14
-
15
-
16
-## @brief An exception class to tell that no ranking exist yet for the group of the object
17
-class EmComponentRankingNotExistError(Exception):
18
-    pass
19
-
20
-
21
-## @brief An exception class to return a failure reason for EmComponent.check() method
22
-class EmComponentCheckError(Exception):
23
-    pass
24
-
25
-
26
-class MigrationHandlerChangeError(Exception):
27
-    pass

+ 0
- 151
EditorialModel/fields.py View File

@@ -1,151 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import copy
4
-import importlib
5
-import warnings
6
-
7
-from EditorialModel.components import EmComponent
8
-from EditorialModel.exceptions import EmComponentCheckError
9
-from EditorialModel.fieldtypes.generic import GenericFieldType
10
-import EditorialModel
11
-import EditorialModel.fieldtypes
12
-#from django.db import models
13
-
14
-
15
-## EmField (Class)
16
-#
17
-# Represents one data for a lodel2 document
18
-class EmField(EmComponent):
19
-
20
-    ranked_in = 'class_id'
21
-
22
-    ## Instanciate a new EmField
23
-    # @todo define and test type for icon
24
-    # @warning nullable == True by default
25
-    # @param model Model : Editorial model
26
-    # @param uid int : Uniq id
27
-    # @param fieldtype str : Fieldtype name ( see Editorialmodel::fieldtypes )
28
-    # @param optional bool : Indicate if a field is optional or not
29
-    # @param internal str|bool : If False the field is not internal, else it can takes value in "object" or "automatic"
30
-    # @param rel_field_id int|None : If not None indicates that the field is a relation attribute (and the value is the UID of the rel2type field)
31
-    # @param nullable bool : If True None values are allowed
32
-    # @param uniq bool : if True the value should be uniq in the db table
33
-    # @param **kwargs : more keywords arguments for the fieldtype
34
-    def __init__(self, model, uid, name, class_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=False, uniq=False, **kwargs):
35
-        self.class_id = class_id
36
-        self.check_type('class_id', int)
37
-        self.optional = bool(optional)
38
-
39
-        if not internal:
40
-            self.internal = False
41
-        else:
42
-            if internal.lower() not in ['automatic', 'autosql']:
43
-                raise ValueError("The internal arguments possible values are : [False, 'autosql', 'automatic']")
44
-            self.internal = internal.lower()
45
-
46
-        if self.internal == 'object' and name not in EditorialModel.classtypes.common_fields.keys():
47
-            raise ValueError("Only common_fields are allowed to have the argument internal='object'")
48
-
49
-        self.rel_field_id = rel_field_id
50
-        self.check_type('rel_field_id', (int, type(None)))
51
-        self.icon = icon
52
-
53
-        #Field type elements
54
-        self._fieldtype_cls = GenericFieldType.from_name(fieldtype)
55
-
56
-        self.fieldtype = fieldtype
57
-        self._fieldtype_args = kwargs
58
-        self._fieldtype_args.update({'nullable': nullable, 'uniq': uniq, 'internal': self.internal})
59
-        self.set_fieldtype_options(**self._fieldtype_args)
60
-
61
-        if 'default' in kwargs:
62
-            if not fieldtype_instance.check(default):
63
-                raise TypeError("Default value ('%s') is not valid given the fieldtype '%s'" % (default, fieldtype))
64
-
65
-        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)
66
-
67
-    @staticmethod
68
-    ## @brief Return the list of allowed field type
69
-    def fieldtypes_list():
70
-        return [f for f in EditorialModel.fieldtypes.__all__ if f != '__init__' and f != 'generic']
71
-
72
-    ##@brief Return the EmFieldgroup this EmField belongs to
73
-    @property
74
-    def _fieldgroup(self):
75
-        return self.model.component(self.fieldgroup_id)
76
-
77
-    ## @brief Returns the EmClass this EmField belongs to
78
-    @property
79
-    def em_class(self):
80
-        return self.model.component(self.class_id)
81
-    
82
-    ## @brief Update the fieldtype of a field
83
-    def set_fieldtype(self, fieldtype_name):
84
-        self.fieldtype = fieldtype_name
85
-        self._fieldtype_cls = GenericFieldType.from_name(self.fieldtype)
86
-        self.set_fieldtype_options(**self._fieldtype_args)
87
-
88
-    ## @brief Set fieldtype options
89
-    def set_fieldtype_options(self, nullable = False, uniq = False, internal=False, **kwargs):
90
-        # Cleaning old options
91
-        if hasattr(self, 'name'): # If not called by __init__
92
-            for opt_name in self._fieldtype_args:
93
-                if hasattr(self, opt_name):
94
-                    delattr(self,opt_name)
95
-        self._fieldtype_args = kwargs
96
-        self._fieldtype_args.update({'nullable': nullable, 'uniq': uniq, 'internal': internal})
97
-        try:
98
-            fieldtype_instance = self._fieldtype_cls(**self._fieldtype_args)
99
-        except AttributeError as e:
100
-            raise AttributeError("Error will instanciating fieldtype : %s" % e)
101
-        for opt_name, opt_val in self._fieldtype_args.items():
102
-            setattr(self, opt_name, opt_val)
103
-
104
-    ## @brief Getter for private property EmField._fieldtype_args
105
-    # @return A copy of private dict _fieldtype_args
106
-    def fieldtype_options(self):
107
-        return copy.copy(self._fieldtype_args)
108
-
109
-    ## @brief Get the fieldtype instance
110
-    # @return a fieldtype instance
111
-    def fieldtype_instance(self):
112
-        return self._fieldtype_cls(**self._fieldtype_args)
113
-
114
-    ## @brief Return the list of relation fields for a rel_to_type
115
-    # @return None if the field is not a rel_to_type else return a list of EmField
116
-    def rel_to_type_fields(self):
117
-        if not self.rel_to_type_id:  # TODO Ajouter cette propriété
118
-            return None
119
-
120
-        return [f for f in self.model.components(EmField) if f.rel_field_id == self.uid]
121
-
122
-    ## Check if the EmField is valid
123
-    #
124
-    # Check multiple things :
125
-    # - the fieldgroup_id is valid
126
-    # - the name is uniq in the EmClass
127
-    # - if its a rel2type check that the linked_type is uniq in the EmClass
128
-    # @return True if valid False if not
129
-    def check(self):
130
-        super(EmField, self).check()
131
-        """
132
-        #Fieldgroup check
133
-        em_fieldgroup = self.model.component(self.fieldgroup_id)
134
-        if not em_fieldgroup:
135
-            raise EmComponentCheckError("fieldgroup_id contains a non existing uid : '%d'" % self.fieldgroup_id)
136
-        if not isinstance(em_fieldgroup, EditorialModel.fieldgroups.EmFieldGroup):
137
-            raise EmComponentCheckError("fieldgroup_id contains an uid from a component that is not an EmFieldGroup but a %s" % str(type(em_fieldgroup)))
138
-        """
139
-        #Uniq Name check
140
-        if len([f for f in self.em_class.fields() if f.name == self.name]) > 1:
141
-            raise EmComponentCheckError("The field %d has a name '%s' that is not uniq in the EmClass %d" % (self.uid, self.name, self.em_class.uid))
142
-        #rel2type uniq check
143
-        if self.fieldtype == 'rel2type':
144
-            if len([f for f in self.em_class.fields() if f.fieldtype == 'rel2type' and f.rel_to_type_id == self.rel_to_type_id]) > 1:
145
-                raise EmComponentCheckError("The rel2type %d is not uniq, another field is linked to the same type '%s' in the same class '%s'" % (self.uid, self.model.component(self.rel_to_type_id).name, self.em_class.name))
146
-
147
-    ## @brief Delete a field if it's not linked
148
-    # @return bool : True if deleted False if deletion aborded
149
-    # @todo Check if unconditionnal deletion is correct
150
-    def delete_check(self):
151
-        return True

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

@@ -1 +0,0 @@
1
-

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

@@ -1,25 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from .generic import SingleValueFieldType
4
-
5
-
6
-class EmFieldType(SingleValueFieldType):
7
-
8
-    help = 'A basic boolean field'
9
-
10
-    ftype = 'bool'
11
-
12
-    ## @brief A boolean field
13
-    # @brief max_length int : The maximum length of this field
14
-    def __init__(self, **kwargs):
15
-        if 'check_data_value' not in kwargs:
16
-            kwargs['check_data_value'] = self.check_value
17
-        super(EmFieldType, self).__init__(ftype='bool', **kwargs)
18
-
19
-    def check_value(self, value):
20
-        error = None
21
-        try:
22
-            value = bool(value)
23
-        except (ValueError, TypeError):
24
-            error = TypeError("the value '%s' is not, and will never be a boolean" % value)
25
-        return (value, error)

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

@@ -1,15 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from .generic import SingleValueFieldType
4
-
5
-
6
-class EmFieldType(SingleValueFieldType):
7
-
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 = int(max_length)
14
-        super().__init__(**kwargs)
15
-

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

@@ -1,17 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from .generic import SingleValueFieldType
4
-
5
-
6
-class EmFieldType(SingleValueFieldType):
7
-
8
-    help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
9
-
10
-    ## @brief A datetime field
11
-    # @param now_on_update bool : If true the date is set to NOW on update
12
-    # @param now_on_create bool : If true the date is set to NEW on creation
13
-    # @param **kwargs
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(EmFieldType, self).__init__(**kwargs)

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

@@ -1,15 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from .generic import MultiValueFieldType
4
-from . import char
5
-
6
-
7
-class EmFieldType(MultiValueFieldType):
8
-    
9
-    help = 'Fieldtype designed to handle translations'
10
-    
11
-    def __init__(self, value_max_length = 64, **args):
12
-        args['keyname'] = 'key'
13
-        args['key_fieldtype'] = char.EmFieldType(max_length = 4)
14
-        args['value_fieldtype'] = char.EmFieldType(value_max_length)
15
-        super().__init__(**args)

+ 0
- 39
EditorialModel/fieldtypes/emuid.py View File

@@ -1,39 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import leapi.lecrud as lecrud
4
-import leapi.letype as letype
5
-
6
-from .generic import FieldTypeError
7
-from . import integer
8
-
9
-class EmFieldType(integer.EmFieldType):
10
-    
11
-    help = 'Fieldtypes designed to handle editorial model UID for LeObjects'
12
-
13
-    _construct_datas_deps = []
14
-
15
-    def __init__(self, is_id_class, **kwargs):
16
-        self._is_id_class = is_id_class
17
-        kwargs['internal'] = 'automatic'
18
-        super().__init__(is_id_class = is_id_class, **kwargs)
19
-
20
-    def _check_data_value(self, value):
21
-        return (value, None)
22
-
23
-    def construct_data(self, lec, fname, datas, cur_value):
24
-        ret = None
25
-        if self.is_id_class:
26
-            if lec.implements_leclass():
27
-                ret = lec._class_id
28
-        else:
29
-            if lec.implements_letype():
30
-                ret = lec._type_id
31
-        return ret
32
-    
33
-    def check_data_consistency(self, lec, fname, datas):
34
-        if datas[fname] != (lec._class_id if self.is_id_class else lec._type_id):
35
-            return FieldTypeError("Given Editorial model uid doesn't fit with given LeObject")
36
-                
37
-            
38
-        
39
-        

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

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

+ 0
- 25
EditorialModel/fieldtypes/format.py View File

@@ -1,25 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import warnings
4
-
5
-from . import char
6
-
7
-class EmFieldType(char.EmFieldType):
8
-    help = 'Automatic string field, designed to use the str % operator to build its content'
9
-
10
-    ## @brief Build its content with a field list and a format string
11
-    # @param format_string str :  
12
-    # @param max_length int : The maximum length of the handled value
13
-    # @param field_list list : List of field to use
14
-    # @param **kwargs
15
-    def __init__(self, format_string, field_list, max_length, **kwargs):
16
-        self._field_list = field_list
17
-        self._format_string = format_string
18
-        super().__init__(internal='automatic', max_length = max_length)
19
-
20
-    def construct_data(self, lec, fname, datas, cur_value):
21
-        ret = self._format_string % tuple([ datas[fname] for fname in self._field_list ])
22
-        if len(ret) > self.max_length:
23
-            warnings.warn("Format field overflow. Truncating value")
24
-            ret = ret[:self.max_length-1]
25
-        return ret

+ 0
- 293
EditorialModel/fieldtypes/generic.py View File

@@ -1,293 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.fieldtypes.generic Class definition for fieldtypes
4
-
5
-import copy
6
-import types
7
-import importlib
8
-
9
-## @brief Abstract class for all fieldtypes
10
-class GenericFieldType(object):
11
-    
12
-    help_text = 'Generic field type : abstract class for every fieldtype'
13
-    
14
-    ## @brief List fields that will be exposed to the construct_data_method
15
-    _construct_datas_deps = []
16
-
17
-    ## @brief Generic constructor for fieldtypes 
18
-    # @param internal False | str : define wheter or not a field is internal
19
-    # @param immutable bool : indicate if the fieldtype has to be defined in child classes of LeObject or if it is defined globally and immutable
20
-    # @param **args
21
-    # @throw NotImplementedError if called from bad class
22
-    def __init__(self, internal = False, immutable = False, **args):
23
-        if self.__class__ == GenericFieldType:
24
-            raise NotImplementedError("Abstract class")
25
-        self.internal = internal #Check this value ?
26
-        self.immutable = bool(immutable)
27
-    
28
-        for argname, argval in args.items():
29
-            setattr(self, argname, argval)
30
-    
31
-    ## Fieldtype name
32
-    # @todo should be a staticmethod
33
-    @property
34
-    def name(self):
35
-        return self.__module__.split('.')[-1]
36
-    
37
-    ## @return True if a fieldtype is internal
38
-    def is_internal(self):
39
-        return self.internal != False
40
-    
41
-    ## @brief Take care to call the fieldtype defined _check_data_value() method
42
-    # @return a tuple (value, error|None)
43
-    def check_data_value(self, value):
44
-        return self._check_data_value(value)
45
-
46
-    def _check_data_value(self, value):
47
-        return (value, None)
48
-    
49
-    ## @brief Build field value
50
-    # @param lec LeCrud : A LeCrud child class
51
-    # @param fname str : The field name
52
-    # @param datas dict : dict storing fields values (from the lec)
53
-    # @param cur_value : the value for the current field (identified by fieldname)
54
-    # @return constructed datas (for the fname field)
55
-    # @throw RuntimeError if unable to construct data
56
-    def construct_data(self, lec, fname, datas, cur_value):
57
-        if fname in datas.keys():
58
-            return cur_value
59
-        elif hasattr(lec.fieldtypes()[fname], 'default'):
60
-            return lec.fieldtypes()[fname].default
61
-        elif lec.fieldtypes()[fname].nullable:
62
-            return None
63
-        raise RuntimeError("Unable to construct data for field %s", fname)
64
-
65
-    ## @brief Check datas consistency
66
-    # @param lec LeCrud : A LeCrud child class instance
67
-    # @param fname str : The field name
68
-    # @param datas dict : dict storing fields values
69
-    # @return an Exception instance if fails else True
70
-    def check_data_consistency(self, lec, fname, datas):
71
-        return True
72
-
73
-    ## @brief Given a fieldtype name return the associated python class
74
-    # @param fieldtype_name str : A fieldtype name
75
-    # @return An GenericFieldType derivated class
76
-    @staticmethod
77
-    def from_name(fieldtype_name):
78
-        mod = importlib.import_module(GenericFieldType.module_name(fieldtype_name))
79
-        return mod.EmFieldType
80
-
81
-    ## @brief Get a module name given a fieldtype name
82
-    # @param fieldtype_name str : A fieldtype name
83
-    # @return a string representing a python module name
84
-    @staticmethod
85
-    def module_name(fieldtype_name):
86
-        return 'EditorialModel.fieldtypes.%s' % (fieldtype_name)
87
-
88
-    ## @brief __hash__ implementation for fieldtypes
89
-    def __hash__(self):
90
-        hash_dats = [self.__class__.__module__]
91
-        for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
92
-            hash_dats.append((kdic, getattr(self, kdic)))
93
-        return hash(tuple(hash_dats))
94
-
95
-## @brief Represent fieldtypes handling a single value
96
-class SingleValueFieldType(GenericFieldType):
97
-    
98
-    ## @brief Instanciate a new fieldtype
99
-    # @param nullable bool : is None allowed as value ?
100
-    # @param uniq bool : Indicate if a field should handle uniq value
101
-    # @param primary bool : If True the field is a primary key
102
-    # @param internal str|False : if False the field is not internal. Else can be 'autosql' or 'internal'
103
-    # @param **args : Other arguments
104
-    # @throw NotImplementedError if called from bad class
105
-    def __init__(self, internal = False, nullable = True, uniq = False, primary = False, **args):
106
-        if self.__class__ == SingleValueFieldType:
107
-            raise NotImplementedError("Asbtract class")
108
-
109
-        super().__init__(internal, **args)
110
-
111
-        self.nullable = nullable
112
-        self.uniq = uniq
113
-        self.primary = primary
114
-        if 'default' in args:
115
-            self.default, error = self.check_data_value(args['default'])
116
-            if error:
117
-                raise error
118
-            del(args['default'])
119
-
120
-    def check_data_value(self, value):
121
-        if value is None:
122
-            if not self.nullable:
123
-                return (None, TypeError("'None' value but field is not nullable"))
124
-            return (None, None)
125
-        return super().check_data_value(value)
126
-
127
-## @brief Abstract class for fieldtype representing references
128
-#
129
-# In MySQL its foreign keys (for example leo fieldtypes)
130
-class ReferenceFieldType(SingleValueFieldType):
131
-
132
-    ## @brief Instanciate a new fieldtype
133
-    #
134
-    #
135
-    # @param reference str : A string that defines the reference (can be 'object' or 'relation')
136
-    # @param nullable bool : is None allowed as value ?
137
-    # @param uniq bool : Indicate if a field should handle uniq value
138
-    # @param primary bool : If True the field is a primary key
139
-    # @param internal str|False : if False the field is not internal. Else can be 'autosql' or 'internal'
140
-    # @param **args : Other arguments
141
-    # @throw NotImplementedError if called from bad class
142
-    def __init__(self, reference, internal=False, nullable = True, uniq = False, primary = False, **args):
143
-        if reference.lower() not in ['relation', 'object']:
144
-            raise ValueError("Bad value for reference : %s. Excepted on of 'relation', 'object'" % reference)
145
-        self.reference = reference.lower()
146
-        super().__init__(
147
-                            internal = internal,
148
-                            nullable = nullable,
149
-                            uniq = uniq,
150
-                            primary = primary,
151
-                            **args
152
-        );
153
-        pass
154
-
155
-## @brief Abstract class for fieldtypes handling multiple values identified by a key
156
-#
157
-# For example i18n fieldtype
158
-class MultiValueFieldType(GenericFieldType):
159
-    
160
-    ## @brief Instanciate a new multivalue fieldtype
161
-    #
162
-    # This fieldtype describe a field that handles multiple values referenced by a key (like a dict).
163
-    # A typicall example is the i18n fieldtype
164
-    # @param keyname str : The identifier key name
165
-    # @param key_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
166
-    # @param value_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
167
-    # @param internal str|False : if False the field is not internal. Else can be 'autosql' or 'internal'
168
-    # @param **args
169
-    def __init__(self, keyname, key_fieldtype, value_fieldtype, internal = False, **args):
170
-        super().__init__(internal)
171
-        ## stores the keyname
172
-        self.keyname = keyname
173
-        ## stores the fieldtype that handles the key
174
-        self.key_fieldtype = key_fieldtype
175
-        ## stores the fieldtype that handles the values
176
-        self.value_fieldtype = value_fieldtype
177
-
178
-## @brief Class designed to handle datas access will fieldtypes are constructing datas
179
-#
180
-# This class is designed to allow automatic scheduling of construct_data calls. 
181
-#
182
-# In theory it's able to detect circular dependencies
183
-# @todo test circular deps detection
184
-# @todo test circulat deps false positiv
185
-class DatasConstructor(object):
186
-    
187
-    ## @brief Init a DatasConstructor
188
-    # @param lec LeCrud : LeCrud child class 
189
-    # @param datas dict : dict with field name as key and field values as value
190
-    # @param fieldtypes dict : dict with field name as key and fieldtype as value
191
-    def __init__(self, lec, datas, fieldtypes):
192
-        ## Stores concerned class
193
-        self._lec = lec
194
-        ## Stores datas and constructed datas
195
-        self._datas = copy.copy(datas)
196
-        ## Stores fieldtypes
197
-        self._fieldtypes = fieldtypes
198
-        ## Stores list of fieldname for constructed datas
199
-        self._constructed = []
200
-        ## Stores construct calls list
201
-        self._construct_calls = []
202
-    
203
-    ## @brief Implements the dict.keys() method on instance
204
-    def keys(self):
205
-        return self._datas.keys()
206
-    
207
-    ## @brief Allows to access the instance like a dict
208
-    def __getitem__(self, fname):
209
-        if fname not in self._constructed:
210
-            if fname in self._construct_calls:
211
-                raise RuntimeError('Probably circular dependencies in fieldtypes')
212
-            cur_value = self._datas[fname] if fname in self._datas else None
213
-            self._datas[fname] = self._fieldtypes[fname].construct_data(self._lec, fname, self, cur_value)
214
-            self._constructed.append(fname)
215
-        return self._datas[fname]
216
-    
217
-    ## @brief Allows to set instance values like a dict
218
-    # @warning Should not append in theory
219
-    def __setitem__(self, fname, value):
220
-        self._datas[fname] = value
221
-        warnings.warn("Setting value of an DatasConstructor instance")
222
-        
223
-
224
-#
225
-#
226
-#   Exceptions
227
-#
228
-#
229
-class FieldTypeError(Exception):
230
-    pass
231
-
232
-class FieldTypeDataCheckError(FieldTypeError):
233
-
234
-    ## @brief Instanciate a new data check error
235
-    # @param expt_l list : A list of data check Exception
236
-    def __init__(self, expt_l):
237
-        self._expt_l = expt_l
238
-
239
-    def __str__(self):
240
-        msg = "Data check errors : "
241
-        for expt in self._expt_l:
242
-            msg += "{expt_name}:{expt_msg}; ".format(expt_name=expt.__class__.__name__, expt_msg=str(expt))
243
-        return msg
244
-
245
-## @page lodel2_fieldtypes Lodel2 fieldtypes
246
-#
247
-# @section fieldtypes_features Main features
248
-#
249
-# Lodel2 defines Class and Types containing fields that handle values.
250
-# Fields are defined by FieldTypes. It's objects that are able to check datas value, to construct values (~cast) and to check datas consistency given the list of datas of an Lodel2 object (Class or Type)
251
-#
252
-# @subsection fieldtypes_hierarchy Fieldtypes family
253
-#
254
-# Fieldtypes are python objects. We use inheritance to defines FieldTypes. Here is a list of main FieldTypes inheritance :
255
-# - GenericFieldType
256
-#  - SingleValueFieldType <- handles a single value
257
-#   - ReferenceFieldType <- handles a reference to another field
258
-#    - leo.EmFieldType <- handles references to a LeObject (designed for LeRelation)
259
-#   - char.EmFieldType <- handles string
260
-#   - integer.EmFieldType <- handles integer
261
-#    - pk.EmFieldType <- handles primary keys (identifier)
262
-#  - MultiValueFieldType <- handles multiple values identified by a key
263
-#   - i18n.EmFieldType <- handles a string and its translations
264
-#
265
-# @subsection fieldtypes_options Fieldtypes main options
266
-#
267
-# There is 2 options that are common for every fieldtypes :
268
-# - internal : that indicate who construct the data. Possible values are
269
-#  - False : the field is not internal, its user that provides datas
270
-#  - 'automatic' : The field is internal, its leapi that provides datas (see construct in @ref fieldtypes_validator )
271
-#  - 'autosql' : BAD NAME. The field is internal but it is the datasource that provide the data
272
-# - immutable : Its a boolean that indicate if a fieldtype defined in EditorialModel.classtypes is immutable or HAVE TO be defined in EmClass
273
-# 
274
-# @subsubsection fieldtypes_options_single_value SingleValueFieldType options
275
-#
276
-# SingleValueFieldType have more standart options :
277
-# - nullable (boolean) : is None allowed as value
278
-# - uniq (boolean) : if True the value has to be uniq in all instances of concerned Lodel2 API object
279
-# - primary (boolean) : if True the field is an identifier (primary key)
280
-# - default : if given as argument defines a default value for the FieldType
281
-#
282
-# @subsection fieldtypes_validator Data validation
283
-#
284
-# For each Lodel2 API objects (LeObject, LeRelation, ...) there is a sequence that is run to check datas and transform them. Each step is run for each fieldtypes of a Lodel2 API object
285
-#
286
-# -# Check data : this is a basic data check (for example if an integer is expected and the string 'foo' is given the check will fails)
287
-# -# Construct data : this is a data 'casting' step, this method is called with all other datas from the Lodel2 API object as argument (to be able to construct datas with other parts of the object) @ref fieldtypes_construct_order
288
-# -# Datas consistency checks : this step, as the construct step, has all datas from the Lodel2 API object as argument, and check for the whole datas consistency
289
-#
290
-# @subsubsection fieldtypes_construct_order Data construction dependencies
291
-#
292
-# To handle data construction dependencies there is an object DatasConstructor able to call the data construction when needed and to detect (not tested yet) circular dependencies
293
-# 

+ 0
- 45
EditorialModel/fieldtypes/i18n.py View File

@@ -1,45 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import json
4
-
5
-from Lodel.utils.mlstring import MlString
6
-
7
-from .generic import MultiValueFieldType
8
-from . import char
9
-
10
-class EmFieldType(MultiValueFieldType):
11
-    
12
-    help = 'Fieldtype designed to handle translations'
13
-    
14
-    def __init__(self, value_max_length = 64, **args):
15
-        args['keyname'] = 'lang'
16
-        args['key_fieldtype'] = char.EmFieldType(max_length = 4)
17
-        args['value_fieldtype'] = char.EmFieldType(value_max_length)
18
-        super().__init__(**args)
19
-
20
-    def _check_data_value(self, value):
21
-        if value is None:
22
-            return (None, None)
23
-        if isinstance(value, MlString):
24
-            return (value, None)
25
-        if isinstance(value, dict):
26
-            for val in value.values():
27
-                if not(isinstance(val, str) or val is None) :
28
-                    return (None, ValueError("Expected str as dict values. Bad dict : '%s'" % value))
29
-            return (value, None)
30
-        if isinstance(value, str):
31
-            try:
32
-                MlString(value)
33
-                return (value, None)
34
-            except ValueError:
35
-                return (None, ValueError("Unable to load an MlString from value '%s'" % value))
36
-        return (None, ValueError("Bad value : '%s'" % value))
37
-
38
-    def construct_data(self, lec, fname, datas, cur_value):
39
-        if not isinstance(cur_value, MlString):
40
-            ret = MlString(cur_value)
41
-        else:
42
-            ret = cur_value
43
-        if len(ret.get_default()) == 0 and len(ret.values()) > 0:
44
-            ret.set_default(ret[list(ret.keys())[0]])
45
-        return ret

+ 0
- 21
EditorialModel/fieldtypes/integer.py View File

@@ -1,21 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from .generic import SingleValueFieldType
4
-
5
-
6
-class EmFieldType(SingleValueFieldType):
7
-
8
-    help = 'Basic integer field'
9
-
10
-    ftype = 'int'
11
-
12
-    def __init__(self, **kwargs):
13
-        super(EmFieldType, self).__init__(ftype='int', **kwargs)
14
-
15
-    def _check_data_value(self, value):
16
-        error = None
17
-        try:
18
-            value = int(value)
19
-        except (ValueError, TypeError):
20
-            error = TypeError("the value '%s' is not, and will never be an integer" % value)
21
-        return (value, error)

+ 0
- 26
EditorialModel/fieldtypes/join.py View File

@@ -1,26 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import warnings
4
-
5
-from . import char
6
-
7
-class EmFieldType(char.EmFieldType):
8
-    help = 'Automatic string field, defined as concatenation of other fields'
9
-    
10
-    ## @brief Instanciate a fieldtype designed to concatenate fields
11
-    # @param field_list list : fieldname list
12
-    # @param glue str : separator
13
-    # @param max_length int : Field max length
14
-    # @param **kwargs
15
-    def __init__(self, field_list, max_length, glue = ' ', **kwargs):
16
-        self._field_list = field_list
17
-        super().__init__(internal='automatic', max_length = max_length)
18
-
19
-    def construct_data(self, lec, fname, datas, cur_value):
20
-        ret = ''
21
-        for fname in self._field_list:
22
-            ret += datas[fname]
23
-        if len(ret) > self.max_length:
24
-            warnings.warn("Join field overflow. Truncating value")
25
-            ret = ret[:self.max_length-1]
26
-        return ret

+ 0
- 68
EditorialModel/fieldtypes/leo.py View File

@@ -1,68 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import leapi.letype as letype
4
-import leapi.leclass as leclass
5
-
6
-from .generic import ReferenceFieldType, FieldTypeError
7
-
8
-class EmFieldType(ReferenceFieldType):
9
-    
10
-    help = 'Fieldtypes designed to handle pk of LeObject in LeRelations'
11
-
12
-    _construct_data_deps = []
13
-    
14
-    ## @todo Replace hardcoded string for reference initialisation
15
-    def __init__(self, superior=True, **kwargs):
16
-        super().__init__(superior = superior, reference='object', **kwargs)
17
-
18
-    def _check_data_value(self, value):
19
-        if not isinstance(value, int):
20
-            if not letype._LeType.implements_leobject():
21
-                return (None, ValueError("An instance of a child class of LeType was expected"))
22
-            if not hasattr(value, 'lodel_id'):
23
-                return (None, ValueError("The LeType instance given has no lodel_id !"))
24
-        return (value, None)
25
-    
26
-    ## @brief If field value is an integer, returns a partially instanciated LeObject (only with an ID)
27
-    # @todo what should we do if the get fails ? Raise ?
28
-    def construct_data(self, lec, fname, datas, cur_value):
29
-        if isinstance(cur_value, str):
30
-            # Cast to int
31
-            try:
32
-                cur_value = int(cur_value)
33
-            except (ValueError, TypeError) as e:
34
-                raise e # Raise Here !?
35
-        if hasattr(cur_value, 'is_leobject') and cur_value.is_leobject():
36
-            # Its an object not populated (we dont now its type)
37
-            cur_value = cur_value.lodel_id #Optimize here giving only class_id and type_id to populate ?
38
-        if isinstance(cur_value, int):
39
-            # Get instance with id
40
-            resget = lec.name2class('LeObject').get(['lodel_id = %d' % cur_value])
41
-            if resget is None or len(resget) != 1:
42
-                # Bad filter or bad id... raise ?
43
-                raise Exception("BAAAAAD")
44
-        return cur_value
45
-    
46
-    ## @brief checks datas consistency
47
-    # @param lec LeCrud : A LeCrud child instance
48
-    # @param fname str : concerned field name
49
-    # @param datas dict : Datas of lec
50
-    # @return True if ok else an Exception instance
51
-    def check_data_consistency(self, lec, fname, datas):
52
-        if self.superior:
53
-            return self.check_sup_consistency(lec, fname, datas)
54
-        else:
55
-            return self.check_sub_consistency(lec, fname, datas)
56
-
57
-    def check_sup_consistency(self, lec, fname, datas):
58
-        if lec.implements_lerel2type():
59
-            # Checking consistency for a rel2type relation
60
-            lesup = datas[lec._superior_field_name]
61
-            lesub = datas[lec._subordinate_field_name]
62
-            if lesub.__class__ not in lesup._linked_types.values():
63
-                return FieldTypeError("Rel2type not authorized between %s and %s"%(lesup, lesub))
64
-        pass
65
-            
66
-
67
-    def check_sub_consistency(self, lec, fname, datas):
68
-        pass

+ 0
- 24
EditorialModel/fieldtypes/namerelation.py View File

@@ -1,24 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from EditorialModel import classtypes as lodelconst
4
-
5
-from . import char
6
-from .generic import FieldTypeError
7
-
8
-class EmFieldType(char.EmFieldType):
9
-    help = 'Only designed to handle relation_name field value'
10
-
11
-    def __init__(self, **kwargs):
12
-        super().__init__(**kwargs)
13
-
14
-    def check_data_consistency(self, lec, fname, datas):
15
-        # We are in a context where lec is a LeRelation child class
16
-        if lec.implements_lerel2type():
17
-            superior = datas[lodelconst.relation_superior]
18
-            if datas[fname] not in superior._linked_types.keys():
19
-                return FieldTypeError("Bad relation_name for rel2type %s : '%s'" % (lec.__name__, datas[fname]))
20
-        elif  (datas[fname] is not None) and len(datas[fname] > 0):
21
-            return FieldTypeError("No relation_name allowed for hierarchical relations")
22
-        return True
23
-            
24
-

+ 0
- 48
EditorialModel/fieldtypes/naturerelation.py View File

@@ -1,48 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from . import char
4
-
5
-import EditorialModel.classtypes as classtypes
6
-import leapi.lerelation as lerelation
7
-
8
-class EmFieldType(char.EmFieldType):
9
-    
10
-    help = 'Stores a relation\'s nature'
11
-
12
-    def __init__(self, **kwargs):
13
-        kwargs.update({'nullable': True})
14
-        super().__init__(**kwargs)
15
-
16
-    def _check_data_value(self, value):
17
-        if value is None or ( value in classtypes.EmNature.getall()):
18
-            return value, None
19
-        else:
20
-            return None, ValueError("Unknown relation nature '%s'"%value)
21
-    
22
-    ## @todo implements types checks
23
-    def check_data_consistency(self, lec, fname, datas):
24
-        #Checking given component
25
-        #Replace with a test from _LeCrud
26
-        #if not isinstance(lec, lerelation._LeRelation):
27
-        #    return ValueError("A field naturerelation has to be in a LeRelation object, but this one is in a '%s'"%lec.__name__)
28
-        nature = datas[fname]
29
-        lesup = datas[lec._superior_field_name]
30
-        lesub = datas[lec._subordinate_field_name]
31
-        if nature is None:
32
-            #if not isinstance(lec, lerelation.LeRel2Type):
33
-            #Replace with a test from _LeCrud
34
-            #    return ValueError("Only LeRel2Type are allowed to have NULL nature")
35
-            pass
36
-        else:
37
-            #if not isinstance(lec, lerelation.LeHierarch):
38
-            #Replace with a test from _LeCrud
39
-            #    return ValueError("Only LeHierarch has not NULL nature")
40
-            #Checking if nature <=> lesup & lesub classtypes
41
-            if not lesub.is_partial():
42
-                if nature not in classtypes.EmClassType.natures(lesub._classtype):
43
-                    return ValueError("Invalid nature '%s' for %s"%(nature, lesub.__class__.__name__))
44
-            
45
-            if not lesup.is_partial() and not lesup.is_root():
46
-                if nature not in classtypes.EmClassType.natures(lesup._classtype):
47
-                    return ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
48
-        return True

+ 0
- 26
EditorialModel/fieldtypes/pk.py View File

@@ -1,26 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-from . import integer
4
-
5
-
6
-## @todo This EmFieldType is a bit dirty....
7
-class EmFieldType(integer.EmFieldType):
8
-
9
-    help = 'Integer primary key fieldtype'
10
-
11
-    def __init__(self, **kwargs):
12
-        # Allowed argument => value for this EmFieldType
13
-        allowed = {
14
-            'nullable': False,
15
-            'uniq': False,
16
-            'internal': 'autosql',
17
-            'primary': True,
18
-        }
19
-        # Checking args
20
-        for name, value in kwargs.items():
21
-            if name in allowed and value != allowed[name]:
22
-                raise ValueError("The value '%s' for argument '%s' for pk EmFieldType is not allowed" % (value, name))
23
-
24
-        kwargs.update(allowed)
25
-
26
-        super(EmFieldType, self).__init__(**kwargs)

+ 0
- 27
EditorialModel/fieldtypes/rank.py View File

@@ -1,27 +0,0 @@
1
-#-*- coding utf-8 -*-
2
-
3
-import EditorialModel.classtypes
4
-import leapi.lerelation as lerelation
5
-
6
-from .generic import FieldTypeError
7
-from . import integer
8
-
9
-class EmFieldType(integer.EmFieldType):
10
-    
11
-    help = 'Fieldtype designed to handle relations rank'
12
-
13
-    _construct_datas_deps = [EditorialModel.classtypes.relation_superior]
14
-
15
-    def __init__(self, **kwargs):
16
-        super().__init__(**kwargs)
17
-
18
-    def construct_data(self, lec, fname, datas, cur_value):
19
-        superior = datas[EditorialModel.classtypes.relation_superior]
20
-        if lec.is_lerel2type():
21
-            relation_name = datas[EditorialModel.classtypes.relation_name]
22
-            cur_value = lec.get_max_rank(superior, relation_name)
23
-        elif lec.is_lehierarch():
24
-            cur_value = lec.get_max_rank(superior, datas['nature'])
25
-        else:
26
-            raise ValueError("Called with bad class : ", lec.__name__)
27
-        return cur_value

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

@@ -1,27 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import re
4
-from . import char
5
-
6
-
7
-class EmFieldType(char.EmFieldType):
8
-
9
-    help = 'String field validated with a regex. Take two options : max_length and regex'
10
-
11
-    ## @brief A char field validated with a regex
12
-    # @param regex str : a regex string (passed as argument to re.compile() )
13
-    # @param max_length int : the maximum length for this field
14
-    # @param **kwargs
15
-    def __init__(self, regex='', max_length=10, **kwargs):
16
-        self.regex = regex
17
-        self.compiled_re = re.compile(regex)  # trigger an error if invalid regex
18
-
19
-
20
-        super().__init__(check_data_value=check_value, max_length=max_length, **kwargs)
21
-
22
-    def _check_data_value(self,value):
23
-        error = None
24
-        if not self.compiled_re.match(value):
25
-            value = ''
26
-            error = TypeError('"%s" don\'t match the regex "%s"' % (value, self.regex))
27
-        return (value, error)

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

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

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

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

+ 0
- 330
EditorialModel/model.py View File

@@ -1,330 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.model
4
-# Contains the class managing and editorial model
5
-
6
-import EditorialModel
7
-from DataSource.dummy.migrationhandler import DummyMigrationHandler
8
-from EditorialModel.backend.dummy_backend import EmBackendDummy
9
-from EditorialModel.classes import EmClass
10
-from EditorialModel.fields import EmField
11
-from EditorialModel.types import EmType
12
-from EditorialModel.exceptions import EmComponentCheckError, EmComponentNotExistError, MigrationHandlerChangeError
13
-import hashlib
14
-
15
-
16
-## @brief Manages the Editorial Model
17
-class Model(object):
18
-
19
-    components_class = [EmClass, EmType, EmField]
20
-
21
-    ## Constructor
22
-    #
23
-    # @param backend unknown: A backend object instanciated from one of the classes in the backend module
24
-    # @param migration_handler : A migration handler
25
-    def __init__(self, backend, migration_handler=None):
26
-        if migration_handler is None:
27
-            self.migration_handler = DummyMigrationHandler()
28
-        elif issubclass(migration_handler.__class__, DummyMigrationHandler):
29
-            self.migration_handler = migration_handler
30
-        else:
31
-            raise TypeError("migration_handler should be an instance from a subclass of DummyMigrationhandler")
32
-        self.backend = None
33
-        self.set_backend(backend)
34
-
35
-        self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': []}
36
-        self.load()
37
-
38
-    def __hash__(self):
39
-        components_dump = ""
40
-        for _, comp in self._components['uids'].items():
41
-            components_dump += str(hash(comp))
42
-        hashstring = hashlib.new('sha512')
43
-        hashstring.update(components_dump.encode('utf-8'))
44
-        return int(hashstring.hexdigest(), 16)
45
-
46
-    def __eq__(self, other):
47
-        return self.__hash__() == other.__hash__()
48
-
49
-    @staticmethod
50
-    ## Given a name return an EmComponent child class
51
-    # @param class_name str : The name to identify an EmComponent class
52
-    # @return A python class or False if the class_name is not a name of an EmComponent child class
53
-    def emclass_from_name(class_name):
54
-        for cls in Model.components_class:
55
-            if cls.__name__ == class_name:
56
-                return cls
57
-        return False
58
-
59
-    @staticmethod
60
-    ## Given a python class return a name
61
-    # @param em_class : The python class we want the name
62
-    # @return A class name as string or False if cls is not an EmComponent child class
63
-    def name_from_emclass(em_class):
64
-        if em_class not in Model.components_class:
65
-            return False
66
-        return em_class.__name__
67
-
68
-    ## Loads the structure of the Editorial Model
69
-    #
70
-    # Gets all the objects contained in that structure and creates a dict indexed by their uids
71
-    # @todo Change the thrown exception when a components check fails
72
-    # @throw ValueError When a component class don't exists
73
-    def load(self):
74
-        datas = self.backend.load()
75
-        for uid, kwargs in datas.items():
76
-            #Store and delete the EmComponent class name from datas
77
-            cls_name = kwargs['component']
78
-            del kwargs['component']
79
-
80
-            cls = self.emclass_from_name(cls_name)
81
-
82
-            if cls:
83
-                kwargs['uid'] = uid
84
-                # create a dict for the component and one indexed by uids, store instanciated component in it
85
-                self._components['uids'][uid] = cls(model=self, **kwargs)
86
-                self._components[cls_name].append(self._components['uids'][uid])
87
-            else:
88
-                raise ValueError("Unknow EmComponent class : '" + cls_name + "'")
89
-
90
-        #Sorting by rank
91
-        for component_class in Model.components_class:
92
-            self.sort_components(component_class)
93
-
94
-        #Check integrity
95
-        loaded_comps = [(uid, component) for uid, component in self._components['uids'].items()]
96
-        for uid, component in loaded_comps:
97
-            try:
98
-                component.check()
99
-            except EmComponentCheckError as exception_object:
100
-                raise EmComponentCheckError("The component with uid %d is not valid. Check returns the following error : \"%s\"" % (uid, str(exception_object)))
101
-            #Everything is done. Indicating that the component initialisation is over
102
-            component.init_ended()
103
-
104
-    ## Saves data using the current backend
105
-    # @param filename str | None : if None use the current backend file (provided at backend instanciation)
106
-    def save(self, filename=None):
107
-        return self.backend.save(self, filename)
108
-
109
-    ## Given a EmComponent child class return a list of instances
110
-    # @param cls EmComponent|str : A python class
111
-    # @return a list of instances or False if the class is not an EmComponent child
112
-    # @todo better implementation
113
-    def components(self, cls=None):
114
-        if isinstance(cls, str):
115
-            cls = self.emclass_from_name(cls)
116
-            if not cls:
117
-                return False
118
-        if cls is None:
119
-            return [self.component(uid) for uid in self._components['uids']]
120
-        key_name = self.name_from_emclass(cls)
121
-        return False if key_name is False else self._components[key_name]
122
-
123
-    ## Return an EmComponent given an uid
124
-    # @param uid int : An EmComponent uid
125
-    # @return The corresponding instance or False if uid don't exists
126
-    def component(self, uid):
127
-        return False if uid not in self._components['uids'] else self._components['uids'][uid]
128
-
129
-    ## @brief Search in all the editorial model for a component with a specific name
130
-    # @param name str : the searched name
131
-    # @param comp_cls str|EmComponent : filter on component type (see components() method)
132
-    # @return a list of component with a specific name
133
-    def component_from_name(self, name, comp_cls=None):
134
-        if comp_cls == EmField or comp_cls == 'EmField':
135
-            res = list()
136
-            for field, fieldname in [(f, f.name) for f in self.components('EmField')]:
137
-                if fieldname == name:
138
-                    res.append(field)
139
-            return res
140
-
141
-        for comp, compname in [(c, c.name) for c in self.components(comp_cls)]:
142
-            if compname == name:
143
-                return comp
144
-
145
-        return False
146
-
147
-    ## Sort components by rank in Model::_components
148
-    # @param component_class pythonClass : The type of components to sort
149
-    # @throw AttributeError if emclass is not valid
150
-    # @warning disabled the test on component_class because of EmField new way of working
151
-    def sort_components(self, component_class):
152
-        #if component_class not in self.components_class:
153
-        #    raise AttributeError("Bad argument emclass : '" + str(component_class) + "', excpeting one of " + str(self.components_class))
154
-
155
-        self._components[self.name_from_emclass(component_class)] = sorted(self.components(component_class), key=lambda comp: comp.rank)
156
-
157
-    ## Return a new uid
158
-    # @return a new uid
159
-    def new_uid(self):
160
-        used_uid = [int(uid) for uid in self._components['uids'].keys()]
161
-        return sorted(used_uid)[-1] + 1 if len(used_uid) > 0 else 1
162
-
163
-    ## Create a component from a component type and datas
164
-    #
165
-    # @note if datas does not contains a rank the new component will be added last
166
-    # @note datas['rank'] can be an integer or two specials strings 'last' or 'first'
167
-    #
168
-    # @warning The uid parameter is designed to be used only by Model.load()
169
-    # @param uid int|None : If given, don't generate a new uid
170
-    # @param component_type str : a component type ( component_class, component_fieldgroup, component_field or component_type )
171
-    # @param datas dict : the options needed by the component creation
172
-    # @return The created EmComponent
173
-    # @throw ValueError if datas['rank'] is not valid (too big or too small, not an integer nor 'last' or 'first' )
174
-    # @todo Handle a raise from the migration handler
175
-    # @todo Transform the datas arg in **datas ?
176
-    def create_component(self, component_type, datas, uid=None):
177
-        if not (uid is None) and (not isinstance(uid, int) or uid <= 0 or uid in self._components['uids']):
178
-            raise ValueError("Invalid uid provided : %s" % repr(uid))
179
-
180
-        if component_type not in [n for n in self._components.keys() if n != 'uids']:
181
-            raise ValueError("Invalid component_type rpovided")
182
-        else:
183
-            em_obj = self.emclass_from_name(component_type)
184
-
185
-        rank = 'last'
186
-        if 'rank' in datas:
187
-            rank = datas['rank']
188
-            del datas['rank']
189
-
190
-        datas['uid'] = uid if uid else self.new_uid()
191
-        em_component = em_obj(model=self, **datas)
192
-
193
-        em_component.rank = em_component.get_max_rank() + 1  # Inserting last by default
194
-
195
-        self._components['uids'][em_component.uid] = em_component
196
-        self._components[component_type].append(em_component)
197
-
198
-        if rank != 'last':
199
-            em_component.set_rank(1 if rank == 'first' else rank)
200
-
201
-        #everything done, indicating that initialisation is over
202
-        em_component.init_ended()
203
-
204
-        #register the creation in migration handler
205
-        try:
206
-            self.migration_handler.register_change(self, em_component.uid, None, em_component.attr_dump())
207
-        except MigrationHandlerChangeError as exception_object:
208
-            #Revert the creation
209
-            self.components(em_component.__class__).remove(em_component)
210
-            del self._components['uids'][em_component.uid]
211
-            raise exception_object
212
-
213
-        self.migration_handler.register_model_state(self, hash(self))
214
-
215
-        if uid is None:
216
-            #Checking the component
217
-            em_component.check()
218
-            if component_type == 'EmClass':
219
-                # !!! If uid is not None it means that we shouldn't create components automatically !!!
220
-                self.add_default_class_fields(em_component.uid)
221
-
222
-        return em_component
223
-
224
-    ## @brief Add to a class (if not exists) the default fields
225
-    #
226
-    # @param class_uid int : An EmClass uid
227
-    # @throw ValueError if class_uid in not an EmClass uid
228
-    def add_default_class_fields(self, class_uid):
229
-        if class_uid not in self._components['uids']:
230
-            raise ValueError("The uid '%d' don't exists" % class_uid)
231
-        emclass = self._components['uids'][class_uid]
232
-        if not isinstance(emclass, EditorialModel.classes.EmClass):
233
-            raise ValueError("The uid '%d' is not an EmClass uid" % class_uid)
234
-
235
-        """
236
-        fgroup_name = EmClass.default_fieldgroup
237
-
238
-        if fgroup_name not in [fg.name for fg in emclass.fieldgroups() ]:
239
-            #Creating the default fieldgroup if not existing
240
-            fg_datas = { 'name' : fgroup_name, 'class_id': emclass.uid }
241
-            fgroup = self.create_component('EmFieldGroup', fg_datas)
242
-            fgid = fgroup.uid
243
-        else:
244
-            for fg in emclass.fieldgroups():
245
-                if fg.name == fgroup_name:
246
-                    fgid = fg.uid
247
-                    break
248
-        """
249
-
250
-        default_fields = emclass.default_fields_list()
251
-        for fname, fdatas in default_fields.items():
252
-            if not (fname in [f.name for f in emclass.fields()]):
253
-                #Adding the field
254
-                fdatas['name'] = fname
255
-                fdatas['class_id'] = class_uid
256
-                self.create_component('EmField', fdatas)
257
-
258
-    ## Delete a component
259
-    # @param uid int : Component identifier
260
-    # @throw EmComponentNotExistError
261
-    # @todo unable uid check
262
-    # @todo Handle a raise from the migration handler
263
-    def delete_component(self, uid):
264
-        em_component = self.component(uid)
265
-        if not em_component:
266
-            raise EmComponentNotExistError()
267
-
268
-        if em_component.delete_check():
269
-            #register the deletion in migration handler
270
-            self.migration_handler.register_change(self, uid, self.component(uid).attr_dump(), None)
271
-
272
-            # delete internal lists
273
-            self._components[self.name_from_emclass(em_component.__class__)].remove(em_component)
274
-            del self._components['uids'][uid]
275
-
276
-            #Register the new EM state
277
-            self.migration_handler.register_model_state(self, hash(self))
278
-            return True
279
-
280
-        return False
281
-
282
-    ## Changes the current backend
283
-    #
284
-    # @param backend unknown: A backend object
285
-    def set_backend(self, backend):
286
-        if issubclass(backend.__class__, EmBackendDummy):
287
-            self.backend = backend
288
-        else:
289
-            raise TypeError('Backend should be an instance of a EmBackednDummy subclass')
290
-
291
-    ## Returns a list of all the EmClass objects of the model
292
-    def classes(self):
293
-        return list(self._components[self.name_from_emclass(EmClass)])
294
-
295
-    ## Use a new migration handler, re-apply all the ME to this handler
296
-    #
297
-    # @param new_mh MigrationHandler: A migration_handler object
298
-    # @warning : if a relational-attribute field (with 'rel_field_id') comes before it's relational field (with 'rel_to_type_id'), this will blow up
299
-    def migrate_handler(self, new_mh):
300
-        new_me = Model(EmBackendDummy(), new_mh)
301
-        relations = {'fields_list': [], 'superiors_list': []}
302
-
303
-        # re-create component one by one, in components_class[] order
304
-        for cls in self.components_class:
305
-            for component in self.components(cls):
306
-                component_type = self.name_from_emclass(cls)
307
-                component_dump = component.attr_dump()
308
-                # Save relations between component to apply them later
309
-                for relation in relations.keys():
310
-                    if relation in component_dump and component_dump[relation]:
311
-                        relations[relation].append((component.uid, component_dump[relation]))
312
-                        del component_dump[relation]
313
-                new_me.create_component(component_type, component_dump, component.uid)
314
-
315
-        # apply selected field  to types
316
-        for fields_list in relations['fields_list']:
317
-            uid, fields = fields_list
318
-            for field_id in fields:
319
-                new_me.component(uid).select_field(new_me.component(field_id))
320
-
321
-        # add superiors to types
322
-        for superiors_list in relations['superiors_list']:
323
-            uid, sup_list = superiors_list
324
-            for nature, superiors_uid in sup_list.items():
325
-                for superior_uid in superiors_uid:
326
-                    new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
327
-
328
-        del new_me
329
-
330
-        self.migration_handler = new_mh

+ 0
- 193
EditorialModel/randomem.py View File

@@ -1,193 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @package EditorialModel.randomem
4
-#
5
-# @warning BROKEN because there is no more fieldgroups
6
-# Provide methods for random EM generation
7
-
8
-import random
9
-from EditorialModel.backend.dummy_backend import EmBackendDummy
10
-from EditorialModel.model import Model
11
-from EditorialModel.fields import EmField
12
-from EditorialModel.types import EmType
13
-from EditorialModel.classtypes import EmClassType
14
-from Lodel.utils.mlstring import MlString
15
-
16
-
17
-class RandomEm(object):
18
-
19
-    ## @brief Instanciate a class allowing to generate random EM
20
-    # @see RandomEm::random_em()
21
-    def __init__(self, backend=None, **kwargs):
22
-        self.backend = backend
23
-        self.kwargs = kwargs
24
-
25
-    ## @brief Return a random EM
26
-    # @return A random EM
27
-    def gen(self):
28
-        return self.random_em(self.backend, **self.kwargs)
29
-
30
-    @classmethod
31
-    ## @brief Generate a random editorial model
32
-    #
33
-    # The random generator can be tuned with integer parameters
34
-    # that represent probability or maximum numbers of items.
35
-    # The probability (chances) works like 1/x chances to append
36
-    # with x the tunable parameter
37
-    # Tunable generator parameters :
38
-    # - classtype : Chances for a classtype to be empty (default 0)
39
-    # - nclass : Maximum number of classes per classtypes (default 5)
40
-    # - nofg : Chances for a classe to have no fieldgroup associated to it (default 10)
41
-    # - notype : Chances for a classe to have no type associated to it (default 5)
42
-    # - seltype : Chances for a type to select an optionnal field (default 2)
43
-    # - ntypesuperiors : Chances for a type to link with a superiors (default 3)
44
-    # - nofields : Chances for a fieldgroup to be empty (default 10)
45
-    # - nfields : Maximum number of field per fieldgroups (default 8)
46
-    # - rfields : Maximum number of relation_to_type attributes fields (default 5)
47
-    # - optfield : Chances for a field to be optionnal (default 2)
48
-    # @param backend : A backend to use with the new EM
49
-    # @param **kwargs dict : Provide tunable generation parameter
50
-    # @param cls
51
-    # @return A randomly generate EM
52
-    def random_em(cls, backend=None, **kwargs):
53
-        ed_mod = Model(EmBackendDummy if backend is None else backend)
54
-
55
-        chances = {
56
-            'classtype': 0,         # a class in classtype
57
-            'nclass': 5,            # max number of classes per classtype
58
-            'nofg': 10,             # no fieldgroup in a class
59
-            'nfg': 5,               # max number of fieldgroups per classes
60
-            'notype': 10,           # no types in a class
61
-            'ntype': 8,             # max number of types in a class
62
-            'seltype': 2,           # chances to select an optional field
63
-            'ntypesuperiors': 2,    # chances to link with a superior
64
-            'nofields': 10,         # no fields in a fieldgroup
65
-            'nfields': 8,           # max number of fields per fieldgroups
66
-            'nr2tfields': 1,        # max number of rel2type fields per fieldgroups
67
-            'rfields': 5,           # max number of attributes relation fields
68
-            'optfield': 2,          # chances to be optionnal
69
-        }
70
-
71
-        for name, value in kwargs.items():
72
-            if name not in chances:
73
-                #warning
74
-                pass
75
-            else:
76
-                chances[name] = value
77
-
78
-        #classes creation
79
-        for classtype in EmClassType.getall():
80
-            if random.randint(0, chances['classtype']) == 0:
81
-                for _ in range(random.randint(1, chances['nclass'])):
82
-                    cdats = cls._rnd_component_datas()
83
-                    cdats['classtype'] = classtype['name']
84
-                    ed_mod.create_component('EmClass', cdats)
85
-
86
-        for emclass in ed_mod.classes():
87
-            #fieldgroups creation
88
-            """
89
-            if random.randint(0, chances['nofg']) != 0:
90
-                for _ in range(random.randint(1, chances['nfg'])):
91
-                    fgdats = cls._rnd_component_datas()
92
-                    fgdats['class_id'] = emclass.uid
93
-                    ed_mod.create_component('EmFieldGroup', fgdats)
94
-            """
95
-
96
-            #types creation
97
-            if random.randint(0, chances['notype']) != 0:
98
-                for _ in range(random.randint(1, chances['ntype'])):
99
-                    tdats = cls._rnd_component_datas()
100
-                    tdats['class_id'] = emclass.uid
101
-                    ed_mod.create_component('EmType', tdats)
102
-
103
-        #random type hierarchy
104
-        for emtype in ed_mod.components(EmType):
105
-            possible = emtype.possible_superiors()
106
-            for nat in possible:
107
-                if len(possible[nat]) > 0 and random.randint(0, chances['ntypesuperiors']) == 0:
108
-                    random.shuffle(possible[nat])
109
-                    for i in range(random.randint(1, len(possible[nat]))):
110
-                        emtype.add_superior(possible[nat][i], nat)
111
-
112
-        #fields creation
113
-        ft_l = [ftname for ftname in EmField.fieldtypes_list() if ftname != 'pk' and ftname != 'rel2type']
114
-        for emc in ed_mod.components('EmClass'):
115
-            if random.randint(0, chances['nofields']) != 0:
116
-                for _ in range(random.randint(1, chances['nfields'])):
117
-                    field_type = ft_l[random.randint(0, len(ft_l) - 1)]
118
-                    fdats = cls._rnd_component_datas()
119
-                    fdats['fieldtype'] = field_type
120
-                    fdats['class_id'] = emc.uid
121
-                    if random.randint(0, chances['optfield']) == 0:
122
-                        fdats['optional'] = True
123
-                    ed_mod.create_component('EmField', fdats)
124
-
125
-        #rel2type creation (in case none where created before
126
-        for emc in ed_mod.components('EmClass'):
127
-            for _ in range(random.randint(0, chances['nr2tfields'])):
128
-                field_type = 'rel2type'
129
-                fdats = cls._rnd_component_datas()
130
-                fdats['fieldtype'] = field_type
131
-                fdats['class_id'] = emc.uid
132
-                emtypes = ed_mod.components(EmType)
133
-                fdats['rel_to_type_id'] = emtypes[random.randint(0, len(emtypes) - 1)].uid
134
-                if random.randint(0, chances['optfield']) == 0:
135
-                    fdats['optional'] = True
136
-                ed_mod.create_component('EmField', fdats)
137
-
138
-        # relationnal fields creation
139
-        ft_l = [field_type for field_type in EmField.fieldtypes_list() if field_type != 'rel2type' and field_type != 'pk']
140
-        for emrelf in [f for f in ed_mod.components(EmField) if f.fieldtype == 'rel2type']:
141
-            for _ in range(0, chances['rfields']):
142
-                field_type = ft_l[random.randint(0, len(ft_l) - 1)]
143
-                fdats = cls._rnd_component_datas()
144
-                fdats['rel_field_id'] = emrelf.uid
145
-                fdats['fieldtype'] = field_type
146
-                fdats['class_id'] = emrelf.class_id
147
-                if random.randint(0, chances['optfield']) == 0:
148
-                    fdats['optional'] = True
149
-                ed_mod.create_component('EmField', fdats)
150
-
151
-        #selection optionnal fields
152
-        for emtype in ed_mod.components(EmType):
153
-            selectable = [field for field in emtype.em_class.fields() if field.optional]
154
-            for field in selectable:
155
-                if random.randint(0, chances['seltype']) == 0:
156
-                    emtype.select_field(field)
157
-        return ed_mod
158
-
159
-    @staticmethod
160
-    ## @brief Generate a random string
161
-    # @warning dirty cache trick with globals()
162
-    # @return a randomly selected string
163
-    def _rnd_str(words_src='/usr/share/dict/words'):
164
-        if '_words' not in globals() or globals()['_words_fname'] != words_src:
165
-            globals()['_words_fname'] = words_src
166
-            with open(words_src, 'r') as fpw:
167
-                globals()['_words'] = [l.strip() for l in fpw]
168
-        words = globals()['_words']
169
-        return words[random.randint(0, len(words) - 1)]
170
-
171
-    @classmethod
172
-    ## @brief Generate a random MlString
173
-    # @param cls
174
-    # @param nlng : Number of langs in the MlString
175
-    # @return a random MlString with nlng translations
176
-    # @todo use a dict to generated langages
177
-    def _rnd_mlstr(cls, nlng):
178
-        ret = MlString()
179
-        for _ in range(nlng):
180
-            ret.set(cls._rnd_str(), cls._rnd_str())
181
-        return ret
182
-
183
-    @classmethod
184
-    ## @brief returns randomly generated datas for an EmComponent
185
-    # @return a dict with name, string and help_text
186
-    def _rnd_component_datas(cls):
187
-        mlstr_nlang = 2
188
-        ret = dict()
189
-        ret['name'] = cls._rnd_str()
190
-        ret['string'] = cls._rnd_mlstr(mlstr_nlang)
191
-        ret['help_text'] = cls._rnd_mlstr(mlstr_nlang)
192
-
193
-        return ret

+ 0
- 0
EditorialModel/test/__init__.py View File


+ 0
- 603
EditorialModel/test/me.json View File

@@ -1,603 +0,0 @@
1
-{
2
- "1": {
3
-  "date_create": "Fri Oct 16 11:05:04 2015",
4
-  "component": "EmClass",
5
-  "name": "textes",
6
-  "rank": 1,
7
-  "sortcolumn": "rank",
8
-  "date_update": "Fri Oct 16 11:05:04 2015",
9
-  "classtype": "entity",
10
-  "icon": "0",
11
-  "string": "{\"___\": \"\", \"fre\": \"Texte\"}",
12
-  "help_text": "{\"___\": \"\"}"
13
- },
14
- "2": {
15
-  "date_create": "Fri Oct 16 11:05:04 2015",
16
-  "component": "EmClass",
17
-  "name": "personnes",
18
-  "rank": 1,
19
-  "sortcolumn": "rank",
20
-  "date_update": "Fri Oct 16 11:05:04 2015",
21
-  "classtype": "person",
22
-  "icon": "0",
23
-  "string": "{\"___\": \"\", \"fre\": \"Personnes\"}",
24
-  "help_text": "{\"___\": \"\"}"
25
- },
26
- "4": {
27
-  "nullable": false,
28
-  "name": "titre",
29
-  "date_update": "Fri Oct 16 11:05:04 2015",
30
-  "string": "{\"___\": \"\", \"fre\": \"Titre\"}",
31
-  "help_text": "{\"___\": \"\"}",
32
-  "component": "EmField",
33
-  "date_create": "Fri Oct 16 11:05:04 2015",
34
-  "optional": false,
35
-  "rank": 1,
36
-  "icon": "0",
37
-  "fieldtype": "i18n",
38
-  "rel_field_id": null,
39
-  "class_id": 1,
40
-  "uniq": false,
41
-  "internal": false
42
- },
43
- "5": {
44
-  "component": "EmType",
45
-  "name": "article",
46
-  "date_update": "Fri Oct 16 11:05:04 2015",
47
-  "string": "{\"___\": \"\", \"fre\": \"Article\"}",
48
-  "sortcolumn": "rank",
49
-  "date_create": "Fri Oct 16 11:05:04 2015",
50
-  "help_text": "{\"___\": \"\"}",
51
-  "rank": 1,
52
-  "icon": "0",
53
-  "class_id": 1,
54
-  "fields_list": [
55
-   7
56
-  ],
57
-  "superiors_list": {
58
-   "parent": [
59
-    14
60
-   ]
61
-  }
62
- },
63
- "6": {
64
-  "component": "EmType",
65
-  "name": "personne",
66
-  "date_update": "Fri Oct 16 11:05:04 2015",
67
-  "string": "{\"___\": \"\", \"fre\": \"Personne\"}",
68
-  "sortcolumn": "rank",
69
-  "date_create": "Fri Oct 16 11:05:04 2015",
70
-  "help_text": "{\"___\": \"\"}",
71
-  "rank": 1,
72
-  "icon": "0",
73
-  "class_id": 2,
74
-  "fields_list": [
75
-   10
76
-  ],
77
-  "superiors_list": {}
78
- },
79
- "7": {
80
-  "nullable": false,
81
-  "name": "soustitre",
82
-  "date_update": "Fri Oct 16 11:05:04 2015",
83
-  "string": "{\"___\": \"\", \"fre\": \"Sous-titre\"}",
84
-  "help_text": "{\"___\": \"\"}",
85
-  "component": "EmField",
86
-  "date_create": "Fri Oct 16 11:05:04 2015",
87
-  "optional": true,
88
-  "rank": 5,
89
-  "icon": "0",
90
-  "fieldtype": "i18n",
91
-  "rel_field_id": null,
92
-  "class_id": 1,
93
-  "uniq": false,
94
-  "internal": false
95
- },
96
- "9": {
97
-  "nullable": true,
98
-  "name": "nom",
99
-  "date_update": "Fri Oct 16 11:05:04 2015",
100
-  "string": "{\"___\": \"\", \"fre\": \"Nom\"}",
101
-  "help_text": "{\"___\": \"\"}",
102
-  "component": "EmField",
103
-  "date_create": "Fri Oct 16 11:05:04 2015",
104
-  "optional": false,
105
-  "rank": 1,
106
-  "icon": "0",
107
-  "fieldtype": "char",
108
-  "rel_field_id": null,
109
-  "class_id": 2,
110
-  "uniq": false,
111
-  "internal": false
112
- },
113
- "10": {
114
-  "nullable": true,
115
-  "name": "prenom",
116
-  "date_update": "Fri Oct 16 11:05:04 2015",
117
-  "string": "{\"___\": \"\", \"fre\": \"Pr\\u00e9nom\"}",
118
-  "help_text": "{\"___\": \"\"}",
119
-  "component": "EmField",
120
-  "date_create": "Fri Oct 16 11:05:04 2015",
121
-  "optional": true,
122
-  "rank": 3,
123
-  "icon": "0",
124
-  "fieldtype": "char",
125
-  "rel_field_id": null,
126
-  "class_id": 2,
127
-  "uniq": false,
128
-  "internal": false
129
- },
130
- "11": {
131
-  "nullable": true,
132
-  "name": "auteur",
133
-  "uniq": false,
134
-  "date_update": "Fri Oct 16 11:05:04 2015",
135
-  "string": "{\"___\": \"\", \"fre\": \"Auteur\"}",
136
-  "help_text": "{\"___\": \"\"}",
137
-  "component": "EmField",
138
-  "date_create": "Fri Oct 16 11:05:04 2015",
139
-  "optional": false,
140
-  "rank": 2,
141
-  "icon": "0",
142
-  "fieldtype": "rel2type",
143
-  "rel_field_id": null,
144
-  "class_id": 1,
145
-  "rel_to_type_id": 6,
146
-  "internal": false
147
- },
148
- "12": {
149
-  "nullable": true,
150
-  "name": "adresse",
151
-  "date_update": "Fri Oct 16 11:05:04 2015",
152
-  "string": "{\"___\": \"\", \"fre\": \"Adresse\"}",
153
-  "help_text": "{\"___\": \"\"}",
154
-  "component": "EmField",
155
-  "date_create": "Fri Oct 16 11:05:04 2015",
156
-  "optional": false,
157
-  "rank": 6,
158
-  "icon": "0",
159
-  "fieldtype": "char",
160
-  "rel_field_id": 11,
161
-  "class_id": 1,
162
-  "uniq": false,
163
-  "internal": false
164
- },
165
- "13": {
166
-  "date_create": "Fri Oct 16 11:05:04 2015",
167
-  "component": "EmClass",
168
-  "name": "publication",
169
-  "rank": 2,
170
-  "sortcolumn": "rank",
171
-  "date_update": "Fri Oct 16 11:05:04 2015",
172
-  "classtype": "entity",
173
-  "icon": "0",
174
-  "string": "{\"___\": \"\", \"fre\": \"Publication\"}",
175
-  "help_text": "{\"___\": \"\"}"
176
- },
177
- "14": {
178
-  "component": "EmType",
179
-  "name": "rubrique",
180
-  "date_update": "Fri Oct 16 11:05:04 2015",
181
-  "string": "{\"___\": \"\", \"fre\": \"Rubrique\"}",
182
-  "sortcolumn": "rank",
183
-  "date_create": "Fri Oct 16 11:05:04 2015",
184
-  "help_text": "{\"___\": \"\"}",
185
-  "rank": 1,
186
-  "icon": "0",
187
-  "class_id": 13,
188
-  "fields_list": [],
189
-  "superiors_list": {
190
-   "parent": [
191
-    14,
192
-    19
193
-   ]
194
-  }
195
- },
196
- "16": {
197
-  "nullable": true,
198
-  "name": "titre",
199
-  "date_update": "Fri Oct 16 11:05:04 2015",
200
-  "string": "{\"___\": \"\", \"fre\": \"Titre\"}",
201
-  "help_text": "{\"___\": \"\"}",
202
-  "component": "EmField",
203
-  "date_create": "Fri Oct 16 11:05:04 2015",
204
-  "optional": false,
205
-  "rank": 1,
206
-  "icon": "0",
207
-  "fieldtype": "char",
208
-  "rel_field_id": null,
209
-  "class_id": 13,
210
-  "uniq": false,
211
-  "internal": false
212
- },
213
- "18": {
214
-  "nullable": true,
215
-  "name": "age",
216
-  "date_update": "Fri Oct 16 11:05:04 2015",
217
-  "string": "{\"___\": \"\", \"fre\": \"Age\"}",
218
-  "help_text": "{\"___\": \"\"}",
219
-  "component": "EmField",
220
-  "date_create": "Fri Oct 16 11:05:04 2015",
221
-  "optional": true,
222
-  "rank": 5,
223
-  "icon": "0",
224
-  "fieldtype": "char",
225
-  "rel_field_id": null,
226
-  "class_id": 2,
227
-  "uniq": false,
228
-  "internal": false
229
- },
230
- "19": {
231
-  "component": "EmType",
232
-  "name": "numero",
233
-  "date_update": "Fri Oct 16 11:05:04 2015",
234
-  "string": "{\"___\": \"\", \"fre\": \"Num\\u00e9ro\"}",
235
-  "sortcolumn": "rank",
236
-  "date_create": "Fri Oct 16 11:05:04 2015",
237
-  "help_text": "{\"___\": \"\"}",
238
-  "rank": 2,
239
-  "icon": "0",
240
-  "class_id": 13,
241
-  "fields_list": [],
242
-  "superiors_list": {}
243
- },
244
- "21": {
245
-  "nullable": true,
246
-  "name": "bleu",
247
-  "date_update": "Fri Oct 16 11:05:04 2015",
248
-  "string": "{\"___\": \"\", \"fre\": \"Bleu\"}",
249
-  "help_text": "{\"___\": \"\"}",
250
-  "component": "EmField",
251
-  "date_create": "Fri Oct 16 11:05:04 2015",
252
-  "optional": true,
253
-  "rank": 3,
254
-  "icon": "0",
255
-  "fieldtype": "char",
256
-  "rel_field_id": null,
257
-  "class_id": 1,
258
-  "uniq": false,
259
-  "internal": false
260
- },
261
- "23": {
262
-  "is_id_class": true,
263
-  "name": "class_id",
264
-  "component": "EmField",
265
-  "date_update": "Fri Oct 16 11:05:04 2015",
266
-  "string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
267
-  "help_text": "{\"___\": \"\"}",
268
-  "optional": false,
269
-  "nullable": false,
270
-  "rel_field_id": null,
271
-  "rank": 4,
272
-  "internal": "automatic",
273
-  "fieldtype": "emuid",
274
-  "immutable": true,
275
-  "date_create": "Fri Oct 16 11:05:04 2015",
276
-  "class_id": 1,
277
-  "uniq": false,
278
-  "icon": "0"
279
- },
280
- "24": {
281
-  "nullable": true,
282
-  "name": "string",
283
-  "component": "EmField",
284
-  "date_update": "Fri Oct 16 11:05:04 2015",
285
-  "string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
286
-  "help_text": "{\"___\": \"\"}",
287
-  "internal": "automatic",
288
-  "date_create": "Fri Oct 16 11:05:04 2015",
289
-  "rel_field_id": null,
290
-  "rank": 7,
291
-  "immutable": false,
292
-  "fieldtype": "char",
293
-  "class_id": 1,
294
-  "optional": false,
295
-  "max_length": 128,
296
-  "uniq": false,
297
-  "icon": "0"
298
- },
299
- "25": {
300
-  "is_id_class": false,
301
-  "name": "type_id",
302
-  "date_update": "Fri Oct 16 11:05:04 2015",
303
-  "string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
304
-  "help_text": "{\"___\": \"\"}",
305
-  "component": "EmField",
306
-  "date_create": "Fri Oct 16 11:05:04 2015",
307
-  "rel_field_id": null,
308
-  "rank": 8,
309
-  "internal": "automatic",
310
-  "fieldtype": "emuid",
311
-  "class_id": 1,
312
-  "optional": false,
313
-  "immutable": true,
314
-  "nullable": false,
315
-  "uniq": false,
316
-  "icon": "0"
317
- },
318
- "26": {
319
-  "nullable": false,
320
-  "name": "lodel_id",
321
-  "date_update": "Fri Oct 16 11:05:04 2015",
322
-  "string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
323
-  "help_text": "{\"___\": \"\"}",
324
-  "component": "EmField",
325
-  "date_create": "Fri Oct 16 11:05:04 2015",
326
-  "rel_field_id": null,
327
-  "rank": 9,
328
-  "internal": "autosql",
329
-  "fieldtype": "pk",
330
-  "immutable": true,
331
-  "optional": false,
332
-  "class_id": 1,
333
-  "uniq": false,
334
-  "icon": "0"
335
- },
336
- "28": {
337
-  "is_id_class": true,
338
-  "name": "class_id",
339
-  "date_update": "Fri Oct 16 11:05:04 2015",
340
-  "string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
341
-  "help_text": "{\"___\": \"\"}",
342
-  "component": "EmField",
343
-  "date_create": "Fri Oct 16 11:05:04 2015",
344
-  "rel_field_id": null,
345
-  "rank": 2,
346
-  "internal": "automatic",
347
-  "fieldtype": "emuid",
348
-  "class_id": 2,
349
-  "optional": false,
350
-  "immutable": true,
351
-  "nullable": false,
352
-  "uniq": false,
353
-  "icon": "0"
354
- },
355
- "29": {
356
-  "nullable": true,
357
-  "name": "string",
358
-  "component": "EmField",
359
-  "date_update": "Fri Oct 16 11:05:04 2015",
360
-  "string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
361
-  "help_text": "{\"___\": \"\"}",
362
-  "internal": "automatic",
363
-  "date_create": "Fri Oct 16 11:05:04 2015",
364
-  "rel_field_id": null,
365
-  "rank": 4,
366
-  "immutable": false,
367
-  "fieldtype": "char",
368
-  "class_id": 2,
369
-  "optional": false,
370
-  "max_length": 128,
371
-  "uniq": false,
372
-  "icon": "0"
373
- },
374
- "30": {
375
-  "is_id_class": false,
376
-  "name": "type_id",
377
-  "date_update": "Fri Oct 16 11:05:04 2015",
378
-  "string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
379
-  "help_text": "{\"___\": \"\"}",
380
-  "component": "EmField",
381
-  "date_create": "Fri Oct 16 11:05:04 2015",
382
-  "rel_field_id": null,
383
-  "rank": 6,
384
-  "internal": "automatic",
385
-  "fieldtype": "emuid",
386
-  "class_id": 2,
387
-  "optional": false,
388
-  "immutable": true,
389
-  "nullable": false,
390
-  "uniq": false,
391
-  "icon": "0"
392
- },
393
- "31": {
394
-  "nullable": false,
395
-  "name": "lodel_id",
396
-  "date_update": "Fri Oct 16 11:05:04 2015",
397
-  "string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
398
-  "help_text": "{\"___\": \"\"}",
399
-  "component": "EmField",
400
-  "date_create": "Fri Oct 16 11:05:04 2015",
401
-  "rel_field_id": null,
402
-  "rank": 7,
403
-  "internal": "autosql",
404
-  "fieldtype": "pk",
405
-  "immutable": true,
406
-  "optional": false,
407
-  "class_id": 2,
408
-  "uniq": false,
409
-  "icon": "0"
410
- },
411
- "33": {
412
-  "is_id_class": true,
413
-  "name": "class_id",
414
-  "date_update": "Fri Oct 16 11:05:04 2015",
415
-  "string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
416
-  "help_text": "{\"___\": \"\"}",
417
-  "component": "EmField",
418
-  "date_create": "Fri Oct 16 11:05:04 2015",
419
-  "rel_field_id": null,
420
-  "rank": 2,
421
-  "internal": "automatic",
422
-  "fieldtype": "emuid",
423
-  "class_id": 13,
424
-  "optional": false,
425
-  "immutable": true,
426
-  "nullable": false,
427
-  "uniq": false,
428
-  "icon": "0"
429
- },
430
- "34": {
431
-  "nullable": true,
432
-  "name": "string",
433
-  "component": "EmField",
434
-  "date_update": "Fri Oct 16 11:05:04 2015",
435
-  "string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
436
-  "help_text": "{\"___\": \"\"}",
437
-  "internal": "automatic",
438
-  "date_create": "Fri Oct 16 11:05:04 2015",
439
-  "rel_field_id": null,
440
-  "rank": 3,
441
-  "immutable": false,
442
-  "fieldtype": "char",
443
-  "class_id": 13,
444
-  "optional": false,
445
-  "max_length": 128,
446
-  "uniq": false,
447
-  "icon": "0"
448
- },
449
- "35": {
450
-  "is_id_class": false,
451
-  "name": "type_id",
452
-  "date_update": "Fri Oct 16 11:05:04 2015",
453
-  "string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
454
-  "help_text": "{\"___\": \"\"}",
455
-  "component": "EmField",
456
-  "date_create": "Fri Oct 16 11:05:04 2015",
457
-  "rel_field_id": null,
458
-  "rank": 4,
459
-  "internal": "automatic",
460
-  "fieldtype": "emuid",
461
-  "class_id": 13,
462
-  "optional": false,
463
-  "immutable": true,
464
-  "nullable": false,
465
-  "uniq": false,
466
-  "icon": "0"
467
- },
468
- "36": {
469
-  "nullable": false,
470
-  "name": "lodel_id",
471
-  "date_update": "Fri Oct 16 11:05:04 2015",
472
-  "string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
473
-  "help_text": "{\"___\": \"\"}",
474
-  "component": "EmField",
475
-  "date_create": "Fri Oct 16 11:05:04 2015",
476
-  "rel_field_id": null,
477
-  "rank": 5,
478
-  "internal": "autosql",
479
-  "fieldtype": "pk",
480
-  "immutable": true,
481
-  "optional": false,
482
-  "class_id": 13,
483
-  "uniq": false,
484
-  "icon": "0"
485
- },
486
- "37": {
487
-  "nullable": false,
488
-  "name": "modification_date",
489
-  "date_update": "Wed Nov  4 10:52:13 2015",
490
-  "string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
491
-  "help_text": "{\"___\": \"\"}",
492
-  "component": "EmField",
493
-  "date_create": "Wed Nov  4 10:52:13 2015",
494
-  "optional": false,
495
-  "class_id": 1,
496
-  "rank": 10,
497
-  "internal": "autosql",
498
-  "fieldtype": "datetime",
499
-  "now_on_create": true,
500
-  "rel_field_id": null,
501
-  "immutable": true,
502
-  "uniq": false,
503
-  "now_on_update": true,
504
-  "icon": "0"
505
- },
506
- "38": {
507
-  "nullable": false,
508
-  "name": "creation_date",
509
-  "component": "EmField",
510
-  "date_update": "Wed Nov  4 10:52:13 2015",
511
-  "string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
512
-  "help_text": "{\"___\": \"\"}",
513
-  "internal": "autosql",
514
-  "date_create": "Wed Nov  4 10:52:13 2015",
515
-  "rel_field_id": null,
516
-  "rank": 11,
517
-  "immutable": true,
518
-  "fieldtype": "datetime",
519
-  "now_on_create": true,
520
-  "optional": false,
521
-  "class_id": 1,
522
-  "uniq": false,
523
-  "icon": "0"
524
- },
525
- "39": {
526
-  "nullable": false,
527
-  "name": "modification_date",
528
-  "date_update": "Wed Nov  4 10:52:13 2015",
529
-  "string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
530
-  "help_text": "{\"___\": \"\"}",
531
-  "component": "EmField",
532
-  "date_create": "Wed Nov  4 10:52:13 2015",
533
-  "optional": false,
534
-  "class_id": 2,
535
-  "rank": 8,
536
-  "internal": "autosql",
537
-  "fieldtype": "datetime",
538
-  "now_on_create": true,
539
-  "rel_field_id": null,
540
-  "immutable": true,
541
-  "uniq": false,
542
-  "now_on_update": true,
543
-  "icon": "0"
544
- },
545
- "40": {
546
-  "nullable": false,
547
-  "name": "creation_date",
548
-  "component": "EmField",
549
-  "date_update": "Wed Nov  4 10:52:13 2015",
550
-  "string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
551
-  "help_text": "{\"___\": \"\"}",
552
-  "internal": "autosql",
553
-  "date_create": "Wed Nov  4 10:52:13 2015",
554
-  "rel_field_id": null,
555
-  "rank": 9,
556
-  "immutable": true,
557
-  "fieldtype": "datetime",
558
-  "now_on_create": true,
559
-  "optional": false,
560
-  "class_id": 2,
561
-  "uniq": false,
562
-  "icon": "0"
563
- },
564
- "41": {
565
-  "nullable": false,
566
-  "name": "modification_date",
567
-  "date_update": "Wed Nov  4 10:52:13 2015",
568
-  "string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
569
-  "help_text": "{\"___\": \"\"}",
570
-  "component": "EmField",
571
-  "date_create": "Wed Nov  4 10:52:13 2015",
572
-  "optional": false,
573
-  "class_id": 13,
574
-  "rank": 6,
575
-  "internal": "autosql",
576
-  "fieldtype": "datetime",
577
-  "now_on_create": true,
578
-  "rel_field_id": null,
579
-  "immutable": true,
580
-  "uniq": false,
581
-  "now_on_update": true,
582
-  "icon": "0"
583
- },
584
- "42": {
585
-  "nullable": false,
586
-  "name": "creation_date",
587
-  "component": "EmField",
588
-  "date_update": "Wed Nov  4 10:52:13 2015",
589
-  "string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
590
-  "help_text": "{\"___\": \"\"}",
591
-  "internal": "autosql",
592
-  "date_create": "Wed Nov  4 10:52:13 2015",
593
-  "rel_field_id": null,
594
-  "rank": 7,
595
-  "immutable": true,
596
-  "fieldtype": "datetime",
597
-  "now_on_create": true,
598
-  "optional": false,
599
-  "class_id": 13,
600
-  "uniq": false,
601
-  "icon": "0"
602
- }
603
-}

+ 0
- 8026
EditorialModel/test/model-lodel-1.0.xml
File diff suppressed because it is too large
View File


+ 0
- 194
EditorialModel/test/test_classes.py View File

@@ -1,194 +0,0 @@
1
-"""
2
-    Tests for the EmClass class
3
-"""
4
-
5
-import os
6
-import logging
7
-
8
-from unittest import TestCase
9
-import unittest
10
-
11
-import EditorialModel
12
-from EditorialModel.classes import EmClass
13
-from EditorialModel.classtypes import EmClassType
14
-from EditorialModel.types import EmType
15
-from EditorialModel.fields import EmField
16
-from EditorialModel.model import Model
17
-from EditorialModel.backend.json_backend import EmBackendJson
18
-from DataSource.dummy.migrationhandler import DummyMigrationHandler
19
-
20
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
21
-EM_TEST = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'me.json')
22
-EM_TEST_OBJECT = None
23
-
24
-
25
-## run once for this module
26
-def setUpModule():
27
-    global EM_TEST_OBJECT
28
-    #EM_TEST_OBJECT = Model(EmBackendJson(EM_TEST), migration_handler=DjandoMigrationHandler('LodelTestInstance'))
29
-    EM_TEST_OBJECT = Model(EmBackendJson(EM_TEST), migration_handler=DummyMigrationHandler())
30
-    logging.basicConfig(level=logging.CRITICAL)
31
-
32
-
33
-class ClassesTestCase(TestCase):
34
-
35
-    # run before every instanciation of the class
36
-    @classmethod
37
-    def setUpClass(cls):
38
-        pass
39
-        #sqlsetup.init_db()
40
-
41
-    # run before every function of the class
42
-    def setUp(self):
43
-        pass
44
-
45
-
46
-# creating an new EmClass should
47
-# - create a table named like the created EmClass
48
-# - insert a new line in em_classes
49
-class TestEmClassCreation(ClassesTestCase):
50
-
51
-    # create a new EmClass, then test on it
52
-    def test_create(self):
53
-        ClassesTestCase.setUpClass()
54
-        test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testclass1', 'classtype': EmClassType.entity['name']})
55
-
56
-        # We check that the class has been added in the right list in the model object
57
-        class_components_records = EM_TEST_OBJECT.components(EmClass)
58
-        self.assertIn(test_class, class_components_records)
59
-
60
-        # the name should be the one given
61
-        test_class = EM_TEST_OBJECT.component(test_class.uid)
62
-        self.assertEqual(test_class.name, 'testclass1')
63
-
64
-        # the classtype should have the name of the EmClassType
65
-        test_class = EM_TEST_OBJECT.component(test_class.uid)
66
-        self.assertEqual(test_class.classtype, EmClassType.entity['name'])
67
-    
68
-    def test_default_fields(self):
69
-        """ Test if the default + common_fields are created when an EmClass is created """
70
-        classtype = EmClassType.entity['name']
71
-        test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testdefaultfieldclass', 'classtype': classtype})
72
-        ctype = EditorialModel.classtypes.EmClassType.get(classtype)
73
-        default_fields = ctype['default_fields']
74
-        default_fields.update(EditorialModel.classtypes.common_fields)
75
-        
76
-        fnames = [ f.name for f in test_class.fields() ]
77
-        self.assertEqual(sorted(fnames), sorted(list(default_fields.keys())))
78
-
79
-
80
-# Testing class deletion (and associated table drop)
81
-class TestEmClassDeletion(ClassesTestCase):
82
-
83
-    def setUp(self):
84
-        self.names = ['testClasse1', 'testClasse2', 'testClasse3']
85
-        self.emclasses = []
86
-        self.emclasses.append(EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': self.names[0], 'classtype': EmClassType.entity['name']}))
87
-        self.emclasses.append(EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': self.names[1], 'classtype': EmClassType.entry['name']}))
88
-        self.emclasses.append(EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': self.names[2], 'classtype': EmClassType.person['name']}))
89
-
90
-    # tests if the table is deleted after a call to delete
91
-    def test_table_delete(self):
92
-        """ Test associated table deletion on EmClass deletion """
93
-        for _, class_object in enumerate(self.emclasses):
94
-            self.assertTrue(EM_TEST_OBJECT.delete_component(class_object.uid), "delete method didn't return True but the class has no fieldgroups")
95
-
96
-        # TODO check : "table still exists but the class was deleted"
97
-        # TODO check : "table doesn't exist but the class was not deleted"
98
-
99
-    # tests if delete refuse to delete if a class had fieldgroups
100
-    def test_table_refuse_delete(self):
101
-        """ Test delete on an EmClass that has fieldgroup """
102
-        test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testfgclass1', 'classtype': EmClassType.entity['name']})
103
-
104
-        test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassFalseEmpty', 'classtype': EmClassType.entity['name']})
105
-        foo_field = EM_TEST_OBJECT.create_component('EmField', {'name': 'ahah', 'fieldtype':'char', 'class_id':test_class.uid})
106
-        self.assertFalse(EM_TEST_OBJECT.delete_component(test_class.uid), "delete method returns True but the class has a non-default field")
107
-
108
-        # TODO check : "table has been deleted but the class has fieldgroup"
109
-
110
-        try:
111
-            EM_TEST_OBJECT.component(test_class.uid)
112
-        except Exception:
113
-            self.fail("The class has been deleted but it has non-default field in the default fieldgroup")
114
-
115
-
116
-# Interface to types
117
-class TestEmClassTypes(ClassesTestCase):
118
-
119
-    @classmethod
120
-    def setUpClass(cls):
121
-        pass
122
-
123
-    def setUp(self):
124
-        ClassesTestCase.setUpClass()
125
-        self.test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassType', 'classtype': EmClassType.entity['name']})
126
-
127
-    # tests if types() returns a list of EmType
128
-    def test_types(self):
129
-        """ Tests if types method returns the right list of EmType """
130
-        test_class = EM_TEST_OBJECT.component(self.test_class.uid)
131
-        EM_TEST_OBJECT.create_component(EmType.__name__, {'name': 'testClassType1', 'class_id': test_class.uid})
132
-        EM_TEST_OBJECT.create_component(EmType.__name__, {'name': 'testClassType2', 'class_id': test_class.uid})
133
-        types = test_class.types()
134
-        self.assertIsInstance(types, list)
135
-        for t in types:
136
-            self.assertIsInstance(t, EmType)
137
-
138
-    # with no type, types() should return an empty list
139
-    def test_no_types(self):
140
-        """ Test types method on an EmClass with no associated types """
141
-        test_class = EM_TEST_OBJECT.component(self.test_class.uid)
142
-        types = test_class.types()
143
-        self.assertEqual(types, [])
144
-
145
-
146
-# interface to fields
147
-class TestEmClassFields(ClassesTestCase):
148
-
149
-    @classmethod
150
-    def setUpClass(cls):
151
-        pass
152
-
153
-    def setUp(self):
154
-        ClassesTestCase.setUpClass()
155
-        self.test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassFields', 'classtype': EmClassType.entity['name']})
156
-
157
-    # tests if fields() returns a list of EmField
158
-    def test_fields(self):
159
-        """ testing fields method """
160
-        test_class = EM_TEST_OBJECT.component(self.test_class.uid)
161
-        EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'f1', 'class_id': test_class.uid, 'fieldtype': 'char'})
162
-        EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'f2', 'class_id': test_class.uid, 'fieldtype': 'char'})
163
-        fields = test_class.fields()
164
-        self.assertIsInstance(fields, list)
165
-        for field in fields:
166
-            self.assertIsInstance(field, EmField)
167
-
168
-    # with no field fields() should return an empty list
169
-    def test_default_fields(self):
170
-        """ Testing fields method on an EmClass with only defaults fields """
171
-        test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassNoFields', 'classtype': EmClassType.entity['name']})
172
-        fields = test_class.fields()
173
-    
174
-        for fname in [f.name for f in fields]:
175
-            self.assertIn(fname, EmClassType.get(test_class.classtype)['default_fields'].keys())
176
-
177
-
178
-# Creating a new EmClass should :
179
-# - create a table named like the created EmClass
180
-# - insert a new line in em_classes
181
-class TestEmClassLinkType(ClassesTestCase):
182
-
183
-    # create a new EmClass, then test on it
184
-    @classmethod
185
-    def setUpClass(cls):
186
-        ClassesTestCase.setUpClass()
187
-        test_entity = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testEntity', 'classtype': EmClassType.entity['name']})
188
-        test_entry = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testEntry', 'classtype': EmClassType.entry['name']})
189
-
190
-    def testLinkedTypes(self):
191
-        """ Test the EmClass.linked_types() method """
192
-        for field, linked_type in [ (f, EM_TEST_OBJECT.component(f.rel_to_type_id)) for f in EM_TEST_OBJECT.components(EmField) if 'rel_to_type_id' in f.__dict__]:
193
-            self.assertIn(linked_type, field.em_class.linked_types())
194
-

+ 0
- 113
EditorialModel/test/test_component.py View File

@@ -1,113 +0,0 @@
1
-import unittest
2
-
3
-from EditorialModel.model import Model
4
-from EditorialModel.components import EmComponent
5
-from EditorialModel.classes import EmClass
6
-from EditorialModel.types import EmType
7
-from EditorialModel.fields import EmField
8
-
9
-from Lodel.utils.mlstring import MlString
10
-
11
-from EditorialModel.backend.json_backend import EmBackendJson
12
-from DataSource.dummy.migrationhandler import DummyMigrationHandler
13
-
14
-
15
-class TestEmComponent(unittest.TestCase):
16
-    def setUp(self):
17
-        self.model = Model(EmBackendJson('EditorialModel/test/me.json'))
18
-
19
-    def test_hashes(self):
20
-        """ Testing __hash__ and __eq__ methods """
21
-        me1 = Model(EmBackendJson('EditorialModel/test/me.json'))
22
-        me2 = Model(EmBackendJson('EditorialModel/test/me.json'), migration_handler=DummyMigrationHandler())
23
-
24
-        for comp_class in [EmClass, EmType, EmField]:
25
-            comp_l1 = me1.components(comp_class)
26
-            comp_l2 = me2.components(comp_class)
27
-
28
-            for i, comp1 in enumerate(comp_l1):
29
-                comp2 = comp_l2[i]
30
-                self.assertEqual(hash(comp1), hash(comp2), "hashes differs for two EmComponent({}) instanciated from the same backend and files".format(comp_class.__name__))
31
-                self.assertTrue(comp1 == comp2)
32
-
33
-                if not comp1.modify_rank(1):
34
-                    continue  # modification not made, drop this test
35
-
36
-                self.assertNotEqual(hash(comp1), hash(comp2), "hashes are the same after a modification of rank on one of the two components")
37
-                self.assertFalse(comp1 == comp2)
38
-
39
-                comp2.modify_rank(1)
40
-
41
-                self.assertEqual(hash(comp1), hash(comp2), "hashes differs for two EmComponent({}) after applying the same modifications on both".format(comp_class.__name__))
42
-                self.assertTrue(comp1 == comp2)
43
-
44
-    def test_virtual_methods(self):
45
-        """ Testing virtual methods """
46
-        with self.assertRaises(NotImplementedError):
47
-            _ = EmComponent(self.model, self.model.new_uid(), 'Invalide')
48
-
49
-    def test_modify_rank(self):
50
-        """ Testing modify_rank and set_rank method """
51
-        cls = self.model.classes()[0]
52
-        orig_rank = cls.rank
53
-
54
-        cls.modify_rank(1)
55
-        self.assertEqual(orig_rank, cls.rank - 1)
56
-
57
-        cls.modify_rank(-1)
58
-        self.assertEqual(orig_rank, cls.rank)
59
-
60
-        cls.set_rank(1)
61
-        self.assertEqual(cls.rank, 1)
62
-
63
-        cls.set_rank(2)
64
-        self.assertEqual(cls.rank, 2)
65
-
66
-        max_rank = cls.get_max_rank()
67
-        cls.set_rank(max_rank)
68
-        self.assertEqual(cls.rank, max_rank)
69
-
70
-        self.assertFalse(cls.modify_rank(1))
71
-
72
-        self.assertFalse(cls.modify_rank(-10))
73
-
74
-        with self.assertRaises(ValueError):
75
-            cls.set_rank(0)
76
-
77
-        with self.assertRaises(ValueError):
78
-            cls.set_rank(10)
79
-
80
-        with self.assertRaises(ValueError):
81
-            cls.set_rank(-10)
82
-
83
-    def test_check(self):
84
-        """ Testing check method """
85
-        cls = self.model.classes()[0]
86
-        cls.rank = 10000
87
-
88
-        cls.check()
89
-        self.assertEqual(cls.rank, cls.get_max_rank())
90
-
91
-        cls.rank = -1000
92
-        cls.check()
93
-        self.assertEqual(cls.rank, 1)
94
-
95
-    def test_dump(self):
96
-        """ Testing dump methods """
97
-        for comp in self.model.components():
98
-            dmp = comp.attr_dump()
99
-            self.assertNotIn('uid', dmp)
100
-            self.assertNotIn('model', dmp)
101
-            self.assertTrue(dmp['help_text'] is None or isinstance(dmp['help_text'], MlString))
102
-            self.assertTrue(dmp['string'] is None or isinstance(dmp['string'], MlString))
103
-            for dmp_f in dmp:
104
-                self.assertFalse(dmp_f.startswith('_'))
105
-
106
-    def test_uniq_name(self):
107
-        """ Testing uniq_name method """
108
-        names_l = []
109
-        for comp in self.model.components():
110
-            #Should be uniq only for types and classes
111
-            if isinstance(comp, EmType) or isinstance(comp, EmClass):
112
-                self.assertNotIn(comp.uniq_name, names_l)
113
-                names_l.append(comp.uniq_name)

+ 0
- 115
EditorialModel/test/test_field.py View File

@@ -1,115 +0,0 @@
1
-import os
2
-
3
-from unittest import TestCase
4
-from EditorialModel.fields import EmField
5
-from EditorialModel.model import Model
6
-from EditorialModel.backend.json_backend import EmBackendJson
7
-from EditorialModel.exceptions import EmComponentCheckError
8
-
9
-EM_TEST = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'me.json')
10
-EM_TEST_OBJECT = None
11
-
12
-
13
-## SetUpModule
14
-#
15
-# This function is called once for this module.
16
-def setUpModule():
17
-    global EM_TEST_OBJECT
18
-    EM_TEST_OBJECT = Model(EmBackendJson(EM_TEST))
19
-
20
-
21
-def tearDownModule():
22
-    #cleanDb(TEST_FIELD_DBNAME)
23
-    pass
24
-
25
-
26
-## FieldTestCase (Class)
27
-#
28
-# The parent class of all other test cases for the fields module.
29
-# It defines a SetUp function and some utility functions for EmField tests.
30
-class FieldTestCase(TestCase):
31
-
32
-    @classmethod
33
-    def setUpClass(cls):
34
-        pass
35
-
36
-    def setUp(self):
37
-        self.test_fieldtype = 'integer'
38
-        self.test_class = EM_TEST_OBJECT.components('EmClass')[0]
39
-
40
-
41
-## TestField (Class)
42
-#
43
-# The test class for the fields module
44
-class TestField(FieldTestCase):
45
-
46
-    ## Test_create (Function)
47
-    #
48
-    # tests the creation process of a field
49
-    def test_create(self):
50
-
51
-        field = EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'testfield1', 'class_id': self.test_class.uid, 'fieldtype': self.test_fieldtype})
52
-
53
-        # We check that the field has been added
54
-        field_records = EM_TEST_OBJECT.component(field.uid)
55
-        self.assertIsNot(field_records, False)
56
-
57
-        # We check that the field has been added in the right list in the model object
58
-        field_components_records = EM_TEST_OBJECT.components(EmField)
59
-        self.assertIn(field, field_components_records)
60
-
61
-    def test_invalid_internal(self):
62
-        """ Test that internal='object' is reserved for common_fields """
63
-        with self.assertRaises(ValueError, msg="Only common_fields should be internal='object'"):
64
-            field = EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'testbadinternal','internal': 'object', 'class_id': self.test_class.uid, 'fieldtype': self.test_fieldtype})
65
-
66
-    def test_double_rel2type(self):
67
-        """ Test the rel2type unicity """
68
-        em = EM_TEST_OBJECT
69
-        emtype = em.components('EmType')[0]
70
-        emclass = [c for c in em.components('EmClass') if c != emtype.em_class][0]
71
-
72
-        f1 = em.create_component('EmField', {'name': 'testr2t', 'class_id': emclass.uid, 'fieldtype': 'rel2type', 'rel_to_type_id': emtype.uid})
73
-
74
-        with self.assertRaises(EmComponentCheckError):
75
-            f2 = em.create_component('EmField', {'name': 'testr2t2', 'class_id': emclass.uid, 'fieldtype': 'rel2type', 'rel_to_type_id': emtype.uid})
76
-
77
-    def test_same_name(self):
78
-        """ Test the name unicity is the same EmClass"""
79
-        em = EM_TEST_OBJECT
80
-        emtype = em.components('EmType')[0]
81
-        emclass = [c for c in em.components('EmClass') if c != emtype.em_class][0]
82
-
83
-        f1 = em.create_component('EmField', {'name': 'samename', 'class_id': emclass.uid, 'fieldtype': 'char'})
84
-
85
-        with self.assertRaises(EmComponentCheckError):
86
-            f2 = em.create_component('EmField', {'name': 'samename', 'class_id': emclass.uid, 'fieldtype': 'integer'} )
87
-
88
-        
89
-
90
-    ## Test_Deletion
91
-    #
92
-    # tests the deletion process of a field
93
-    def test_deletion(self):
94
-        fields = []
95
-        field_names = ['field1', 'field2']
96
-
97
-        # We create the two fields
98
-        for name in field_names:
99
-            fields.append(EM_TEST_OBJECT.create_component(EmField.__name__, {'name': name, 'class_id': self.test_class.uid, 'fieldtype': self.test_fieldtype}))
100
-
101
-        for field in fields:
102
-            # We check if the delete process was performed to the end
103
-            self.assertTrue(EM_TEST_OBJECT.delete_component(field.uid))
104
-
105
-            # We check that the field object is not in the editorial model anymore
106
-            self.assertFalse(EM_TEST_OBJECT.component(field.uid))
107
-
108
-            # We check that the field object is not in the EmField components list
109
-            field_components_records = EM_TEST_OBJECT.components(EmField)
110
-            self.assertNotIn(field, field_components_records)
111
-
112
-    def test_emclass(self):
113
-        """ Test if the EmField.em_class \@property method is correct """
114
-        for field in EM_TEST_OBJECT.components(EmField):
115
-            self.assertIn(field, field.em_class.fields())

+ 0
- 314
EditorialModel/test/test_model.py View File

@@ -1,314 +0,0 @@
1
-import unittest
2
-from unittest.mock import patch
3
-
4
-from EditorialModel.model import Model
5
-from EditorialModel.classes import EmClass
6
-from EditorialModel.types import EmType
7
-from EditorialModel.fields import EmField
8
-from EditorialModel.components import EmComponent
9
-from Lodel.utils.mlstring import MlString
10
-
11
-
12
-from EditorialModel.backend.json_backend import EmBackendJson
13
-from EditorialModel.backend.dummy_backend import EmBackendDummy
14
-from DataSource.dummy.migrationhandler import DummyMigrationHandler
15
-
16
-
17
-class TestModel(unittest.TestCase):
18
-
19
-    def setUp(self):
20
-        self.ed_mod = Model(EmBackendJson('EditorialModel/test/me.json'))
21
-
22
-    def test_init(self):
23
-        """ Instanciation test """
24
-        model = Model(EmBackendJson('EditorialModel/test/me.json'))
25
-        self.assertTrue(isinstance(model, Model))
26
-
27
-        model = Model(EmBackendJson('EditorialModel/test/me.json'), migration_handler=DummyMigrationHandler())
28
-        self.assertTrue(isinstance(model, Model))
29
-
30
-    def test_bad_init(self):
31
-        """ Test initialisation with bad arguments """
32
-        for bad in [None, int, EmBackendDummy, DummyMigrationHandler, 'foobar']:
33
-            with self.assertRaises(TypeError, msg="Tried to instanciate a Model with a bad backend"):
34
-                Model(bad)
35
-        for bad in [int, EmBackendDummy, DummyMigrationHandler, 'foobar']:
36
-            with self.assertRaises(TypeError, msg="Tried to instanciate a Model with a migration_handler"):
37
-                Model(EmBackendDummy(), bad)
38
-
39
-    def test_components(self):
40
-        """ Test components fetching """
41
-        uid_l = list()
42
-        for comp_class in [EmClass, EmType, EmField]:
43
-            comp_l = self.ed_mod.components(comp_class)
44
-            #Testing types of returned components
45
-            for component in comp_l:
46
-                self.assertTrue(isinstance(component, comp_class), "Model.components method doesn't return EmComponent of the right type. Asked for {} but got {}".format(type(comp_class), type(component)))
47
-                uid_l.append(component.uid)
48
-
49
-        #Testing that we have fetched all the components
50
-        for uid in self.ed_mod._components['uids']:
51
-            self.assertIn(uid, uid_l, "Component with uid %d was not fetched" % uid)
52
-
53
-        #Testing components method without parameters
54
-        uid_l = [comp.uid for comp in self.ed_mod.components()]
55
-
56
-        for uid in self.ed_mod._components['uids']:
57
-            self.assertIn(uid, uid_l, "Component with uid %d was not fetched using me.components()" % uid)
58
-
59
-        self.assertFalse(self.ed_mod.components(EmComponent))
60
-        self.assertFalse(self.ed_mod.components(int))
61
-
62
-    def test_component(self):
63
-        """ Test component fetching by uid """
64
-        #assert that __hash__, __eq__ and components() are corrects
65
-        for comp in self.ed_mod.components():
66
-            self.assertEqual(comp, self.ed_mod.component(comp.uid))
67
-
68
-        for baduid in [-1, 0xffffff, "hello"]:
69
-            self.assertFalse(self.ed_mod.component(baduid))
70
-
71
-    def test_sort_components(self):
72
-        """ Test that Model.sort_components method actually sort components """
73
-        # disordering an EmClass
74
-        cl_l = self.ed_mod.components(EmClass)
75
-        last_class = cl_l[0]
76
-        last_class.rank = 10000
77
-        self.ed_mod.sort_components(EmClass)
78
-        self.assertEqual(self.ed_mod._components['EmClass'][-1].uid, last_class.uid, "The sort_components method doesn't really sort by rank")
79
-
80
-    def test_new_uid(self):
81
-        """ Test that model.new_uid return a new uniq uid """
82
-        new_uid = self.ed_mod.new_uid()
83
-        self.assertNotIn(new_uid, self.ed_mod._components['uids'].keys())
84
-
85
-    def test_create_component_fails(self):
86
-        """ Test the create_component method with invalid arguments """
87
-
88
-        test_datas = {
89
-            'name': 'FooBar',
90
-            'classtype': 'entity',
91
-            'help_text': None,
92
-            'string': None,
93
-        }
94
-
95
-        #Invalid uid
96
-        used_uid = self.ed_mod.components()[0].uid
97
-        for bad_uid in [used_uid, -1, -1000, 'HelloWorld']:
98
-            with self.assertRaises(ValueError, msg="The component was created but the given uid (%s) whas invalid (allready used, negative or WTF)" % bad_uid):
99
-                self.ed_mod.create_component('EmClass', test_datas, uid=bad_uid)
100
-
101
-        #Invalid component_type
102
-        for bad_comp_name in ['EmComponent', 'EmFoobar', 'int', int]:
103
-            with self.assertRaises(ValueError, msg="The create_component don't raise when an invalid classname (%s) is given as parameter" % bad_comp_name):
104
-                self.ed_mod.create_component(bad_comp_name, test_datas)
105
-
106
-        #Invalid rank
107
-        for invalid_rank in [-1, 10000]:
108
-            with self.assertRaises(ValueError, msg="A invalid rank (%s) was given" % invalid_rank):
109
-                foodat = test_datas.copy()
110
-                foodat['rank'] = invalid_rank
111
-                self.ed_mod.create_component('EmClass', foodat)
112
-        with self.assertRaises(TypeError, msg="A non integer rank was given"):
113
-            foodat = test_datas.copy()
114
-            foodat['rank'] = 'hello world'
115
-            self.ed_mod.create_component('EmClass', foodat)
116
-
117
-        #Invalid datas
118
-        for invalid_datas in [dict(), [1, 2, 3, 4], ('hello', 'world')]:
119
-            with self.assertRaises(TypeError, msg="Invalid datas was given in parameters"):
120
-                self.ed_mod.create_component('EmClass', invalid_datas)
121
-
122
-    def test_create_components(self):
123
-        """ Test the create_component method mocking the EmComponent constructors """
124
-
125
-        params = {
126
-            'EmClass': {
127
-                'cls': EmClass,
128
-                'cdats': {
129
-                    'name': 'FooClass',
130
-                    'classtype': 'entity',
131
-                }
132
-            },
133
-            'EmType': {
134
-                'cls': EmType,
135
-                'cdats': {
136
-                    'name': 'FooType',
137
-                    'class_id': self.ed_mod.components(EmClass)[0].uid,
138
-                    'fields_list': [],
139
-                }
140
-            },
141
-            'EmField': {
142
-                'cls': EmField,
143
-                'cdats': {
144
-                    'name': 'FooField',
145
-                    'class_id': self.ed_mod.components(EmClass)[0].uid,
146
-                    'fieldtype': 'char',
147
-                    'max_length': 64,
148
-                    'optional': True,
149
-                    'internal': False,
150
-                    'rel_field_id': None,
151
-                    'icon': '0',
152
-                    'nullable': False,
153
-                    'uniq': True,
154
-                }
155
-            }
156
-        }
157
-
158
-        for cnt in params:
159
-            tmpuid = self.ed_mod.new_uid()
160
-            cdats = params[cnt]['cdats']
161
-            cdats['string'] = MlString()
162
-            cdats['help_text'] = MlString()
163
-            with patch.object(params[cnt]['cls'], '__init__', return_value=None) as initmock:
164
-                try:
165
-                    self.ed_mod.create_component(cnt, params[cnt]['cdats'])
166
-                except AttributeError:  # Raises because the component is a MagicMock
167
-                    pass
168
-                cdats['uid'] = tmpuid
169
-                cdats['model'] = self.ed_mod
170
-                #Check that the component __init__ method was called with the good arguments
171
-                initmock.assert_called_once_with(**cdats)
172
-
173
-    def test_delete_component(self):
174
-        """ Test the delete_component method """
175
-
176
-        #Test that the delete_check() method is called
177
-        for comp in self.ed_mod.components():
178
-            with patch.object(comp.__class__, 'delete_check', return_value=False) as del_check_mock:
179
-                ret = self.ed_mod.delete_component(comp.uid)
180
-                del_check_mock.assert_called_once_with()
181
-                #Check that when the delete_check() returns False de delete_component() too
182
-                self.assertFalse(ret)
183
-
184
-        #Using a new me for deletion test
185
-        new_em = Model(EmBackendJson('EditorialModel/test/me.json'))
186
-        for comp in new_em.components():
187
-            cuid = comp.uid
188
-            cname = new_em.name_from_emclass(comp.__class__)
189
-            #Simulate that the delete_check() method returns True
190
-            with patch.object(comp.__class__, 'delete_check', return_value=True) as del_check_mock:
191
-                ret = new_em.delete_component(cuid)
192
-                self.assertTrue(ret)
193
-                self.assertNotIn(cuid, new_em._components['uids'])
194
-                self.assertNotIn(comp, new_em._components[cname])
195
-
196
-    def test_set_backend(self):
197
-        """ Test the set_backend method """
198
-
199
-        for backend in [EmBackendJson('EditorialModel/test/me.json'), EmBackendDummy()]:
200
-            self.ed_mod.set_backend(backend)
201
-            self.assertEqual(self.ed_mod.backend, backend)
202
-
203
-        for bad_backend in [None, 'wow', int, EmBackendJson]:
204
-            with self.assertRaises(TypeError, msg="But bad argument (%s %s) was given" % (type(bad_backend), bad_backend)):
205
-                self.ed_mod.set_backend(bad_backend)
206
-
207
-    def test_migrate_handler_order(self):
208
-        """ Test that the migrate_handler() method create component in a good order """
209
-        with patch.object(Model, 'create_component', return_value=None) as create_mock:
210
-            try:
211
-                self.ed_mod.migrate_handler(None)
212
-            except AttributeError:  # Raises because of the mock
213
-                pass
214
-            order_comp = ['EmClass', 'EmType', 'EmFieldGroup', 'EmField']  # Excpected creation order
215
-            cur_comp = 0
216
-            for mcall in create_mock.mock_calls:
217
-                #Testing EmComponent order of creation
218
-                while order_comp[cur_comp] != mcall[1][0]:
219
-                    cur_comp += 1
220
-                    if cur_comp >= len(order_comp):
221
-                        self.fail('The order of create_component() calls was not respected by migrate_handler() methods')
222
-
223
-                #Testing uid
224
-                comp = self.ed_mod.component(mcall[1][2])
225
-                ctype = self.ed_mod.name_from_emclass(comp.__class__)
226
-                self.assertEqual(mcall[1][0], ctype, "A component was created using a uid belonging to another component type")
227
-                #Testing arguments of create_component
228
-                comp_dump = comp.attr_dump()
229
-                if 'fields_list' in comp_dump and comp_dump['fields_list']:
230
-                    del(comp_dump['fields_list'])
231
-                if 'superiors_list' in comp_dump and comp_dump['superiors_list']:
232
-                    del(comp_dump['superiors_list'])
233
-
234
-                self.assertEqual(mcall[1][1], comp_dump)
235
-
236
-    def test_migrate_handler_relations(self):
237
-        """ Test that the migrate_handler() method create Type relations correctly (selected fields and type hierarchy) """
238
-        field_list_orig = []
239
-        superiors_list_orig = dict()
240
-        field_list_new = []
241
-        superiors_list_new = dict()
242
-
243
-        for emtype in self.ed_mod.components(EmType):
244
-            for fluid in emtype.fields_list:
245
-                field_list_orig.append(fluid)
246
-            for nat, sup_l in emtype.superiors().items():
247
-                superiors_list_orig[nat] = [sup.uid for sup in sup_l]
248
-
249
-        with patch.object(DummyMigrationHandler, 'register_change', return_value=None) as mh_mock:
250
-            self.ed_mod.migrate_handler(DummyMigrationHandler())
251
-            #Getting cloned EM instance
252
-            new_me = mh_mock.mock_calls[-1][1][0]
253
-            for emtype in new_me.components(EmType):
254
-                for fluid in emtype.fields_list:
255
-                    field_list_new.append(fluid)
256
-                for nat, sup_l in emtype.superiors().items():
257
-                    superiors_list_new[nat] = [sup.uid for sup in sup_l]
258
-
259
-            for fluid in field_list_orig:
260
-                self.assertIn(fluid, field_list_orig, "Missing selected field (%d) when migrate_handler() is run" % fluid)
261
-            for fluid in field_list_new:
262
-                self.assertIn(fluid, field_list_new, "A field (%d) is selected when migrate_handler() is run but shouldn't be" % fluid)
263
-            for nat, sup_l in superiors_list_orig.items():
264
-                for supuid in sup_l:
265
-                    self.assertIn(supuid, superiors_list_new[nat], "Missing superiors (%d) when migrate_handler() is run" % supuid)
266
-            for nat, sup_l in superiors_list_new.items():
267
-                for supuid in sup_l:
268
-                    self.assertIn(supuid, superiors_list_orig[nat], "A superior (%d) is present when migrate_handler is called but shouldn't be" % supuid)
269
-
270
-    def test_migrate_handler_hashes(self):
271
-        """ Testing that the migrate_handler() method create an EM with the same hash as the original EM """
272
-        with patch.object(DummyMigrationHandler, 'register_change', return_value=None) as mh_mock:
273
-            self.ed_mod.migrate_handler(DummyMigrationHandler())
274
-            #Getting the cloned EM instance
275
-            last_call = mh_mock.mock_calls[-1]
276
-            self.assertEqual(hash(last_call[1][0]), hash(self.ed_mod))
277
-
278
-    def test_hash(self):
279
-        """ Test that __hash__ and __eq__ work properly on models """
280
-        me1 = Model(EmBackendJson('EditorialModel/test/me.json'))
281
-        me2 = Model(EmBackendJson('EditorialModel/test/me.json'), migration_handler=DummyMigrationHandler())
282
-
283
-        self.assertEqual(hash(me1), hash(me2), "When instanciate from the same backend & file but with another migration handler the hashes differs")
284
-        self.assertTrue(me1.__eq__(me2))
285
-
286
-        cl_l = me1.classes()
287
-        cl_l[0].modify_rank(1)
288
-
289
-        self.assertNotEqual(hash(me1), hash(me2), "After a class rank modification the hashes are the same")
290
-        self.assertFalse(me1.__eq__(me2))
291
-
292
-        cl_l = me2.classes()
293
-        cl_l[0].modify_rank(1)
294
-
295
-        self.assertEqual(hash(me1), hash(me2), "After doing sames modifications in the two models the hashes differs")
296
-        self.assertTrue(me1.__eq__(me2))
297
-
298
-    def test_compclass_getter(self):
299
-        """ Test the Model methods that handles name <-> EmComponent conversion """
300
-        for classname in ['EmField', 'EmClass', 'EmType']:
301
-            cls = Model.emclass_from_name(classname)
302
-            self.assertNotEqual(cls, False, "emclass_from_name return False when '%s' given as parameter" % classname)
303
-            self.assertEqual(cls.__name__, classname)
304
-
305
-        for classname in ['EmComponent', 'EmFoobar', int, EmClass]:
306
-            self.assertFalse(Model.emclass_from_name(classname))
307
-
308
-        for comp_cls in [EmClass, EmField, EmType]:
309
-            self.assertEqual(Model.name_from_emclass(comp_cls), comp_cls.__name__)
310
-        for comp in self.ed_mod.components(EmField):
311
-            self.assertEqual(Model.name_from_emclass(comp.__class__), 'EmField')
312
-
313
-        for cls in [EmComponent, int, str]:
314
-            self.assertFalse(Model.name_from_emclass(cls))

+ 0
- 115
EditorialModel/test/test_types.py View File

@@ -1,115 +0,0 @@
1
-import unittest
2
-
3
-from EditorialModel.model import Model
4
-from EditorialModel.classtypes import EmNature
5
-from EditorialModel.backend.json_backend import EmBackendJson
6
-
7
-
8
-class TypeTestCase(unittest.TestCase):
9
-
10
-    def setUp(self):
11
-        self.model = Model(EmBackendJson('EditorialModel/test/me.json'))
12
-        self.rubrique = self.model.component(14)
13
-        self.article = self.model.component(5)
14
-        self.personne = self.model.component(6)
15
-        self.soustitre_field = self.model.component(7)
16
-        self.age_field = self.model.component(18)
17
-        self.nom_field = self.model.component(9)
18
-        self.numero = self.model.component(19)
19
-        self.gens_group = self.model.component(17)
20
-        self.info_group = self.model.component(3)
21
-        self.couleur_group = self.model.component(20)
22
-        self.couleur_field = self.model.component(21)
23
-
24
-
25
-class TestSelectField(TypeTestCase):
26
-
27
-    def test_select_field(self):
28
-        """ Testing optionnal field selection """
29
-        self.personne.select_field(self.age_field)
30
-
31
-        self.assertIn(self.age_field, self.personne.selected_fields())
32
-        self.assertIn(self.age_field.uid, self.personne.fields_list)
33
-
34
-    def test_unselect_field(self):
35
-        """ Testing optionnal field unselection """
36
-        self.article.unselect_field(self.soustitre_field)
37
-
38
-        self.assertNotIn(self.soustitre_field, self.article.selected_fields())
39
-        self.assertNotIn(self.soustitre_field.uid, self.article.fields_list)
40
-
41
-    def test_select_field_invalid(self):
42
-        """ Testing optionnal field selection with invalid fields """
43
-        with self.assertRaises(ValueError):
44
-            self.personne.select_field(self.nom_field)
45
-        with self.assertRaises(ValueError):
46
-            self.personne.select_field(self.soustitre_field)
47
-
48
-
49
-class TestTypeHierarchy(TypeTestCase):
50
-
51
-    def test_add_superior(self):
52
-        """ Testing add_superior() """
53
-        self.numero.add_superior(self.rubrique, EmNature.PARENT)
54
-
55
-        self.assertIn(self.rubrique, self.numero.superiors()[EmNature.PARENT])
56
-        self.assertIn(self.rubrique.uid, self.numero.superiors_list[EmNature.PARENT])
57
-        self.assertIn(self.numero, self.rubrique.subordinates()[EmNature.PARENT])
58
-
59
-        # add it twice, it should not be listed twice
60
-        self.numero.add_superior(self.rubrique, EmNature.PARENT)
61
-        self.assertEqual(1, len(self.numero.superiors()[EmNature.PARENT]))
62
-
63
-    def test_del_superior(self):
64
-        """ Testing del_superior() """
65
-
66
-        # rubrique should be a superior of article
67
-        self.assertIn(self.rubrique, self.article.superiors()[EmNature.PARENT])
68
-        # article should be in rubrique subordinates
69
-        self.assertIn(self.article, self.rubrique.subordinates()[EmNature.PARENT])
70
-
71
-        self.article.del_superior(self.rubrique, EmNature.PARENT)
72
-
73
-        # article should not have EmNature.PARENT superior anymore
74
-        with self.assertRaises(KeyError):
75
-            _ = self.article.superiors()[EmNature.PARENT]
76
-
77
-        # article should not be in rubrique subordinates
78
-        self.assertNotIn(self.article, self.rubrique.subordinates()[EmNature.PARENT])
79
-
80
-        # but rubrique should still be a subordinate of itself
81
-        self.assertIn(self.rubrique, self.rubrique.subordinates()[EmNature.PARENT])
82
-
83
-        # test preservation of superiors of other nature
84
-
85
-    def test_bad_hierarchy(self):
86
-        """ testing bad use of hierarchy """
87
-
88
-        # add a superior of different classtype
89
-        with self.assertRaises(ValueError):
90
-            self.numero.add_superior(self.personne, EmNature.PARENT)
91
-
92
-        # add a superior with bad nature
93
-        with self.assertRaises(ValueError):
94
-            self.numero.add_superior(self.rubrique, EmNature.IDENTITY)
95
-
96
-        # delete an invalid superior
97
-        self.article.del_superior(self.numero, EmNature.PARENT)
98
-
99
-
100
-class TestDeleteTypes(TypeTestCase):
101
-
102
-    def test_delete_types(self):
103
-        """ Testing EmType deletion """
104
-
105
-        # should be okay to delete article
106
-        article_id = self.article.uid
107
-        self.assertTrue(self.model.delete_component(self.article.uid))
108
-
109
-        # and it should not exist anymore
110
-        self.assertFalse(self.model.component(article_id))
111
-        # relations with other type should be deleted
112
-        self.assertNotIn(self.article, self.rubrique.subordinates()[EmNature.PARENT])
113
-
114
-        # rubrique has subordinates, should not be okay to delete
115
-        self.assertFalse(self.model.delete_component(self.rubrique.uid))

+ 0
- 357
EditorialModel/types.py View File

@@ -1,357 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import EditorialModel
4
-from EditorialModel.components import EmComponent
5
-from EditorialModel.fields import EmField
6
-from EditorialModel.classtypes import EmClassType, EmNature
7
-from EditorialModel.exceptions import MigrationHandlerChangeError, EmComponentCheckError
8
-import EditorialModel.classes
9
-
10
-
11
-## Represents type of documents
12
-# A type is a specialisation of a class, it can select optional field,
13
-# they have hooks, are organized in hierarchy and linked to other
14
-# EmType with special fields called relation_to_type fields
15
-#
16
-# @see EditorialModel::components::EmComponent
17
-# @todo sortcolumn handling
18
-class EmType(EmComponent):
19
-    ranked_in = 'class_id'
20
-
21
-    ## Instanciate a new EmType
22
-    # @todo define and check types for icon and sortcolumn
23
-    # @todo better check self.subordinates
24
-    def __init__(self, model, uid, name, class_id, fields_list=None, superiors_list=None, icon='0', sortcolumn='rank', string=None, help_text=None, date_update=None, date_create=None, rank=None):
25
-        self.class_id = class_id
26
-        self.check_type('class_id', int)
27
-        self.fields_list = fields_list if fields_list is not None else []
28
-        self.check_type('fields_list', list)
29
-        for field_uid in self.fields_list:
30
-            if not isinstance(field_uid, int):
31
-                raise AttributeError("Excepted fields_list to be a list of integers, but found a " + str(type(field_uid)) + " in it")
32
-
33
-        self.superiors_list = superiors_list if superiors_list is not None else {}
34
-        self.check_type('superiors_list', dict)
35
-        for nature, superiors_uid in self.superiors_list.items():
36
-            if nature not in [EmNature.PARENT, EmNature.TRANSLATION, EmNature.IDENTITY]:
37
-                raise AttributeError("Nature '%s' of superior is not allowed !" % nature)
38
-            if not isinstance(superiors_uid, list):
39
-                raise AttributeError("Excepted superiors of nature '%s' to be an list !" % nature)
40
-            for superior_uid in superiors_uid:
41
-                if not isinstance(superior_uid, int):
42
-                    raise AttributeError("Excepted superiors_list of nature '%s' to be a list of integers, but found a '%s' in it" % str(type(superior_uid)))
43
-
44
-        self.icon = icon
45
-        self.sortcolumn = sortcolumn
46
-        super(EmType, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
47
-
48
-    @classmethod
49
-    ## Create a new EmType and instanciate it
50
-    # @param name str: The name of the new type
51
-    # @param em_class EmClass: The class that the new type will specialize
52
-    # @param sortcolumn str : The name of the field that will be used to sort
53
-    # @param **em_component_args : @ref EditorialModel::components::create()
54
-    # @param cls
55
-    # @return An EmType instance
56
-    # @throw EmComponentExistError if an EmType with this name but different attributes exists
57
-    # @see EmComponent::__init__()
58
-    #
59
-    # @todo check that em_class is an EmClass object (fieldtypes can handle it)
60
-    def create(cls, name, em_class, sortcolumn='rank', **em_component_args):
61
-        return super(EmType, cls).create(name=name, class_id=em_class.uid, sortcolumn=sortcolumn, **em_component_args)
62
-
63
-    @property
64
-    ## Return the EmClassType of the type
65
-    # @return EditorialModel.classtypes.*
66
-    def classtype(self):
67
-        return getattr(EmClassType, self.em_class.classtype)
68
-
69
-    @property
70
-    ## Return an instance of the class this type belongs to
71
-    # @return EditorialModel.EmClass
72
-    def em_class(self):
73
-        return self.model.component(self.class_id)
74
-
75
-    ## @brief Delete an EmType
76
-    # The deletion is only possible if a type is not linked by any EmClass
77
-    # and if it has no subordinates
78
-    # @return True if delete False if not deleted
79
-    # @todo Check if the type is not linked by any EmClass
80
-    # @todo Check if there is no other ''non-deletion'' conditions
81
-    def delete_check(self):
82
-        if len(self.subordinates()) > 0:
83
-            return False
84
-        #Delete all relation with superiors
85
-        for nature, sups in self.superiors().items():
86
-            for sup in sups:
87
-                self.del_superior(sup, nature)
88
-        return True
89
-
90
-    ## Return selected optional field
91
-    # @return A list of EmField instance
92
-    def selected_fields(self):
93
-        selected = [self.model.component(field_id) for field_id in self.fields_list]
94
-        return selected
95
-
96
-    ## Return the list of associated fields
97
-    # @return A list of EmField instance
98
-    def fields(self, relational=True):
99
-        return [field for field in self.em_class.fields(relational) if not field.optional or (field.optional and field.uid in self.fields_list)]
100
-
101
-    ## Select_field (Function)
102
-    #
103
-    # Indicates that an optional field is used
104
-    #
105
-    # @param field EmField: The optional field to select
106
-    #
107
-    # @throw TypeError if field is not an EmField instance
108
-    # @throw ValueError if field is not optional or is not associated with this type
109
-    # @throw MigrationHandlerChangeError if migration handler is not happy with the change
110
-    # @see EmType::_change_field_list()
111
-    def select_field(self, field):
112
-        if field.uid in self.fields_list:
113
-            return True
114
-        self._change_field_list(field, True)
115
-
116
-    ## Unselect_field (Function)
117
-    #
118
-    # Indicates that an optional field will not be used
119
-    #
120
-    # @param field EmField: The optional field to unselect
121
-    #
122
-    # @throw TypeError if field is not an EmField instance
123
-    # @throw ValueError if field is not optional or is not associated with this type
124
-    # @throw MigrationHandlerChangeError if migration handler is not happy with the change
125
-    # @see EmType::_change_field_list()
126
-    def unselect_field(self, field):
127
-        if field.uid not in self.fields_list:
128
-            return True
129
-        self._change_field_list(field, False)
130
-
131
-    ## @brief Select or unselect an optional field
132
-    # @param field EmField: The EmField to select or unselect
133
-    # @param select bool: If True select field, else unselect it
134
-    #
135
-    # @throw TypeError if field is not an EmField instance
136
-    # @throw ValueError if field is not optional or is not associated with this type
137
-    # @throw MigrationHandlerChangeError if migration handler is not happy with the change
138
-    def _change_field_list(self, field, select=True):
139
-        if not isinstance(field, EmField):
140
-            raise TypeError("Excepted <class EmField> as field argument. But got " + str(type(field)))
141
-        if field not in self.em_class.fields():
142
-            raise ValueError("This field " + str(field) + "is not part of the type " + str(self))
143
-        if not field.optional:
144
-            raise ValueError("This field is not optional")
145
-
146
-        try:
147
-            if select:
148
-                self.fields_list.append(field.uid)
149
-                self.model.migration_handler.register_change(self.model, self.uid, None, {'fields_list': field.uid})
150
-            else:
151
-                self.fields_list.remove(field.uid)
152
-                self.model.migration_handler.register_change(self.model, self.uid, {'fields_list': field.uid}, None)
153
-        except MigrationHandlerChangeError as exception_object:
154
-            if select:
155
-                self.fields_list.remove(field.uid)
156
-            else:
157
-                self.fields_list.append(field.uid)
158
-            raise exception_object
159
-
160
-        self.model.migration_handler.register_model_state(self.model, hash(self.model))
161
-
162
-    ## Get the list of associated hooks
163
-    # @note Not conceptualized yet
164
-    # @todo Conception
165
-    def hooks(self):
166
-        raise NotImplementedError()
167
-
168
-    ## Add a new hook
169
-    # @param hook EmHook: An EmHook instance
170
-    # @throw TypeError
171
-    # @note Not conceptualized yet
172
-    # @todo Conception
173
-    def add_hook(self, hook):
174
-        raise NotImplementedError()
175
-
176
-    ## Delete a hook
177
-    # @param hook EmHook: An EmHook instance
178
-    # @throw TypeError
179
-    # @note Not conceptualized yet
180
-    # @todo Conception
181
-    # @todo Maybe we don't need a EmHook instance but just a hook identifier
182
-    def del_hook(self, hook):
183
-        raise NotImplementedError()
184
-
185
-    ## @brief Get the list of subordinates EmType
186
-    # Get a list of EmType instance that have this EmType for superior
187
-    # @return Return a dict with relation nature as keys and values as a list of subordinates
188
-    # EmType instance
189
-    # @throw RuntimeError if a nature fetched from db is not valid
190
-    def subordinates(self):
191
-        subordinates = {}
192
-        for em_type in self.model.components(EmType):
193
-            for nature, superiors_uid in em_type.superiors_list.items():
194
-                if self.uid in superiors_uid:
195
-                    if nature in subordinates:
196
-                        subordinates[nature].append(em_type)
197
-                    else:
198
-                        subordinates[nature] = [em_type]
199
-        return subordinates
200
-
201
-    ## @brief Get the list of superiors by relation's nature
202
-    # Get a list of EmType that are superiors of this type
203
-    # @return Return a dict with relation nature as keys and an EmType as value
204
-    # @throw RuntimeError if a nature has multiple superiors
205
-    def superiors(self):
206
-        return {nature: [self.model.component(superior_uid) for superior_uid in superiors_uid] for nature, superiors_uid in self.superiors_list.items()}
207
-
208
-    ## @brief Given a relation's nature return all the possible type to add as superiors
209
-    # @param relation_nature str | None : if None check for all natures
210
-    # @return a list or a dict with nature as key
211
-    def possible_superiors(self, relation_nature=None):
212
-        if relation_nature is None:
213
-            ret = {}
214
-            for nat in EmNature.getall():
215
-                ret[nat] = self.possible_superiors(nat)
216
-            return ret
217
-
218
-        #One nature
219
-        if relation_nature not in self.classtype['hierarchy']:
220
-            return []
221
-
222
-        att = self.classtype['hierarchy'][relation_nature]['attach']
223
-        if att == 'type':
224
-            return [self]
225
-        else:
226
-            return [t for t in self.model.components(EmType) if t.classtype == self.classtype]
227
-
228
-    ## Add a superior in the type hierarchy
229
-    # @param em_type EmType: An EmType instance
230
-    # @param relation_nature str: The name of the relation's nature
231
-    # @return False if no modification made, True if modifications success
232
-    #
233
-    # @throw TypeError when em_type not an EmType instance
234
-    # @throw ValueError when relation_nature isn't reconized or not allowed for this type
235
-    # @throw ValueError when relation_nature don't allow to link this types together
236
-    def add_superior(self, em_type, relation_nature):
237
-        # check if relation_nature is valid for this type
238
-        if relation_nature not in EmClassType.natures(self.classtype['name']):
239
-            raise ValueError("Invalid nature for add_superior : '" + relation_nature + "'. Allowed relations for this type are " + str(EmClassType.natures(self.classtype['name'])))
240
-
241
-        if relation_nature in self.superiors_list and em_type.uid in self.superiors_list[relation_nature]:
242
-            return True
243
-
244
-        att = self.classtype['hierarchy'][relation_nature]['attach']
245
-        if att == 'classtype':
246
-            if self.classtype['name'] != em_type.classtype['name']:
247
-                raise ValueError("Not allowed to put an em_type with a different classtype as superior")
248
-        elif self.name != em_type.name:
249
-            raise ValueError("Not allowed to put a different em_type as superior in a relation of nature '" + relation_nature + "'")
250
-
251
-        self._change_superiors_list(em_type, relation_nature, True)
252
-
253
-    ## Delete a superior in the type hierarchy
254
-    # @param em_type EmType: An EmType instance
255
-    # @param relation_nature str: The name of the relation's nature
256
-    # @throw TypeError when em_type isn't an EmType instance
257
-    # @throw ValueError when relation_nature isn't reconized or not allowed for this type
258
-    def del_superior(self, em_type, relation_nature):
259
-        if relation_nature not in self.superiors_list or em_type.uid not in self.superiors_list[relation_nature]:
260
-            return True
261
-        self._change_superiors_list(em_type, relation_nature, False)
262
-
263
-    ## Apply changes to the superiors_list
264
-    # @param em_type EmType: An EmType instance
265
-    # @param relation_nature str: The name of the relation's nature
266
-    # @param add bool: Add or delete relation
267
-    def _change_superiors_list(self, em_type, relation_nature, add=True):
268
-        # check instance of parameters
269
-        if not isinstance(em_type, EmType) or not isinstance(relation_nature, str):
270
-            raise TypeError("Excepted <class EmType> and <class str> as em_type argument. But got : " + str(type(em_type)) + " " + str(type(relation_nature)))
271
-
272
-        try:
273
-            if add:
274
-                if relation_nature in self.superiors_list:
275
-                    self.superiors_list[relation_nature].append(em_type.uid)
276
-                else:
277
-                    self.superiors_list[relation_nature] = [em_type.uid]
278
-                self.model.migration_handler.register_change(self.model, self.uid, None, {'superiors_list': {relation_nature: em_type.uid}})
279
-            else:
280
-                self.superiors_list[relation_nature].remove(em_type.uid)
281
-                if len(self.superiors_list[relation_nature]) == 0:
282
-                    del self.superiors_list[relation_nature]
283
-                self.model.migration_handler.register_change(self.model, self.uid, {'superiors_list': {relation_nature: em_type.uid}}, None)
284
-        # roll-back
285
-        except MigrationHandlerChangeError as exception_object:
286
-            if add:
287
-                self.superiors_list[relation_nature].remove(em_type.uid)
288
-                if len(self.superiors_list[relation_nature]) == 0:
289
-                    del self.superiors_list[relation_nature]
290
-            else:
291
-                if relation_nature in self.superiors_list:
292
-                    self.superiors_list[relation_nature].append(em_type.uid)
293
-                else:
294
-                    self.superiors_list[relation_nature] = [em_type.uid]
295
-            raise exception_object
296
-
297
-        self.model.migration_handler.register_model_state(self.model, hash(self.model))
298
-
299
-    ## Checks if the EmType is valid
300
-    # @throw EmComponentCheckError if check fails
301
-    def check(self):
302
-        super(EmType, self).check()
303
-        em_class = self.model.component(self.class_id)
304
-        if not em_class:
305
-            raise EmComponentCheckError("class_id contains an uid that does not exists '%d'" % self.class_id)
306
-        if not isinstance(em_class, EditorialModel.classes.EmClass):
307
-            raise EmComponentCheckError("class_id contains an uid from a component that is not an EmClass but a %s" % str(type(em_class)))
308
-
309
-        for i, f_uid in enumerate(self.fields_list):
310
-            field = self.model.component(f_uid)
311
-            if not field:
312
-                raise EmComponentCheckError("The element %d of selected_field is a non existing uid '%d'" % (i, f_uid))
313
-            if not isinstance(field, EmField):
314
-                raise EmComponentCheckError("The element %d of selected_field is not an EmField but a %s" % (i, str(type(field))))
315
-            if not field.optional:
316
-                raise EmComponentCheckError("The element %d of selected_field is an EmField not optional" % i)
317
-            """
318
-            if field.fieldgroup_id not in [fg.uid for fg in self.fieldgroups()]:
319
-                raise EmComponentCheckError("The element %d of selected_field is an EmField that is part of an EmFieldGroup that is not associated with this EmType" % i)
320
-            """
321
-
322
-        for nature, superiors_uid in self.superiors_list.items():
323
-            for superior_uid in superiors_uid:
324
-                em_type = self.model.component(superior_uid)
325
-                if not em_type:
326
-                    raise EmComponentCheckError("The superior is a non existing uid '%d'" % (superior_uid))
327
-                if not isinstance(em_type, EmType):
328
-                    raise EmComponentCheckError("The superior is a component that is not an EmType but a %s" % (str(type(em_type))))
329
-                if nature not in EmClassType.natures(self.em_class.classtype):
330
-                    raise EmComponentCheckError("The relation nature '%s' of the superior is not valid for this EmType classtype '%s'", (nature, self.classtype))
331
-
332
-                nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
333
-
334
-                if nat_spec['attach'] == 'classtype':
335
-                    if self.classtype != em_type.classtype:
336
-                        raise EmComponentCheckError("The superior is of '%s' classtype. But the current type is of '%s' classtype, and relation nature '%s' require two EmType of same classtype" % (em_type.classtype, self.classtype, nature))
337
-                elif nat_spec['attach'] == 'type':
338
-                    if self.uid != em_type.uid:
339
-                        raise EmComponentCheckError("The superior is a different EmType. But the relation nature '%s' require the same EmType" % (nature))
340
-                else:
341
-                    raise NotImplementedError("The nature['attach'] '%s' is not implemented in this check !" % nat_spec['attach'])
342
-
343
-                if 'max_depth' in nat_spec and nat_spec['max_depth'] > 0:
344
-                    depth = 1
345
-                    cur_type = em_type
346
-                    while depth >= nat_spec['max_depth']:
347
-                        depth += 1
348
-                        if len(cur_type.subordinates()[nature]) == 0:
349
-                            break
350
-                    else:
351
-                        raise EmComponentCheckError("The relation with superior %d  has a depth superior than the maximum depth (%d) allowed by the relation's nature '%s'" % (superior_uid, nat_spec['max_depth'], nature))
352
-
353
-        for nature in self.subordinates():
354
-            nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
355
-            if 'max_child' in nat_spec and nat_spec['max_child'] > 0:
356
-                if len(self.subordinates()[nature]) > nat_spec['max_child']:
357
-                    raise EmComponentCheckError("The EmType has more child than allowed in the relation's nature : %d > %d" % (len(self.subordinates()[nature], nat_spec['max_child'])))

+ 0
- 91
Lodel/__init__.py View File

@@ -1,91 +0,0 @@
1
-## @mainpage
2
-#
3
-# @section Introduction
4
-#
5
-# For basics Lodel2 configuration & usage see README.md
6
-#
7
-# - @subpage lodel2_doc
8
-# - Main packages
9
-#  - Lodel
10
-#   - Lodel.settings
11
-#  - EditorialModel
12
-#   - EditorialModel.fieldtypes
13
-#   - EditorialModel.backend
14
-#  - @ref leapi
15
-#  - DataSource
16
-#   - DataSource.MySQL
17
-
18
-## @page lodel2_doc Lodel2 documentation
19
-# @tableofcontents
20
-#
21
-# @section mainpage_docs Documentation
22
-#
23
-# - @subpage lodel2_arch 
24
-#  - @subpage lodel_settings
25
-#  - @subpage api_user_side
26
-#   - @subpage lecrud_instanciation
27
-#  - @subpage lodel2_fieldtypes
28
-#
29
-#
30
-
31
-
32
-## @page lodel2_arch Lodel2 architecture
33
-#
34
-# @tableofcontents
35
-#
36
-# @section lodel2_arch_basic Lodel2
37
-#
38
-# Lodel 2 is a CMS that allows to define content types ......
39
-#
40
-# @section lodel2_arch_edmod Lodel2 Editorial Model
41
-#
42
-# The editorial model is the part of lodel2 that allows to defines content types.
43
-#
44
-# To handle this the editorial model has 3  abstractions :
45
-# - Classes
46
-# - Types
47
-# - Fields
48
-#
49
-# @warning An editorial model does NOT contains values.
50
-#
51
-# The editorial model is used to generate the dynamic part of the Lodel2 API ( see @ref leapi )
52
-#
53
-# @subsection lodel2_arch_edmod_classes Editorial model classes ( EmClass )
54
-#
55
-# An EmClass is basically a named collection of EmFields ( see @ref lodel2_arch_edmod_fields ) associated with a classtype.
56
-#
57
-# Classtypes are "family" of EmClasses. They defines allowed hierarchical relations between EmClass.
58
-# 
59
-# @subsection lodel2_arch_edmod_types Editorial model types ( EmType )
60
-#
61
-# An EmType is a named EmClass specialization. In fact some EmField in an EmClass can be defined as optionnal and then be selected
62
-# or not to be included in an EmType.
63
-# 
64
-# @subsection lodel2_arch_edmod_fields Editorial model fields ( EmField )
65
-#
66
-# EmFields defines what kind of datas can be stored in EmTypes. Actually its the associationg with an EmFieldtype ( see @ref lodel2_fieldtypes )
67
-# that really defines what kind of datas can be stored.
68
-#
69
-# @section lodel2_arch_ui Lodel2 user interfaces
70
-#
71
-# All access to datas are made via UI. UI actions are composed with 3 or 4 elements :
72
-# - user
73
-# - action (Crud actions ?)
74
-# - target
75
-# - sometimes datas
76
-#
77
-# Each actions are send to Lodel2 ACL that check permissions and forward the actions to leapi. leapi send back a reply to the
78
-# UI. And the response get formatted by UI templates. ( see @ref lodel2_arch_ui_fig )
79
-#
80
-# @section lodel2_arch_fig Figures
81
-#
82
-# @subsection lodel2_arch_edmod_fig_components Editorial model main components
83
-# @image html graphviz/em_components.png
84
-# @subsection lodel2_arch_edmod_fig_relations Editorial model relations between components
85
-# @image html graphviz/em_relations.png
86
-# @subsection lodel2_arch_edmod_fig_hierarchy Hierarchical relations between EmTypes given a classtype
87
-# @image html graphviz/em_types_hierarch.png
88
-# @subsection lodel2_arch_edmod_fig_em_example Example of editorial model
89
-# @image html graphviz/example_em_graph.png
90
-# @subsection lodel2_arch_ui_fig Lodel2 UI schema
91
-# @image html graphviz/lodel2_ui.png

+ 0
- 87
Lodel/hooks.py View File

@@ -1,87 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import os
4
-import copy
5
-from importlib.machinery import SourceFileLoader
6
-
7
-from Lodel.settings import Settings
8
-
9
-## @brief Class designed to handle a hook's callback with a priority
10
-class DecoratedWrapper(object):
11
-    ## @brief Constructor
12
-    # @param hook function : the function to wrap
13
-    # @param priority int : the callbacl priority
14
-    def __init__(self, hook, priority):
15
-        self._priority = priority
16
-        self._hook = hook
17
-    
18
-    ## @brief Call the callback
19
-    # @param hook_name str : The name of the called hook
20
-    # @param caller * : The caller (depends on the hook)
21
-    # @param payload * : Datas that depends on the hook
22
-    # @return modified payload
23
-    def __call__(self, hook_name, caller, payload):
24
-        return self._hook(hook_name, caller, payload)
25
-
26
-## @brief Decorator designed to register hook's callbacks
27
-#
28
-# @note Decorated functions are expected to take 3 arguments :
29
-#  - hook_name : the called hook name
30
-#  - caller : the hook caller (depends on the hook)
31
-#  - payload : datas depending on the hook
32
-class LodelHook(object):
33
-    
34
-    ## @brief Stores all hooks (DecoratedWrapper instances)
35
-    _hooks = dict()
36
-    
37
-    ## @brief Decorator constructor
38
-    # @param hook_name str : the name of the hook to register to
39
-    # @param priority int : the hook priority
40
-    def __init__(self, hook_name, priority = None):
41
-        self._hook_name = hook_name
42
-        self._priority = 0xFFFF if priority is None else priority
43
-    
44
-    ## @brief called just after __init__
45
-    # @param hook function : the decorated function
46
-    # @return the hook argument
47
-    def __call__(self, hook):
48
-        if not self._hook_name in self._hooks:
49
-            self._hooks[self._hook_name] = list()
50
-        wrapped = DecoratedWrapper(hook, self._priority)
51
-        self._hooks[self._hook_name].append(wrapped)
52
-        self._hooks[self._hook_name] = sorted(self._hooks[self._hook_name], key = lambda h: h._priority)
53
-        return hook
54
-
55
-    ## @brief Call hooks
56
-    # @param hook_name str : the hook's name
57
-    # @param caller * : the hook caller (depends on the hook)
58
-    # @param payload * : datas for the hook
59
-    # @param cls
60
-    # @return modified payload
61
-    @classmethod
62
-    def call_hook(cls, hook_name, caller, payload):
63
-        if hook_name in cls._hooks:
64
-            for hook in cls._hooks[hook_name]:
65
-                payload = hook(hook_name, caller, payload)
66
-        return payload
67
-    
68
-    ## @brief Fetch registered hooks
69
-    # @param names list | None : optionnal filter on name
70
-    # @param cls
71
-    # @return a list of functions
72
-    @classmethod
73
-    def hook_list(cls, names = None):
74
-        res = None
75
-        if names is not None:
76
-            res = { name: copy.copy(cls._hooks[name]) for name in cls._hooks if name in names}
77
-        else:
78
-            res = copy.copy(cls._hooks)
79
-        return { name: [(hook._hook, hook._priority) for hook in hooks] for name, hooks in res.items() }
80
-    
81
-    ## @brief Unregister all hooks
82
-    # @param cls
83
-    # @warning REALLY NOT a good idea !
84
-    # @note implemented for testing purpose
85
-    @classmethod
86
-    def __reset__(cls):
87
-        cls._hooks = dict()

+ 0
- 113
Lodel/logger.py View File

@@ -1,113 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import logging, logging.handlers
4
-import os.path
5
-from Lodel.settings import Settings
6
-
7
-# Variables & constants definitions
8
-default_format = '%(asctime)-15s %(levelname)s %(_pathname)s:%(_lineno)s:%(_funcName)s() : %(message)s'
9
-simple_format = '%(asctime)-15s %(levelname)s : %(message)s'
10
-SECURITY_LOGLEVEL = 35
11
-logging.addLevelName(SECURITY_LOGLEVEL, 'SECURITY')
12
-handlers = dict() # Handlers list (generated from settings)
13
-
14
-# Fetching default root logger
15
-logger = logging.getLogger()
16
-
17
-# Setting options from Lodel.settings.Settings.logging
18
-def __init_from_settings():
19
-    # Disabled, because the custom format raises error (enable to give the _ preffixed arguments to logger resulting into a KeyError exception )
20
-    #logging.captureWarnings(True) # Log warnings
21
-
22
-    logger.setLevel(logging.DEBUG)
23
-    for name, logging_opt in Settings.logging.items():
24
-        add_handler(name, logging_opt)
25
-
26
-## @brief Add an handler, identified by a name, to a given logger 
27
-#
28
-# logging_opt is a dict with logger option. Allowed keys are : 
29
-# - filename : take a filepath as value and cause the use of a logging.handlers.RotatingFileHandler
30
-# - level : the minimum logging level for a logger, takes values [ 'DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL' ]
31
-# - format : DONT USE THIS OPTION (or if you use it be sure to includes %(_pathname)s %(_lineno)s %(_funcName)s format variables in format string
32
-# - context : boolean, if True include the context (module:lineno:function_name) in the log format
33
-# @todo Move the logging_opt documentation somewhere related with settings
34
-# 
35
-# @param name str : The handler name
36
-# @param logging_opt dict : dict containing options ( see above )
37
-def add_handler(name, logging_opt):
38
-    logger = logging.getLogger()
39
-    if name in handlers:
40
-        raise KeyError("A handler named '%s' allready exists")
41
-
42
-    if 'filename' in logging_opt:
43
-        maxBytes = (1024 * 10) if 'maxBytes' not in logging_opt else logging_opt['maxBytes']
44
-        backupCount = 10 if 'backupCount' not in logging_opt else logging_opt['backupCount']
45
-
46
-        handler = logging.handlers.RotatingFileHandler(
47
-                                        logging_opt['filename'],
48
-                                        maxBytes = maxBytes,
49
-                                        backupCount = backupCount,
50
-                                        encoding = 'utf-8')
51
-    else:
52
-        handler = logging.StreamHandler()
53
-    
54
-    if 'level' in logging_opt:
55
-        handler.setLevel(getattr(logging, logging_opt['level'].upper()))
56
-
57
-    if 'format' in logging_opt:
58
-        formatter = logging.Formatter(logging_opt['format'])
59
-    else:
60
-        if 'context' in logging_opt and not logging_opt['context']:
61
-            formatter = logging.Formatter(simple_format)
62
-        else:
63
-            formatter = logging.Formatter(default_format)
64
-
65
-    handler.setFormatter(formatter)
66
-    handlers[name] = handler
67
-    logger.addHandler(handler)
68
-    
69
-
70
-## @brief Remove an handler generated from configuration (runtime logger configuration)
71
-# @param name str : handler name
72
-def remove_handler(name):
73
-    if name in handlers:
74
-        logger.removeHandler(handlers[name])
75
-    # else: can we do anything ?
76
-
77
-## @brief Utility function that disable unconditionnaly handlers that implies console output
78
-# @note In fact, this function disables handlers generated from settings wich are instances of logging.StreamHandler
79
-def remove_console_handlers():
80
-    for name, handler in handlers.items():
81
-        if isinstance(handler, logging.StreamHandler):
82
-            remove_handler(name)
83
-    
84
-
85
-# Utility functions
86
-
87
-## @brief Generic logging function
88
-# @param lvl int : Log severity
89
-# @param msg str : log message
90
-# @param *args : additional positionnal arguments
91
-# @param **kwargs : additional named arguments
92
-def log(lvl, msg, *args, **kwargs):
93
-    caller = logger.findCaller() # Opti warning : small overhead
94
-    extra = {
95
-        '_pathname': os.path.relpath(
96
-                                        caller[0],
97
-                                        start=Settings.lodel2_lib_path
98
-        ), # os.path.relpath add another small overhead
99
-        '_lineno': caller[1],
100
-        '_funcName': caller[2],
101
-    }
102
-    logger.log(lvl, msg, extra = extra, *args, **kwargs)
103
-
104
-def debug(msg, *args, **kwargs): log(logging.DEBUG, msg, *args, **kwargs)
105
-def info(msg, *args, **kwargs): log(logging.INFO, msg, *args, **kwargs)
106
-def warning(msg, *args, **kwargs): log(logging.WARNING, msg, *args, **kwargs)
107
-def security(msg, *args, **kwargs): log(SECURITY_LOGLEVEL, msg, *args, **kwargs)
108
-def error(msg, *args, **kwargs): log(logging.ERROR, msg, *args, **kwargs)
109
-def critical(msg, *args, **kwargs): log(logging.CRITICAL, msg, *args, **kwargs)
110
-
111
-# Initialisation triggering
112
-if len(handlers) == 0:
113
-    __init_from_settings()

+ 0
- 87
Lodel/plugins.py View File

@@ -1,87 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import inspect
4
-import os, os.path
5
-import warnings
6
-
7
-from Lodel.settings import Settings
8
-from Lodel.hooks import LodelHook
9
-from Lodel.user import authentication_method, identification_method
10
-
11
-
12
-## @brief Returns a list of human readable registered hooks
13
-# @param names list | None : optionnal filter on name
14
-# @param plugins list | None : optionnal filter on plugin name
15
-# @return A str representing registered hooks
16
-def list_hooks(names = None, plugins = None):
17
-    res = ""
18
-    # Hooks registered and handled by Lodel.usera
19
-    for decorator in [authentication_method, identification_method]:
20
-        if names is None or decorator.__name__ in names:
21
-            res += "%s :\n" % decorator.__name__
22
-            for hook in decorator.list_methods():
23
-                module = inspect.getmodule(hook).__name__
24
-                if plugins is not None: # Filter by plugin
25
-                    spl = module.split('.')
26
-                    if spl[-1] not in plugins:
27
-                        continue
28
-                res += "\t- %s.%s\n" % (module, hook.__name__)
29
-    # Hooks registered and handled by Lodel.hooks
30
-    registered_hooks = LodelHook.hook_list(names)
31
-    for hook_name, hooks in registered_hooks.items():
32
-        if names is None or hook_name in names:
33
-            res += "%s :\n" % hook_name
34
-            for hook, priority in hooks:
35
-                module = inspect.getmodule(hook).__name__
36
-                if plugins is not None: # Filter by plugin
37
-                    spl = module.split('.')
38
-                    if spl[-1] not in plugins:
39
-                        continue
40
-                res += "\t- %s.%s ( priority %d )\n" % (module, hook.__name__, priority)
41
-    return res
42
-
43
-## @brief Return a human readable list of plugins
44
-# @param activated bool | None : Optionnal filter on activated or not plugin
45
-# @return a str
46
-def list_plugins(activated = None):
47
-    res = ""
48
-    # Activated plugins
49
-    if activated is None or activated:
50
-        res += "Activated plugins :\n"
51
-        for plugin_name in Settings.plugins:
52
-            res += "\t- %s\n" % plugin_name
53
-    # Deactivated plugins
54
-    if activated is None or not activated:
55
-        plugin_dir = os.path.join(Settings.lodel2_lib_path, 'plugins')
56
-        res += "Not activated plugins :\n"
57
-        all_plugins = [fname for fname in os.listdir(plugin_dir) if fname != '.' and fname != '..' and fname != '__init__.py']
58
-        for plugin_name in all_plugins:
59
-            if os.path.isfile(os.path.join(plugin_dir, plugin_name)) and plugin_name.endswith('.py'):
60
-                plugin_name = ''.join(plugin_name.split('.')[:-1])
61
-            elif not os.path.isdir(os.path.join(plugin_dir, plugin_name)):
62
-                warnings.warn("Dropped file in plugins directory : '%s'" % (os.path.join(plugin_dir, plugin_name)))
63
-                continue
64
-            elif plugin_name == '__pycache__':
65
-                continue
66
-
67
-            if plugin_name not in Settings.plugins:
68
-                res += "\t- %s\n" % plugin_name
69
-    return res
70
-
71
-## @brief Utility function that generate the __all__ list of the plugins/__init__.py file
72
-# @return A list of module name to import
73
-def _all_plugins():
74
-    plugin_dir = os.path.join(Settings.lodel2_lib_path, 'plugins')
75
-    res = list()
76
-    for plugin_name in Settings.plugins:
77
-        if os.path.isdir(os.path.join(plugin_dir, plugin_name)):
78
-            # plugin is a module
79
-            res.append('%s' % plugin_name)
80
-            #res.append('%s.loader' % plugin_name)
81
-        elif os.path.isfile(os.path.join(plugin_dir, '%s.py' % plugin_name)):
82
-            # plugin is a simple python sourcefile
83
-            res.append('%s' % plugin_name)
84
-    return res
85
-            
86
-            
87
-    

+ 0
- 144
Lodel/settings.py View File

@@ -1,144 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import types
4
-import warnings
5
-from . import settings_format
6
-
7
-## @package Lodel.settings
8
-#
9
-# @brief Defines stuff to handles Lodel2 configuration (see @ref lodel_settings )
10
-#
11
-# To access the confs use the Lodel.settings.Settings SettingsHandler instance
12
-
13
-## @brief A class designed to handles Lodel2 settings
14
-#
15
-# When instanciating a SettingsHandler, the new instance is filled with the content of settings.py (in the root directory of lodel2
16
-#
17
-# @warning You don't have to instanciate this class, you can access to the global instance with the Settings variable in this module
18
-# @todo Forbid module assignement in settings ! and disable tests about this
19
-# @todo Implements a type checking of config value
20
-# @todo Implements default values for config keys
21
-class SettingsHandler(object):
22
-    
23
-    ## @brief Shortcut
24
-    _allowed = settings_format.ALLOWED + settings_format.MANDATORY
25
-    ## @brief Shortcut
26
-    _mandatory = settings_format.MANDATORY
27
-
28
-    def __init__(self):
29
-        try:
30
-            import settings as default_settings
31
-            self._load_module(default_settings)
32
-        except ImportError:
33
-            warnings.warn("Unable to find global default settings")
34
-
35
-        ## @brief A flag set to True when the instance is fully loaded
36
-        self._set_loaded(False if len(self._missings()) > 0 else True)
37
-    
38
-    ## @brief Compat wrapper for getattr
39
-    def get(self, name):
40
-        return getattr(self, name)
41
-    
42
-    ## @brief Compat wrapper for setattr
43
-    def set(self, name, value):
44
-        return setattr(self, name, value)
45
-
46
-    ## @brief Load every module properties in the settings instance
47
-    #
48
-    # Load a module content into a SettingsHandler instance and checks that no mandatory settings are missing
49
-    # @note Example : <pre> import my_cool_settings;
50
-    # Settings._load_module(my_cool_settings);</pre>
51
-    # @param module module|None: a loaded module (if None just check for missing settings)
52
-    # @throw LookupError if invalid settings found or if mandatory settings are missing
53
-    def load_module(self, module = None):
54
-        if not(module is None):
55
-            self._load_module(module)
56
-        missings = self._missings()
57
-        if len(missings) > 0:
58
-            self._loaded = False
59
-            raise LookupError("Mandatory settings are missing : %s"%missings)
60
-        self._set_loaded(True)
61
-    
62
-    ## @brief supersede of default __setattr__ method
63
-    def __setattr__(self, name, value):
64
-        if not hasattr(self, name):
65
-            if name not in self._allowed:
66
-                raise LookupError("Invalid setting : %s"%name)
67
-        super().__setattr__(name, value)
68
-
69
-    ## @brief This method do the job for SettingsHandler.load_module()
70
-    #
71
-    # @note The difference with SettingsHandler.load_module() is that it didn't check if some settings are missing
72
-    # @throw LokkupError if an invalid settings is given
73
-    # @param module : a loaded module
74
-    def _load_module(self, module):
75
-        errors = []
76
-        fatal_errors = []
77
-        conf_dict = {
78
-            name: getattr(module, name)
79
-            for name in dir(module) 
80
-            if not name.startswith('__') and not isinstance(getattr(module, name), types.ModuleType)
81
-        }
82
-        for name, value in conf_dict.items():
83
-            try:
84
-                setattr(self, name, value)
85
-            except LookupError:
86
-                errors.append(name)
87
-        if len(errors) > 0:
88
-            err_msg = "Found invalid settings in %s : %s"%(module.__name__, errors)
89
-            raise LookupError(err_msg)
90
-
91
-    ## @brief Refresh the allowed and mandatory settings list
92
-    @classmethod
93
-    def _refresh_format(cls):
94
-        ## @brief Shortcut
95
-        cls._allowed = settings_format.ALLOWED + settings_format.MANDATORY
96
-        ## @brief Shortcut
97
-        cls._mandatory = settings_format.MANDATORY
98
-
99
-    ## @brief If some settings are missings return their names
100
-    # @return an array of string
101
-    def _missings(self):
102
-        return [ confname for confname in self._mandatory if not hasattr(self, confname) ]
103
-
104
-    def _set_loaded(self, value):
105
-        super().__setattr__('_loaded', bool(value))
106
-
107
-Settings = SettingsHandler()
108
-
109
-## @page lodel_settings Lodel SettingsHandler
110
-#
111
-# This page describe the way settings are handled in Lodel2.
112
-#
113
-# @section lodel_settings_files Lodel settings files
114
-#
115
-# - Lodel/settings.py defines the Lodel.settings package, the SettingsHandler class and the Lodel.settings.Settings instance
116
-# - Lodel/settings_format.py defines the mandatory and allowed configurations keys lists
117
-# - install/instance_settings.py is a model of the file that will be deployed in Lodel2 instances directories
118
-#
119
-# @section Using Lodel.settings.Settings SettingsHandler instance
120
-#
121
-# @subsection lodel_settings_without_loader Without loader
122
-#
123
-# Without any loader you can import Lodel.settings.Settings and acces its property with getattr (or . ) or with SettingsHandler.get() method.
124
-# In the same way you can set a settings by standart affectation of a propery or with SettingsHandler.set() method.
125
-#
126
-# @subsection lodel_settings_loader With a loader in a lodel2 instance
127
-#
128
-# The loader will import Lodel.settings.Settings and then calls the SettingsHandler.load_module() method to load the content of the instance_settings.py file into the SettingsHandler instance
129
-#
130
-# @subsection lodel_settings_example Examples
131
-#
132
-# <pre>
133
-# #!/usr/bin/python
134
-# from Lodel.settings import Settings
135
-# if Settings.debug:
136
-#   print("DEBUG")
137
-# # or
138
-# if Settings.get('debug'):
139
-#   print("DEBUG")
140
-# Settings.debug = False
141
-# # or
142
-# Settings.set('debug', False)
143
-# </pre>
144
-# 

+ 0
- 26
Lodel/settings_format.py View File

@@ -1,26 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-## @package Lodel.settings_format Rules for settings
3
-
4
-## @brief List mandatory configurations keys
5
-MANDATORY = [
6
-    'debug',
7
-    'debug_sql',
8
-    'sitename',
9
-    'lodel2_lib_path',
10
-    'em_file',
11
-    'dynamic_code_file',
12
-    'ds_package',
13
-    'datasource',
14
-    'mh_classname',
15
-    'migration_options',
16
-    'base_path',
17
-    'plugins',
18
-    'logging',
19
-]
20
-
21
-## @brief List allowed (but not mandatory) configurations keys
22
-ALLOWED = [
23
-    'em_graph_output',
24
-    'em_graph_format',
25
-    'templates_base_dir'
26
-]

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


+ 0
- 163
Lodel/test/tests_hooks.py View File

@@ -1,163 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import copy
4
-import unittest
5
-from unittest import mock
6
-from unittest.mock import patch, call, Mock
7
-
8
-import leapi.test.utils
9
-from Lodel.hooks import LodelHook
10
-
11
-class LodelHookTestCase(unittest.TestCase):
12
-
13
-    #Dynamic code generation & import
14
-    @classmethod
15
-    def setUpClass(cls):
16
-        """ Write the generated code in a temporary directory and import it """
17
-        cls.tmpdir = leapi.test.utils.tmp_load_factory_code()
18
-
19
-    @classmethod
20
-    def tearDownClass(cls):
21
-        """ Remove the temporary directory created at class setup """
22
-        leapi.test.utils.cleanup(cls.tmpdir)
23
-
24
-    def test_hook_registration(self):
25
-        """ Testing hooks registration """
26
-        self.assertEqual(LodelHook.hook_list('test_hook'), dict())
27
-        @LodelHook('test_hook', 42)
28
-        def test_hook(hook_name, caller, payload):
29
-            pass
30
-        @LodelHook('test_hook', 1)
31
-        def test2_hook(hook_name, caller, payload):
32
-            pass
33
-        @LodelHook('test_hook')
34
-        def test3_hook(hook_name, caller, payload):
35
-            pass
36
-
37
-        self.assertEqual(   LodelHook.hook_list('test_hook'),
38
-                            {
39
-                                'test_hook':[
40
-                                    (test2_hook, 1),
41
-                                    (test_hook, 42),
42
-                                    (test3_hook, 0xFFFF),
43
-                                ]
44
-                            }
45
-        )
46
-
47
-    def test_hook_call(self):
48
-        """ Testing hooks call """
49
-        # Registering a mock as hook
50
-        mockhook = Mock()
51
-        decorator = LodelHook('test_hook_call')
52
-        decorator(mockhook)
53
-        
54
-        LodelHook.call_hook('test_hook_call', leapi, [1,2,3,42])
55
-        mockhook.assert_called_once_with('test_hook_call', leapi, [1,2,3,42])
56
-        mockhook.reset_mock()
57
-        LodelHook.call_hook('test_hook_call', None, 'datas')
58
-        mockhook.assert_called_once_with('test_hook_call', None, 'datas')
59
-
60
-    def test_leapi_get_hook_leobject(self):
61
-        """ Testing that leapi_get_* hooks get called when calling get on LeObject """
62
-        from dyncode import Numero, Article, Publication, Textes, LeRelation, LeObject, LeRelation
63
-        call_args = {
64
-                        'query_filters': [],
65
-                        'offset': 0,
66
-                        'limit': None,
67
-                        'order': None,
68
-                        'group': None,
69
-                        'field_list': None
70
-        }
71
-        for leo in [Numero, Article, Publication, Textes, LeObject]:
72
-            call_args_full = copy.copy(call_args)
73
-            if 'instanciate' not in call_args_full:
74
-                call_args_full['instanciate'] = True
75
-            if leo.implements_letype():
76
-                call_args_full['query_filters'].append( ('type_id', '=', leo._type_id) )
77
-            if leo.implements_leclass():
78
-                call_args_full['query_filters'].append( ('class_id', '=', leo._class_id) )
79
-
80
-            with patch.object(LodelHook, 'call_hook', return_value=call_args_full) as callhook_mock:
81
-                foo = leo.get(**call_args)
82
-                expected_calls = [
83
-                    call('leapi_get_pre', leo, call_args_full),
84
-                    call('leapi_get_post', leo, None)
85
-                ]
86
-                callhook_mock.assert_has_calls(expected_calls, any_order = False)
87
-
88
-    def test_leapi_get_hook_lerelation(self):
89
-        """ Testing that leapi_get_* hooks get called when calling get on LeRelation """
90
-        from dyncode import LeRelation, LeRel2Type, LeHierarch, RelTextesPersonneAuteur
91
-        call_args = {
92
-                        'query_filters': [],
93
-                        'offset': 0,
94
-                        'limit': None,
95
-                        'order': None,
96
-                        'group': None,
97
-                        'field_list': None
98
-        }
99
-        for lerel in [LeRelation, LeRel2Type, LeHierarch, RelTextesPersonneAuteur]:
100
-            call_args_full = copy.copy(call_args)
101
-            if 'instanciate' not in call_args_full:
102
-                call_args_full['instanciate'] = True
103
-
104
-            with patch.object(LodelHook, 'call_hook', return_value=call_args_full) as callhook_mock:
105
-                foo = lerel.get(**call_args)
106
-                expected_calls = [
107
-                    call('leapi_get_pre', lerel, call_args_full),
108
-                    call('leapi_get_post', lerel, None),
109
-                ]
110
-                callhook_mock.assert_has_calls(expected_calls, any_order = False)
111
-    
112
-    def test_leapi_update_hook(self):
113
-        """ Testing that leapi_update_* hooks get called when calling update on LeCrud child instance"""
114
-        from leapi.lecrud import _LeCrud
115
-        from dyncode import Numero, Publication, LeRelation, RelTextesPersonneAuteur
116
-
117
-        call_args = {'datas':dict()}
118
-        
119
-        for leo in [Numero, Publication, RelTextesPersonneAuteur]:
120
-            with patch.object(_LeCrud, 'populate', return_value = None) as osef_mock:
121
-                with patch.object(LodelHook, 'call_hook', return_value = call_args) as callhook_mock:
122
-                    inst = leo(42)
123
-                    inst.update(**call_args)
124
-                    expected_calls = [
125
-                        call('leapi_update_pre', inst, call_args),
126
-                        call('leapi_update_post', inst, False),
127
-                    ]
128
-                    callhook_mock.assert_has_calls(expected_calls, any_order = False)
129
-
130
-    def test_leapi_delete_hooks(self):
131
-        """ Testing that leapi_delete_* hooks get called when calling delete on LeCrud child instance"""
132
-        from dyncode import Numero, Publication, LeRelation, RelTextesPersonneAuteur
133
-        for leo in [Numero, Publication, RelTextesPersonneAuteur]:
134
-            with patch.object(LodelHook, 'call_hook', return_value = None) as callhook_mock:
135
-                inst = leo(42)
136
-                inst.delete()
137
-                expected_calls = [
138
-                    call('leapi_delete_pre', inst, None),
139
-                    call('leapi_delete_post', inst, None)
140
-                ]
141
-                callhook_mock.assert_has_calls(expected_calls, any_order = False)
142
-
143
-    def test_leapi_insert_hooks(self):
144
-        """ Testing that leapi_insert_* hooks get called when calling insert on LeCrud child instance """
145
-        # Only testing with Article because datas check is anoying
146
-        from dyncode import Article
147
-        call_args = {
148
-            'datas': {
149
-                        'titre': 'test',
150
-                        'soustitre': 'avec des mocks',
151
-            }
152
-        }
153
-
154
-        full_args = copy.copy(call_args)
155
-        full_args['classname'] = None
156
-
157
-        with patch.object(LodelHook, 'call_hook', return_value = full_args) as callhook_mock:
158
-            Article.insert(**call_args)
159
-            expected_calls = [
160
-                call('leapi_insert_pre', Article, full_args),
161
-                call('leapi_insert_post', Article, None),
162
-            ]
163
-            callhook_mock.assert_has_calls(expected_calls)

+ 0
- 155
Lodel/test/tests_user.py View File

@@ -1,155 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import unittest
4
-import unittest.mock
5
-from unittest.mock import Mock
6
-
7
-from Lodel.user import  authentication_method, identification_method, UserIdentity, UserContext
8
-
9
-
10
-class AuthIdMethodTestCase(unittest.TestCase):
11
-    
12
-    def test_authentication_method_registration(self):
13
-        before = set(authentication_method.list_methods())
14
-
15
-        def test_method(self):
16
-            pass
17
-        authentication_method(test_method) # faking test_method decoration
18
-
19
-        after = set(authentication_method.list_methods())
20
-        self.assertEqual(after - before, set([test_method]))
21
-
22
-    def test_identification_method_registration(self):
23
-        before = set(identification_method.list_methods())
24
-
25
-        def test_method(self):
26
-            pass
27
-        identification_method(test_method) # faking test_method decoration
28
-        
29
-        after = set(identification_method.list_methods())
30
-        self.assertEqual(after - before, set([test_method]))
31
-
32
-    def test_authentication_method_calls(self):
33
-        mock_list = list()
34
-        for i in range(5):
35
-            auth_mock = Mock(return_value = False)
36
-            mock_list.append(auth_mock)
37
-            authentication_method(auth_mock)
38
-        ret = authentication_method.authenticate('login', 'password')
39
-        self.assertFalse(ret)
40
-        for mock in mock_list:
41
-            mock.assert_called_once_with('login', 'password')
42
-        # Adding a mock that will fake a successfull auth
43
-        user_id = UserIdentity(42, 'superlogin', authenticated = True)
44
-        authentication_method( Mock(return_value = user_id) )
45
-        ret = authentication_method.authenticate('login', 'password')
46
-        self.assertEqual(ret, user_id)
47
-
48
-    def test_identificatio_method_calls(self):
49
-        mock_list = list()
50
-        for i in range(5):
51
-            id_mock = Mock(return_value = False)
52
-            mock_list.append(id_mock)
53
-            identification_method(id_mock)
54
-        client_infos = {'ip':'127.0.0.1', 'user-agent': 'bla bla'}
55
-        ret = identification_method.identify(client_infos)
56
-        self.assertFalse(ret)
57
-        for mock in mock_list:
58
-            mock.assert_called_once_with(client_infos)
59
-        # Adding a mock that will fake a successfull identification
60
-        user_id = UserIdentity(42, 'login', identified = True)
61
-        identification_method( Mock(return_value = user_id) )
62
-        ret = identification_method.identify(client_infos)
63
-        self.assertEqual(ret, user_id)
64
-
65
-class UserIdentityTestCase(unittest.TestCase):
66
-    
67
-    def test_init(self):
68
-        """ Testing UserIdentity constructor """
69
-        uid = UserIdentity(42, 'login', 'anonymous login')
70
-        self.assertEqual(uid.user_id, 42)
71
-        self.assertEqual(uid.username, 'login')
72
-        self.assertEqual(uid.fullname, 'anonymous login')
73
-        self.assertFalse(uid.is_authenticated)
74
-        self.assertFalse(uid.is_identified)
75
-        self.assertEqual(str(uid), uid.fullname)
76
-
77
-    def test_identified(self):
78
-        """ Testing identified flag relation with authenticated flag """
79
-        uid = UserIdentity(42, 'login', identified = True)
80
-        self.assertTrue(uid.is_identified)
81
-        self.assertFalse(uid.is_authenticated)
82
-
83
-    def test_authentified(self):
84
-        """ Testing identified flag relation with authenticated flag """
85
-        uid = UserIdentity(42, 'login', authenticated = True)
86
-        self.assertTrue(uid.is_identified)
87
-        self.assertTrue(uid.is_authenticated)
88
-
89
-    def test_anonymous(self):
90
-        """ Testing the anonymous UserIdentity """
91
-        anon = UserIdentity.anonymous()
92
-        self.assertEqual(anon, UserIdentity.anonymous())
93
-        self.assertFalse(anon.is_authenticated)
94
-        self.assertFalse(anon.is_identified)
95
-
96
-class UserContextTestCase(unittest.TestCase):
97
-    
98
-    def test_static(self):
99
-        """ Testing that the class is static """
100
-        with self.assertRaises(NotImplementedError):
101
-            UserContext()
102
-
103
-    def test_not_init(self):
104
-        """ Testing method call with a non initialised class """
105
-        UserContext.__reset__()
106
-        with self.assertRaises(AssertionError):
107
-            UserContext.identity()
108
-        with self.assertRaises(AssertionError):
109
-            UserContext.authenticate('login', 'foobar')
110
-
111
-    def test_anon_init(self):
112
-        """ Testing class initialisation """
113
-        identification_method.__reset__()
114
-        authentication_method.__reset__()
115
-        UserContext.__reset__()
116
-        auth_id = UserIdentity(43, 'loggedlogin', authenticated = True)
117
-        # Add a fake authentication method
118
-        auth_mock = Mock(return_value = auth_id)
119
-        authentication_method(auth_mock)
120
-        # Are we anonymous ?
121
-        UserContext.init('localhost')
122
-        self.assertEqual(UserContext.identity(), UserIdentity.anonymous())
123
-        # Can we authenticate ourself ?
124
-        UserContext.authenticate('login', 'pass')
125
-        auth_mock.assert_called_once_with('login', 'pass')
126
-        self.assertEqual(auth_id, UserContext.identity())
127
-
128
-    def test_init(self):
129
-        """ Testing class initialisation being identified by client_infos"""
130
-        identification_method.__reset__()
131
-        authentication_method.__reset__()
132
-        UserContext.__reset__()
133
-        user_id = UserIdentity(42, 'login', identified = True)
134
-        auth_id = UserIdentity(43, 'loggedlogin', authenticated = True)
135
-        # Add a fake id method
136
-        id_mock = Mock(return_value = user_id)
137
-        identification_method(id_mock)
138
-        # Add a fake authentication method
139
-        auth_mock = Mock(return_value = auth_id)
140
-        authentication_method(auth_mock)
141
-        # testing lazy identification
142
-        UserContext.init('localhost')
143
-        id_mock.assert_not_called()
144
-        auth_mock.assert_not_called() # should really not be called yet
145
-        # triggering identification
146
-        ret_id = UserContext.identity()
147
-        id_mock.assert_called_once_with('localhost')
148
-        self.assertEqual(ret_id, user_id)
149
-        auth_mock.assert_not_called() # should really not be called yet
150
-        id_mock.reset_mock()
151
-        # Trying to auth
152
-        UserContext.authenticate('identifier', 'superproof')
153
-        id_mock.assert_not_called()
154
-        auth_mock.assert_called_once_with('identifier', 'superproof')
155
-        self.assertEqual(UserContext.identity(), auth_id)

+ 0
- 282
Lodel/user.py View File

@@ -1,282 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @package Lodel.user Defines classes designed to handler users and user's context
4
-#
5
-# Classes defined in this package are "helpers" for Lodel2 UI
6
-
7
-import warnings
8
-import copy
9
-from Lodel.settings import Settings
10
-
11
-## @brief Represent a Lodel user identity
12
-#
13
-# Class that produce immutable instance representing an identity
14
-class UserIdentity(object):
15
-    
16
-    ## Maintain a reference to a UserIdentity instance that represents an anonymous user
17
-    __anonymous_user = None
18
-
19
-    ## @brief Constructor
20
-    # @note produce immutable instance
21
-    # @param user_id * : user id
22
-    # @param username str : user name
23
-    # @param fullname str | None : user full name
24
-    # @param identified bool : set it to True if the user is identified
25
-    # @param authenticated bool : set it to True if the user is authenticated (force identified = True )
26
-    def __init__(self, user_id, username, fullname = None, identified = False, authenticated = False):
27
-        self.__user_id = user_id
28
-        self.__username = username
29
-        self.__fullname = fullname if fullname is not None else username
30
-        self.__authenticated = bool(authenticated)
31
-        self.__identified = bool(identified) or self.__authenticated
32
-    
33
-    ## @brief getter for user id
34
-    @property
35
-    def user_id(self):
36
-        return self.__user_id
37
-    
38
-    ## @brief getter for username
39
-    @property
40
-    def username(self):
41
-        return self.__username
42
-    
43
-    ## @brief getter for fullname
44
-    @property
45
-    def fullname(self):
46
-        return self.__fullname
47
-    
48
-    ## @return True if the user is considered as authenticated
49
-    @property
50
-    def is_authenticated(self):
51
-        return self.__authenticated
52
-
53
-    ## @return True if the user is considered as identified
54
-    @property
55
-    def is_identified(self):
56
-        return self.__identified
57
-    
58
-    ## @brief String representation of the instance
59
-    def __repr__(self):
60
-        return "User '{user_id}'( username = '{username}', fullname = '{fullname}', identified : {identified}, authentified : {auth}".format(
61
-                                    user_id = self.__user_id,
62
-                                    username = self.__username,
63
-                                    fullname = self.__fullname,
64
-                                    identified = str(self.__identified),
65
-                                    auth = str(self.__authenticated),
66
-        )
67
-    
68
-    ## @brief Human readable text representation of the instance
69
-    def __str__(self):
70
-        return self.__fullname
71
-
72
-    ## @brief Provide an instance of UserIdentity representing an anonymous user
73
-    @classmethod
74
-    def anonymous(cls):
75
-        if cls.__anonymous_user is None:
76
-            cls.__anonymous_user = UserIdentity(False, "anonymous", "Anonymous user")
77
-        return cls.__anonymous_user
78
-
79
-
80
-## @brief Decorator class designed to register user authentication methods
81
-#
82
-# @note Decorated functions are expected to take 2 arguments :
83
-#  - identifier : the user identifier
84
-#  - proof : a proof of identity
85
-# and are expected to return False if authentication fails. When authentication
86
-# is a success the function is expected to return a UserIdentity instance
87
-class authentication_method(object):
88
-    
89
-    ## @brief Stores registered authentication functions
90
-    __methods = set()
91
-    
92
-    ## @brief Constructor
93
-    # @param method function : decorated function
94
-    def __init__(self, method):
95
-        ## @brief Decorated functions
96
-        self._method = method
97
-        self.__methods |= set([method]) # method registration
98
-
99
-    ## @brief Callback called when decorated function is called
100
-    # @return bool
101
-    def __call__(self, identifier, proof):
102
-        return self._method(identifier, proof)
103
-    
104
-    ## @brief Try to authenticate a user with registered functions
105
-    # @param identifier * : user id
106
-    # @param proof * : user authentication proof
107
-    # @param cls
108
-    # @return False or a User Identity instance
109
-    @classmethod
110
-    def authenticate(cls, identifier, proof):
111
-        if len(cls.__methods) == 0:
112
-            raise RuntimeError("No authentication method registered")
113
-        res = False
114
-        for method in cls.__methods:
115
-            ret = method(identifier, proof)
116
-            if ret is not False:
117
-                if Settings.debug:
118
-                    if not isinstance(ret, UserIdentity):
119
-                        raise ValueError("Authentication method returns something that is not False nor a UserIdentity instance")
120
-                    if res is not False:
121
-                        warnings.warn("Multiple authentication methods returns a UserIdentity for given idetifier and proof")
122
-                else:
123
-                    return ret
124
-                res = ret
125
-        return res
126
-
127
-    ## @return registered identification methods
128
-    # @param cls
129
-    @classmethod
130
-    def list_methods(cls):
131
-        return list(copy.copy(cls.__methods))
132
-
133
-    ## @brief Unregister all authentication methods
134
-    # @param cls
135
-    # @warning REALLY NOT a good idead !
136
-    # @note implemented for testing purpose
137
-    @classmethod
138
-    def __reset__(cls):
139
-        cls.__methods = set()
140
-
141
-
142
-## @brief Decorator class designed to register identification methods
143
-#
144
-# @note The decorated functions are expected to take one argument :
145
-# - client_infos : datas for identification
146
-# and are expected to return False if identification fails. When identification is a success
147
-# the function is expected to return a UserIdentity instance
148
-class identification_method(object):
149
-    
150
-    ## @brief Stores registered identification functions
151
-    __methods = set()
152
-
153
-    ## @brief decorator constructor
154
-    # @param method function : decorated function
155
-    def __init__(self, method):
156
-        ## @brief Decorated functions
157
-        self.__method = method
158
-        self.__methods |= set([method])
159
-
160
-    ## @brief Called when decorated function is called
161
-    def __call__(self, client_infos):
162
-        return self._method(client_infos)
163
-
164
-    ## @brief Identify someone given datas
165
-    # @param client_infos * :  datas that may identify a user
166
-    # @param cls
167
-    # @return False if identification fails, else returns an UserIdentity instance
168
-    @classmethod
169
-    def identify(cls, client_infos):
170
-        if len(cls.__methods) == 0:
171
-            warnings.warn("No identification methods registered")
172
-        res = False
173
-        for method in cls.__methods:
174
-            ret = method(client_infos)
175
-            if ret is not False:
176
-                if Settings.debug:
177
-                    if not isinstance(ret, UserIdentity):
178
-                        raise ValueError("Identification method returns something that is not False nor a UserIdentity instance")
179
-                    if res is not False:
180
-                        warnings.warn("Identifying methods returns multiple identity given client_infos")
181
-                    else:
182
-                        res = ret
183
-                else:
184
-                    return ret
185
-        return res
186
-    
187
-    ## @return registered identification methods
188
-    # @param cls
189
-    @classmethod
190
-    def list_methods(cls):
191
-        return list(copy.copy(cls.__methods))
192
-
193
-    ## @brief Unregister all identification methods
194
-    # @param cls
195
-    # @warning REALLY NOT a good idead !
196
-    # @note implemented for testing purpose
197
-    @classmethod
198
-    def __reset__(cls):
199
-        cls.__methods = set()
200
-
201
-
202
-## @brief Static class designed to handle user context
203
-class UserContext(object):
204
-
205
-    ## @brief Client infos given by user interface
206
-    __client_infos = None
207
-    ## @brief Stores a UserIdentity instance
208
-    __identity = None
209
-    ## @brief Blob of datas stored by user interface
210
-    __context = None
211
-
212
-
213
-    ## @brief Not callable, static class
214
-    # @throw NotImplementedError
215
-    def __init__(self):
216
-        raise NotImplementedError("Static class")
217
-
218
-    ## @brief User context constructor
219
-    # @param client_infos * : datas for client identification (typically IP address)
220
-    # @param **kwargs dict : context
221
-    # @param cls
222
-    # @todo find another exception to raise
223
-    @classmethod
224
-    def init(cls, client_infos, **kwargs):
225
-        if cls.initialized():
226
-            raise RuntimeError("Context allready initialised")
227
-        if client_infos is None:
228
-            raise ValueError("Argument clien_infos cannot be None")
229
-        cls.__client_infos = client_infos
230
-        cls.__context = kwargs
231
-        cls.__identity = False
232
-    
233
-    ## @brief Identity getter (lazy identification implementation)
234
-    # @param cls
235
-    # @return a UserIdentity instance
236
-    @classmethod
237
-    def identity(cls):
238
-        cls.assert_init()
239
-        if cls.__identity is False:
240
-            ret = identification_method.identify(cls.__client_infos)
241
-            cls.__identity = UserIdentity.anonymous() if ret is False else ret
242
-        return cls.__identity
243
-
244
-    ## @brief authenticate a user
245
-    # @param identifier * : user identifier
246
-    # @param proof * : proof of identity
247
-    # @param cls
248
-    # @throw an exception if fails
249
-    # @todo find a better exception to raise when auth fails
250
-    @classmethod
251
-    def authenticate(cls, identifier, proof):
252
-        cls.assert_init()
253
-        ret = authentication_method.authenticate(identifier, proof)
254
-        if ret is False:
255
-            raise RuntimeError("Authentication failure")
256
-        cls.__identity = ret
257
-    
258
-    ## @return UserIdentity instance
259
-    # @todo useless alias to identity()
260
-    @classmethod
261
-    def user_identity(cls):
262
-        cls.assert_init()
263
-        return cls.identity()
264
-    
265
-    ## @return True if UserContext is initialized
266
-    @classmethod
267
-    def initialized(cls):
268
-        return cls.__client_infos is not None
269
-    
270
-    ## @brief Assert that UserContext is initialized
271
-    @classmethod
272
-    def assert_init(cls):
273
-        assert cls.initialized(), "User context is not initialized"
274
-    
275
-    ## @brief Reset the UserContext
276
-    # @warning Most of the time IT IS NOT A GOOD IDEAD
277
-    # @note implemented for test purpose
278
-    @classmethod
279
-    def __reset__(cls):
280
-        cls.__client_infos = None
281
-        cls.__identity = None
282
-        cls.__context = None

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


+ 0
- 115
Lodel/utils/mlstring.py View File

@@ -1,115 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-import json
4
-import warnings
5
-
6
-
7
-## Handle string with translations
8
-# @todo define a default language that will be used in case the wanted language is not available for this string (gettext-like behavior)
9
-class MlString(object):
10
-
11
-    default_lang = '___'
12
-    ## Instanciate a new string with translation
13
-    #
14
-    # @param translations dict|str : With key = lang and value the translation  or a json string
15
-    # @param default_value str : The default translation value
16
-    def __init__(self, translations=None, default_value = None):
17
-        if isinstance(translations, str):
18
-            try:
19
-                translations = json.loads(translations)
20
-            except ValueError:
21
-                if default_value is None:
22
-                    default_value = str(translations)
23
-                    translations = None
24
-        if translations is None:
25
-            translations = dict()
26
-
27
-        if not isinstance(translations, dict):
28
-            raise ValueError('Bad value given for translations argument on MlString instanciation')
29
-        else:
30
-            self.translations = translations
31
-
32
-        self.translations[self.default_lang] = '' if default_value is None else default_value
33
-        if default_value is None:
34
-            warnings.warn('No default value when isntanciating an MlString')
35
-    
36
-    ## Return a translation
37
-    # @param lang str: The lang
38
-    # @return An empty string if the wanted lang don't exist
39
-    # @warning Returns an empty string if the wanted translation didn't exists
40
-    # @todo if the asked language is not available, use the default one, defined as a class property
41
-    def get(self, lang = None):
42
-        if lang is None or lang not in self.translations:
43
-            lang = self.default_lang
44
-        return self.translations[lang]
45
-    
46
-    ## Set a translation for this MlString
47
-    # @param lang str: The language
48
-    # @param text str: The translation
49
-    # @todo check if the default language as a translation
50
-    def set(self, lang, text):
51
-        if not text:
52
-            if lang in self.translations:
53
-                del(self.translations[lang])
54
-        else:
55
-            self.translations[lang] = text
56
-
57
-    ## @return Default translation
58
-    def get_default(self):
59
-        return self.get(self.default_lang)
60
-    
61
-    ## @brief Set default value
62
-    def set_default(self, text):
63
-        self.set(self.default_lang, text)
64
-
65
-    ## String representation
66
-    # @return A json dump of the MlString::translations dict
67
-    def __str__(self):
68
-        return self.get_default()
69
-    
70
-    ## @brief Serialize the MlString in Json
71
-    def json_dumps(self):
72
-        if self.translations:
73
-            return json.dumps(self.translations, sort_keys=True)
74
-        else:
75
-            return ""
76
-
77
-    ## @brief Equivalent to json_dumps
78
-    def dumps(self): return self.json_dumps()
79
-
80
-    ## Test if two MlString instance are equivalent
81
-    # @param other MlString|str : Another MlString instance or a string (json formated)
82
-    # @return True or False
83
-    def __eq__(self, other):
84
-        if isinstance(other, str):
85
-            other = MlString(other)
86
-
87
-        if not isinstance(other, MlString):
88
-            return False
89
-        if not set(lng for lng in self.translations) == set( lng for lng in other.translations):
90
-            return False
91
-        for lng in self.translations:
92
-            if self.get(lng) != other.get(lng):
93
-                return False
94
-        return True
95
-    
96
-    ## @brief Allow [] access to translations
97
-    def __getitem__(self, langname): return self.get(langname)
98
-    
99
-    ## @brief Allow [] set 
100
-    def __setitem__(self, langname, txt): return self.set(langname, txt)
101
-    
102
-    ## @brief Implements dict.keys() method
103
-    def keys(self): return self.translations.keys()
104
-        
105
-    ## @brief Implements dict.values() method
106
-    def values(self): return self.translations.values()
107
-    
108
-    ## @brief Implements dict.items() method
109
-    def items(self): return self.translations.items()
110
-
111
-    @staticmethod
112
-    ## Instanciate a MlString from something
113
-    # @deprecated Equivalent to MlString(translations = arg, default_value = None)
114
-    def load(arg):
115
-        return MlString(arg)

+ 0
- 40
Lodel/utils/mosql.py View File

@@ -1,40 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-## @package Lodel.utils.mosql
4
-# @brief Helper function to use mosql library
5
-#
6
-# define create_database and drop_database Query to manipulate databases
7
-# define create, drop and alter Query to manipulate tables
8
-# define create_user, delete_user and grant to manipulate user and rights
9
-
10
-from mosql.util import Clause, Statement, Query, value, identifier, concat_by_comma, paren, raw
11
-
12
-def col_def(cols):
13
-    print("cols", cols)
14
-    ret = []
15
-    for col in cols:
16
-        print("col", col)
17
-        name, definition = col
18
-        ret.append(identifier(name) + ' ' + raw(definition))
19
-    return ret
20
-
21
-# chain definitions
22
-column_list = (col_def, concat_by_comma, paren)
23
-
24
-# Clauses
25
-create_clause = Clause('create table', alias='create', no_argument=True)
26
-if_not_exists = Clause('if not exists', alias='if_not_exists', no_argument=True, default=True)
27
-table_clause = Clause('table', (identifier, ), alias='table', hidden=True)
28
-character_clause = Clause('CHARACTER SET', (value, ), alias='character')
29
-column_clause = Clause('column', column_list, alias='column', hidden=True)
30
-
31
-alter_clause = Clause('alter table', (identifier, ), alias='table')
32
-add_clause = Clause('add column', (col_def, ), alias='column')
33
-
34
-# Statements
35
-create_statement = Statement([create_clause, if_not_exists, table_clause, column_clause, character_clause])
36
-alter_add_statement = Statement([alter_clause, add_clause])
37
-
38
-# queries
39
-create = Query(create_statement, ('table', 'column', 'if_not_exists'), {'create':True})
40
-alter_add = Query(alter_add_statement, ('table', 'column'))

+ 0
- 48
Makefile View File

@@ -1,48 +0,0 @@
1
-all: check doc pip
2
-
3
-# Running unit tests
4
-check:
5
-	python -m unittest -v
6
-
7
-# Rule to update libs
8
-pip: cleanpycache
9
-	pip install --upgrade -r requirements.txt
10
-
11
-#
12
-# Documentation rules
13
-#
14
-graphviz_images_path = doc/img/graphviz
15
-
16
-doc: cleandoc docimages refreshdyn
17
-	doxygen
18
-
19
-# Generating graphviz images
20
-docimages:
21
-	cd $(graphviz_images_path); make
22
-
23
-refreshdyn:
24
-	python refreshdyn.py >/dev/null
25
-
26
-#
27
-# Cleaning rules
28
-#
29
-.PHONY: check doc clean cleanpyc cleandoc cleanpycache cleandyn cleandocimages
30
-
31
-clean: cleanpyc cleandoc cleanpycache cleandocimages cleandyn
32
-
33
-# Documentation cleaning
34
-cleandoc:
35
-	-rm -Rf ./doc/html ./doc/doxygen_sqlite3.db
36
-
37
-cleandocimages:
38
-	cd $(graphviz_images_path); make clean
39
-
40
-# Python cleaning
41
-cleanpyc:
42
-	-find ./ |grep -E "\.pyc$$" |xargs rm -f 2>/dev/null
43
-cleanpycache: cleanpyc
44
-	-find ./ -type d |grep '__pycache__' | xargs rmdir -f 2>/dev/null
45
-
46
-cleandyn:
47
-	-rm leapi/dyn.py
48
-

+ 0
- 56
README.md View File

@@ -1,56 +0,0 @@
1
-- use python 3.4
2
-
3
-** install dependencies
4
-  pip install -r requirements.txt
5
-
6
-------
7
-
8
-Creating a Lodel "instance":
9
-
10
-use the lodel_init.sh script :
11
-	lodel_init.sh INSTANCE_NAME INSTANCE_WANTED_PATH [LODEL2_LIB_PATH]
12
-	cd INSTANCE_PATH
13
-
14
-Create a database for your instance
15
-  mysql
16
-  > CREATE DATABASE `lodel2`  CHARACTER SET utf8 COLLATE utf8_general_ci;
17
-  > GRANT ALL ON `lodel2`.* TO "lodel"@"localhost";
18
-
19
-Edit instance_settings.py according to your database, install database and dynamic code
20
-	make
21
-
22
-Once the instance is created you can run an interactive python interpreter using :
23
-	python loader.py
24
-
25
-If you want to write a script that run is the instance env you have to use
26
-	from loader import *
27
-
28
------
29
-
30
-Lodel2 plugins system:
31
-
32
-In an instance or in the lib dir you can ask Lodel2 wich plugins and wich hooks are activated.
33
-Print a list of plugins :
34
-	python3 manage_lodel.py --list-plugins
35
-Print a list of registered hooks :
36
-	python3 manage_lodel.py --list-hooks
37
-More informations about the script :
38
-	python3 manage_lodel.py --help
39
-
40
------
41
-
42
-** Doxygen generation
43
-  Dependencies : doxygen graphviz doxypy
44
-  Generation : run make doc in the root folder
45
-
46
-** create local config in settings.py
47
-Copy settings.py.example to settings.py, change the conf to your local settings
48
-
49
-** Tools
50
-
51
-  A Makefile is written with common operations :
52
-  - make clean : cleans doc and python pycache (and .pyc files)
53
-  - make pip : upgrade python libs according to requirements.txt
54
-  - make doc : generate the doxygen documentation
55
-  - make check : run the unit tests
56
-  - make : run check doc and pip

+ 0
- 1
Router/__init__.py View File

@@ -1 +0,0 @@
1
-from urls import *

+ 0
- 4
Router/urls.py View File

@@ -1,4 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-# This module contains the different functions used to deal with the Lodel 2 urls
4
-

+ 0
- 56
Template/Loader.py View File

@@ -1,56 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import jinja2
4
-
5
-import settings
6
-from .api import api_lodel_templates
7
-from .exceptions import NotAllowedCustomAPIKeyError
8
-
9
-
10
-class TemplateLoader(object):
11
-
12
-    __reserved_template_keys = ['lodel']
13
-
14
-    ## @brief Initializes a template loader
15
-    #
16
-    # @param search_path str : the base path from which the templates are searched. To use absolute paths, you can set
17
-    #                          it to the root "/". By default, it will be the root of the project, defined in the
18
-    #                          settings of the application
19
-    # @param follow_links bool : indicates whether or not to follow the symbolic links (default: True)
20
-    def __init__(self, search_path=settings.base_path, follow_links=True):
21
-        self.search_path = search_path
22
-        self.follow_links = follow_links
23
-
24
-    ## @brief Renders a HTML content of a template
25
-    #
26
-    # @param template_file str : path to the template file (starting from the base path used to instanciate the
27
-    #                            TemplateLoader)
28
-    # @param template_vars dict : parameters to be used in the template
29
-    # @param template_extra list : list of tuples indicating the custom modules to import in the template (default: None).
30
-    #                              The modules are given as tuples with the format : ('name_to_use_in_the_template', module)
31
-    #
32
-    # @return str. String containing the HTML output of the processed templated
33
-    def render_to_html(self, template_file, template_vars={}, template_extra=None):
34
-
35
-        loader = jinja2.FileSystemLoader(searchpath=self.search_path, followlinks=self.follow_links)
36
-        environment = jinja2.Environment(loader=loader)
37
-        template = environment.get_template(template_file)
38
-
39
-        # Lodel2 default api is loaded
40
-        template.globals['lodel'] = api_lodel_templates
41
-
42
-        # Extra modules are loaded
43
-        if template_extra is not None:
44
-            for extra in template_extra:
45
-                if not self.__is_allowed_template_key(extra[0]):
46
-                    raise NotAllowedCustomAPIKeyError("The name 'lodel' is a reserved one for the loaded APIs in templates")
47
-                template.globals[extra[0]] = extra[1]
48
-
49
-        return template.render(template_vars)
50
-
51
-    ## @brief Checks if the key used for the template is allowed
52
-    #
53
-    # @param key str
54
-    # @return bool
55
-    def __is_allowed_template_key(self, key):
56
-        return False if key in self.__class__.__reserved_template_keys else True

+ 0
- 0
Template/__init__.py View File


+ 0
- 0
Template/api/__init__.py View File


+ 0
- 3
Template/api/api_lodel_templates.py View File

@@ -1,3 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-# Lodel 2 templates API : loaded by default

+ 0
- 7
Template/exceptions/NotAllowedCustomAPIKeyError.py View File

@@ -1,7 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-
4
-class NotAllowedCustomAPIKeyError(Exception):
5
-
6
-    def __init__(self, message):
7
-        self.message = message

+ 0
- 1
Template/exceptions/__init__.py View File

@@ -1 +0,0 @@
1
-from .NotAllowedCustomAPIKeyError import NotAllowedCustomAPIKeyError

+ 0
- 14
doc/img/graphviz/Makefile View File

@@ -1,14 +0,0 @@
1
-dotfiles := $(wildcard *.dot)
2
-images := $(patsubst %.dot,%.png,$(wildcard *.dot))
3
-
4
-all: $(images)
5
-	
6
-%.png: %.dot
7
-	dot -Tpng $< > $@
8
-
9
-.PHONY: clean distclean
10
-
11
-clean:
12
-	-rm $(images)
13
-
14
-distclean: clean

+ 0
- 13
doc/img/graphviz/em_components.dot View File

@@ -1,13 +0,0 @@
1
-digraph editorial_model_components {
2
-
3
-	label="Editorial model components"
4
-
5
-	classtypes [ label="{ entity | person | index }", shape=record]
6
-	class [ label="<f0> Class| { <f2> fields|<f3> optionnal field }", shape=record ]
7
-	type [ label="<f0> Type| { <f2> fields| <f3> selected field }", shape=record ]
8
-
9
-	classtypes -> class [ label="Defines possibles hierarchy" ]
10
-	class:f0 -> type:f0 [ label="specialize", style="dotted" ]
11
-	class:f2 -> type:f2 [ label="inherit"]
12
-	class:f3 -> type:f3 [ label="select" ]
13
-}

+ 0
- 26
doc/img/graphviz/em_relations.dot View File

@@ -1,26 +0,0 @@
1
-digraph editorial_model_relations {
2
-	
3
-	label="Editorial Model relations"
4
-
5
-	classtypes
6
-
7
-	r_class1 [ label="<f0> Class 1| {STR |INTEGER(optionnal) | STR | REL_TO_TYPE}", shape=record ]
8
-
9
-	r_class2 [ label="<f0> Class 2| {STR |STR }", shape=record ]
10
-
11
-	r_type1 [ label="<f0> Type 1| { STR |INTEGER | STR |<fr>REL_TO_TYPE}}", shape=record ]
12
-	r_type2 [ label="<f0> Type 2|{ STR | STR |<fr>REL_TO_TYPE}", shape=record ]
13
-
14
-	r_type3 [ label="<f0> Type 3|{ STR |STR }", shape=record ]
15
-
16
-	classtypes -> r_class1
17
-	classtypes -> r_class2
18
-
19
-	r_class1 -> r_type1 [ label="inherit", style="dotted" ]
20
-	r_class1 -> r_type2 [ label="inherit", style="dotted" ]
21
-
22
-	r_class2 -> r_type3 [ label="inherit", style="dotted" ]
23
-
24
-	r_type1:fr -> r_class2 [ label="link to Class 2" ]
25
-	r_type2:fr -> r_class1 [ label="link to Class 1" ]
26
-}

+ 0
- 77
doc/img/graphviz/em_types_hierarch.dot View File

@@ -1,77 +0,0 @@
1
-digraph em_types_hierarchy {
2
-
3
-	label="Editorial Model types hierarchy"
4
-
5
-	subgraph cluster_person {
6
-		label="Person"
7
-		h_person [ label="Person", shape="tripleoctagon" ]
8
-
9
-		hc3 [ label="Class 3", shape="doubleoctagon"]
10
-		hc4 [ label="Class 4", shape="doubleoctagon"]
11
-
12
-		ht6 [ label="Type 6", shape="octagon" ]
13
-		ht7 [ label="Type 7", shape="octagon" ]
14
-		ht8 [ label="Type 8", shape="octagon" ]
15
-
16
-		h_person -> {hc3, hc4}
17
-
18
-		hc3 -> { ht6,ht7 }
19
-		hc4 -> ht8
20
-
21
-		ht6 -> ht7 [ label="identity", color="blue" ]
22
-		ht6 -> ht8 [ label="identity", color="blue" ]
23
-		ht8 -> ht8 [ label="identity", color="blue" ]
24
-	}
25
-
26
-	subgraph cluster_entity {
27
-		label="Entity"
28
-		h_entity [ label="Entity", shape="tripleoctagon" ]
29
-
30
-		hc1 [ label="Class 1", shape="doubleoctagon"]
31
-		hc2 [ label="Class 2", shape="doubleoctagon"]
32
-
33
-		ht1 [ label="Type 1", shape="octagon" ]
34
-		ht2 [ label="Type 2", shape="octagon" ]
35
-		ht3 [ label="Type 3", shape="octagon" ]
36
-		ht4 [ label="Type 4", shape="octagon" ]
37
-		ht5 [ label="Type 5", shape="octagon" ]
38
-
39
-		
40
-		h_entity -> { hc1, hc2 }
41
-
42
-		hc1 -> { ht1,ht2,ht3 }
43
-		hc2 -> { ht4, ht5 }
44
-
45
-		ht1 -> ht1 [label="translation", color="red"]
46
-		ht3 -> ht3 [label="translation", color="red"]
47
-		ht5 -> ht5 [label="translation", color="red"]
48
-		ht4 -> ht1 [ label="parent", color="green"]
49
-		ht2 -> ht1 [ label="parent", color="green"]
50
-		ht5 -> ht4 [ label="parent", color="green"]
51
-		ht3 -> ht4 [ label="parent", color="green"]
52
-		ht3 -> ht1 [ label="parent", color="green"]
53
-
54
-
55
-	}
56
-
57
-	subgraph cluster_entry {
58
-		label="Entry"
59
-		h_entry [ label="Entry", shape="tripleoctagon" ]
60
-
61
-		hc5 [ label="Class 5", shape="doubleoctagon"]
62
-
63
-		ht9 [ label="Type 9", shape="octagon" ]
64
-		ht10 [ label="Type 10", shape="octagon" ]
65
-
66
-		h_entry -> hc5
67
-
68
-		hc5 -> {ht9, ht10}
69
-
70
-		ht9 -> ht9 [ label="parent", color="green" ]
71
-		ht9 -> ht9 [ label="translation", color="red" ]
72
-		ht10 -> ht10 [ label="translation", color="red" ]
73
-	}
74
-
75
-	ht5 -> hc3 [ label="relation to type", style="dotted"]
76
-	ht2 -> hc5 [ label="relation to type", style="dotted"]
77
-}

+ 0
- 39
doc/img/graphviz/example_em_graph.dot View File

@@ -1,39 +0,0 @@
1
-digraph G {
2
-	rankdir = BT
3
-subgraph cluster_classtype {
4
-style="invis"
5
-
6
-
7
-ctentity [ label="classtype entity" shape="tripleoctagon" ]
8
-
9
-
10
-ctentry [ label="classtype entry" shape="tripleoctagon" ]
11
-
12
-
13
-ctperson [ label="classtype person" shape="tripleoctagon" ]
14
-}
15
-subgraph cluster_class {
16
-style="invis"
17
-	emcomp1[ label="EmClass textes", shape="doubleoctagon" ]
18
-	emcomp2[ label="EmClass personnes", shape="doubleoctagon" ]
19
-	emcomp13[ label="EmClass publication", shape="doubleoctagon" ]
20
-}
21
-	emcomp5[ label="EmType article | { titre|<f0> auteur|soustitre}" shape="record" color="blue" ]
22
-	emcomp5emcomp6 [ label="rel_to_type | { adresse}" shape="record"]
23
-	emcomp6[ label="EmType personne | { nom|prenom}" shape="record" color="blue" ]
24
-	emcomp14[ label="EmType rubrique | { titre}" shape="record" color="blue" ]
25
-	emcomp19[ label="EmType numero | { titre}" shape="record" color="blue" ]
26
-emcomp1 -> ctentity [ style="dashed" ]
27
-emcomp2 -> ctperson [ style="dashed" ]
28
-emcomp13 -> ctentity [ style="dashed" ]
29
-emcomp5:f0 -> emcomp5emcomp6 [ color="purple" dir="both" ]
30
-emcomp5emcomp6 -> emcomp6 [color="purple" dir="both" ]
31
-emcomp5 -> emcomp1 [ style="dotted" ]
32
-emcomp5 -> emcomp14 [ label="parent" color="green" ]
33
-emcomp6 -> emcomp2 [ style="dotted" ]
34
-emcomp14 -> emcomp13 [ style="dotted" ]
35
-emcomp14 -> emcomp14 [ label="parent" color="green" ]
36
-emcomp14 -> emcomp19 [ label="parent" color="green" ]
37
-emcomp19 -> emcomp13 [ style="dotted" ]
38
-
39
-}

+ 0
- 19
doc/img/graphviz/lodel2_ui.dot View File

@@ -1,19 +0,0 @@
1
-digraph Lodel2_architecture {
2
-	
3
-	user [label="Lodel user"]
4
-	ui [label="<ui> UI|{web|cli|lodel2fs}|<tpl> templates", shape=record]
5
-
6
-	action [label="{user|action|target}|<datas> datas}", shape=record]
7
-	ACL
8
-	leapi [label="LeAPI"]
9
-
10
-	user -> ui
11
-	ui:tpl -> user
12
-	ui:ui -> action
13
-	action -> ACL
14
-	ACL -> leapi
15
-	action:datas -> leapi
16
-	leapi -> ui:tpl
17
-	leapi -> leDataSource [dir=both]
18
-
19
-}

BIN
doc/img/openedition_logo.png View File


+ 0
- 26
install/Makefile View File

@@ -1,26 +0,0 @@
1
-all: refreshdyn dbinit dirinit
2
-
3
-refreshdyn: distclean
4
-	python -c "import utils; utils.refreshdyn()"
5
-
6
-dbinit:
7
-	python -c "import utils; utils.db_init()"
8
-
9
-dirinit:
10
-	python -c "import utils; utils.dir_init()"
11
-
12
-emgraph:
13
-	python -c "import utils; utils.em_graph()"
14
-
15
-.PHONY: clean cleanpycache cleanpyc refreshdyn distclean
16
-
17
-distclean: clean
18
-	-@rm -vf dynleapi.py
19
-
20
-clean: cleanpycache
21
-
22
-cleanpyc:
23
-	-@rm -vf *.pyc
24
-
25
-cleanpycache: cleanpyc
26
-	-@rm -vfR __pycache__

+ 0
- 11
install/README.txt View File

@@ -1,11 +0,0 @@
1
-Common operations :
2
-===================
3
-
4
-Refresh leapi dynamic code (when the Editorial Model is updated) :
5
-	make refreshdyn
6
-
7
-Update or init the database :
8
-	make dbinit
9
-
10
-To run an interactive python interpreter in the instance environnment run :
11
-	python loader.py

+ 0
- 1
install/__init__.py View File

@@ -1 +0,0 @@
1
-## @package install Regroup files and files templates designed to compose a lodel instance

+ 0
- 171
install/dynleapi.py View File

@@ -1,171 +0,0 @@
1
-## @author LeFactory
2
-
3
-import EditorialModel
4
-from EditorialModel import fieldtypes
5
-from EditorialModel.fieldtypes import naturerelation, char, integer, datetime, pk
6
-
7
-import leapi
8
-import leapi.lecrud
9
-import leapi.leobject
10
-import leapi.lerelation
11
-from leapi.leclass import _LeClass
12
-from leapi.letype import _LeType
13
-
14
-import DataSource.MySQL.leapidatasource
15
-
16
-
17
-## @brief _LeCrud concret class
18
-# @see leapi.lecrud._LeCrud
19
-class LeCrud(leapi.lecrud._LeCrud):
20
-    _datasource = DataSource.MySQL.leapidatasource.LeDataSourceSQL(**{})
21
-    _uid_fieldtype = None
22
-
23
-## @brief _LeObject concret class
24
-# @see leapi.leobject._LeObject
25
-class LeObject(LeCrud, leapi.leobject._LeObject):
26
-    _me_uid = {1: 'Textes', 2: 'Personnes', 19: 'Numero', 5: 'Article', 6: 'Personne', 13: 'Publication', 14: 'Rubrique'}
27
-    _uid_fieldtype = { 'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'internal': 'automatic'}) }
28
-    _leo_fieldtypes = {
29
-	'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'now_on_create': True, 'internal': 'automatic'}),
30
-	'string': EditorialModel.fieldtypes.char.EmFieldType(**{'max_length': 128, 'internal': 'automatic'}),
31
-	'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
32
-	'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'}),
33
-	'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'})
34
-	}
35
-
36
-## @brief _LeRelation concret class
37
-# @see leapi.lerelation._LeRelation
38
-class LeRelation(LeCrud, leapi.lerelation._LeRelation):
39
-    _uid_fieldtype = { 'id_relation': EditorialModel.fieldtypes.pk.EmFieldType(**{'internal': 'automatic'}) }
40
-    _rel_fieldtypes = {
41
-	'rank': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'}),
42
-	'nature': EditorialModel.fieldtypes.naturerelation.EmFieldType(**{}),
43
-	'depth': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'})
44
-	}
45
-    _rel_attr_fieldtypes = dict()
46
-
47
-class LeHierarch(LeRelation, leapi.lerelation._LeHierarch):
48
-    _rel_attr_fieldtypes = dict()
49
-
50
-class LeRel2Type(LeRelation, leapi.lerelation._LeRel2Type):
51
-    pass
52
-
53
-class LeClass(LeObject, _LeClass):
54
-    pass
55
-
56
-class LeType(LeClass, _LeType):
57
-    pass
58
-
59
-## @brief EmClass Textes LeClass child class
60
-# @see leapi.leclass.LeClass
61
-class Textes(LeClass, LeObject):
62
-    _class_id = 1
63
-
64
-
65
-## @brief EmClass Personnes LeClass child class
66
-# @see leapi.leclass.LeClass
67
-class Personnes(LeClass, LeObject):
68
-    _class_id = 2
69
-
70
-
71
-## @brief EmClass Publication LeClass child class
72
-# @see leapi.leclass.LeClass
73
-class Publication(LeClass, LeObject):
74
-    _class_id = 13
75
-
76
-
77
-## @brief EmType Article LeType child class
78
-# @see leobject::letype::LeType
79
-class Article(LeType, Textes):
80
-    _type_id = 5
81
-
82
-
83
-## @brief EmType Personne LeType child class
84
-# @see leobject::letype::LeType
85
-class Personne(LeType, Personnes):
86
-    _type_id = 6
87
-
88
-
89
-## @brief EmType Rubrique LeType child class
90
-# @see leobject::letype::LeType
91
-class Rubrique(LeType, Publication):
92
-    _type_id = 14
93
-
94
-
95
-## @brief EmType Numero LeType child class
96
-# @see leobject::letype::LeType
97
-class Numero(LeType, Publication):
98
-    _type_id = 19
99
-
100
-
101
-class Rel_textes2personne(LeRel2Type):
102
-    _rel_attr_fieldtypes = {
103
-    'adresse': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False})
104
-}
105
-
106
-
107
-#Initialisation of Textes class attributes
108
-Textes._fieldtypes = {
109
-    'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'nullable': False, 'uniq': False, 'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
110
-    'soustitre': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
111
-    'string': EditorialModel.fieldtypes.char.EmFieldType(**{'nullable': True, 'uniq': False, 'max_length': 128, 'internal': 'automatic'}),
112
-    'titre': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
113
-    'bleu': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
114
-    'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
115
-    'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
116
-    'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
117
-    'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'uniq': False, 'nullable': False, 'now_on_create': True, 'internal': 'automatic'})
118
-}
119
-Textes._linked_types = [Personne]
120
-Textes._classtype = 'entity'
121
-
122
-#Initialisation of Personnes class attributes
123
-Personnes._fieldtypes = {
124
-    'nom': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
125
-    'age': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
126
-    'prenom': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
127
-    'string': EditorialModel.fieldtypes.char.EmFieldType(**{'nullable': True, 'uniq': False, 'max_length': 128, 'internal': 'automatic'}),
128
-    'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
129
-    'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'nullable': False, 'uniq': False, 'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
130
-    'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
131
-    'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
132
-    'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'uniq': False, 'nullable': False, 'now_on_create': True, 'internal': 'automatic'})
133
-}
134
-Personnes._linked_types = []
135
-Personnes._classtype = 'person'
136
-
137
-#Initialisation of Publication class attributes
138
-Publication._fieldtypes = {
139
-    'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'nullable': False, 'uniq': False, 'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
140
-    'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'uniq': False, 'nullable': False, 'now_on_create': True, 'internal': 'automatic'}),
141
-    'string': EditorialModel.fieldtypes.char.EmFieldType(**{'nullable': True, 'uniq': False, 'max_length': 128, 'internal': 'automatic'}),
142
-    'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
143
-    'titre': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
144
-    'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
145
-    'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'})
146
-}
147
-Publication._linked_types = []
148
-Publication._classtype = 'entity'
149
-
150
-#Initialisation of Article class attributes
151
-Article._fields = ['titre', 'class_id', 'soustitre', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
152
-Article._superiors = {'parent': [Rubrique]}
153
-Article._leclass = Textes
154
-
155
-#Initialisation of Personne class attributes
156
-Personne._fields = ['nom', 'class_id', 'prenom', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
157
-Personne._superiors = {}
158
-Personne._leclass = Personnes
159
-
160
-#Initialisation of Rubrique class attributes
161
-Rubrique._fields = ['titre', 'class_id', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
162
-Rubrique._superiors = {'parent': [Rubrique, Numero]}
163
-Rubrique._leclass = Publication
164
-
165
-#Initialisation of Numero class attributes
166
-Numero._fields = ['titre', 'class_id', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
167
-Numero._superiors = {}
168
-Numero._leclass = Publication
169
-
170
-## @brief Dict for getting LeClass and LeType child classes given an EM uid
171
-LeObject._me_uid = {1: Textes, 2: Personnes, 19: Numero, 5: Article, 6: Personne, 13: Publication, 14: Rubrique}

+ 0
- 603
install/em.json View File

@@ -1,603 +0,0 @@
1
-{
2
- "1": {
3
-  "date_create": "Fri Oct 16 11:05:04 2015",
4
-  "component": "EmClass",
5
-  "name": "textes",
6
-  "rank": 1,
7
-  "sortcolumn": "rank",
8
-  "date_update": "Fri Oct 16 11:05:04 2015",
9
-  "classtype": "entity",
10
-  "icon": "0",
11
-  "string": "{\"___\": \"\", \"fre\": \"Texte\"}",
12
-  "help_text": "{\"___\": \"\"}"
13
- },
14
- "2": {
15
-  "date_create": "Fri Oct 16 11:05:04 2015",
16
-  "component": "EmClass",
17
-  "name": "personnes",
18
-  "rank": 1,
19
-  "sortcolumn": "rank",
20
-  "date_update": "Fri Oct 16 11:05:04 2015",
21
-  "classtype": "person",
22
-  "icon": "0",
23
-  "string": "{\"___\": \"\", \"fre\": \"Personnes\"}",
24
-  "help_text": "{\"___\": \"\"}"
25
- },
26
- "4": {
27
-  "nullable": false,
28
-  "name": "titre",
29
-  "date_update": "Fri Oct 16 11:05:04 2015",
30
-  "string": "{\"___\": \"\", \"fre\": \"Titre\"}",
31
-  "help_text": "{\"___\": \"\"}",
32
-  "component": "EmField",
33
-  "date_create": "Fri Oct 16 11:05:04 2015",
34
-  "optional": false,
35
-  "rank": 1,
36
-  "icon": "0",
37
-  "fieldtype": "i18n",
38
-  "rel_field_id": null,
39
-  "class_id": 1,
40
-  "uniq": false,
41
-  "internal": false
42
- },
43
- "5": {
44
-  "component": "EmType",
45
-  "name": "article",
46
-  "date_update": "Fri Oct 16 11:05:04 2015",
47
-  "string": "{\"___\": \"\", \"fre\": \"Article\"}",
48
-  "sortcolumn": "rank",
49
-  "date_create": "Fri Oct 16 11:05:04 2015",
50
-  "help_text": "{\"___\": \"\"}",
51
-  "rank": 1,
52
-  "icon": "0",
53
-  "class_id": 1,
54
-  "fields_list": [
55
-   7
56
-  ],
57
-  "superiors_list": {
58
-   "parent": [
59
-    14
60
-   ]
61
-  }
62
- },
63
- "6": {
64
-  "component": "EmType",
65
-  "name": "personne",
66
-  "date_update": "Fri Oct 16 11:05:04 2015",
67
-  "string": "{\"___\": \"\", \"fre\": \"Personne\"}",
68
-  "sortcolumn": "rank",
69
-  "date_create": "Fri Oct 16 11:05:04 2015",
70
-  "help_text": "{\"___\": \"\"}",
71
-  "rank": 1,
72
-  "icon": "0",
73
-  "class_id": 2,
74
-  "fields_list": [
75
-   10
76
-  ],
77
-  "superiors_list": {}
78
- },
79
- "7": {
80
-  "nullable": false,
81
-  "name": "soustitre",
82
-  "date_update": "Fri Oct 16 11:05:04 2015",
83
-  "string": "{\"___\": \"\", \"fre\": \"Sous-titre\"}",
84
-  "help_text": "{\"___\": \"\"}",
85
-  "component": "EmField",
86
-  "date_create": "Fri Oct 16 11:05:04 2015",
87
-  "optional": true,
88
-  "rank": 5,
89
-  "icon": "0",
90
-  "fieldtype": "i18n",
91
-  "rel_field_id": null,
92
-  "class_id": 1,
93
-  "uniq": false,
94
-  "internal": false
95
- },
96
- "9": {
97
-  "nullable": true,
98
-  "name": "nom",
99
-  "date_update": "Fri Oct 16 11:05:04 2015",
100
-  "string": "{\"___\": \"\", \"fre\": \"Nom\"}",
101
-  "help_text": "{\"___\": \"\"}",
102
-  "component": "EmField",
103
-  "date_create": "Fri Oct 16 11:05:04 2015",
104
-  "optional": false,
105
-  "rank": 1,
106
-  "icon": "0",
107
-  "fieldtype": "char",
108
-  "rel_field_id": null,
109
-  "class_id": 2,
110
-  "uniq": false,
111
-  "internal": false
112
- },
113
- "10": {
114
-  "nullable": true,
115
-  "name": "prenom",
116
-  "date_update": "Fri Oct 16 11:05:04 2015",
117
-  "string": "{\"___\": \"\", \"fre\": \"Pr\\u00e9nom\"}",
118
-  "help_text": "{\"___\": \"\"}",
119
-  "component": "EmField",
120
-  "date_create": "Fri Oct 16 11:05:04 2015",
121
-  "optional": true,
122
-  "rank": 3,
123
-  "icon": "0",
124
-  "fieldtype": "char",
125
-  "rel_field_id": null,
126
-  "class_id": 2,
127
-  "uniq": false,
128
-  "internal": false
129
- },
130
- "11": {
131
-  "nullable": true,
132
-  "name": "auteur",
133
-  "uniq": false,
134
-  "date_update": "Fri Oct 16 11:05:04 2015",
135
-  "string": "{\"___\": \"\", \"fre\": \"Auteur\"}",
136
-  "help_text": "{\"___\": \"\"}",
137
-  "component": "EmField",
138
-  "date_create": "Fri Oct 16 11:05:04 2015",
139
-  "optional": false,
140
-  "rank": 2,
141
-  "icon": "0",
142
-  "fieldtype": "rel2type",
143
-  "rel_field_id": null,
144
-  "class_id": 1,
145
-  "rel_to_type_id": 6,
146
-  "internal": false
147
- },
148
- "12": {
149
-  "nullable": true,
150
-  "name": "adresse",
151
-  "date_update": "Fri Oct 16 11:05:04 2015",
152
-  "string": "{\"___\": \"\", \"fre\": \"Adresse\"}",
153
-  "help_text": "{\"___\": \"\"}",
154
-  "component": "EmField",
155
-  "date_create": "Fri Oct 16 11:05:04 2015",
156
-  "optional": false,
157
-  "rank": 6,
158
-  "icon": "0",
159
-  "fieldtype": "char",
160
-  "rel_field_id": 11,
161
-  "class_id": 1,
162
-  "uniq": false,
163
-  "internal": false
164
- },
165
- "13": {
166
-  "date_create": "Fri Oct 16 11:05:04 2015",
167
-  "component": "EmClass",
168
-  "name": "publication",
169
-  "rank": 2,
170
-  "sortcolumn": "rank",
171
-  "date_update": "Fri Oct 16 11:05:04 2015",
172
-  "classtype": "entity",
173
-  "icon": "0",
174
-  "string": "{\"___\": \"\", \"fre\": \"Publication\"}",
175
-  "help_text": "{\"___\": \"\"}"
176
- },
177
- "14": {
178
-  "component": "EmType",
179
-  "name": "rubrique",
180
-  "date_update": "Fri Oct 16 11:05:04 2015",
181
-  "string": "{\"___\": \"\", \"fre\": \"Rubrique\"}",
182
-  "sortcolumn": "rank",
183
-  "date_create": "Fri Oct 16 11:05:04 2015",
184
-  "help_text": "{\"___\": \"\"}",
185
-  "rank": 1,
186
-  "icon": "0",
187
-  "class_id": 13,
188
-  "fields_list": [],
189
-  "superiors_list": {
190
-   "parent": [
191
-    14,
192
-    19
193
-   ]
194
-  }
195
- },
196
- "16": {
197
-  "nullable": true,
198
-  "name": "titre",
199
-  "date_update": "Fri Oct 16 11:05:04 2015",
200
-  "string": "{\"___\": \"\", \"fre\": \"Titre\"}",
201
-  "help_text": "{\"___\": \"\"}",
202
-  "component": "EmField",
203
-  "date_create": "Fri Oct 16 11:05:04 2015",
204
-  "optional": false,
205
-  "rank": 1,
206
-  "icon": "0",
207
-  "fieldtype": "char",
208
-  "rel_field_id": null,
209
-  "class_id": 13,
210
-  "uniq": false,
211
-  "internal": false
212
- },
213
- "18": {
214
-  "nullable": true,
215
-  "name": "age",
216
-  "date_update": "Fri Oct 16 11:05:04 2015",
217
-  "string": "{\"___\": \"\", \"fre\": \"Age\"}",
218
-  "help_text": "{\"___\": \"\"}",
219
-  "component": "EmField",
220
-  "date_create": "Fri Oct 16 11:05:04 2015",
221
-  "optional": true,
222
-  "rank": 5,
223
-  "icon": "0",
224
-  "fieldtype": "char",
225
-  "rel_field_id": null,
226
-  "class_id": 2,
227
-  "uniq": false,
228
-  "internal": false
229
- },
230
- "19": {
231
-  "component": "EmType",
232
-  "name": "numero",
233
-  "date_update": "Fri Oct 16 11:05:04 2015",
234
-  "string": "{\"___\": \"\", \"fre\": \"Num\\u00e9ro\"}",
235
-  "sortcolumn": "rank",
236
-  "date_create": "Fri Oct 16 11:05:04 2015",
237
-  "help_text": "{\"___\": \"\"}",
238
-  "rank": 2,
239
-  "icon": "0",
240
-  "class_id": 13,
241
-  "fields_list": [],
242
-  "superiors_list": {}
243
- },
244
- "21": {
245
-  "nullable": true,
246
-  "name": "bleu",
247
-  "date_update": "Fri Oct 16 11:05:04 2015",
248
-  "string": "{\"___\": \"\", \"fre\": \"Bleu\"}",
249
-  "help_text": "{\"___\": \"\"}",
250
-  "component": "EmField",
251
-  "date_create": "Fri Oct 16 11:05:04 2015",
252
-  "optional": true,
253
-  "rank": 3,
254
-  "icon": "0",
255
-  "fieldtype": "char",
256
-  "rel_field_id": null,
257
-  "class_id": 1,
258
-  "uniq": false,
259
-  "internal": false
260
- },
261
- "23": {
262
-  "is_id_class": true,
263
-  "name": "class_id",
264
-  "component": "EmField",
265
-  "date_update": "Fri Oct 16 11:05:04 2015",
266
-  "string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
267
-  "help_text": "{\"___\": \"\"}",
268
-  "optional": false,
269
-  "nullable": false,
270
-  "rel_field_id": null,
271
-  "rank": 4,
272
-  "internal": "automatic",
273
-  "fieldtype": "emuid",
274
-  "immutable": true,
275
-  "date_create": "Fri Oct 16 11:05:04 2015",
276
-  "class_id": 1,
277
-  "uniq": false,
278
-  "icon": "0"
279
- },
280
- "24": {
281
-  "nullable": true,
282
-  "name": "string",
283
-  "component": "EmField",
284
-  "date_update": "Fri Oct 16 11:05:04 2015",
285
-  "string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
286
-  "help_text": "{\"___\": \"\"}",
287
-  "internal": "automatic",
288
-  "date_create": "Fri Oct 16 11:05:04 2015",
289
-  "rel_field_id": null,
290
-  "rank": 7,
291
-  "immutable": false,
292
-  "fieldtype": "char",
293
-  "class_id": 1,
294
-  "optional": false,
295
-  "max_length": 128,
296
-  "uniq": false,
297
-  "icon": "0"
298
- },
299
- "25": {
300
-  "is_id_class": false,
301
-  "name": "type_id",
302
-  "date_update": "Fri Oct 16 11:05:04 2015",
303
-  "string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
304
-  "help_text": "{\"___\": \"\"}",
305
-  "component": "EmField",
306
-  "date_create": "Fri Oct 16 11:05:04 2015",
307
-  "rel_field_id": null,
308
-  "rank": 8,
309
-  "internal": "automatic",
310
-  "fieldtype": "emuid",
311
-  "class_id": 1,
312
-  "optional": false,
313
-  "immutable": true,
314
-  "nullable": false,
315
-  "uniq": false,
316
-  "icon": "0"
317
- },
318
- "26": {
319
-  "nullable": false,
320
-  "name": "lodel_id",
321
-  "date_update": "Fri Oct 16 11:05:04 2015",
322
-  "string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
323
-  "help_text": "{\"___\": \"\"}",
324
-  "component": "EmField",
325
-  "date_create": "Fri Oct 16 11:05:04 2015",
326
-  "rel_field_id": null,
327
-  "rank": 9,
328
-  "internal": "autosql",
329
-  "fieldtype": "pk",
330
-  "immutable": true,
331
-  "optional": false,
332
-  "class_id": 1,
333
-  "uniq": false,
334
-  "icon": "0"
335
- },
336
- "28": {
337
-  "is_id_class": true,
338
-  "name": "class_id",
339
-  "date_update": "Fri Oct 16 11:05:04 2015",
340
-  "string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
341
-  "help_text": "{\"___\": \"\"}",
342
-  "component": "EmField",
343
-  "date_create": "Fri Oct 16 11:05:04 2015",
344
-  "rel_field_id": null,
345
-  "rank": 2,
346
-  "internal": "automatic",
347
-  "fieldtype": "emuid",
348
-  "class_id": 2,
349
-  "optional": false,
350
-  "immutable": true,
351
-  "nullable": false,
352
-  "uniq": false,
353
-  "icon": "0"
354
- },
355
- "29": {
356
-  "nullable": true,
357
-  "name": "string",
358
-  "component": "EmField",
359
-  "date_update": "Fri Oct 16 11:05:04 2015",
360
-  "string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
361
-  "help_text": "{\"___\": \"\"}",
362
-  "internal": "automatic",
363
-  "date_create": "Fri Oct 16 11:05:04 2015",
364
-  "rel_field_id": null,
365
-  "rank": 4,
366
-  "immutable": false,
367
-  "fieldtype": "char",
368
-  "class_id": 2,
369
-  "optional": false,
370
-  "max_length": 128,
371
-  "uniq": false,
372
-  "icon": "0"
373
- },
374
- "30": {
375
-  "is_id_class": false,
376
-  "name": "type_id",
377
-  "date_update": "Fri Oct 16 11:05:04 2015",
378
-  "string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
379
-  "help_text": "{\"___\": \"\"}",
380
-  "component": "EmField",
381
-  "date_create": "Fri Oct 16 11:05:04 2015",
382
-  "rel_field_id": null,
383
-  "rank": 6,
384
-  "internal": "automatic",
385
-  "fieldtype": "emuid",
386
-  "class_id": 2,
387
-  "optional": false,
388
-  "immutable": true,
389
-  "nullable": false,
390
-  "uniq": false,
391
-  "icon": "0"
392
- },
393
- "31": {
394
-  "nullable": false,
395
-  "name": "lodel_id",
396
-  "date_update": "Fri Oct 16 11:05:04 2015",
397
-  "string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
398
-  "help_text": "{\"___\": \"\"}",
399
-  "component": "EmField",
400
-  "date_create": "Fri Oct 16 11:05:04 2015",
401
-  "rel_field_id": null,
402
-  "rank": 7,
403
-  "internal": "autosql",
404
-  "fieldtype": "pk",
405
-  "immutable": true,
406
-  "optional": false,
407
-  "class_id": 2,
408
-  "uniq": false,
409
-  "icon": "0"
410
- },
411
- "33": {
412
-  "is_id_class": true,
413
-  "name": "class_id",
414
-  "date_update": "Fri Oct 16 11:05:04 2015",
415
-  "string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
416
-  "help_text": "{\"___\": \"\"}",
417
-  "component": "EmField",
418
-  "date_create": "Fri Oct 16 11:05:04 2015",
419
-  "rel_field_id": null,
420
-  "rank": 2,
421
-  "internal": "automatic",
422
-  "fieldtype": "emuid",
423
-  "class_id": 13,
424
-  "optional": false,
425
-  "immutable": true,
426
-  "nullable": false,
427
-  "uniq": false,
428
-  "icon": "0"
429
- },
430
- "34": {
431
-  "nullable": true,
432
-  "name": "string",
433
-  "component": "EmField",
434
-  "date_update": "Fri Oct 16 11:05:04 2015",
435
-  "string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
436
-  "help_text": "{\"___\": \"\"}",
437
-  "internal": "automatic",
438
-  "date_create": "Fri Oct 16 11:05:04 2015",
439
-  "rel_field_id": null,
440
-  "rank": 3,
441
-  "immutable": false,
442
-  "fieldtype": "char",
443
-  "class_id": 13,
444
-  "optional": false,
445
-  "max_length": 128,
446
-  "uniq": false,
447
-  "icon": "0"
448
- },
449
- "35": {
450
-  "is_id_class": false,
451
-  "name": "type_id",
452
-  "date_update": "Fri Oct 16 11:05:04 2015",
453
-  "string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
454
-  "help_text": "{\"___\": \"\"}",
455
-  "component": "EmField",
456
-  "date_create": "Fri Oct 16 11:05:04 2015",
457
-  "rel_field_id": null,
458
-  "rank": 4,
459
-  "internal": "automatic",
460
-  "fieldtype": "emuid",
461
-  "class_id": 13,
462
-  "optional": false,
463
-  "immutable": true,
464
-  "nullable": false,
465
-  "uniq": false,
466
-  "icon": "0"
467
- },
468
- "36": {
469
-  "nullable": false,
470
-  "name": "lodel_id",
471
-  "date_update": "Fri Oct 16 11:05:04 2015",
472
-  "string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
473
-  "help_text": "{\"___\": \"\"}",
474
-  "component": "EmField",
475
-  "date_create": "Fri Oct 16 11:05:04 2015",
476
-  "rel_field_id": null,
477
-  "rank": 5,
478
-  "internal": "autosql",
479
-  "fieldtype": "pk",
480
-  "immutable": true,
481
-  "optional": false,
482
-  "class_id": 13,
483
-  "uniq": false,
484
-  "icon": "0"
485
- },
486
- "37": {
487
-  "nullable": false,
488
-  "name": "modification_date",
489
-  "date_update": "Wed Nov  4 10:52:13 2015",
490
-  "string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
491
-  "help_text": "{\"___\": \"\"}",
492
-  "component": "EmField",
493
-  "date_create": "Wed Nov  4 10:52:13 2015",
494
-  "optional": false,
495
-  "class_id": 1,
496
-  "rank": 10,
497
-  "internal": "autosql",
498
-  "fieldtype": "datetime",
499
-  "now_on_create": true,
500
-  "rel_field_id": null,
501
-  "immutable": true,
502
-  "uniq": false,
503
-  "now_on_update": true,
504
-  "icon": "0"
505
- },
506
- "38": {
507
-  "nullable": false,
508
-  "name": "creation_date",
509
-  "component": "EmField",
510
-  "date_update": "Wed Nov  4 10:52:13 2015",
511
-  "string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
512
-  "help_text": "{\"___\": \"\"}",
513
-  "internal": "autosql",
514
-  "date_create": "Wed Nov  4 10:52:13 2015",
515
-  "rel_field_id": null,
516
-  "rank": 11,
517
-  "immutable": true,
518
-  "fieldtype": "datetime",
519
-  "now_on_create": true,
520
-  "optional": false,
521
-  "class_id": 1,
522
-  "uniq": false,
523
-  "icon": "0"
524
- },
525
- "39": {
526
-  "nullable": false,
527
-  "name": "modification_date",
528
-  "date_update": "Wed Nov  4 10:52:13 2015",
529
-  "string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
530
-  "help_text": "{\"___\": \"\"}",
531
-  "component": "EmField",
532
-  "date_create": "Wed Nov  4 10:52:13 2015",
533
-  "optional": false,
534
-  "class_id": 2,
535
-  "rank": 8,
536
-  "internal": "autosql",
537
-  "fieldtype": "datetime",
538
-  "now_on_create": true,
539
-  "rel_field_id": null,
540
-  "immutable": true,
541
-  "uniq": false,
542
-  "now_on_update": true,
543
-  "icon": "0"
544
- },
545
- "40": {
546
-  "nullable": false,
547
-  "name": "creation_date",
548
-  "component": "EmField",
549
-  "date_update": "Wed Nov  4 10:52:13 2015",
550
-  "string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
551
-  "help_text": "{\"___\": \"\"}",
552
-  "internal": "autosql",
553
-  "date_create": "Wed Nov  4 10:52:13 2015",
554
-  "rel_field_id": null,
555
-  "rank": 9,
556
-  "immutable": true,
557
-  "fieldtype": "datetime",
558
-  "now_on_create": true,
559
-  "optional": false,
560
-  "class_id": 2,
561
-  "uniq": false,
562
-  "icon": "0"
563
- },
564
- "41": {
565
-  "nullable": false,
566
-  "name": "modification_date",
567
-  "date_update": "Wed Nov  4 10:52:13 2015",
568
-  "string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
569
-  "help_text": "{\"___\": \"\"}",
570
-  "component": "EmField",
571
-  "date_create": "Wed Nov  4 10:52:13 2015",
572
-  "optional": false,
573
-  "class_id": 13,
574
-  "rank": 6,
575
-  "internal": "autosql",
576
-  "fieldtype": "datetime",
577
-  "now_on_create": true,
578
-  "rel_field_id": null,
579
-  "immutable": true,
580
-  "uniq": false,
581
-  "now_on_update": true,
582
-  "icon": "0"
583
- },
584
- "42": {
585
-  "nullable": false,
586
-  "name": "creation_date",
587
-  "component": "EmField",
588
-  "date_update": "Wed Nov  4 10:52:13 2015",
589
-  "string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
590
-  "help_text": "{\"___\": \"\"}",
591
-  "internal": "autosql",
592
-  "date_create": "Wed Nov  4 10:52:13 2015",
593
-  "rel_field_id": null,
594
-  "rank": 7,
595
-  "immutable": true,
596
-  "fieldtype": "datetime",
597
-  "now_on_create": true,
598
-  "optional": false,
599
-  "class_id": 13,
600
-  "uniq": false,
601
-  "icon": "0"
602
- }
603
-}

+ 0
- 26
install/instance_settings.py View File

@@ -1,26 +0,0 @@
1
-#-*- coding:utf8 -*-
2
-
3
-import pymysql
4
-import os
5
-
6
-sitename = 'LODEL2_INSTANCE_NAME'
7
-lodel2_lib_path = 'LODEL2_LIB_ABS_PATH'
8
-
9
-templates_base_dir = 'LODEL2_INSTANCE_TEMPLATES_BASE_DIR'
10
-
11
-debug = False
12
-
13
-em_file = 'em.json'
14
-dynamic_code_file = 'dynleapi.py'
15
-
16
-ds_package = 'MySQL'
17
-mh_classname = 'MysqlMigrationHandler'
18
-datasource = {
19
-    'default': {
20
-        'module': pymysql,
21
-        'host': '127.0.0.1',
22
-        'user': 'DBUSER',
23
-        'passwd': 'DBPASSWORD',
24
-        'db': 'DBNAME'
25
-    }
26
-}

+ 0
- 38
install/loader.py View File

@@ -1,38 +0,0 @@
1
-import instance_settings
2
-import importlib
3
-import sys
4
-import os
5
-
6
-sys.path.append(instance_settings.lodel2_lib_path)
7
-
8
-from Lodel.settings import Settings
9
-
10
-# Settings initialisation
11
-Settings.load_module(instance_settings)
12
-globals()['Settings'] = Settings
13
-
14
-from Lodel import logger # logger import and initialisation
15
-
16
-from plugins import * #Load activated plugins
17
-
18
-# Import dynamic code
19
-if os.path.isfile(Settings.dynamic_code_file):
20
-    from dynleapi import *
21
-
22
-# Import wanted datasource objects
23
-for db_modname in ['leapidatasource', 'migrationhandler']:
24
-    mod = importlib.import_module("DataSource.{pkg_name}.{mod_name}".format(
25
-            pkg_name=Settings.get('ds_package'),
26
-            mod_name=db_modname,
27
-        )
28
-    )
29
-    # Expose the module in globals
30
-    globals()[db_modname] = mod
31
-
32
-if __name__ == '__main__':
33
-    import code
34
-    print("""
35
-     Running interactive python in Lodel2 %s instance environment
36
-
37
-"""%Settings.sitename)
38
-    code.interact(local=locals())

+ 0
- 88
install/netipy.py View File

@@ -1,88 +0,0 @@
1
-#!/usr/bin/python3
2
-#-*- coding: utf-8 -*-
3
-
4
-#
5
-# A server that provide access to interactive python interpreter through network
6
-#
7
-# This is a demo implementation of a Lodel2 interface
8
-
9
-import socket
10
-import threading
11
-import subprocess
12
-import time
13
-import sys
14
-import signal
15
-
16
-PORT = 1337
17
-#BIND = None
18
-BIND = 'localhost'
19
-THREAD_COUNT = 10
20
-SOCK_TIMEOUT = 5
21
-
22
-servsock = None # Stores the server socket in order to close it when exception is raised
23
-
24
-
25
-# Thread function called when client connected
26
-def client_thread(sock, addr):
27
-    # Starting interactive Lodel2 python in a subprocess
28
-    sock.setblocking(True)
29
-    sock_stdin = sock.makefile(mode='r', encoding='utf-8', newline="\n")
30
-    sock_stdout = sock.makefile(mode='w', encoding='utf-8', newline="\n")
31
-
32
-    ipy = subprocess.Popen(['python', 'netipy_loader.py', addr[0]], stdin=sock_stdin, stdout=sock_stdout, stderr=sock_stdout)
33
-    ipy.wait()
34
-    sock.close()
35
-    return True
36
-
37
-# Main loop
38
-def main():
39
-    servsock = socket.socket(family = socket.AF_INET, type=socket.SOCK_STREAM)
40
-    servsock.settimeout(5)
41
-    bind_addr = socket.gethostname() if BIND is None else BIND
42
-    servsock.bind((bind_addr, PORT))
43
-    servsock.listen(5)
44
-    globals()['servsock'] = servsock
45
-
46
-    threads = list()
47
-    
48
-    print("Server listening on %s:%s" % (bind_addr, PORT))
49
-    while True:
50
-        # Accept if rooms left in threads list
51
-        if len(threads) < THREAD_COUNT:
52
-            try:
53
-                (clientsocket, addr) = servsock.accept()
54
-                print("Client connected : %s" % addr[0])
55
-                thread = threading.Thread(target = client_thread, kwargs = {'sock': clientsocket, 'addr': addr})
56
-                threads.append(thread)
57
-                thread.start()
58
-            except socket.timeout:
59
-                pass
60
-
61
-        # Thread cleanup
62
-        for i in range(len(threads)-1,-1,-1):
63
-            thread = threads[i]
64
-            thread.join(0.1)  #useless ?
65
-            if not thread.is_alive():
66
-                print("Thread %d exited" % i)
67
-                threads.pop(i)
68
- 
69
-# Signal handler designed to close socket when SIGINT
70
-def sigint_sock_close(signal, frame):
71
-    if globals()['servsock'] is not None:
72
-        globals()['servsock'].close()
73
-    print("\nCtrl+c pressed, exiting...")
74
-    exit(0)
75
-
76
-if __name__ == '__main__':
77
-    signal.signal(signal.SIGINT, sigint_sock_close)
78
-    try:
79
-        main()
80
-    except Exception as e:
81
-        if globals()['servsock'] is not None:
82
-            globals()['servsock'].close()
83
-        raise e
84
-        
85
-
86
-    
87
-
88
-    

+ 0
- 22
install/netipy_loader.py View File

@@ -1,22 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import sys
4
-import code
5
-from loader import *
6
-from Lodel import logger
7
-from Lodel.user import UserContext
8
-
9
-
10
-logger.remove_console_handlers()
11
-
12
-if __name__ == '__main__':
13
-    if len(sys.argv) < 2:
14
-        raise RuntimeError("Usage : %s client_ip")
15
-    UserContext.init(sys.argv[1])
16
-    print("""
17
-        netipy interface of Lodel2 %s instance environment.
18
-
19
-        Welcome %s.
20
-        To authenticate use UserContext.authenticate(identifier, proof) function
21
-""" % (Settings.sitename, UserContext.identity().username))
22
-    code.interact(local=locals())

+ 0
- 91
install/utils.py View File

@@ -1,91 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-from loader import *
4
-import warnings
5
-
6
-
7
-def refreshdyn():
8
-    import sys
9
-    from EditorialModel.model import Model
10
-    from leapi.lefactory import LeFactory
11
-    from EditorialModel.backend.json_backend import EmBackendJson
12
-    from DataSource.MySQL.leapidatasource import LeDataSourceSQL
13
-    OUTPUT = Settings.dynamic_code_file
14
-    EMJSON = Settings.em_file
15
-    # Load editorial model
16
-    em = Model(EmBackendJson(EMJSON))
17
-    # Generate dynamic code
18
-    fact = LeFactory(OUTPUT)
19
-    # Create the python file
20
-    fact.create_pyfile(em, LeDataSourceSQL, {})
21
-
22
-
23
-def db_init():
24
-    from EditorialModel.backend.json_backend import EmBackendJson
25
-    from EditorialModel.model import Model
26
-    mh = getattr(migrationhandler,Settings.mh_classname)()
27
-    em = Model(EmBackendJson(Settings.em_file))
28
-    em.migrate_handler(mh)
29
-
30
-
31
-def em_graph(output_file = None, image_format = None):
32
-    from EditorialModel.model import Model
33
-    from EditorialModel.backend.json_backend import EmBackendJson
34
-    from EditorialModel.backend.graphviz import EmBackendGraphviz
35
-    import subprocess
36
-    
37
-    if image_format is None:
38
-        if hasattr(Settings, 'em_graph_format'):
39
-            image_format = Settings.em_graph_format
40
-        else:
41
-            image_format = 'png'
42
-    if output_file is None:
43
-        if hasattr(Settings, 'em_graph_output'):
44
-            output_file = Settings.em_graph_output
45
-        else:
46
-            output_file = '/tmp/em_%s_graph.dot'
47
-    image_format = image_format.lower()
48
-    try:
49
-        output_file = output_file%Settings.sitename
50
-    except TypeError:
51
-        warnings.warn("Bad filename for em_graph output. The filename should be in the form '/foo/bar/file_%s_name.png")
52
-        pass
53
-
54
-    dot_file = output_file+".dot"
55
-    
56
-    graphviz_bckend = EmBackendGraphviz(dot_file)
57
-    edmod = Model(EmBackendJson(Settings.em_file))
58
-    graphviz_bckend.save(edmod)
59
-    dot_cmd = [
60
-        "dot",
61
-        "-T%s"%image_format,
62
-        dot_file
63
-    ]
64
-    with open(output_file, "w+") as outfp:
65
-        subprocess.check_call(dot_cmd, stdout=outfp)
66
-    os.unlink(dot_file)
67
-    print("Output image written in file : '%s'" % output_file)
68
-
69
-
70
-def dir_init():
71
-    import os
72
-
73
-    # Templates
74
-    print("Creating Base Templates ...")
75
-    templates = {
76
-        'base': {'file': 'base.html', 'content': '{% extends "templates/base.html" %}'},
77
-        'base_backend': {'file': 'base_backend.html', 'content': '{% extends "templates/base_backend.html" %}'}
78
-    }
79
-
80
-    current_directory = os.path.dirname(os.path.abspath(__file__))
81
-    templates_directory = os.path.join(current_directory,'templates')
82
-    if not os.path.exists(templates_directory):
83
-        os.makedirs(templates_directory)
84
-
85
-    for _, template in templates.items():
86
-        my_file_path = os.path.join(templates_directory, template['file'])
87
-        if os.path.exists(my_file_path):
88
-            os.unlink(my_file_path)
89
-        with open(my_file_path, 'w') as my_file:
90
-            my_file.write(template['content'])
91
-        print("Created %s" % my_file_path)

+ 0
- 1
leapi/__init__.py View File

@@ -1 +0,0 @@
1
-## @package leapi Lodel Editorial API : accessing lodel2 datas & documents

+ 0
- 46
leapi/leclass.py View File

@@ -1,46 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import leapi
4
-
5
-from leapi.leobject import _LeObject
6
-
7
-## @brief Represent an EmClass data instance
8
-# @note Is not a derivated class of LeObject because the concrete class will be a derivated class from LeObject
9
-class _LeClass(_LeObject):
10
-
11
-    ## @brief Stores fieldtypes by field name
12
-    _fieldtypes = dict()
13
-    ## @brief Stores authorized link2type
14
-    _linked_types = list()
15
-    ## @brief Stores fieldgroups and the fields they contains
16
-    _fieldgroups = dict()
17
-    ## @brief Stores the EM uid
18
-    _class_id = None
19
-    ## @brief Stores the classtype
20
-    _classtype = None
21
-        
22
-    ## @brief Return a dict with fieldname as key and a fieldtype instance as value
23
-    # @note not optimised at all
24
-    @classmethod
25
-    def fieldtypes(cls, complete=True):
26
-        if complete:
27
-            ret = dict()
28
-            ret.update(super().fieldtypes())
29
-            ret.update(cls._fieldtypes)
30
-            return ret
31
-        else:
32
-            leobject = cls.name2class('LeObject')
33
-            return { fname: cls._fieldtypes[fname] for fname in cls._fieldtypes if fname not in leobject.fieldtypes().keys() }
34
-
35
-    @classmethod
36
-    def fieldlist(cls, complete=True):
37
-        return list(cls.fieldtypes(complete).keys())
38
-
39
-    @classmethod
40
-    def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
41
-        query_filters.append(('class_id', '=', cls._class_id))
42
-        return super().get(query_filters, field_list, order=order, group=group, limit=limit, offset=offset)
43
-
44
-    @classmethod
45
-    def leo_class(cls):
46
-        return cls

+ 0
- 740
leapi/lecrud.py View File

@@ -1,740 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-## @package leapi.lecrud
4
-# @brief This package contains the abstract class representing Lodel Editorial components
5
-#
6
-
7
-import copy
8
-import warnings
9
-import importlib
10
-import re
11
-
12
-from Lodel import logger
13
-from EditorialModel.fieldtypes.generic import DatasConstructor
14
-from Lodel.hooks import LodelHook
15
-
16
-REL_SUP = 0
17
-REL_SUB = 1
18
-
19
-class LeApiErrors(Exception):
20
-    ## @brief Instanciate a new exceptions handling multiple exceptions
21
-    # @param msg str : Exception message
22
-    # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
23
-    def __init__(self, msg = "Unknow error", exceptions = None):
24
-        self._msg = msg
25
-        self._exceptions = dict() if exceptions is None else exceptions
26
-
27
-    def __repr__(self):
28
-        return self.__str__()
29
-
30
-    def __str__(self):
31
-        msg = self._msg
32
-        for obj, expt in self._exceptions.items():
33
-            msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
34
-                    expt_obj = obj,
35
-                    expt_name=expt.__class__.__name__,
36
-                    expt_msg=str(expt)
37
-            )
38
-        return msg
39
-
40
-
41
-## @brief When an error concern a query
42
-class LeApiQueryError(LeApiErrors): pass
43
-
44
-## @brief When an error concerns a datas
45
-class LeApiDataCheckError(LeApiErrors): pass
46
-    
47
-
48
-## @brief Main class to handler lodel editorial components (relations and objects)
49
-class _LeCrud(object):
50
-    ## @brief The datasource
51
-    _datasource = None
52
-
53
-    ## @brief abstract property to store the fieldtype representing the component identifier
54
-    _uid_fieldtype = None #Will be a dict fieldname => fieldtype
55
-    
56
-    ## @brief will store all the fieldtypes (child classes handle it)
57
-    _fieldtypes_all = None
58
-
59
-    ## @brief Stores a regular expression to parse query filters strings
60
-    _query_re = None
61
-    ## @brief Stores Query filters operators
62
-    _query_operators = ['=', '<=', '>=', '!=', '<', '>', ' in ', ' not in ', ' like ', ' not like ']
63
-
64
-    
65
-    ## @brief Asbtract constructor for every child classes
66
-    # @param uid int : lodel_id if LeObject, id_relation if its a LeRelation
67
-    # @param **kwargs : datas !
68
-    # @throw NotImplementedError if trying to instanciate a class that cannot be instanciated
69
-    def __init__(self, uid, **kwargs):
70
-        if len(kwargs) > 0:
71
-            if not self.implements_leobject() and not self.implements_lerelation():
72
-                raise NotImplementedError("Abstract class !")
73
-        # Try to get the name of the uid field (lodel_id for objects, id_relation for relations)
74
-        try:
75
-                uid_name = self.uidname()
76
-        except NotImplementedError: #Should never append
77
-            raise NotImplementedError("Abstract class ! You can only do partial instanciation on classes that have an uid name ! (LeObject & childs + LeRelation & childs)")
78
-        
79
-        # Checking uid value
80
-        uid, err = self._uid_fieldtype[uid_name].check_data_value(uid)
81
-        if isinstance(err, Exception):
82
-            raise err
83
-        setattr(self, uid_name, uid)
84
-        if uid_name in kwargs:
85
-            warnings.warn("When instanciating the uid was given in the uid argument but was also provided in kwargs. Droping the kwargs uid")
86
-            del(kwargs[uid_name])
87
-        
88
-        # Populating the object with given datas
89
-        errors = dict()
90
-        for name, value in kwargs.items():
91
-            if name not in self.fieldlist():
92
-                errors[name] = AttributeError("No such field '%s' for %s"%(name, self.__class__.__name__))
93
-            else:
94
-                cvalue, err = self.fieldtypes()[name].check_data_value(value)
95
-                if isinstance(err, Exception):
96
-                    errors[name] = err
97
-                else:
98
-                    setattr(self, name, cvalue)
99
-        if len(errors) > 0:
100
-            raise LeApiDataCheckError("Invalid arguments given to constructor", errors)
101
-
102
-        ## @brief A flag to indicate if the object was fully intanciated or not
103
-        self._instanciation_complete = len(kwargs) + 1 == len(self.fieldlist())
104
-
105
-    ## @brief Convert an EmType or EmClass name in a python class name
106
-    # @param name str : The name
107
-    # @return name.title()
108
-    @staticmethod
109
-    def name2classname(name):
110
-        if not isinstance(name, str):
111
-            raise AttributeError("Argument name should be a str and not a %s" % type(name))
112
-        return name.title()
113
-
114
-    ## @brief Convert an EmCalss and EmType name in a rel2type class name
115
-    # @param class_name str : The name of concerned class
116
-    # @param type_name str : The name of the concerned type
117
-    # @param relation_name str : The name of the relation (the name of the rel2type field in the LeClass)
118
-    # @return name.title()
119
-    @staticmethod
120
-    def name2rel2type(class_name, type_name, relation_name):
121
-        cls_name = "Rel%s%s%s"%(_LeCrud.name2classname(class_name), _LeCrud.name2classname(type_name), relation_name.title())
122
-        return cls_name
123
-
124
-    ## @brief Given a dynamically generated class name return the corresponding python Class
125
-    # @param name str : a concrete class name
126
-    # @param cls
127
-    # @return False if no such component
128
-    @classmethod
129
-    def name2class(cls, name):
130
-        if not isinstance(name, str):
131
-            raise ValueError("Expected name argument as a string but got %s instead"%(type(name)))
132
-        mod = importlib.import_module(cls.__module__)
133
-        try:
134
-            return getattr(mod, name)
135
-        except AttributeError:
136
-            return False
137
-
138
-    ## @return LeObject class
139
-    @classmethod
140
-    def leobject(cls):
141
-        return cls.name2class('LeObject')
142
-
143
-    ## @return A dict with key field name and value a fieldtype instance
144
-    @classmethod
145
-    def fieldtypes(cls):
146
-        raise NotImplementedError("Abstract method") #child classes should return their uid fieldtype
147
-    
148
-    ## @return A dict with fieldtypes marked as internal
149
-    # @todo check if this method is in use, else delete it
150
-    @classmethod
151
-    def fieldtypes_internal(self):
152
-        return { fname: ft for fname, ft in cls.fieldtypes().items() if hasattr(ft, 'internal') and ft.internal }
153
-    
154
-    ## @return A list of field name
155
-    @classmethod
156
-    def fieldlist(cls):
157
-        return list(cls.fieldtypes().keys())
158
-    
159
-    ## @return The name of the uniq id field
160
-    # @todo test for abstract method !!!
161
-    @classmethod
162
-    def uidname(cls):
163
-        raise NotImplementedError("Abstract method uid_name for %s!"%cls.__name__)
164
-    
165
-    ## @return maybe Bool: True if cls implements LeType
166
-    # @param cls Class: a Class or instanciated object
167
-    @classmethod
168
-    def implements_letype(cls):
169
-        return hasattr(cls, '_leclass')
170
-
171
-    ## @return maybe Bool: True if cls implements LeClass
172
-    # @param cls Class: a Class or instanciated object
173
-    @classmethod
174
-    def implements_leclass(cls):
175
-        return hasattr(cls, '_class_id')
176
-
177
-    ## @return maybe Bool: True if cls implements LeObject
178
-    # @param cls Class: a Class or instanciated object
179
-    @classmethod
180
-    def implements_leobject(cls):
181
-        return hasattr(cls, '_me_uid')
182
-
183
-    ## @return maybe Bool: True if cls is a LeType or an instance of LeType
184
-    # @param cls Class: a Class or instanciated object
185
-    @classmethod
186
-    def is_letype(cls):
187
-        return cls.implements_letype()
188
-
189
-    ## @return maybe Bool: True if cls is a LeClass or an instance of LeClass
190
-    # @param cls Class: a Class or instanciated object
191
-    @classmethod
192
-    def is_leclass(cls):
193
-        return cls.implements_leclass() and not cls.implements_letype()
194
-
195
-    ## @return maybe Bool: True if cls is a LeClass or an instance of LeClass
196
-    # @param cls Class: a Class or instanciated object
197
-    @classmethod
198
-    def is_leobject(cls):
199
-        return cls.implements_leobject() and not cls.implements_leclass()
200
-
201
-    ## @return maybe Bool: True if cls implements LeRelation
202
-    # @param cls Class: a Class or instanciated object
203
-    @classmethod
204
-    def implements_lerelation(cls):
205
-        return hasattr(cls, '_superior_field_name')
206
-    
207
-    ## @return maybe Bool: True if cls implements LeRel2Type
208
-    # @param cls Class: a Class or instanciated object
209
-    @classmethod
210
-    def implements_lerel2type(cls):
211
-        return hasattr(cls, '_rel_attr_fieldtypes')
212
-    
213
-    ## @return maybe Bool: True if cls is a LeHierarch or an instance of LeHierarch
214
-    # @param cls Class: a Class or instanciated object
215
-    @classmethod
216
-    def is_lehierarch(cls):
217
-        return cls.implements_lerelation() and not cls.implements_lerel2type()
218
-
219
-    ## @return maybe Bool: True if cls is a LeRel2Type or an instance of LeRel2Type
220
-    # @param cls Class: a Class or instanciated object
221
-    @classmethod
222
-    def is_lerel2type(cls):
223
-        return cls.implements_lerel2type()
224
-
225
-    def uidget(self):
226
-        return getattr(self, self.uidname())
227
-
228
-    ## @brief Returns object datas
229
-    # @param internal bool : If True return all datas including internal fields
230
-    # @param lang str | None : if None return datas indexed with field name, else datas are indexed with field name translation
231
-    # @return a dict of fieldname : value
232
-    def datas(self, internal = True, lang = None):
233
-        res = dict()
234
-        for fname, ftt in self.fieldtypes().items():
235
-            if (internal or (not internal and not ftt.is_internal)) and hasattr(self, fname):
236
-                if lang is None:
237
-                    res[fname] = getattr(self, fname)
238
-                else:
239
-                    res[self.ml_fields_strings[fname][lang]] = getattr(self, fname)
240
-        return res
241
-
242
-    ## @brief Indicates if an instance is complete
243
-    # @return a bool
244
-    def is_complete(self):
245
-        return self._instanciation_complete
246
-
247
-    ## @brief Populate the LeType wih datas from DB
248
-    # @param field_list None|list : List of fieldname to fetch. If None fetch all the missing datas
249
-    # @todo Add checks to forbid the use of this method on abtract classes (LeObject, LeClass, LeType, LeRel2Type, LeRelation etc...)
250
-    def populate(self, field_list=None):
251
-        if not self.is_complete():
252
-            if field_list == None:
253
-                field_list = [ fname for fname in self.fieldlist() if not hasattr(self, fname) ]
254
-            filters = [self._id_filter()]
255
-            rel_filters = []
256
-            # Getting datas from db
257
-            fdatas = self._datasource.select(self.__class__, field_list, filters, rel_filters)
258
-            if fdatas is None or len(fdatas) == 0:
259
-                raise LeApiQueryError("Error when trying to populate an object. For type %s id : %d"% (self.__class__.__name__, self.lodel_id))
260
-            # Setting datas
261
-            for fname, fval in fdatas[0].items():
262
-                setattr(self, fname, fval)
263
-            self._instanciation_complete = True
264
-    
265
-    ## @brief Return the corresponding instance
266
-    #
267
-    # @note this method is a kind of factory. Allowing to make a partial instance
268
-    # of abstract types using only an uid and then fetching an complete instance of
269
-    # the correct class
270
-    # @return Corresponding populated LeObject
271
-    def get_instance(self):
272
-        if self.is_complete():
273
-            return self
274
-        uid_fname = self.uidname()
275
-        qfilter = '{uid_fname} = {uid}'.format(uid_fname = uid_fname, uid = getattr(self, uid_fname))
276
-        return leobject.get([qfilter])[0]
277
-    
278
-    ## @brief Update a component in DB
279
-    # @param datas dict : If None use instance attributes to update de DB
280
-    # @return True if success
281
-    # @todo better error handling
282
-    # @todo for check_data_consistency, datas must be populated to make update safe !
283
-    def update(self, datas=None):
284
-        kwargs = locals()
285
-        del(kwargs['self'])
286
-        kwargs = LodelHook.call_hook('leapi_update_pre', self, kwargs)
287
-        ret = self.__update_unsafe(**kwargs)
288
-        return LodelHook.call_hook('leapi_update_post', self, ret)
289
-
290
-    ## @brief Unsafe, without hooks version of insert method
291
-    # @see _LeCrud.update()
292
-    def __update_unsafe(self, datas=None):
293
-        if not self.is_complete():
294
-            self.populate()
295
-            warnings.warn("\nThis object %s is not complete and has been populated when update was called. This is very unsafe\n" % self)
296
-        datas = self.datas(internal=False) if datas is None else datas
297
-        upd_datas = self.prepare_datas(datas, complete = False, allow_internal = False)
298
-        filters = [self._id_filter()]
299
-        rel_filters = []
300
-        ret = self._datasource.update(self.__class__, self.uidget(), **upd_datas)
301
-        if ret == 1:
302
-            return True
303
-        else:
304
-            #ERROR HANDLING
305
-            return False
306
-    
307
-    ## @brief Delete a component
308
-    # @return True if success
309
-    # @todo better error handling
310
-    def delete(self):
311
-        LodelHook.call_hook('leapi_delete_pre', self, None)
312
-        ret = self._datasource.delete(self.__class__, self.uidget())
313
-        return LodelHook.call_hook('leapi_delete_post', self, ret)
314
-
315
-    ## @brief Check that datas are valid for this type
316
-    # @param datas dict : key == field name value are field values
317
-    # @param complete bool : if True expect that datas provide values for all non internal fields
318
-    # @param allow_internal bool : if True don't raise an error if a field is internal
319
-    # @param cls
320
-    # @return Checked datas
321
-    # @throw LeApiDataCheckError if errors reported during check
322
-    @classmethod
323
-    def check_datas_value(cls, datas, complete = False, allow_internal = True):
324
-        err_l = dict() #Stores errors
325
-        correct = [] #Valid fields name
326
-        mandatory = [] #mandatory fields name
327
-        for fname, ftt in cls.fieldtypes().items():
328
-            if allow_internal or not ftt.is_internal():
329
-                correct.append(fname)
330
-                if complete and not hasattr(ftt, 'default'):
331
-                    mandatory.append(fname)
332
-        mandatory = set(mandatory)
333
-        correct = set(correct)
334
-        provided = set(datas.keys())
335
-
336
-        #searching unknow fields
337
-        unknown = provided - correct
338
-        for u_f in unknown:
339
-            #here we can check if the field is unknown or rejected because it is internal
340
-            err_l[u_f] = AttributeError("Unknown or unauthorized field '%s'"%u_f)
341
-        #searching missings fields
342
-        missings = mandatory - provided
343
-        for miss_field in missings:
344
-            err_l[miss_field] = AttributeError("The data for field '%s' is missing"%miss_field)
345
-        #Checks datas
346
-        checked_datas = dict()
347
-        for name, value in [ (name, value) for name, value in datas.items() if name in correct ]:
348
-            ft = cls.fieldtypes()
349
-            ft = ft[name]
350
-            r = ft.check_data_value(value)
351
-            checked_datas[name], err = r
352
-            #checked_datas[name], err = cls.fieldtypes()[name].check_data_value(value)
353
-            if err:
354
-                err_l[name] = err
355
-
356
-        if len(err_l) > 0:
357
-            raise LeApiDataCheckError("Error while checking datas", err_l)
358
-        return checked_datas
359
-    
360
-    ## @brief Retrieve a collection of lodel editorial components
361
-    #
362
-    # @param query_filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
363
-    # @param field_list list|None : list of string representing fields see @ref leobject_filters
364
-    # @param order list : A list of field names or tuple (FIELDNAME, [ASC | DESC])
365
-    # @param group list : A list of field names or tuple (FIELDNAME, [ASC | DESC])
366
-    # @param limit int : The maximum number of returned results
367
-    # @param offset int : offset
368
-    # @param instanciate bool : If True return an instance, else return a dict
369
-    # @param cls
370
-    # @return A list of lodel editorial components instance
371
-    # @todo think about LeObject and LeClass instanciation (partial instanciation, etc)
372
-    @classmethod
373
-    def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
374
-        kwargs = locals()
375
-        del(kwargs['cls'])
376
-        kwargs = LodelHook.call_hook('leapi_get_pre', cls, kwargs)
377
-        ret = cls.__get_unsafe(**kwargs)
378
-        return LodelHook.call_hook('leapi_get_post', cls, ret)
379
-
380
-    ## @brief Unsafe, without hooks version of get() method
381
-    # @see _LeCrud.get()
382
-    @classmethod
383
-    def __get_unsafe(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
384
-
385
-        if field_list is None or len(field_list) == 0:
386
-            #default field_list
387
-            field_list = cls.fieldlist()
388
-
389
-        field_list = cls._prepare_field_list(field_list) #Can raise LeApiDataCheckError
390
-
391
-        #preparing filters
392
-        filters, relational_filters = cls._prepare_filters(query_filters)
393
-        
394
-        #preparing order
395
-        if order:
396
-            order = cls._prepare_order_fields(order)
397
-            if isinstance(order, Exception):
398
-                raise order #can be buffered and raised later, but _prepare_filters raise when fails
399
-
400
-        #preparing groups
401
-        if group:
402
-            group = cls._prepare_order_fields(group)
403
-            if isinstance(group, Exception):
404
-                raise group # can also be buffered and raised later
405
-
406
-        #checking limit and offset values
407
-        if not (limit is None):
408
-            if limit <= 0:
409
-                raise ValueError("Invalid limit given : %d"%limit)
410
-        if not (offset is None):
411
-            if offset < 0:
412
-                raise ValueError("Invalid offset given : %d"%offset)
413
-
414
-        #Fetching editorial components from datasource
415
-        results = cls._datasource.select(
416
-                                            target_cls = cls,
417
-                                            field_list = field_list,
418
-                                            filters = filters,
419
-                                            rel_filters = relational_filters,
420
-                                            order=order,
421
-                                            group=group,
422
-                                            limit=limit,
423
-                                            offset=offset,
424
-                                            instanciate=instanciate
425
-                                        )
426
-
427
-        return results
428
-
429
-    ## @brief Insert a new component
430
-    # @param datas dict : The value of object we want to insert
431
-    # @param classname str : The class name
432
-    # @param cls
433
-    # @return A new id if success else False
434
-    @classmethod
435
-    def insert(cls, datas, classname=None):
436
-        kwargs = locals()
437
-        del(kwargs['cls'])
438
-        kwargs = LodelHook.call_hook('leapi_insert_pre', cls, kwargs)
439
-        ret = cls.__insert_unsafe(**kwargs)
440
-        return LodelHook.call_hook('leapi_insert_post', cls, ret)
441
-
442
-    ## @brief Unsafe, without hooks version of insert() method
443
-    # @see _LeCrud.insert()
444
-    @classmethod
445
-    def __insert_unsafe(cls, datas, classname=None):
446
-        callcls = cls if classname is None else cls.name2class(classname)
447
-        if not callcls:
448
-            raise LeApiErrors("Error when inserting",{'error':ValueError("The class '%s' was not found"%classname)})
449
-        if not callcls.implements_letype() and not callcls.implements_lerelation():
450
-            raise ValueError("You can only insert relations and LeTypes objects but tying to insert a '%s'"%callcls.__name__)
451
-        insert_datas = callcls.prepare_datas(datas, complete = True, allow_internal = False)
452
-        return callcls._datasource.insert(callcls, **insert_datas)
453
-    
454
-    ## @brief Check and prepare datas
455
-    # 
456
-    # @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
457
-    # 
458
-    # @param datas dict : {fieldname : fieldvalue, ...}
459
-    # @param complete bool : If True you MUST give all the datas
460
-    # @param allow_internal : Wether or not interal fields are expected in datas
461
-    # @param cls
462
-    # @return Datas ready for use
463
-    # @todo: complete is very unsafe, find a way to get rid of it
464
-    @classmethod
465
-    def prepare_datas(cls, datas, complete=False, allow_internal=True):
466
-        if not complete:
467
-            warnings.warn("\nActual implementation can make datas construction and consitency unsafe when datas are not complete\n")
468
-        ret_datas = cls.check_datas_value(datas, complete, allow_internal)
469
-        if isinstance(ret_datas, Exception):
470
-            raise ret_datas
471
-
472
-        if complete:
473
-            ret_datas = cls._construct_datas(ret_datas)
474
-            cls._check_datas_consistency(ret_datas)
475
-        return ret_datas
476
-
477
-    #-###################-#
478
-    #   Private methods   #
479
-    #-###################-#
480
-    
481
-    ## @brief Build a filter to select an object with a specific ID
482
-    # @warning assert that the uid is not composed with multiple fieldtypes
483
-    # @return A filter of the form tuple(UID, '=', self.UID)
484
-    # @todo This method should not be private
485
-    def _id_filter(self):
486
-        id_name = self.uidname()
487
-        return ( id_name, '=', getattr(self, id_name) )
488
-
489
-    ## @brief Construct datas values
490
-    #
491
-    # @param cls
492
-    # @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
493
-    # @return A new dict of datas
494
-    @classmethod
495
-    def _construct_datas(cls, datas):
496
-        constructor = DatasConstructor(cls, datas, cls.fieldtypes())
497
-        ret = {
498
-                fname:constructor[fname]
499
-                for fname, ftype in cls.fieldtypes().items()
500
-                if not ftype.is_internal() or ftype.internal != 'autosql'
501
-        }
502
-        return ret
503
-
504
-    ## @brief Check datas consistency
505
-    # 
506
-    # @warning assert that datas is complete
507
-    # @param cls
508
-    # @param datas dict : Datas that have been returned by LeCrud._construct_datas() method
509
-    # @throw LeApiDataCheckError if fails
510
-    @classmethod
511
-    def _check_datas_consistency(cls, datas):
512
-        err_l = []
513
-        err_l = dict()
514
-        for fname, ftype in cls.fieldtypes().items():
515
-            ret = ftype.check_data_consistency(cls, fname, datas)
516
-            if isinstance(ret, Exception):
517
-                err_l[fname] = ret
518
-
519
-        if len(err_l) > 0:
520
-            raise LeApiDataCheckError("Datas consistency checks fails", err_l)
521
-        
522
-
523
-    ## @brief Prepare a field_list
524
-    # @param cls
525
-    # @param field_list list : List of string representing fields
526
-    # @return A well formated field list
527
-    # @throw LeApiDataCheckError if invalid field given
528
-    @classmethod
529
-    def _prepare_field_list(cls, field_list):
530
-        err_l = dict()
531
-        ret_field_list = list()
532
-        for field in field_list:
533
-            if cls._field_is_relational(field):
534
-                ret = cls._prepare_relational_fields(field)
535
-            else:
536
-                ret = cls._check_field(field)
537
-
538
-            if isinstance(ret, Exception):
539
-                err_l[field] = ret
540
-            else:
541
-                ret_field_list.append(ret)
542
-
543
-        if len(err_l) > 0:
544
-            raise LeApiDataCheckError(err_l)
545
-        return ret_field_list
546
-     
547
-    ## @brief Check that a relational field is valid
548
-    # @param cls
549
-    # @param field str : a relational field
550
-    # @return a nature
551
-    @classmethod
552
-    def _prepare_relational_fields(cls, field):
553
-        raise NotImplementedError("Abstract method")
554
-    
555
-    ## @brief Check that the field list only contains fields that are in the current class
556
-    # @param cls
557
-    # @param field : a field
558
-    # @return None if no problem, else returns a list of exceptions that occurs during the check
559
-    @classmethod
560
-    def _check_field(cls, field):
561
-        if field not in cls.fieldlist():
562
-            return ValueError("No such field '%s' in %s"%(field, cls.__name__))
563
-        return field
564
-    
565
-    ## @brief Prepare the order parameter for the get method
566
-    # @note if an item in order_list is just a str it is considered as ASC by default
567
-    # @param cls
568
-    # @param order_field_list list : A list of field name or tuple (FIELDNAME, [ASC|DESC])
569
-    # @return a list of tuple (FIELDNAME, [ASC|DESC] )
570
-    @classmethod
571
-    def _prepare_order_fields(cls, order_field_list):
572
-        errors = dict()
573
-        result = []
574
-        for order_field in order_field_list:
575
-            if not isinstance(order_field, tuple):
576
-                order_field = (order_field, 'ASC')
577
-            if len(order_field) != 2 or order_field[1].upper() not in ['ASC', 'DESC']:
578
-                errors[order_field] = ValueError("Expected a string or a tuple with (FIELDNAME, ['ASC'|'DESC']) but got : %s"%order_field)
579
-            else:
580
-                ret = cls._check_field(order_field[0])
581
-                if isinstance(ret, Exception):
582
-                    errors[order_field] = ret
583
-            order_field = (order_field[0], order_field[1].upper())
584
-            result.append(order_field)
585
-        if len(errors) > 0:
586
-            return LeApiErrors("Errors when preparing ordering fields", errors)
587
-        return result
588
-
589
-    ## @brief Prepare filters for datasource
590
-    # 
591
-    # This method divide filters in two categories :
592
-    #  - filters : standart FIELDNAME OP VALUE filter
593
-    #  - relationnal_filters : filter on object relation RELATION_NATURE OP VALUE
594
-    # 
595
-    # Both categories of filters are represented in the same way, a tuple with 3 elements (NAME|NAT , OP, VALUE )
596
-    # 
597
-    # @param cls
598
-    # @param filters_l list : This list can contain str "FIELDNAME OP VALUE" and tuples (FIELDNAME, OP, VALUE)
599
-    # @return a tuple(FILTERS, RELATIONNAL_FILTERS
600
-    #
601
-    # @see @ref datasource_side
602
-    @classmethod
603
-    def _prepare_filters(cls, filters_l):
604
-        filters = list()
605
-        res_filters = list()
606
-        rel_filters = list()
607
-        err_l = dict()
608
-        #Splitting in tuple if necessary
609
-        for fil in filters_l:
610
-            if len(fil) == 3 and not isinstance(fil, str):
611
-                filters.append(tuple(fil))
612
-            else:
613
-                filters.append(cls._split_filter(fil))
614
-
615
-        for field, operator, value in filters:
616
-            if cls._field_is_relational(field):
617
-                #Checks relational fields
618
-                ret = cls._prepare_relational_fields(field)
619
-                if isinstance(ret, Exception):
620
-                    err_l[field] = ret
621
-                else:
622
-                    rel_filters.append((ret, operator, value))
623
-            else:
624
-                #Checks other fields
625
-                ret = cls._check_field(field)
626
-                if isinstance(ret, Exception):
627
-                    err_l[field] = ret
628
-                else:
629
-                    res_filters.append((field,operator, value))
630
-        
631
-        if len(err_l) > 0:
632
-            raise LeApiDataCheckError("Error while preparing filters : ", err_l)
633
-        return (res_filters, rel_filters)
634
-
635
-
636
-    ## @brief Check and split a query filter
637
-    # @note The query_filter format is "FIELD OPERATOR VALUE"
638
-    # @param query_filter str : A query_filter string
639
-    # @param cls
640
-    # @return a tuple (FIELD, OPERATOR, VALUE)
641
-    @classmethod
642
-    def _split_filter(cls, query_filter):
643
-        if cls._query_re is None:
644
-            cls._compile_query_re()
645
-
646
-        matches = cls._query_re.match(query_filter)
647
-        if not matches:
648
-            raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
649
-
650
-        result = (matches.group('field'), re.sub(r'\s', ' ', matches.group('operator'), count=0), matches.group('value').strip())
651
-        for r in result:
652
-            if len(r) == 0:
653
-                raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
654
-        return result
655
-
656
-    ## @brief Compile the regex for query_filter processing
657
-    # @note Set _LeObject._query_re
658
-    @classmethod
659
-    def _compile_query_re(cls):
660
-        op_re_piece = '(?P<operator>(%s)'%cls._query_operators[0].replace(' ', '\s')
661
-        for operator in cls._query_operators[1:]:
662
-            op_re_piece += '|(%s)'%operator.replace(' ', '\s')
663
-        op_re_piece += ')'
664
-        cls._query_re = re.compile('^\s*(?P<field>(((superior)|(subordinate))\.)?[a-z_][a-z0-9\-_]*)\s*'+op_re_piece+'\s*(?P<value>[^<>=!].*)\s*$', flags=re.IGNORECASE)
665
-        pass
666
-    
667
-    ## @brief Check if a field is relational or not
668
-    # @param field str : the field to test
669
-    # @return True if the field is relational else False
670
-    @staticmethod
671
-    def _field_is_relational(field):
672
-        return field.startswith('superior.') or field.startswith('subordinate.')
673
-
674
-## @page leobject_filters LeObject query filters
675
-# The LeObject API provide methods that accept filters allowing the user
676
-# to query the database and fetch LodelEditorialObjects.
677
-#
678
-# The LeObject API translate those filters for the datasource. 
679
-# 
680
-# @section api_user_side API user side filters
681
-# Filters are string expressing a condition. The string composition
682
-# is as follow : "<FIELD> <OPERATOR> <VALUE>"
683
-# @subsection fpart FIELD
684
-# @subsubsection standart fields
685
-# Standart fields, represents a value of the LeObject for example "title", "lodel_id" etc.
686
-# @subsubsection rfields relationnal fields
687
-# relationnal fields, represents a relation with the object hierarchy. Those fields are composed as follow :
688
-# "<RELATION>.<NATURE>".
689
-#
690
-# - Relation can takes two values : superiors or subordinates
691
-# - Nature is a relation nature ( see EditorialModel.classtypes )
692
-# Examples : "superiors.parent", "subordinates.translation" etc.
693
-# @note The field_list arguement of leapi.leapi._LeObject.get() use the same syntax than the FIELD filter part 
694
-# @subsection oppart OPERATOR
695
-# The OPERATOR part of a filter is a comparison operator. There is
696
-# - standart comparison operators : = , <, > , <=, >=, !=
697
-# - vagueness string comparison 'like' and 'not like'
698
-# - list operators : 'in' and 'not in'
699
-# The list of allowed operators is sotred at leapi.leapi._LeObject._query_operators . 
700
-# @subsection valpart VALUE
701
-# The VALUE part of a filter is... just a value...
702
-#
703
-# @section datasource_side Datasource side filters
704
-# As said above the API "translate" filters before forwarding them to the datasource. 
705
-#
706
-# The translation process transform filters in tuple composed of 3 elements
707
-# ( @ref fpart , @ref oppart , @ref valpart ). Each element is a string.
708
-#
709
-# There is a special case for @ref rfields : the field element is a tuple composed with two elements
710
-# ( RELATION, NATURE ) where NATURE is a string ( see EditorialModel.classtypes ) and RELATION is one of
711
-# the defined constant : 
712
-#
713
-# - leapi.lecrud.REL_SUB for "subordinates"
714
-# - leapi.lecrud.REL_SUP for "superiors"
715
-#
716
-# @note The filters translation process also check if given field are valids compared to the concerned letype and/or the leclass
717
-
718
-## @page lecrud_instanciation LeCrud child classes instanciations
719
-#
720
-# _LeCrud provide a generic __init__ method for all its child classes. The following notes are
721
-# important parts of the instanciation mechanism.
722
-#
723
-# The constructor takes 2 parameters : 
724
-# - a uniq identifier (uid)
725
-# - **kwargs for object datas
726
-#
727
-# @section lecrud_pi Partial instancation
728
-#
729
-# You can make partial instanciations by giving only parts of datas and even by giving only a uid
730
-#
731
-# @warning Partial instanciation needs an uid field name (lodel_id for LeObject and id_relation for LeRelation). This implies that you cannot make partial instance of a LeCrud.
732
-#
733
-# @subsection lecrud_pitools Partial instances tools
734
-#
735
-# The _LeCrud.is_complete() method indicates whether or not an instance is partial.
736
-#
737
-# The _LeCrud.populate() method fetch missing datas
738
-#
739
-# You partially instanciate an abtract class (like LeClass or LeRelation) using only a uid. Then you cannot populate this kind of instance (you cannot dinamically change the type of an instance). The _LeCrud.get_instance() method returns a populated instance with the good type.
740
-#

+ 0
- 331
leapi/lefactory.py View File

@@ -1,331 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import importlib
4
-import copy
5
-import os.path
6
-
7
-import EditorialModel
8
-from EditorialModel import classtypes
9
-from EditorialModel.model import Model
10
-from EditorialModel.fieldtypes.generic import GenericFieldType
11
-from leapi.lecrud import _LeCrud
12
-
13
-
14
-## @brief This class is designed to generated the leobject API given an EditorialModel.model
15
-# @note Contains only static methods
16
-#
17
-# The name is not good but i've no other ideas for the moment
18
-class LeFactory(object):
19
-
20
-    output_file = 'dyn.py'
21
-    modname = None
22
-
23
-
24
-    def __init__(self, code_filename = 'leapi/dyn.py'):
25
-        self._code_filename = code_filename
26
-        self._dyn_file = os.path.basename(code_filename)
27
-        self._modname = os.path.dirname(code_filename).strip('/').replace('/', '.') #Warning Windaube compatibility
28
-
29
-    ## @brief Return a call to a FieldType constructor given an EmField
30
-    # @param emfield EmField : An EmField
31
-    # @return a string representing the python code to instanciate a EmFieldType
32
-    @staticmethod
33
-    def fieldtype_construct_from_field(emfield):
34
-        return '%s.EmFieldType(**%s)' % (
35
-            GenericFieldType.module_name(emfield.fieldtype),
36
-            repr(emfield._fieldtype_args),
37
-        )
38
-    
39
-    ## @brief Write generated code to a file
40
-    # @todo better options/params for file creation
41
-    def create_pyfile(self, model, datasource_cls, datasource_args):
42
-        with open(self._code_filename, "w+") as dynfp:
43
-            dynfp.write(self.generate_python(model, datasource_cls, datasource_args))
44
-
45
-    ## @brief Generate fieldtypes for concret classes
46
-    # @param ft_dict dict : key = fieldname value = fieldtype __init__ args
47
-    # @return (uid_fieldtypes, fieldtypes) designed to be printed in generated code
48
-    def concret_fieldtypes(self, ft_dict):
49
-        res_ft_l = list()
50
-        res_uid_ft = None
51
-        for fname, ftargs in ft_dict.items():
52
-            if ftargs is None:
53
-                res_ft_l.append('%s: None' % repr(fname))
54
-            else:
55
-                ftargs = copy.copy(ftargs)
56
-                fieldtype = ftargs['fieldtype']
57
-                self.needed_fieldtypes |= set([fieldtype])
58
-                del(ftargs['fieldtype'])
59
-
60
-                constructor = '{ftname}.EmFieldType(**{ftargs})'.format(
61
-                    ftname = GenericFieldType.module_name(fieldtype),
62
-                    ftargs = ftargs,
63
-                )
64
-
65
-                if fieldtype == 'pk':
66
-                    #
67
-                    #       WARNING multiple PK not supported
68
-                    #
69
-                    res_uid_ft = "{ %s: %s }"%(repr(fname),constructor)
70
-                else:
71
-                    res_ft_l.append( '%s: %s'%(repr(fname), constructor) )
72
-        return (res_uid_ft, res_ft_l)
73
-
74
-    ## @brief Given a Model generate concrete instances of LeRel2Type classes to represent relations
75
-    # @param model : the EditorialModel
76
-    # @return python code
77
-    def emrel2type_pycode(self, model):
78
-        res_code = ""
79
-        for field in [ f for f in model.components('EmField') if f.fieldtype == 'rel2type']:
80
-            related = model.component(field.rel_to_type_id)
81
-            src = field.em_class
82
-            cls_name = _LeCrud.name2rel2type(src.name, related.name, field.name)
83
-            relation_name = field.name
84
-
85
-            attr_l = dict()
86
-            for attr in [ f for f in model.components('EmField') if f.rel_field_id == field.uid]:
87
-                attr_l[attr.name] = LeFactory.fieldtype_construct_from_field(attr)
88
-
89
-            rel_code = """
90
-class {classname}(LeRel2Type):
91
-    _rel_attr_fieldtypes = {attr_dict}
92
-    _superior_cls = {supcls}
93
-    _subordinate_cls = {subcls}
94
-    _relation_name = {relation_name}
95
-
96
-""".format(
97
-    classname = cls_name,
98
-    attr_dict = "{" + (','.join(['\n    %s: %s' % (repr(f), v) for f,v in attr_l.items()])) + "\n}",
99
-    supcls = _LeCrud.name2classname(src.name),
100
-    subcls = _LeCrud.name2classname(related.name),
101
-    relation_name = repr(relation_name),
102
-)
103
-            res_code += rel_code
104
-        return res_code
105
-
106
-    ## @brief Given a Model and an EmClass instances generate python code for corresponding LeClass
107
-    # @param model Model : A Model instance
108
-    # @param emclass EmClass : An EmClass instance from model
109
-    # @return A string representing the python code for the corresponding LeClass child class
110
-    def emclass_pycode(self, model, emclass):
111
-
112
-        cls_fields = dict()
113
-        cls_linked_types = dict() # Stores rel2type referenced by fieldname
114
-        #Populating linked_type attr
115
-        for rfield in [ f for f in emclass.fields() if f.fieldtype == 'rel2type']:
116
-            fti = rfield.fieldtype_instance()
117
-            cls_linked_types[rfield.name] = _LeCrud.name2classname(model.component(fti.rel_to_type_id).name)
118
-        # Populating fieldtype attr
119
-        for field in emclass.fields(relational = False):
120
-            if field.name not in EditorialModel.classtypes.common_fields.keys() or not ( hasattr(field, 'immutable') and field.immutable):
121
-                self.needed_fieldtypes |= set([field.fieldtype])
122
-                cls_fields[field.name] = LeFactory.fieldtype_construct_from_field(field)
123
-                fti = field.fieldtype_instance()
124
-        ml_fieldnames = dict()
125
-        for field in emclass.fields():
126
-            if field.string.get() == '':
127
-                field.string.set_default(field.name)
128
-            ml_fieldnames[field.name] = field.string.dumps()
129
-
130
-        return """
131
-#Initialisation of {name} class attributes
132
-{name}._fieldtypes = {ftypes}
133
-{name}.ml_fields_strings = {fieldnames}
134
-{name}._linked_types = {ltypes}
135
-{name}._classtype = {classtype}
136
-""".format(
137
-            name = _LeCrud.name2classname(emclass.name),
138
-            ftypes = "{" + (','.join(['\n    %s: %s' % (repr(f), v) for f, v in cls_fields.items()])) + "\n}",
139
-            fieldnames = '{' + (','.join(['\n   %s: MlString(%s)' % (repr(f), v) for f,v in ml_fieldnames.items()])) + '\n}',
140
-            ltypes = "{" + (','.join(['\n    %s: %s' % (repr(f), v) for f, v in cls_linked_types.items()])) + "\n}",
141
-
142
-            classtype = repr(emclass.classtype),
143
-        )
144
-
145
-    ## @brief Given a Model and an EmType instances generate python code for corresponding LeType
146
-    # @param model Model : A Model instance
147
-    # @param emtype EmType : An EmType instance from model
148
-    # @return A string representing the python code for the corresponding LeType child class
149
-    def emtype_pycode(self, model, emtype):
150
-        type_fields = list()
151
-        type_superiors = list()
152
-        for field in emtype.fields(relational=False):
153
-            if not field.name in EditorialModel.classtypes.common_fields:
154
-                type_fields.append(field.name)
155
-
156
-        for nat, sup_l in emtype.superiors().items():
157
-            type_superiors.append('%s: [%s]' % (
158
-                repr(nat),
159
-                ', '.join([_LeCrud.name2classname(sup.name) for sup in sup_l])
160
-            ))
161
-
162
-        return """
163
-#Initialisation of {name} class attributes
164
-{name}._fields = {fields}
165
-{name}._superiors = {dsups}
166
-{name}._leclass = {leclass}
167
-""".format(
168
-            name=_LeCrud.name2classname(emtype.name),
169
-            fields=repr(type_fields),
170
-            dsups='{' + (', '.join(type_superiors)) + '}',
171
-            leclass=_LeCrud.name2classname(emtype.em_class.name)
172
-        )
173
-
174
-    ## @brief Generate python code containing the LeObject API
175
-    # @param model EditorialModel.model.Model : An editorial model instance
176
-    # @param datasource_cls Datasource : A datasource class
177
-    # @param datasource_args dict : A dict representing arguments for datasource_cls instanciation
178
-    # @return A string representing python code
179
-    def generate_python(self, model, datasource_cls, datasource_args):
180
-        self.needed_fieldtypes = set() #Stores the list of fieldtypes that will be used by generated code
181
-
182
-        model = model
183
-
184
-        result = ""
185
-        #result += "#-*- coding: utf-8 -*-\n"
186
-        #Putting import directives in result
187
-        heading = """## @author LeFactory
188
-
189
-import EditorialModel
190
-from EditorialModel import fieldtypes
191
-from EditorialModel.fieldtypes import {needed_fieldtypes_list}
192
-from Lodel.utils.mlstring import MlString
193
-
194
-import leapi
195
-import leapi.lecrud
196
-import leapi.leobject
197
-import leapi.lerelation
198
-from leapi.leclass import _LeClass
199
-from leapi.letype import _LeType
200
-"""
201
-
202
-        result += """
203
-import %s
204
-
205
-""" % (datasource_cls.__module__)
206
-
207
-        #Generating the code for LeObject class
208
-        leobj_me_uid = dict()
209
-        for comp in model.components('EmType') + model.components('EmClass'):
210
-            leobj_me_uid[comp.uid] = _LeCrud.name2classname(comp.name)
211
-        
212
-        #Building the fieldtypes dict of LeObject
213
-        common_fieldtypes = dict()
214
-        for ftname, ftdef in EditorialModel.classtypes.common_fields.items():
215
-            common_fieldtypes[ftname] = ftdef if 'immutable' in ftdef and ftdef['immutable'] else None
216
-        (leobj_uid_fieldtype, leobj_fieldtypes) = self.concret_fieldtypes(common_fieldtypes)
217
-        #Building the fieldtypes dict for LeRelation
218
-        common_fieldtypes = dict()
219
-        for ftname, ftdef in EditorialModel.classtypes.relations_common_fields.items():
220
-            common_fieldtypes[ftname] = ftdef if 'immutable' in ftdef and ftdef['immutable'] else None
221
-        (lerel_uid_fieldtype, lerel_fieldtypes) = self.concret_fieldtypes(common_fieldtypes)
222
-
223
-        result += """
224
-## @brief _LeCrud concret class
225
-# @see leapi.lecrud._LeCrud
226
-class LeCrud(leapi.lecrud._LeCrud):
227
-    _datasource = {ds_classname}(**{ds_kwargs})
228
-    _uid_fieldtype = None
229
-
230
-## @brief _LeObject concret class
231
-# @see leapi.leobject._LeObject
232
-class LeObject(LeCrud, leapi.leobject._LeObject):
233
-    _me_uid = {me_uid_l}
234
-    _me_uid_field_names = ({class_id}, {type_id})
235
-    _uid_fieldtype = {leo_uid_fieldtype}
236
-    _leo_fieldtypes = {leo_fieldtypes}
237
-
238
-## @brief _LeRelation concret class
239
-# @see leapi.lerelation._LeRelation
240
-class LeRelation(LeCrud, leapi.lerelation._LeRelation):
241
-    _uid_fieldtype = {lerel_uid_fieldtype}
242
-    _rel_fieldtypes = {lerel_fieldtypes}
243
-    ## WARNING !!!! OBSOLETE ! DON'T USE IT
244
-    _superior_field_name = {lesup_name}
245
-    ## WARNING !!!! OBSOLETE ! DON'T USE IT
246
-    _subordinate_field_name = {lesub_name}
247
-
248
-class LeHierarch(LeRelation, leapi.lerelation._LeHierarch):
249
-    pass
250
-
251
-class LeRel2Type(LeRelation, leapi.lerelation._LeRel2Type):
252
-    pass
253
-
254
-class LeClass(LeObject, _LeClass):
255
-    pass
256
-
257
-class LeType(LeClass, _LeType):
258
-    pass
259
-""".format(
260
-            ds_classname = datasource_cls.__module__ + '.' + datasource_cls.__name__,
261
-            ds_kwargs = repr(datasource_args),
262
-            me_uid_l = repr(leobj_me_uid),
263
-            leo_uid_fieldtype = leobj_uid_fieldtype,
264
-            leo_fieldtypes = '{\n\t' + (',\n\t'.join(leobj_fieldtypes))+ '\n\t}',
265
-            lerel_fieldtypes = '{\n\t' + (',\n\t'.join(lerel_fieldtypes))+ '\n\t}',
266
-            lerel_uid_fieldtype = lerel_uid_fieldtype,
267
-            lesup_name = repr(classtypes.relation_superior),
268
-            lesub_name = repr(classtypes.relation_subordinate),
269
-            class_id = repr(classtypes.object_em_class_id),
270
-            type_id = repr(classtypes.object_em_type_id),
271
-        )
272
-
273
-        emclass_l = model.components(EditorialModel.classes.EmClass)
274
-        emtype_l = model.components(EditorialModel.types.EmType)
275
-
276
-        #LeClass child classes definition
277
-        for emclass in emclass_l:
278
-            if emclass.string.get() == '':
279
-                emclass.string.set_default(emclass.name)
280
-            result += """
281
-## @brief EmClass {name} LeClass child class
282
-# @see leapi.leclass.LeClass
283
-class {name}(LeClass, LeObject):
284
-    _class_id = {uid}
285
-    ml_string = MlString({name_translations})
286
-
287
-""".format(
288
-                name=_LeCrud.name2classname(emclass.name),
289
-                uid=emclass.uid,
290
-                name_translations = repr(emclass.string.__str__()),
291
-            )
292
-        #LeType child classes definition
293
-        for emtype in emtype_l:
294
-            if emtype.string.get() == '':
295
-                emtype.string.set_default(emtype.name)
296
-            result += """
297
-## @brief EmType {name} LeType child class
298
-# @see leobject::letype::LeType
299
-class {name}(LeType, {leclass}):
300
-    _type_id = {uid}
301
-    ml_string = MlString({name_translations})
302
-
303
-""".format(
304
-                name=_LeCrud.name2classname(emtype.name),
305
-                leclass=_LeCrud.name2classname(emtype.em_class.name),
306
-                uid=emtype.uid,
307
-                name_translations = repr(emtype.string.__str__()),
308
-            )
309
-
310
-        #Generating concret class of LeRel2Type
311
-        result += self.emrel2type_pycode(model)
312
-
313
-        #Set attributes of created LeClass and LeType child classes
314
-        for emclass in emclass_l:
315
-            result += self.emclass_pycode(model, emclass)
316
-        for emtype in emtype_l:
317
-            result += self.emtype_pycode(model, emtype)
318
-
319
-
320
-        #Populating LeObject._me_uid dict for a rapid fetch of LeType and LeClass given an EM uid
321
-        me_uid = {comp.uid: _LeCrud.name2classname(comp.name) for comp in emclass_l + emtype_l}
322
-        result += """
323
-## @brief Dict for getting LeClass and LeType child classes given an EM uid
324
-LeObject._me_uid = %s""" % "{" + (', '.join(['%s: %s' % (k, v) for k, v in me_uid.items()])) + "}"
325
-        result += "\n"
326
-        
327
-        heading = heading.format(needed_fieldtypes_list = ', '.join(self.needed_fieldtypes))
328
-        result = heading + result
329
-
330
-        del(self.needed_fieldtypes)
331
-        return result

+ 0
- 0
leapi/leobject.py View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save