Browse Source

Merge branch 'newlodel' of git.labocleo.org:lodel2 into newlodel

m.orban 8 years ago
parent
commit
0ffbe0845a
52 changed files with 736 additions and 164 deletions
  1. 4
    0
      README.txt
  2. 13
    5
      em_test.py
  3. BIN
      examples/em_test.pickle
  4. 1
    1
      install/conf.d/lodel2.ini
  5. 8
    2
      install/loader.py
  6. 2
    0
      lodel/__init__.py
  7. 37
    7
      lodel/editorial_model/components.py
  8. 9
    0
      lodel/editorial_model/exceptions.py
  9. 16
    7
      lodel/editorial_model/model.py
  10. 9
    11
      lodel/editorial_model/translator/xmlfile.py
  11. 6
    3
      lodel/leapi/datahandlers/base_classes.py
  12. 3
    3
      lodel/leapi/datahandlers/datas.py
  13. 4
    6
      lodel/leapi/datahandlers/references.py
  14. 2
    0
      lodel/leapi/lefactory.py
  15. 23
    13
      lodel/leapi/leobject.py
  16. 25
    16
      lodel/leapi/query.py
  17. 11
    9
      lodel/plugin/plugins.py
  18. 1
    1
      plugins/mongodb_datasource/confspec.py
  19. 45
    18
      plugins/mongodb_datasource/datasource.py
  20. 3
    0
      plugins/mongodb_datasource/utils.py
  21. 3
    0
      plugins/webui/confspec.py
  22. 51
    0
      plugins/webui/exceptions.py
  23. 3
    0
      plugins/webui/interface/controllers/__init__.py
  24. 130
    0
      plugins/webui/interface/controllers/admin.py
  25. 28
    10
      plugins/webui/interface/controllers/base.py
  26. 6
    0
      plugins/webui/interface/controllers/document.py
  27. 19
    0
      plugins/webui/interface/controllers/listing.py
  28. 9
    12
      plugins/webui/interface/router.py
  29. 5
    0
      plugins/webui/interface/template/api/api_lodel_templates.py
  30. 9
    1
      plugins/webui/interface/template/loader.py
  31. 15
    5
      plugins/webui/interface/urls.py
  32. 15
    2
      plugins/webui/main.py
  33. 22
    5
      plugins/webui/run.py
  34. 9
    2
      plugins/webui/templates/admin/admin.html
  35. 20
    0
      plugins/webui/templates/admin/admin_create.html
  36. 19
    0
      plugins/webui/templates/admin/admin_edit.html
  37. 10
    0
      plugins/webui/templates/admin/editable_component.html
  38. 3
    5
      plugins/webui/templates/base_backend.html
  39. 9
    0
      plugins/webui/templates/components/components.html
  40. 5
    0
      plugins/webui/templates/documents/show.html
  41. 0
    0
      plugins/webui/templates/empty.html
  42. 9
    0
      plugins/webui/templates/error.html
  43. 7
    1
      plugins/webui/templates/index/index.html
  44. 16
    0
      plugins/webui/templates/listing/list_classes.html
  45. 30
    0
      plugins/webui/templates/listing/show_class.html
  46. 21
    0
      plugins/webui/templates/listing/show_object.html
  47. 10
    0
      plugins/webui/templates/test.html
  48. 19
    7
      scripts/create_instance.sh
  49. BIN
      tests/editorial_model.pickle
  50. 2
    2
      tests/leapi/query/test_datasource.py
  51. 9
    9
      tests/leapi/query/test_filtered.py
  52. 1
    1
      tests/leapi/test_leobject.py

+ 4
- 0
README.txt View File

20
 	make dyncode # Leapi dynamic code creation ( in leapi_dyncode.py in lodel2 instance root dir)
20
 	make dyncode # Leapi dynamic code creation ( in leapi_dyncode.py in lodel2 instance root dir)
21
 	make init_db # Call migration handlers to tell them to init all needed databases. (note : this target has dyncode as dependencie)
21
 	make init_db # Call migration handlers to tell them to init all needed databases. (note : this target has dyncode as dependencie)
22
     make list_hooks # List all the hooks registered
22
     make list_hooks # List all the hooks registered
23
+
24
+Instance loader uppdate :
25
+	If the install/loader.py is updated you can update instance's loader.py using
26
+		scripts/create_instance.sh -u INSTANCE_PATH

+ 13
- 5
em_test.py View File

87
                         'fre': 'Prénom',
87
                         'fre': 'Prénom',
88
                     },
88
                     },
89
                     data_handler = 'varchar',
89
                     data_handler = 'varchar',
90
+                    group = base_group,
90
 )
91
 )
91
 person.new_field(   'lastname',
92
 person.new_field(   'lastname',
92
                     display_name = {
93
                     display_name = {
94
                         'fre': 'Nom de famille',
95
                         'fre': 'Nom de famille',
95
                     },
96
                     },
96
                     data_handler = 'varchar',
97
                     data_handler = 'varchar',
98
+                    group = base_group,
97
 )
99
 )
98
 person.new_field(   'fullname',
100
 person.new_field(   'fullname',
99
                     display_name = {
101
                     display_name = {
115
                     allowed_classes = [person],
117
                     allowed_classes = [person],
116
                     default = None,
118
                     default = None,
117
                     nullable = True,
119
                     nullable = True,
120
+                    group = base_group,
118
 )
121
 )
119
 
122
 
120
 
123
 
324
     display_name = {
327
     display_name = {
325
         'eng': 'name',
328
         'eng': 'name',
326
         'fre': 'nom'},
329
         'fre': 'nom'},
327
-    data_handler = 'varchar')
330
+    data_handler = 'varchar',
331
+    group = index_group)
328
 
332
 
329
 index_value = index_abstract.new_field(
333
 index_value = index_abstract.new_field(
330
     'value',
334
     'value',
331
     display_name = {
335
     display_name = {
332
         'eng': 'value',
336
         'eng': 'value',
333
         'fre': 'valeur'},
337
         'fre': 'valeur'},
334
-    data_handler = 'varchar')
338
+    data_handler = 'varchar',
339
+    group = index_group)
335
 
340
 
336
 text.new_field( 'indexes',
341
 text.new_field( 'indexes',
337
     display_name = {
342
     display_name = {
341
     back_reference = ('Indexabs', 'texts'),
346
     back_reference = ('Indexabs', 'texts'),
342
     allowed_classes = [index_abstract],
347
     allowed_classes = [index_abstract],
343
     default = None,
348
     default = None,
344
-    nullable = True)
349
+    nullable = True,
350
+    group = index_group)
345
 
351
 
346
 index_abstract.new_field( 'texts',
352
 index_abstract.new_field( 'texts',
347
     display_name = {
353
     display_name = {
349
         'fre': 'Texte contenant cette index'},
355
         'fre': 'Texte contenant cette index'},
350
     data_handler = 'list',
356
     data_handler = 'list',
351
     back_reference = ('Text', 'indexes'),
357
     back_reference = ('Text', 'indexes'),
352
-    allowed_classes = [text])
358
+    allowed_classes = [text],
359
+    group = index_group)
353
 
360
 
354
 index_theme = em.new_class(
361
 index_theme = em.new_class(
355
     'indexTheme',
362
     'indexTheme',
364
     'theme',
371
     'theme',
365
     display_name = {
372
     display_name = {
366
         'eng': 'theme'},
373
         'eng': 'theme'},
367
-    data_handler = 'varchar')
374
+    data_handler = 'varchar',
375
+    group = index_group)
368
 
376
 
369
 #em.save('xmlfile', filename = 'examples/em_test.xml')
377
 #em.save('xmlfile', filename = 'examples/em_test.xml')
370
 pickle_file = 'examples/em_test.pickle'
378
 pickle_file = 'examples/em_test.pickle'

BIN
examples/em_test.pickle View File


+ 1
- 1
install/conf.d/lodel2.ini View File

10
 context = True
10
 context = True
11
 
11
 
12
 [lodel2.editorialmodel]
12
 [lodel2.editorialmodel]
13
-groups = 
13
+groups = base_group, editorial_abstract, editorial_person
14
 emfile = editorial_model.pickle
14
 emfile = editorial_model.pickle
15
 dyncode = leapi_dyncode.py
15
 dyncode = leapi_dyncode.py
16
 
16
 

+ 8
- 2
install/loader.py View File

14
     print("Unable to load lodel module. exiting...")
14
     print("Unable to load lodel module. exiting...")
15
     exit(1)
15
     exit(1)
16
 
16
 
17
-
18
 #
17
 #
19
 # Loading settings
18
 # Loading settings
20
 #
19
 #
29
 
28
 
30
 def start():
29
 def start():
31
     #Load plugins
30
     #Load plugins
31
+    from lodel import logger
32
     from lodel.plugin import Plugin
32
     from lodel.plugin import Plugin
33
+    logger.debug("Loader.start() called")
33
     Plugin.load_all()
34
     Plugin.load_all()
34
 
35
 
35
     LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
36
     LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
36
 
37
 
37
 
38
 
38
 if __name__ == '__main__':
39
 if __name__ == '__main__':
39
-    start()
40
 
40
 
41
+    start()
41
     if Settings.runtest:
42
     if Settings.runtest:
43
+        start()
42
         import unittest
44
         import unittest
43
         import tests
45
         import tests
44
         loader = unittest.TestLoader()
46
         loader = unittest.TestLoader()
51
         runner.run(suite)
53
         runner.run(suite)
52
         exit()
54
         exit()
53
 
55
 
56
+    import lodel
57
+    import leapi_dyncode as dyncode
58
+    lodel.dyncode = dyncode
54
     LodelHook.call_hook('lodel2_loader_main', '__main__', None)
59
     LodelHook.call_hook('lodel2_loader_main', '__main__', None)
60
+
55
     #Run interative python
61
     #Run interative python
56
     import code
62
     import code
57
     print("""
63
     print("""

+ 2
- 0
lodel/__init__.py View File

1
 #-*- coding: utf-8 -*-
1
 #-*- coding: utf-8 -*-
2
 
2
 
3
+dyncode = None
4
+
3
 ##@page lodel2_start Lodel2 boot mechanism
5
 ##@page lodel2_start Lodel2 boot mechanism
4
 #
6
 #
5
 # @par Lodel2 boot sequence
7
 # @par Lodel2 boot sequence

+ 37
- 7
lodel/editorial_model/components.py View File

7
 
7
 
8
 from lodel.utils.mlstring import MlString
8
 from lodel.utils.mlstring import MlString
9
 
9
 
10
+from lodel.settings import Settings
10
 from lodel.editorial_model.exceptions import *
11
 from lodel.editorial_model.exceptions import *
11
 from lodel.leapi.leobject import CLASS_ID_FIELDNAME
12
 from lodel.leapi.leobject import CLASS_ID_FIELDNAME
12
 
13
 
84
         ##@brief Stores EmFields instances indexed by field uid
85
         ##@brief Stores EmFields instances indexed by field uid
85
         self.__fields = dict() 
86
         self.__fields = dict() 
86
         
87
         
88
+        self.group = group
89
+        if group is None:
90
+            warnings.warn("NO GROUP FOR EMCLASS %s" % uid)
91
+        else:
92
+            group.add_components([self])
93
+    
87
         #Adding common field
94
         #Adding common field
88
         if not self.abstract:
95
         if not self.abstract:
89
             self.new_field(
96
             self.new_field(
95
                     'eng': "Allow to create instance of the good class when\
102
                     'eng': "Allow to create instance of the good class when\
96
  fetching arbitrary datas from DB"},
103
  fetching arbitrary datas from DB"},
97
                 data_handler = 'LeobjectSubclassIdentifier',
104
                 data_handler = 'LeobjectSubclassIdentifier',
98
-                internal = True)
99
-    
105
+                internal = True,
106
+                group = group)
107
+
100
     ##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
108
     ##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
101
     # @todo use Settings.editorialmodel.groups to determine wich fields should be returned
109
     # @todo use Settings.editorialmodel.groups to determine wich fields should be returned
102
     @property
110
     @property
136
             return list(fields.values()) if uid is None else fields[uid]
144
             return list(fields.values()) if uid is None else fields[uid]
137
         except KeyError:
145
         except KeyError:
138
             raise EditorialModelError("No such EmField '%s'" % uid)
146
             raise EditorialModelError("No such EmField '%s'" % uid)
147
+    
148
+    ##@brief Keep in __fields only fields contained in active groups
149
+    def _set_active_fields(self, active_groups):
150
+        if not Settings.editorialmodel.editormode:
151
+            active_fields = []
152
+            for grp_name, agrp in active_groups.items():
153
+                active_fields += [ emc for emc in agrp.components()
154
+                    if isinstance(emc, EmField)]
155
+            self.__fields = { fname:fdh for fname, fdh in self.__fields.items()
156
+                if fdh in active_fields }
139
 
157
 
140
     ##@brief Add a field to the EmClass
158
     ##@brief Add a field to the EmClass
141
     # @param emfield EmField : an EmField instance
159
     # @param emfield EmField : an EmField instance
143
     # @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
161
     # @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
144
     # @todo End the override checks (needs methods in data_handlers)
162
     # @todo End the override checks (needs methods in data_handlers)
145
     def add_field(self, emfield):
163
     def add_field(self, emfield):
164
+        assert_edit()
146
         if emfield.uid in self.__fields:
165
         if emfield.uid in self.__fields:
147
             raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
166
             raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
148
         # Incomplete field override check
167
         # Incomplete field override check
151
             if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
170
             if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
152
                 raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
171
                 raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
153
         self.__fields[emfield.uid] = emfield
172
         self.__fields[emfield.uid] = emfield
154
-        emfield._emclass = self
155
         return emfield
173
         return emfield
156
     
174
     
157
     ##@brief Create a new EmField and add it to the EmClass
175
     ##@brief Create a new EmField and add it to the EmClass
159
     # @param uid str : the EmField uniq id
177
     # @param uid str : the EmField uniq id
160
     # @param **field_kwargs :  EmField constructor parameters ( see @ref EmField.__init__() ) 
178
     # @param **field_kwargs :  EmField constructor parameters ( see @ref EmField.__init__() ) 
161
     def new_field(self, uid, data_handler, **field_kwargs):
179
     def new_field(self, uid, data_handler, **field_kwargs):
162
-        return self.add_field(EmField(uid, data_handler, **field_kwargs))
180
+        assert_edit()
181
+        return self.add_field(EmField(uid, data_handler, self, **field_kwargs))
163
 
182
 
164
     def d_hash(self):
183
     def d_hash(self):
165
         m = hashlib.md5()
184
         m = hashlib.md5()
196
     # @param help_text MlString|str|dict : help text
215
     # @param help_text MlString|str|dict : help text
197
     # @param group EmGroup :
216
     # @param group EmGroup :
198
     # @param **handler_kwargs : data handler arguments
217
     # @param **handler_kwargs : data handler arguments
199
-    def __init__(self, uid, data_handler, display_name = None, help_text = None, group = None, **handler_kwargs):
218
+    def __init__(self, uid, data_handler, em_class = None, display_name = None, help_text = None, group = None, **handler_kwargs):
200
         from lodel.leapi.datahandlers.base_classes import DataHandler
219
         from lodel.leapi.datahandlers.base_classes import DataHandler
201
         super().__init__(uid, display_name, help_text, group)
220
         super().__init__(uid, display_name, help_text, group)
202
         ##@brief The data handler name
221
         ##@brief The data handler name
208
         ##@brief Stores data handler instanciation options
227
         ##@brief Stores data handler instanciation options
209
         self.data_handler_options = handler_kwargs
228
         self.data_handler_options = handler_kwargs
210
         ##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
229
         ##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
211
-        self._emclass = None
230
+        self._emclass = em_class
231
+        if self._emclass is None:
232
+            warnings.warn("No EmClass for field %s" %uid)
233
+        if group is None:
234
+            warnings.warn("No EmGroup for field  %s" % uid)
235
+        else:
236
+            group.add_components([self])
212
 
237
 
213
     ##@brief Returns data_handler_name attribute
238
     ##@brief Returns data_handler_name attribute
214
     def get_data_handler_name(self):
239
     def get_data_handler_name(self):
315
     ##@brief Add components in a group
340
     ##@brief Add components in a group
316
     # @param components list : EmComponent instances list
341
     # @param components list : EmComponent instances list
317
     def add_components(self, components):
342
     def add_components(self, components):
343
+        assert_edit()
318
         for component in components:
344
         for component in components:
319
             if isinstance(component, EmField):
345
             if isinstance(component, EmField):
320
                 if component._emclass is None:
346
                 if component._emclass is None:
321
-                    warnings.warn("Adding an orphan EmField to an EmGroup")
347
+                    msg = "Adding an orphan EmField '%s' to EmGroup '%s'"
348
+                    msg %= (component, self)
349
+                    warnings.warn(msg)
322
             elif not isinstance(component, EmClass):
350
             elif not isinstance(component, EmClass):
323
                 raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
351
                 raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
324
         self.__components |= set(components)
352
         self.__components |= set(components)
326
     ##@brief Add a dependencie
354
     ##@brief Add a dependencie
327
     # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
355
     # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
328
     def add_dependencie(self, grp):
356
     def add_dependencie(self, grp):
357
+        assert_edit()
329
         try:
358
         try:
330
             for group in grp:
359
             for group in grp:
331
                 self.add_dependencie(group)
360
                 self.add_dependencie(group)
343
     # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
372
     # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
344
     # Useless ???
373
     # Useless ???
345
     def add_applicant(self, grp):
374
     def add_applicant(self, grp):
375
+        assert_edit()
346
         try:
376
         try:
347
             for group in grp:
377
             for group in grp:
348
                 self.add_applicant(group)
378
                 self.add_applicant(group)

+ 9
- 0
lodel/editorial_model/exceptions.py View File

3
 class EditorialModelError(Exception):
3
 class EditorialModelError(Exception):
4
     pass
4
     pass
5
 
5
 
6
+
7
+def assert_edit():
8
+    try:
9
+        from lodel import Settings
10
+    except ImportError: #Very dirty, but don't know how to fix the tests
11
+        return
12
+    if not Settings.editorialmodel.editormode:
13
+        raise EditorialModelError("EM is readonly : editormode is OFF")
14
+

+ 16
- 7
lodel/editorial_model/model.py View File

5
 import copy
5
 import copy
6
 
6
 
7
 from lodel.utils.mlstring import MlString
7
 from lodel.utils.mlstring import MlString
8
-from lodel.logger import logger
8
+from lodel import logger
9
 from lodel.settings import Settings
9
 from lodel.settings import Settings
10
 from lodel.settings.utils import SettingsError
10
 from lodel.settings.utils import SettingsError
11
 
11
 
128
     #EditorialModel.__active_classes attibutes
128
     #EditorialModel.__active_classes attibutes
129
     def __set_actives(self):
129
     def __set_actives(self):
130
         if Settings.editorialmodel.editormode:
130
         if Settings.editorialmodel.editormode:
131
+            logger.warning("All EM groups active because editormode in ON")
131
             # all groups & classes actives because we are in editor mode
132
             # all groups & classes actives because we are in editor mode
132
             self.__active_groups = self.__groups
133
             self.__active_groups = self.__groups
133
             self.__active_classes = self.__classes
134
             self.__active_classes = self.__classes
134
         else:
135
         else:
135
             #determine groups first
136
             #determine groups first
136
             self.__active_groups = dict()
137
             self.__active_groups = dict()
138
+            self.__active_classes = dict()
137
             for agrp in Settings.editorialmodel.groups:
139
             for agrp in Settings.editorialmodel.groups:
138
                 if agrp not in self.__groups:
140
                 if agrp not in self.__groups:
139
                     raise SettingsError('Invalid group found in settings : %s' % agrp)
141
                     raise SettingsError('Invalid group found in settings : %s' % agrp)
142
+                logger.debug("Set group '%s' as active" % agrp)
140
                 grp = self.__groups[agrp]
143
                 grp = self.__groups[agrp]
141
                 self.__active_groups[grp.uid] = grp
144
                 self.__active_groups[grp.uid] = grp
142
-                for acls in grp.components():
145
+                for acls in [cls for cls in grp.components() if isinstance(cls, EmClass)]:
143
                     self.__active_classes[acls.uid] = acls
146
                     self.__active_classes[acls.uid] = acls
147
+            if len(self.__active_groups) == 0:
148
+                raise RuntimeError("No groups activated, abording...")
149
+            if len(self.__active_classes) == 0:
150
+                raise RuntimeError("No active class found. Abording")
151
+            for clsname, acls in self.__active_classes.items():
152
+                acls._set_active_fields(self.__active_groups)
144
     
153
     
145
     ##@brief EmField getter
154
     ##@brief EmField getter
146
     # @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
155
     # @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
167
     # @param emclass EmClass : the EmClass instance to add
176
     # @param emclass EmClass : the EmClass instance to add
168
     # @return emclass
177
     # @return emclass
169
     def add_class(self, emclass):
178
     def add_class(self, emclass):
170
-        self.raise_if_ro()
179
+        assert_edit()
171
         if not isinstance(emclass, EmClass):
180
         if not isinstance(emclass, EmClass):
172
             raise ValueError("<class EmClass> expected but got %s " % type(emclass))
181
             raise ValueError("<class EmClass> expected but got %s " % type(emclass))
173
         if emclass.uid in self.classes():
182
         if emclass.uid in self.classes():
179
     # @param emgroup EmGroup : the EmGroup instance to add
188
     # @param emgroup EmGroup : the EmGroup instance to add
180
     # @return emgroup
189
     # @return emgroup
181
     def add_group(self, emgroup):
190
     def add_group(self, emgroup):
182
-        self.raise_if_ro()
191
+        assert_edit()
183
         if not isinstance(emgroup, EmGroup):
192
         if not isinstance(emgroup, EmGroup):
184
             raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
193
             raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
185
         if emgroup.uid in self.groups():
194
         if emgroup.uid in self.groups():
192
     #@param **kwargs : EmClass constructor options ( 
201
     #@param **kwargs : EmClass constructor options ( 
193
     # see @ref lodel.editorial_model.component.EmClass.__init__() )
202
     # see @ref lodel.editorial_model.component.EmClass.__init__() )
194
     def new_class(self, uid, **kwargs):
203
     def new_class(self, uid, **kwargs):
195
-        self.raise_if_ro()
204
+        assert_edit()
196
         return self.add_class(EmClass(uid, **kwargs))
205
         return self.add_class(EmClass(uid, **kwargs))
197
     
206
     
198
     ##@brief Add a new EmGroup to the editorial model
207
     ##@brief Add a new EmGroup to the editorial model
200
     #@param *kwargs : EmGroup constructor keywords arguments (
209
     #@param *kwargs : EmGroup constructor keywords arguments (
201
     # see @ref lodel.editorial_model.component.EmGroup.__init__() )
210
     # see @ref lodel.editorial_model.component.EmGroup.__init__() )
202
     def new_group(self, uid, **kwargs):
211
     def new_group(self, uid, **kwargs):
203
-        self.raise_if_ro()
212
+        assert_edit()
204
         return self.add_group(EmGroup(uid, **kwargs))
213
         return self.add_group(EmGroup(uid, **kwargs))
205
 
214
 
206
     ##@brief Save a model
215
     ##@brief Save a model
207
     # @param translator module : The translator module to use
216
     # @param translator module : The translator module to use
208
     # @param **translator_args
217
     # @param **translator_args
209
     def save(self, translator, **translator_kwargs):
218
     def save(self, translator, **translator_kwargs):
210
-        self.raise_if_ro()
219
+        assert_edit()
211
         if isinstance(translator, str):
220
         if isinstance(translator, str):
212
             translator = self.translator_from_name(translator)
221
             translator = self.translator_from_name(translator)
213
         return translator.save(self, **translator_kwargs)
222
         return translator.save(self, **translator_kwargs)

+ 9
- 11
lodel/editorial_model/translator/xmlfile.py View File

316
         
316
         
317
     fields = elem.find('fields')
317
     fields = elem.find('fields')
318
     for field in fields:
318
     for field in fields:
319
-        emfield = load_field_xml(model, field)
319
+        emfield = load_field_xml(model, field, emclass)
320
         l_emfields = emclass.fields()
320
         l_emfields = emclass.fields()
321
         incls = False
321
         incls = False
322
         for emf in l_emfields:
322
         for emf in l_emfields:
329
     return emclass
329
     return emclass
330
     
330
     
331
 ##@brief Creates a EmField from a xml description
331
 ##@brief Creates a EmField from a xml description
332
-# @param elem : the element which represents the EmField
333
-# @param model  : the model which will contain the new field
334
-# @return a new EmField object
335
-def load_field_xml(model, elem):
332
+#@param elem : the element which represents the EmField
333
+#@param model  : the model which will contain the new field
334
+#@param emclass EmClass : the EmClass of the field
335
+#@return a new EmField object
336
+def load_field_xml(model, elem, emclass):
336
     uid = elem.find('uid').text
337
     uid = elem.find('uid').text
337
     if elem.find('display_name').text is None:
338
     if elem.find('display_name').text is None:
338
         name = None
339
         name = None
354
         group = None
355
         group = None
355
         
356
         
356
     dhdl = elem.find('datahandler_name')
357
     dhdl = elem.find('datahandler_name')
358
+    dhdl_opts = {}
357
     if dhdl.text is not None:
359
     if dhdl.text is not None:
358
         dhdl_opts = elem.find('datahandler_options')
360
         dhdl_opts = elem.find('datahandler_options')
359
-
360
         if dhdl_opts is not None:
361
         if dhdl_opts is not None:
361
             dhdl_options = load_dhdl_options_xml(model, dhdl_opts) 
362
             dhdl_options = load_dhdl_options_xml(model, dhdl_opts) 
362
-            emfield = EmField(uid, dhdl.text, name, help_text, group, **dhdl_options)
363
-        else:
364
-            emfield = EmField(uid, dhdl.text, name, help_text, group)
365
-    else:
366
-        emfield = EmField(uid, dhdl.text, name, help_text, group)
363
+    emfield = EmField(
364
+        uid, dhdl.text, emclass, name, help_text, group, **dhdl_options)
367
     
365
     
368
     return emfield
366
     return emfield
369
 
367
 

+ 6
- 3
lodel/leapi/datahandlers/base_classes.py View File

110
                 return data_handler.default
110
                 return data_handler.default
111
         elif data_handler is not None and data_handler.nullable:
111
         elif data_handler is not None and data_handler.nullable:
112
                 return None
112
                 return None
113
-
114
-        return RuntimeError("Unable to construct data for field %s", fname)
113
+        return cur_value
115
 
114
 
116
     ##@brief Check datas consistency
115
     ##@brief Check datas consistency
117
     # @param emcomponent EmComponent : An EmComponent child class instance
116
     # @param emcomponent EmComponent : An EmComponent child class instance
184
 # References are fields that stores a reference to another
183
 # References are fields that stores a reference to another
185
 # editorial object
184
 # editorial object
186
 class Reference(DataHandler):
185
 class Reference(DataHandler):
186
+    base_type="ref"
187
 
187
 
188
     ##@brief Instanciation
188
     ##@brief Instanciation
189
     # @param allowed_classes list | None : list of allowed em classes if None no restriction
189
     # @param allowed_classes list | None : list of allowed em classes if None no restriction
249
 ##@brief This class represent a data_handler for multiple references to another object
249
 ##@brief This class represent a data_handler for multiple references to another object
250
 #
250
 #
251
 # The fields using this data handlers are like SingleRef but can store multiple references in one field
251
 # The fields using this data handlers are like SingleRef but can store multiple references in one field
252
-# @note SQL implementation could be tricky
252
+# @note for the moment split on ',' chars
253
 class MultipleRef(Reference):
253
 class MultipleRef(Reference):
254
     
254
     
255
     ##
255
     ##
256
     # @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
256
     # @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
257
     def __init__(self, max_item = None, **kwargs):
257
     def __init__(self, max_item = None, **kwargs):
258
+        self.max_item = max_item
258
         super().__init__(**kwargs)
259
         super().__init__(**kwargs)
259
 
260
 
260
         
261
         
261
     def _check_data_value(self, value):
262
     def _check_data_value(self, value):
263
+        value = value.split(',')
262
         if self.max_item is not None:
264
         if self.max_item is not None:
263
             if self.max_item < len(value):
265
             if self.max_item < len(value):
264
                 return None, FieldValidationError("To many items")
266
                 return None, FieldValidationError("To many items")
267
+        return value, None
265
 
268
 
266
 ## @brief Class designed to handle datas access will fieldtypes are constructing datas
269
 ## @brief Class designed to handle datas access will fieldtypes are constructing datas
267
 #
270
 #

+ 3
- 3
lodel/leapi/datahandlers/datas.py View File

72
         kwargs['internal'] = 'automatic'
72
         kwargs['internal'] = 'automatic'
73
         super(self.__class__, self).__init__(primary_key = True, **kwargs)
73
         super(self.__class__, self).__init__(primary_key = True, **kwargs)
74
 
74
 
75
-    def _check_data_value(self, value):
76
-        return value, None
77
-
78
     def construct_data(self, emcomponent, fname, datas, cur_value):
75
     def construct_data(self, emcomponent, fname, datas, cur_value):
79
         if cur_value is None:
76
         if cur_value is None:
80
             #Ask datasource to provide a new uniqID
77
             #Ask datasource to provide a new uniqID
114
         super().__init__(
111
         super().__init__(
115
             format_string = format_string, field_list = field_list, **kwargs)
112
             format_string = format_string, field_list = field_list, **kwargs)
116
 
113
 
114
+class Password(Varchar):
115
+    help = 'Handle passwords'
116
+    pass

+ 4
- 6
lodel/leapi/datahandlers/references.py View File

20
     # @param value *
20
     # @param value *
21
     # @return tuple(value, exception)
21
     # @return tuple(value, exception)
22
     def _check_data_value(self, value):
22
     def _check_data_value(self, value):
23
-        val, expt = super()._check_data_value()
23
+        val, expt = super()._check_data_value(value)
24
         if not isinstance(expt, Exception):
24
         if not isinstance(expt, Exception):
25
             val = list(val)
25
             val = list(val)
26
-        val, expt = super()._check_data_value(value.values())
27
         return val, expt
26
         return val, expt
28
 
27
 
29
 
28
 
41
     # @param value *
40
     # @param value *
42
     # @return tuple(value, exception)
41
     # @return tuple(value, exception)
43
     def _check_data_value(self, value):
42
     def _check_data_value(self, value):
44
-        val, expt = super()._check_data_value()
43
+        val, expt = super()._check_data_value(value)
45
         if not isinstance(expt, Exception):
44
         if not isinstance(expt, Exception):
46
-            val = set(val)
47
-        val, expt = super()._check_data_value(value.values())
45
+            val = tuple(set(val))
48
         return val, expt
46
         return val, expt
49
 
47
 
50
 
48
 
62
     # @param value *
60
     # @param value *
63
     # @return tuple(value, exception)
61
     # @return tuple(value, exception)
64
     def _check_data_value(self, value):
62
     def _check_data_value(self, value):
63
+        val, expt = super()._check_data_value(value)
65
         if not isinstance(value, dict):
64
         if not isinstance(value, dict):
66
             return None, FieldValidationError("Values for dict fields should be dict")
65
             return None, FieldValidationError("Values for dict fields should be dict")
67
-        val, expt = super()._check_data_value(value.values())
68
         return (
66
         return (
69
                 None if isinstance(expt, Exception) else value,
67
                 None if isinstance(expt, Exception) else value,
70
                 expt)
68
                 expt)

+ 2
- 0
lodel/leapi/lefactory.py View File

4
 #from lodel.editorial_model.components import *
4
 #from lodel.editorial_model.components import *
5
 from lodel.leapi.leobject import LeObject
5
 from lodel.leapi.leobject import LeObject
6
 from lodel.leapi.datahandlers.base_classes import DataHandler
6
 from lodel.leapi.datahandlers.base_classes import DataHandler
7
+from lodel import logger
7
 
8
 
8
 ##@brief Generate python module code from a given model
9
 ##@brief Generate python module code from a given model
9
 # @param model lodel.editorial_model.model.EditorialModel
10
 # @param model lodel.editorial_model.model.EditorialModel
112
     bootstrap = ""
113
     bootstrap = ""
113
     # Generating field list for LeObjects generated from EmClass
114
     # Generating field list for LeObjects generated from EmClass
114
     for em_class in get_classes(model):
115
     for em_class in get_classes(model):
116
+        logger.info("Generating a dynamic class for %s" % em_class.uid)
115
         uid = list()        # List of fieldnames that are part of the EmClass primary key
117
         uid = list()        # List of fieldnames that are part of the EmClass primary key
116
         parents = list()    # List of parents EmClass
118
         parents = list()    # List of parents EmClass
117
         # Determine pk
119
         # Determine pk

+ 23
- 13
lodel/leapi/leobject.py View File

180
                                                             cls.__name__))
180
                                                             cls.__name__))
181
     ##@return A dict with fieldname as key and datahandler as instance
181
     ##@return A dict with fieldname as key and datahandler as instance
182
     @classmethod
182
     @classmethod
183
-    def fields(cls):
184
-        return copy.copy(cls._fields)
183
+    def fields(cls, include_ro = False):
184
+        if include_ro:
185
+            return copy.copy(cls._fields)
186
+        else:
187
+            return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
185
     
188
     
186
     ##@brief Return the list of parents classes
189
     ##@brief Return the list of parents classes
187
     #
190
     #
223
         uid_handlers = set( cls._fields[name] for name in cls._uid )
226
         uid_handlers = set( cls._fields[name] for name in cls._uid )
224
         for pcls in cls.hierarch()[1:]:
227
         for pcls in cls.hierarch()[1:]:
225
             puid_handlers = set(cls._fields[name] for name in pcls._uid)
228
             puid_handlers = set(cls._fields[name] for name in pcls._uid)
226
-            if set(pcls._uid) != set(pcls._uid) \
229
+            if set(pcls._uid) != set(prev._uid) \
227
                 or puid_handlers != uid_handlers:
230
                 or puid_handlers != uid_handlers:
228
                 break
231
                 break
229
             prev = pcls
232
             prev = pcls
242
             ro_ds, rw_ds = cls._datasource_name
245
             ro_ds, rw_ds = cls._datasource_name
243
         #Read only datasource initialisation
246
         #Read only datasource initialisation
244
         cls._ro_datasource = cls._init_datasource(ro_ds, True)
247
         cls._ro_datasource = cls._init_datasource(ro_ds, True)
245
-        log_msg = "Read only datasource %s initialized for LeObject %s"
246
-        log_msg %= (ro_ds, cls.__name__)
247
-        logger.debug(log_msg)
248
+        if cls._ro_datasource is None:
249
+            log_msg = "No read only datasource set for LeObject %s"
250
+            log_msg %= cls.__name__
251
+            logger.debug(log_msg)
252
+        else:
253
+            log_msg = "Read only datasource '%s' initialized for LeObject %s"
254
+            log_msg %= (ro_ds, cls.__name__)
255
+            logger.debug(log_msg)
248
         #Read write datasource initialisation
256
         #Read write datasource initialisation
249
         cls._rw_datasource = cls._init_datasource(rw_ds, False)
257
         cls._rw_datasource = cls._init_datasource(rw_ds, False)
250
-        log_msg = "Read&write only datasource %s initialized for LeObject %s"
251
-        log_msg %= (rw_ds, cls.__name__)
252
-        logger.debug(log_msg)
258
+        if cls._ro_datasource is None:
259
+            log_msg = "No read/write datasource set for LeObject %s"
260
+            log_msg %= cls.__name__
261
+            logger.debug(log_msg)
262
+        else:
263
+            log_msg = "Read/write datasource '%s' initialized for LeObject %s"
264
+            log_msg %= (ro_ds, cls.__name__)
265
+            logger.debug(log_msg)
253
         
266
         
254
 
267
 
255
     ##@brief Replace the _datasource attribute value by a datasource instance
268
     ##@brief Replace the _datasource attribute value by a datasource instance
586
         query_filter = list()
599
         query_filter = list()
587
         for uid in uids:
600
         for uid in uids:
588
             query_filter.append((uid, '=', self.data(uid)))
601
             query_filter.append((uid, '=', self.data(uid)))
589
-        
590
         try:
602
         try:
591
             query = LeUpdateQuery(self.__class__, query_filter)
603
             query = LeUpdateQuery(self.__class__, query_filter)
592
         except Exception as err:
604
         except Exception as err:
648
     #@return a list of items (lists of (fieldname, fieldvalue))
660
     #@return a list of items (lists of (fieldname, fieldvalue))
649
     @classmethod
661
     @classmethod
650
     def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
662
     def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
651
-        if field_list is None:
652
-            field_list = cls.fieldnames(True)
653
-        else:
663
+        if field_list is not None:
654
             for uid in [ uidname
664
             for uid in [ uidname
655
                 for uidname in cls.uid_fieldname()
665
                 for uidname in cls.uid_fieldname()
656
                 if uidname not in field_list ]:
666
                 if uidname not in field_list ]:

+ 25
- 16
lodel/leapi/query.py View File

188
                 other_ds_filters[cur_ds].append(
188
                 other_ds_filters[cur_ds].append(
189
                     ((rfield, ref_dict), op, value))
189
                     ((rfield, ref_dict), op, value))
190
         #deduplication of std filters
190
         #deduplication of std filters
191
-        filters_orig = list(set(filters_orig))
191
+        if not isinstance(filters_orig, set):
192
+            filters_orig = set(filters_orig)
193
+        filters_orig = list(filters_orig)
192
         # Sets _query_filter attribute of self query
194
         # Sets _query_filter attribute of self query
193
         self._query_filter = (filters_orig, result_rel_filters)
195
         self._query_filter = (filters_orig, result_rel_filters)
194
 
196
 
290
                 err_l[field] = ret
292
                 err_l[field] = ret
291
                 continue
293
                 continue
292
             field_datahandler = self._target_class.field(field)
294
             field_datahandler = self._target_class.field(field)
295
+            if isinstance(field_datahandler, Exception):
296
+                err_l[field] = error
297
+                continue
293
             if ref_field is not None and not field_datahandler.is_reference():
298
             if ref_field is not None and not field_datahandler.is_reference():
294
                 # inconsistency
299
                 # inconsistency
295
                 err_l[field] = NameError(   "The field '%s' in %s is not \
300
                 err_l[field] = NameError(   "The field '%s' in %s is not \
330
                 else:
335
                 else:
331
                     rel_filters.append((ret, operator, value))
336
                     rel_filters.append((ret, operator, value))
332
             else:
337
             else:
338
+                # Casting value given datahandler
339
+                value, error = field_datahandler._check_data_value(value)
333
                 res_filters.append((field,operator, value))
340
                 res_filters.append((field,operator, value))
334
         
341
         
335
         if len(err_l) > 0:
342
         if len(err_l) > 0:
336
-            raise LeApiDataCheckError(
343
+            raise LeApiDataCheckErrors(
337
                                         "Error while preparing filters : ",
344
                                         "Error while preparing filters : ",
338
                                         err_l)
345
                                         err_l)
339
         return (res_filters, rel_filters)
346
         return (res_filters, rel_filters)
517
             if target_class.initialized:
524
             if target_class.initialized:
518
                 self.__leobject_instance_datas = target.datas(True)
525
                 self.__leobject_instance_datas = target.datas(True)
519
             else:
526
             else:
520
-                query_filters = [(target._uid[0], '=', str(target.uid()))]
527
+                query_filters = [(target._uid[0], '=', target.uid())]
521
     
528
     
522
         super().__init__(target_class, query_filters)
529
         super().__init__(target_class, query_filters)
523
 
530
 
552
                 res_data.update(datas)
559
                 res_data.update(datas)
553
                 res_datas = self._target_class.prepare_datas(
560
                 res_datas = self._target_class.prepare_datas(
554
                     res_data, True, True)
561
                     res_data, True, True)
555
-                filters = [(uid_name, '=', str(res_data[uid_name]))]
562
+                filters = [(uid_name, '=', res_data[uid_name])]
556
                 res = self._rw_datasource.update(
563
                 res = self._rw_datasource.update(
557
                     self._target_class, filters, [],
564
                     self._target_class, filters, [],
558
                     res_datas)
565
                     res_datas)
658
     # @throw LeApiQueryError if unknown field given
665
     # @throw LeApiQueryError if unknown field given
659
     def set_field_list(self, field_list):
666
     def set_field_list(self, field_list):
660
         err_l = dict()
667
         err_l = dict()
661
-        for fieldname in field_list:
662
-            ret = self._check_field(self._target_class, fieldname)
663
-            if isinstance(ret, Exception):
664
-                msg = "No field named '%s' in %s"
665
-                msg %= (fieldname, self._target_class.__name__)
666
-                expt = NameError(msg)
667
-                err_l[fieldname] =  expt
668
-        if len(err_l) > 0:
669
-            msg = "Error while setting field_list in a get query"
670
-            raise LeApiQueryErrors(msg = msg, exceptions = err_l)
671
-        self._field_list = list(set(field_list))
668
+        if field_list is not None:
669
+            for fieldname in field_list:
670
+                ret = self._check_field(self._target_class, fieldname)
671
+                if isinstance(ret, Exception):
672
+                    msg = "No field named '%s' in %s"
673
+                    msg %= (fieldname, self._target_class.__name__)
674
+                    expt = NameError(msg)
675
+                    err_l[fieldname] =  expt
676
+            if len(err_l) > 0:
677
+                msg = "Error while setting field_list in a get query"
678
+                raise LeApiQueryErrors(msg = msg, exceptions = err_l)
679
+            self._field_list = list(set(field_list))
672
     
680
     
673
     ##@brief Execute the get query
681
     ##@brief Execute the get query
674
     def execute(self, datas = None):
682
     def execute(self, datas = None):
678
     # @returns a list containing the item(s)
686
     # @returns a list containing the item(s)
679
     def _query(self, datas = None):
687
     def _query(self, datas = None):
680
         # select datas corresponding to query_filter
688
         # select datas corresponding to query_filter
689
+        fl = list(self._field_list) if self._field_list is not None else None
681
         l_datas=self._ro_datasource.select( 
690
         l_datas=self._ro_datasource.select( 
682
             target = self._target_class,
691
             target = self._target_class,
683
-            field_list = list(self._field_list),
692
+            field_list = fl,
684
             filters = self._query_filter[0],
693
             filters = self._query_filter[0],
685
             relational_filters = self._query_filter[1],
694
             relational_filters = self._query_filter[1],
686
             order = self._order,
695
             order = self._order,

+ 11
- 9
lodel/plugin/plugins.py View File

7
 from importlib.machinery import SourceFileLoader, SourcelessFileLoader
7
 from importlib.machinery import SourceFileLoader, SourcelessFileLoader
8
 
8
 
9
 import plugins
9
 import plugins
10
+from .exceptions import *
10
 
11
 
11
 ## @package lodel.plugins Lodel2 plugins management
12
 ## @package lodel.plugins Lodel2 plugins management
12
 #
13
 #
25
 PLUGIN_DEPS_VARNAME = '__plugin_deps__'
26
 PLUGIN_DEPS_VARNAME = '__plugin_deps__'
26
 ACTIVATE_METHOD_NAME = '_activate'
27
 ACTIVATE_METHOD_NAME = '_activate'
27
 
28
 
28
-class PluginError(Exception):
29
-    pass
30
 
29
 
31
 ##@brief Handle plugins
30
 ##@brief Handle plugins
32
 #
31
 #
119
     #@throw PluginError if the filename was not valid
118
     #@throw PluginError if the filename was not valid
120
     def _import_from_init_var(self, varname):
119
     def _import_from_init_var(self, varname):
121
         # Read varname
120
         # Read varname
122
-        filename = getattr(self.module, varname)
121
+        try:
122
+            filename = getattr(self.module, varname)
123
+        except AttributeError:
124
+            msg = "Malformed plugin {plugin}. No {varname} found in __init__.py"
125
+            msg = msg.format(
126
+                plugin = self.name,
127
+                varname = LOADER_FILENAME_VARNAME)
128
+            raise PluginError(msg)
123
         #Path are not allowed
129
         #Path are not allowed
124
         if filename != os.path.basename(filename):
130
         if filename != os.path.basename(filename):
125
             msg = "Invalid {varname} content : '{fname}' for plugin {name}"
131
             msg = "Invalid {varname} content : '{fname}' for plugin {name}"
213
         #Loading the plugin
219
         #Loading the plugin
214
         try:
220
         try:
215
             self.__loader_module = self._import_from_init_var(LOADER_FILENAME_VARNAME)
221
             self.__loader_module = self._import_from_init_var(LOADER_FILENAME_VARNAME)
216
-        except AttributeError:
217
-            msg = "Malformed plugin {plugin}. No {varname} found in __init__.py"
218
-            msg = msg.format(
219
-                plugin = self.name,
220
-                varname = LOADER_FILENAME_VARNAME)
221
-            raise PluginError(msg)
222
+        except PluginError as e:
223
+            raise e
222
         except ImportError as e:
224
         except ImportError as e:
223
             msg = "Broken plugin {plugin} : {expt}"
225
             msg = "Broken plugin {plugin} : {expt}"
224
             msg = msg.format(
226
             msg = msg.format(

+ 1
- 1
plugins/mongodb_datasource/confspec.py View File

4
 
4
 
5
 CONFSPEC = {
5
 CONFSPEC = {
6
     'lodel2.datasource.mongodb_datasource.*':{
6
     'lodel2.datasource.mongodb_datasource.*':{
7
-        'read_only': (True, SettingValidator('bool')),
7
+        'read_only': (False, SettingValidator('bool')),
8
         'host': ('localhost', SettingValidator('host')),
8
         'host': ('localhost', SettingValidator('host')),
9
         'port': (None, SettingValidator('string')),
9
         'port': (None, SettingValidator('string')),
10
         'db_name':('lodel', SettingValidator('string')),
10
         'db_name':('lodel', SettingValidator('string')),

+ 45
- 18
plugins/mongodb_datasource/datasource.py View File

13
 from lodel.leapi.leobject import CLASS_ID_FIELDNAME
13
 from lodel.leapi.leobject import CLASS_ID_FIELDNAME
14
 
14
 
15
 from . import utils
15
 from . import utils
16
-from .utils import object_collection_name,\
17
-    MONGODB_SORT_OPERATORS_MAP, connection_string
16
+from .utils import object_collection_name, collection_name, \
17
+    MONGODB_SORT_OPERATORS_MAP, connection_string, mongo_fieldname
18
 
18
 
19
 class MongoDbDataSourceError(Exception):
19
 class MongoDbDataSourceError(Exception):
20
     pass
20
     pass
75
     #@warning multiple UID broken by this method
75
     #@warning multiple UID broken by this method
76
     #@return an integer
76
     #@return an integer
77
     def new_numeric_id(self, emcomp):
77
     def new_numeric_id(self, emcomp):
78
-        target = emcomp.uid_source()
78
+        target = emcomp #.uid_source()
79
         tuid = target._uid[0] # Multiple UID broken here
79
         tuid = target._uid[0] # Multiple UID broken here
80
         results = self.select(
80
         results = self.select(
81
             target, field_list = [tuid], filters = [], 
81
             target, field_list = [tuid], filters = [], 
130
 
130
 
131
         query_filters = self.__process_filters(
131
         query_filters = self.__process_filters(
132
             target, filters, relational_filters)
132
             target, filters, relational_filters)
133
+        
133
         query_result_ordering = None
134
         query_result_ordering = None
134
         if order is not None:
135
         if order is not None:
135
             query_result_ordering = utils.parse_query_order(order)
136
             query_result_ordering = utils.parse_query_order(order)
136
-        results_field_list = None if len(field_list) == 0 else field_list
137
-        limit = limit if limit is not None else 0
138
-
137
+        
139
         if group is None:
138
         if group is None:
139
+            if field_list is None:
140
+                field_list = dict()
141
+            else:
142
+                f_list=dict()
143
+                for fl in field_list:
144
+                    f_list[fl] = 1
145
+                field_list = f_list
146
+            field_list['_id'] = 0
140
             cursor = collection.find(
147
             cursor = collection.find(
141
-                filter=query_filters, projection=results_field_list,
142
-                skip=offset, limit=limit, sort=query_result_ordering)
148
+                spec = query_filters,
149
+                fields=field_list,
150
+                skip=offset,
151
+                limit=limit if limit != None else 0,
152
+                sort=query_result_ordering)
143
         else:
153
         else:
144
             pipeline = list()
154
             pipeline = list()
145
             unwinding_list = list()
155
             unwinding_list = list()
156
             sorting_list.extends(query_result_ordering)
166
             sorting_list.extends(query_result_ordering)
157
 
167
 
158
             pipeline.append({'$match': query_filters})
168
             pipeline.append({'$match': query_filters})
159
-            if results_field_list is not None:
169
+            if field_list is not None:
160
                 pipeline.append({
170
                 pipeline.append({
161
                     '$project': SON([{field_name: 1}
171
                     '$project': SON([{field_name: 1}
162
                     for field_name in field_list])})
172
                     for field_name in field_list])})
180
     #@param relational_filters list : List of relational filters
190
     #@param relational_filters list : List of relational filters
181
     #@return int : number of deleted records
191
     #@return int : number of deleted records
182
     def delete(self, target, filters, relational_filters):
192
     def delete(self, target, filters, relational_filters):
183
-        if target.is_asbtract():
193
+        if target.is_abstract():
184
             #Deletion with abstract LeObject as target (reccursiv calls)
194
             #Deletion with abstract LeObject as target (reccursiv calls)
185
             return self.__act_on_abstract(target, filters,
195
             return self.__act_on_abstract(target, filters,
186
                 relational_filters, self.delete)
196
                 relational_filters, self.delete)
197
     #@param upd_datas dict : datas to update (new values)
207
     #@param upd_datas dict : datas to update (new values)
198
     #@return int : Number of updated records
208
     #@return int : Number of updated records
199
     def update(self, target, filters, relational_filters, upd_datas):
209
     def update(self, target, filters, relational_filters, upd_datas):
200
-        if target.is_asbtract():
210
+        if target.is_abstract():
201
             #Update using abstract LeObject as target (reccursiv calls)
211
             #Update using abstract LeObject as target (reccursiv calls)
202
             return self.__act_on_abstract(target, filters,
212
             return self.__act_on_abstract(target, filters,
203
                 relational_filters, self.update, upd_datas = upd_datas)
213
                 relational_filters, self.update, upd_datas = upd_datas)
204
         #Non abstract beahavior
214
         #Non abstract beahavior
205
         mongo_filters = self.__process_filters(
215
         mongo_filters = self.__process_filters(
206
             target, filters, relational_filters)
216
             target, filters, relational_filters)
207
-        res = self.__collection(target).update_many(mongo_filters, upd_datas)
208
-        return res.modified_count()
217
+        res = self.__collection(target).update(mongo_filters, upd_datas)
218
+        return res['n']
209
 
219
 
210
     ## @brief Inserts a record in a given collection
220
     ## @brief Inserts a record in a given collection
211
     # @param target Emclass : class of the object to insert
221
     # @param target Emclass : class of the object to insert
252
                         fname, op, val))
262
                         fname, op, val))
253
                     del(new_filters[i])
263
                     del(new_filters[i])
254
             new_filters.append(
264
             new_filters.append(
255
-                (CLASS_ID_FIELDNAME, '=', target_child.__name__))
265
+                (CLASS_ID_FIELDNAME, '=',
266
+                    collection_name(target_child.__name__)))
256
             result += act(
267
             result += act(
257
                 target = target_child,
268
                 target = target_child,
258
                 filters = new_filters,
269
                 filters = new_filters,
348
             if '$in' in res[fname]:
359
             if '$in' in res[fname]:
349
                 #WARNING we allready have a IN on this field, doing dedup
360
                 #WARNING we allready have a IN on this field, doing dedup
350
                 #from result
361
                 #from result
351
-                deduped = set(res[fname]['$in']) & subq
362
+                deduped = set(res[fname]['$in']) & subq_results
352
                 if len(deduped) == 0:
363
                 if len(deduped) == 0:
353
                     del(res[fname]['$in'])
364
                     del(res[fname]['$in'])
354
                 else:
365
                 else:
419
                 #here we are filling a dict with leobject as index but
430
                 #here we are filling a dict with leobject as index but
420
                 #we are doing a UNIQ on collection name
431
                 #we are doing a UNIQ on collection name
421
                 cur_collname = object_collection_name(leobject)
432
                 cur_collname = object_collection_name(leobject)
422
-                if cur_collname not in collnames:
423
-                    leo_collname[cur_collame] = leobject
433
+                if cur_collname not in leo_collname:
434
+                    leo_collname[cur_collname] = leobject
424
                     rfilters[fname][leobject] = dict()
435
                     rfilters[fname][leobject] = dict()
425
                 #Fecthing the collection's representative leobject
436
                 #Fecthing the collection's representative leobject
426
                 repr_leo = leo_collname[cur_collname]
437
                 repr_leo = leo_collname[cur_collname]
436
     @classmethod
447
     @classmethod
437
     def __filters2mongo(cls, filters):
448
     def __filters2mongo(cls, filters):
438
         res = dict()
449
         res = dict()
450
+        eq_fieldname = [] #Stores field with equal comparison OP
439
         for fieldname, op, value in filters:
451
         for fieldname, op, value in filters:
440
             oop = op
452
             oop = op
441
             ovalue = value
453
             ovalue = value
442
             op, value = cls.__op_value_conv(op, value)
454
             op, value = cls.__op_value_conv(op, value)
455
+            if op == '=':
456
+                eq_fieldname.append(fieldname)
457
+                if fieldname in res:
458
+                    logger.warning("Dropping previous condition. Overwritten \
459
+by an equality filter")
460
+                res[fieldname] = value
461
+                continue
462
+            if fieldname in eq_fieldname:
463
+                logger.warning("Dropping condition : '%s %s %s'" % (
464
+                    fieldname, op, value))
465
+                continue
466
+
443
             if fieldname not in res:
467
             if fieldname not in res:
444
                 res[fieldname] = dict()
468
                 res[fieldname] = dict()
445
             if op in res[fieldname]:
469
             if op in res[fieldname]:
446
                 logger.warning("Dropping condition : '%s %s %s'" % (
470
                 logger.warning("Dropping condition : '%s %s %s'" % (
447
                     fieldname, op, value))
471
                     fieldname, op, value))
448
             else:
472
             else:
449
-                res[fieldname][op] = value
473
+                if op not in cls.lodel2mongo_op_map:
474
+                    raise ValueError("Invalid operator : '%s'" % op)
475
+                new_op = cls.lodel2mongo_op_map[op]
476
+                res[fieldname][new_op] = value
450
         return res
477
         return res
451
 
478
 
452
 
479
 

+ 3
- 0
plugins/mongodb_datasource/utils.py View File

69
 def object_collection_name(class_object):
69
 def object_collection_name(class_object):
70
     return class_object.__name__
70
     return class_object.__name__
71
 
71
 
72
+def collection_name(class_name):
73
+    return class_name
74
+
72
 ## @brief Determine a collection field name given a lodel2 fieldname
75
 ## @brief Determine a collection field name given a lodel2 fieldname
73
 # @note For the moment this method only return the argument but EVERYWHERE
76
 # @note For the moment this method only return the argument but EVERYWHERE
74
 # in the datasource we should use this method to gather proper fieldnames
77
 # in the datasource we should use this method to gather proper fieldnames

+ 3
- 0
plugins/webui/confspec.py View File

8
                             SettingValidator('dummy')),
8
                             SettingValidator('dummy')),
9
         'listen_port': (    '9090',
9
         'listen_port': (    '9090',
10
                             SettingValidator('int')),
10
                             SettingValidator('int')),
11
+        'virtualenv': ('',
12
+                       SettingValidator('path')),
13
+        'uwsgicmd': ('uwsgi_python3', SettingValidator('dummy')),
11
     },
14
     },
12
     'lodel2.webui.sessions': {
15
     'lodel2.webui.sessions': {
13
         'directory': (  '/tmp/lodel2_session',
16
         'directory': (  '/tmp/lodel2_session',

+ 51
- 0
plugins/webui/exceptions.py View File

1
+#-*- coding: utf-8 -*-
2
+
3
+from werkzeug.wrappers import Response
4
+
5
+class HttpException(Exception):
6
+
7
+    STATUS_STR = {
8
+        4:{
9
+            400: 'Bad request',
10
+            401: 'Unauthorized',
11
+            402: 'Payment required',
12
+            403: 'Forbidden',
13
+            404: 'Not found',
14
+            418: 'I\'m a teapot', #RFC 2324
15
+        },
16
+        5:{
17
+            500: 'Internal server error',
18
+            501: 'Not implemented',
19
+        },
20
+    }
21
+
22
+    def __init__(self, status_code = 500, tpl = 'error.html', custom = None):
23
+        self.status_code = status_code
24
+        self.tpl = tpl
25
+        self.custom = custom
26
+
27
+    def render(self, request):
28
+        from .interface.template.loader import TemplateLoader
29
+        loader = TemplateLoader()
30
+        tpl_vars = {
31
+            'status_code': self.status_code,
32
+            'status_str': self.status_str(self.status_code),
33
+            'custom': self.custom }
34
+        response = Response(
35
+            loader.render_to_response(self.tpl, template_vars = tpl_vars),
36
+            mimetype = 'text/html')
37
+        response.status_code = self.status_code
38
+        return response
39
+
40
+    @staticmethod
41
+    def status_str(status_code):
42
+        status_fam = status_code / 100
43
+        if status_fam not in HttpException.STATUS_STR or \
44
+            status_code not in HttpException.STATUS_STR[status_fam]:
45
+            return 'Unknown'
46
+        else:
47
+            return HttpException.STATUS_STR[status_fam][status_code]
48
+
49
+
50
+
51
+        

+ 3
- 0
plugins/webui/interface/controllers/__init__.py View File

1
 from .base import *
1
 from .base import *
2
 from .admin import *
2
 from .admin import *
3
+from .document import *
4
+from .listing import *
5
+

+ 130
- 0
plugins/webui/interface/controllers/admin.py View File

1
+from ...exceptions import *
2
+from .base import get_response
1
 
3
 
4
+from lodel.leapi.exceptions import *
5
+from lodel import logger
6
+
7
+import leapi_dyncode as dyncode
8
+import warnings
9
+from lodel import logger
10
+
11
+def index_admin(request):
12
+    return get_response('admin/admin.html')
13
+
14
+def admin_update(request):
15
+    msg=''
16
+    if request.method == 'POST':
17
+
18
+        error = None
19
+        datas = list()
20
+        classname = request.form['classname']
21
+        uid = request.form['uid']
22
+        try:
23
+            target_leo = dyncode.Object.name2class(classname)
24
+        except LeApiError:
25
+            classname = None
26
+        if classname is None or target_leo.is_abstract():
27
+            raise HttpException(400)
28
+        fieldnames = target_leo.fieldnames()
29
+        fields = dict()
30
+
31
+        for in_put, in_value in request.form.items():
32
+            if in_put != 'classname' and  in_put != 'uid':
33
+                fields[in_put[12:]] = in_value
34
+        obj = (target_leo.get(('lodel_id = %s' % (uid))))[0]
35
+        inserted = obj.update(fields)
36
+        
37
+        if inserted==1:
38
+            msg = 'Successfully updated';
39
+        else:
40
+            msg = 'Oops something wrong happened...object not saved'
41
+        return get_response('admin/admin_edit.html', target=target_leo, lodel_id = uid, msg = msg)
42
+
43
+    test_valid = 'lodel_id' in request.GET \
44
+        and len(request.GET['lodel_id']) == 1
45
+
46
+    if test_valid:
47
+        try:
48
+            lodel_id = int(request.GET['lodel_id'][0])
49
+        except (ValueError, TypeError):
50
+            test_valid = False
51
+
52
+    if not test_valid:
53
+        raise HttpException(400)
54
+    else:
55
+        obj = dyncode.Object.get(['lodel_id = %d' % lodel_id])
56
+        if len(obj) == 0:
57
+            raise HttpException(404)
58
+    if 'classname' in request.GET:
59
+        classname = request.GET['classname']
60
+        if len(classname) > 1:
61
+            raise HttpException(400)
62
+        classname = classname[0]
63
+        try:
64
+            target_leo = dyncode.Object.name2class(classname)
65
+        except LeApiError:
66
+            classname = None
67
+
68
+    return get_response('admin/admin_edit.html', target=target_leo, lodel_id =lodel_id)
69
+
70
+def admin_create(request):
71
+    classname = None
72
+
73
+    if request.method == 'POST':
74
+        error = None
75
+        datas = list()
76
+        classname = request.form['classname']
77
+        try:
78
+            target_leo = dyncode.Object.name2class(classname)
79
+        except LeApiError:
80
+            classname = None
81
+        if classname is None or target_leo.is_abstract():
82
+            raise HttpException(400)
83
+        fieldnames = target_leo.fieldnames()
84
+        fields = dict()
85
+
86
+        for in_put, in_value in request.form.items():
87
+            if in_put != 'classname':
88
+                fields[in_put[12:]] = in_value
89
+        new_uid = target_leo.insert(fields)
90
+        
91
+        if not new_uid is None:
92
+            msg = 'Successfull creation';
93
+        else:
94
+            msg = 'Oops something wrong happened...object not saved'
95
+        return get_response('admin/admin_create.html', target=target_leo, msg = msg)
96
+    
97
+    if 'classname' in request.GET:
98
+        classname = request.GET['classname']
99
+        if len(classname) > 1:
100
+            raise HttpException(400)
101
+        classname = classname[0]
102
+        try:
103
+            target_leo = dyncode.Object.name2class(classname)
104
+        except LeApiError:
105
+            classname = None
106
+    msg = None
107
+    if 'msg' in request.GET:
108
+        msg = request.GET['msg']
109
+    if classname is None or target_leo.is_abstract():
110
+        raise HttpException(400)
111
+    return get_response('admin/admin_create.html', target=target_leo)
112
+
113
+def admin_classes(request):
114
+    return get_response('admin/list_classes_admin.html', my_classes = dyncode.dynclasses)
115
+
116
+def admin_class(request):
117
+    if 'classname' in request.GET:
118
+        classname = request.GET['classname']
119
+        if len(classname) > 1:
120
+            raise HttpException(400)
121
+        classname = classname[0]
122
+        try:
123
+            target_leo = dyncode.Object.name2class(classname)
124
+        except LeApiError:
125
+            classname = None
126
+    if classname is None or target_leo.is_abstract():
127
+        raise HttpException(400)
128
+    return get_response('admin/show_class_admin.html', target=target_leo)
129
+   
2
 def admin(request):
130
 def admin(request):
3
     return get_response('admin/admin.html')
131
     return get_response('admin/admin.html')
4
 
132
 
133
+        
134
+            
5
 
135
 

+ 28
- 10
plugins/webui/interface/controllers/base.py View File

5
 
5
 
6
 # This module contains the web UI controllers that will be called from the web ui class
6
 # This module contains the web UI controllers that will be called from the web ui class
7
 
7
 
8
-
9
-def get_response(tpl, mimetype='text/html', status_code=200):
8
+##@brief Render a template and return a respone
9
+#@param tpl str : template relativ path
10
+#@param tpl_vars : templates variables (obsolete)
11
+#@param mimetype
12
+#@param status_code
13
+#@param **kwargs : new version of tpl_vars
14
+#@return a response...
15
+def get_response(tpl='empty.html', tpl_vars={}, mimetype='text/html', status_code=200, **kwargs):
16
+    tpl_vars.update(kwargs)
10
     loader = TemplateLoader()
17
     loader = TemplateLoader()
11
-    response = Response(loader.render_to_response(tpl), mimetype=mimetype)
18
+    response = Response(loader.render_to_response(tpl, template_vars=tpl_vars), mimetype=mimetype)
12
     response.status_code = status_code
19
     response.status_code = status_code
13
     return response
20
     return response
14
 
21
 
22
+## @brief gets the html template corresponding to a given component type
23
+# @param type str : name of the component type
24
+# @param params dict : extra parameters to customize the template
25
+def get_component_html(type='text', params={}):
26
+    params['type'] = type
27
+    template_loader = TemplateLoader()
28
+    return template_loader.render_to_html(template_file='components/components.html', template_vars=params)
15
 
29
 
16
 def index(request):
30
 def index(request):
17
     return get_response('index/index.html')
31
     return get_response('index/index.html')
22
 
36
 
23
 
37
 
24
 def test(request):
38
 def test(request):
25
-    return get_response('test.html')
26
-
27
-
28
-def list_classes(request):
29
-    # TODO Add the method to get the classes list
30
-
31
-    return get_response('list_classes.html')
39
+    if 'id' not in request.url_args:
40
+        id = None
41
+    else:
42
+        id = request.url_args['id']
43
+
44
+    template_vars = {
45
+        'id': id,
46
+        'params': request.GET
47
+    }
48
+    return get_response('test.html', tpl_vars=template_vars)
49
+    

+ 6
- 0
plugins/webui/interface/controllers/document.py View File

1
+from .base import get_response
2
+
3
+
4
+def show_document(request):
5
+    template_vars = {'id': request.url_args['id']}
6
+    return get_response('documents/show.html', tpl_vars=template_vars)

+ 19
- 0
plugins/webui/interface/controllers/listing.py View File

1
+# -*- coding: utf-8 -*-
2
+from .base import get_response
3
+import leapi_dyncode as dyncode
4
+
5
+def list_classes(request):
6
+    template_vars = {'my_classes': dyncode.dynclasses}
7
+    return get_response('listing/list_classes.html', tpl_vars=template_vars)
8
+
9
+def show_class(request):
10
+    template_vars = {
11
+        'params': request.GET
12
+    }
13
+    return get_response('listing/show_class.html', tpl_vars=template_vars)
14
+
15
+def show_object(request):
16
+    template_vars = {
17
+        'params': request.GET
18
+    }
19
+    return get_response('listing/show_object.html', tpl_vars=template_vars)

+ 9
- 12
plugins/webui/interface/router.py View File

3
 
3
 
4
 from .controllers import *
4
 from .controllers import *
5
 from .urls import urls
5
 from .urls import urls
6
+from ..main import root_url
6
 from lodel.settings import Settings
7
 from lodel.settings import Settings
7
 
8
 
8
-
9
 def format_url_rule(url_rule):
9
 def format_url_rule(url_rule):
10
-    if url_rule == '^$':
11
-        return "^%s$" % Settings.sitename
12
-
13
-    formatted_rule = ''
14
     if url_rule.startswith('^'):
10
     if url_rule.startswith('^'):
15
-        formatted_rule += "^"
16
-
17
-    formatted_rule += "%s/%s" % (Settings.sitename, url_rule)
18
-    return formatted_rule
11
+        res = url_rule.replace('^', '^'+root_url())
12
+    else:
13
+        res = root_url()+'.*'+url_rule
14
+    return res
19
 
15
 
20
 
16
 
21
 def get_controller(request):
17
 def get_controller(request):
26
 
22
 
27
     # Returning the right controller to call
23
     # Returning the right controller to call
28
     for regex, callback in url_rules:
24
     for regex, callback in url_rules:
29
-        match = re.search(regex, request.PATH)
30
-        if match is not None:
31
-            request.url_args = match.groups()
25
+        p = re.compile(regex)
26
+        m = p.search(request.PATH)
27
+        if m is not None:
28
+            request.url_args = m.groupdict()
32
             return callback
29
             return callback
33
 
30
 
34
     return not_found
31
     return not_found

+ 5
- 0
plugins/webui/interface/template/api/api_lodel_templates.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 
2
 
3
 # Lodel 2 templates API : loaded by default
3
 # Lodel 2 templates API : loaded by default
4
+
5
+class Test(object):
6
+
7
+    def ok(self):
8
+        return 'ok'

+ 9
- 1
plugins/webui/interface/template/loader.py View File

2
 import jinja2
2
 import jinja2
3
 import os
3
 import os
4
 
4
 
5
-import settings
5
+from lodel.settings import Settings
6
+import leapi_dyncode
7
+
6
 from .api import api_lodel_templates
8
 from .api import api_lodel_templates
7
 from .exceptions.not_allowed_custom_api_key_error import NotAllowedCustomAPIKeyError
9
 from .exceptions.not_allowed_custom_api_key_error import NotAllowedCustomAPIKeyError
10
+from ...main import root_url
8
 
11
 
9
 from ...main import PLUGIN_PATH
12
 from ...main import PLUGIN_PATH
10
 TEMPLATE_PATH = os.path.realpath(os.path.join(PLUGIN_PATH, 'templates/'))
13
 TEMPLATE_PATH = os.path.realpath(os.path.join(PLUGIN_PATH, 'templates/'))
39
         # lodel2 default api is loaded
42
         # lodel2 default api is loaded
40
         # TODO change this if needed
43
         # TODO change this if needed
41
         template.globals['lodel'] = api_lodel_templates
44
         template.globals['lodel'] = api_lodel_templates
45
+        template.globals['leapi'] = leapi_dyncode
46
+        template.globals['settings'] = Settings
47
+        template.globals['url'] = lambda sufix='': root_url()\
48
+            + ('' if sufix.startswith('/') else '/')\
49
+            + sufix
42
 
50
 
43
         # Extra modules are loaded
51
         # Extra modules are loaded
44
         if template_extra is not None:
52
         if template_extra is not None:

+ 15
- 5
plugins/webui/interface/urls.py View File

1
+# -*- coding: utf-8 -*-
1
 from .controllers import *
2
 from .controllers import *
2
 
3
 
3
 urls = (
4
 urls = (
4
-    (r'^$', index),
5
-    (r'admin/?$', admin),
6
-    (r'admin/(.+)$', admin),
7
-    (r'test/(.+)$', test),
8
-    (r'test/?$', test)
5
+    (r'^/?$', index),
6
+    (r'^/admin/?$', admin),
7
+    (r'^/admin/create$', admin_create),
8
+    (r'^/admin/update$', admin_update),
9
+    (r'^/admin/classes_admin$', admin_classes),
10
+    (r'^/admin/class_admin$', admin_class),
11
+    (r'/test/(?P<id>.*)$', test),
12
+    (r'^/test/?$', test),
13
+    #(r'/show/(?P<id>.*)$', show_document),
14
+    (r'^/list_classes', list_classes),
15
+    #(r'^/show_object/(.+)$', show_object),
16
+    (r'^/show_object?$', show_object),
17
+    #(r'^/show_class/(.+)$', show_class),
18
+    (r'^/show_class?$', show_class)
9
 )
19
 )

+ 15
- 2
plugins/webui/main.py View File

6
 
6
 
7
 PLUGIN_PATH = os.path.dirname(__file__)
7
 PLUGIN_PATH = os.path.dirname(__file__)
8
 
8
 
9
+##@brief Return the root url of the instance
10
+#@warning no trailing slash
11
+def root_url():
12
+    return Settings.sitename
13
+
14
+
9
 ##@brief uwsgi startup demo
15
 ##@brief uwsgi startup demo
10
 @LodelHook('lodel2_loader_main')
16
 @LodelHook('lodel2_loader_main')
11
 def uwsgi_fork(hook_name, caller, payload):
17
 def uwsgi_fork(hook_name, caller, payload):
18
+    from lodel.plugin.plugins import Plugin
19
+    Plugin.from_name('users')
20
+
12
     if Settings.webui.standalone:
21
     if Settings.webui.standalone:
13
-        cmd='uwsgi_python3 --http-socket {addr}:{port} --module plugins.webui.run'
22
+        cmd='{uwsgi} --http-socket {addr}:{port} --module plugins.webui.run'
14
         cmd = cmd.format(
23
         cmd = cmd.format(
15
                     addr = Settings.webui.listen_address,
24
                     addr = Settings.webui.listen_address,
16
-                    port = Settings.webui.listen_port)
25
+                    port = Settings.webui.listen_port,
26
+                    uwsgi= Settings.webui.uwsgicmd)
27
+        if Settings.webui.virtualenv != '':
28
+            cmd += " --virtualenv %s" % Settings.webui.virtualenv
29
+
17
         exit(os.system(cmd))
30
         exit(os.system(cmd))

+ 22
- 5
plugins/webui/run.py View File

3
 
3
 
4
 import os
4
 import os
5
 from werkzeug.contrib.sessions import FilesystemSessionStore
5
 from werkzeug.contrib.sessions import FilesystemSessionStore
6
+from werkzeug.wrappers import Response
6
 
7
 
7
 from lodel.settings import Settings
8
 from lodel.settings import Settings
8
 from .interface.router import get_controller
9
 from .interface.router import get_controller
9
 from .interface.lodelrequest import LodelRequest
10
 from .interface.lodelrequest import LodelRequest
11
+from .exceptions import *
10
 from lodel.utils.datetime import get_utc_timestamp
12
 from lodel.utils.datetime import get_utc_timestamp
13
+from lodel.plugin.hooks import LodelHook
11
 
14
 
12
 SESSION_FILES_BASE_DIR = Settings.webui.sessions.directory
15
 SESSION_FILES_BASE_DIR = Settings.webui.sessions.directory
13
 SESSION_FILES_TEMPLATE = Settings.webui.sessions.file_template
16
 SESSION_FILES_TEMPLATE = Settings.webui.sessions.file_template
15
 
18
 
16
 session_store = FilesystemSessionStore(path=SESSION_FILES_BASE_DIR, filename_template=SESSION_FILES_TEMPLATE)
19
 session_store = FilesystemSessionStore(path=SESSION_FILES_BASE_DIR, filename_template=SESSION_FILES_TEMPLATE)
17
 
20
 
21
+#Starting instance
22
+loader.start()
18
 
23
 
19
 # TODO déplacer dans un module "sessions.py"
24
 # TODO déplacer dans un module "sessions.py"
20
 def delete_old_session_files(timestamp_now):
25
 def delete_old_session_files(timestamp_now):
53
             request.session = session_store.new()
58
             request.session = session_store.new()
54
             request.session['user_context'] = None
59
             request.session['user_context'] = None
55
         request.session['last_accessed'] = current_timestamp
60
         request.session['last_accessed'] = current_timestamp
56
-
57
-    controller = get_controller(request)
58
-    response = controller(request)
61
+    
62
+    try:
63
+        controller = get_controller(request)
64
+        response = controller(request)
65
+    except HttpException as e:
66
+        try:
67
+            response = e.render(request)
68
+        except Exception as eb:
69
+            res = Response()
70
+            res.status_code = 500
71
+            return res
72
+        
73
+        
59
     if request.session.should_save:
74
     if request.session.should_save:
60
         session_store.save(request.session)
75
         session_store.save(request.session)
61
         response.set_cookie('sid', request.session.sid)
76
         response.set_cookie('sid', request.session.sid)
62
-
63
-    return response(env, start_response)
77
+    
78
+    res = response(env, start_response)
79
+    LodelHook.call_hook('lodel2_session_end', __file__, None)
80
+    return res

+ 9
- 2
plugins/webui/templates/admin/admin.html View File

1
 {% extends "base_backend.html" %}
1
 {% extends "base_backend.html" %}
2
-{% block title %}Lodel 2 - ADMIN{% endblock %}
3
-{% block content %}ADMIN{% endblock %}
2
+{% block title %}- Index{% endblock %}
3
+{% block body %}
4
+<h1>{{settings.sitename}} administration</h1>
5
+{{url('admin')}}
6
+<ul>
7
+    <li><a href="classes_admin">List of Classes</a></li>
8
+</ul>
9
+
10
+{% endblock %}

+ 20
- 0
plugins/webui/templates/admin/admin_create.html View File

1
+{% extends "base_backend.html" %}
2
+{% import "admin/editable_component.html" as edit %}
3
+
4
+{% block title %}- Creating a new {{target.__name__}}{% endblock %}
5
+{% block body %}
6
+{% if msg is not none %}
7
+{% block msg %} <p style="color:red; font-size:20pt; font-weight:bold">{{ msg }}</p> {% endblock %}
8
+{% endif %}
9
+<h1>Creating a new {{target.__name__}}</h1>
10
+ <form action="" method ="post">
11
+     <input type="hidden" name="classname" id="classname" value="{{target.__name__}}" />
12
+	{% for fieldname, field in target.fields().items() %}
13
+     <div style="padding-bottom:15px;"> {{edit.input(fieldname, field) }}</div>
14
+	{% endfor %}
15
+     <p>&nbsp;</p>
16
+ <input type="submit" value="Save">
17
+ </form>
18
+{% endblock %}
19
+
20
+

+ 19
- 0
plugins/webui/templates/admin/admin_edit.html View File

1
+{% extends "base_backend.html" %}
2
+{% import "admin/editable_component.html" as edit %}
3
+{% set objects = target.get(('lodel_id = %s') % (lodel_id)) %}
4
+{% set obj = objects.pop() %}
5
+{% block title %}- Edit Object{% endblock %}
6
+{% block body %}
7
+{% if msg is not none %}
8
+{% block msg %} <p style="color:red; font-size:20pt; font-weight:bold">{{ msg }}</p> {% endblock %}
9
+{% endif %}
10
+<h1>Lodel 2 - Edit Object {{ lodel_id }} of {{ target.__name__ }}</h1>
11
+    <form action="" method ="post">
12
+    <input type="hidden" name="uid" value="{{ lodel_id}}" >
13
+    <input type="hidden" name="classname" value={{ target.__name__ }} />
14
+    {% for fieldname, fieldvalue in obj.fields().items() %}
15
+       <div style="padding-bottom:15px;"> {{edit.input(fieldname, fieldvalue, obj.data(fieldname)) }} </div>
16
+    {% endfor %}
17
+     <input type="submit" value="Save">
18
+    </form>
19
+{% endblock %}

+ 10
- 0
plugins/webui/templates/admin/editable_component.html View File

1
+{% macro input(fieldname, field, value='') -%}
2
+	<label for="field_input_{{fieldname}}">{{fieldname}}</label>
3
+	{% if field.base_type == 'bool' %}
4
+		<input id="field_input_{{fieldname}}" name="field_input_{{fieldname}}" type="checkbox" checked="{% if value %}checked{% endif %}" />
5
+	{% elif field.base_type == 'char' or field.base_type == 'int' %}
6
+		<input id="{{fieldname}}" name="field_input_{{fieldname}}" type="text" value="{{value}}" />
7
+	{% else %}
8
+		Unsupported base type "{{field.base_type}}" </br>
9
+	{% endif %}
10
+{%- endmacro %}

+ 3
- 5
plugins/webui/templates/base_backend.html View File

2
 <html lang="en">
2
 <html lang="en">
3
 <head>
3
 <head>
4
     <meta charset="UTF-8" />
4
     <meta charset="UTF-8" />
5
-    <title>{% block title %}{% endblock %}</title>
5
+    <title>{{ settings.sitename }} Admin{% block title %}{% endblock %}</title>
6
     {% block style %}{% endblock %}
6
     {% block style %}{% endblock %}
7
     {% block scripts %}{% endblock %}
7
     {% block scripts %}{% endblock %}
8
 </head>
8
 </head>
9
 <body>
9
 <body>
10
-    <div id="content">
11
-        {% block content %}{% endblock %}
12
-    </div>
10
+    {% block body %}{% endblock %}
13
     <script type="text/javascript">{% block javascript %}{% endblock %}</script>
11
     <script type="text/javascript">{% block javascript %}{% endblock %}</script>
14
 </body>
12
 </body>
15
-</html>
13
+</html>

+ 9
- 0
plugins/webui/templates/components/components.html View File

1
+{% macro input(name, value='', type='text') -%}
2
+    <input type="{{ type }}" value="{{ value }}" name="{{ name }}" id= "{{ name }}"/>
3
+{%- endmacro %}
4
+
5
+{% macro textarea(name, value='', rows=10, cols=40) -%}
6
+    <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">
7
+        {{ value|e }}
8
+    </textarea>
9
+{%- endmacro %}

+ 5
- 0
plugins/webui/templates/documents/show.html View File

1
+{% extends "base_backend.html" %}
2
+{% block title %}Lodel 2 - Document {{ id }}{% endblock %}
3
+{% block content %}
4
+    {{ leapi.Section.get(['lodel_id = %s' % id]) }}
5
+{% endblock %}

+ 0
- 0
plugins/webui/templates/empty.html View File


+ 9
- 0
plugins/webui/templates/error.html View File

1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+		<title>{{status_code}} {{status_str}}</title>
5
+	</head>
6
+	<body>
7
+		{{status_code}} {{status_str}}
8
+	</body>
9
+</html>

+ 7
- 1
plugins/webui/templates/index/index.html View File

1
 {% extends "base.html" %}
1
 {% extends "base.html" %}
2
 {% block title %}Lodel 2 - DASHBOARD{% endblock %}
2
 {% block title %}Lodel 2 - DASHBOARD{% endblock %}
3
-{% block content %}DASHBOARD{% endblock %}
3
+{% block content %}
4
+    DASHBOARD <br />
5
+    {{ lodel.Test().ok() }}
6
+<ul>
7
+    <li><a href="list_classes">Tous les types</a></li>
8
+</ul>
9
+{% endblock %}

+ 16
- 0
plugins/webui/templates/listing/list_classes.html View File

1
+{% extends "base.html" %}
2
+{% block title %}Lodel 2 - List of Classes{% endblock %}
3
+{% block content %} 
4
+<h1>Lodel 2 - List of Classes</h1>
5
+<ul>
6
+{% for classe in my_classes %}
7
+    {% set abst = '' %}
8
+    {% if classe.is_abstract() %}
9
+    {% set abst = ' - Abstract class ' %}
10
+    {% else %}
11
+    {% set abst = ' - ' ~ classe.get(None)|length %}
12
+    {% endif %}
13
+    <li> <a href="show_class?name={{ classe.__name__ }}" target="_blank">{{ classe.__name__ }} </a>{{ abst }}</li>
14
+    {% endfor %}
15
+</ul>
16
+{% endblock %}

+ 30
- 0
plugins/webui/templates/listing/show_class.html View File

1
+{% extends "base.html" %}
2
+{% set my_classname = params['name'].pop() %}
3
+{% block title %}Lodel 2 - Class {{ my_classname }} {% endblock %}
4
+{% block content %} 
5
+<h1>Lodel 2 - Class {{ my_classname }} </h1>
6
+ {% set my_class  = leapi.Object.name2class(my_classname) %}
7
+ {% if my_class.child_classes()|length >0 %}
8
+     <h2> Childs classes</h2>
9
+     <ul>
10
+     {% for child in my_class.child_classes() %}
11
+         {% if child.is_abstract() %}
12
+            {% set abst = ' - Abstract class ' %}
13
+            {% else %}
14
+            {% set abst = ' - ' ~ child.get(None)|length %}
15
+         {% endif %}
16
+     <li><a href="show_class?name={{ child.__name__ }}" target="_blank">{{ child.__name__ }}</a>{{ abst }}</li>
17
+     {% endfor %}
18
+     </ul>
19
+  {% endif %}
20
+ {% if not my_class.is_abstract() %}
21
+    {% set uid_f = my_class.uid_fieldname() %}
22
+    {% set objects = my_class.get(None) %}
23
+    <ul>
24
+    {% for obj in objects %}
25
+        <li><a href="show_object?classe={{ my_classname }}&id={{ obj.uid() }}" target="_blank">{{ obj.uid() }} </a></li>
26
+    {% endfor %}
27
+    </ul>
28
+ {% endif %}
29
+
30
+{% endblock %} 

+ 21
- 0
plugins/webui/templates/listing/show_object.html View File

1
+{% extends "base.html" %}
2
+{% import 'components/components.html' as components %}
3
+{% set my_classname = params['classe'].pop() %}
4
+{% set my_id = params['id'].pop() %}
5
+{% set my_class = leapi.Object.name2class(my_classname) %}
6
+{% set objects = my_class.get(('%s = %s') % ('lodel_id', my_id)) %}
7
+{% set obj = objects.pop() %}
8
+{% block title %}Lodel 2 - Object {{ my_id }} {% endblock %}
9
+{% import "components/components.html" as components %}
10
+{% block content %}
11
+<h1>Lodel 2 - Object {{ my_id }} of the class {{ my_classname }}</h1>
12
+    
13
+<ul>
14
+    <!-- To get a component HTML code, it is necessary to call : components.<macro_name>(args) -->
15
+    {% for fieldname, fieldvalue in obj.datas().items() %}
16
+        {% if fieldvalue is not none %}
17
+    <li> {{ fieldname }} : {{ fieldvalue }} </li>
18
+        {% endif %}
19
+    {% endfor %}
20
+</ul>
21
+{% endblock %}

+ 10
- 0
plugins/webui/templates/test.html View File

1
+{% import "components/components.html" as components %}
2
+
1
 <html>
3
 <html>
2
 <head></head>
4
 <head></head>
3
 <body>
5
 <body>
6
+    {{ components.textarea('test', value='ceci est un test', rows=10, cols=20) }}<br/>
7
+    URL arg : id = {{ id }}<br />
8
+    GET values :<br />
9
+    <ul>
10
+    {% for argument_name, argument_value in params.items() %}
11
+        <li>{{argument_name}} = {{ argument_value }}</li>
12
+    {% endfor %}
13
+    </ul>
4
     <form action="http://localhost:9090/admin?r=1&rand[]=7&rand[]=5" method="POST" enctype="multipart/form-data">
14
     <form action="http://localhost:9090/admin?r=1&rand[]=7&rand[]=5" method="POST" enctype="multipart/form-data">
5
         <input type="text" name="re[]" value="3"><br />
15
         <input type="text" name="re[]" value="3"><br />
6
         <input type="text" name="re[]" value="1"><br />
16
         <input type="text" name="re[]" value="1"><br />

+ 19
- 7
scripts/create_instance.sh View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
 usage() {
3
 usage() {
4
-	echo "Usage : $0 instance_name instance_dir [lodel_libdir]" 1>&2
4
+	echo -e "Usage : $0 instance_name (instance_dir|-u) [lodel_libdir]" 1>&2
5
+	echo -e "\n\tIf -u given as first argument update instance's loader.py" 1>&2
5
 	exit 1
6
 	exit 1
6
 }
7
 }
7
 
8
 
9
+cp_loader() {
10
+	cp -Rv $libdir/install/loader.py $instdir/
11
+	# Adding lib path to loader
12
+	sed -i -E "s#^(LODEL2_LIB_ABS_PATH = )None#\1'$libdir'#" "$loader"
13
+}
14
+
15
+
8
 if [ $# -lt 2 ]
16
 if [ $# -lt 2 ]
9
 then
17
 then
10
 	echo "Not enough arguments" 1>&2
18
 	echo "Not enough arguments" 1>&2
12
 fi
20
 fi
13
 
21
 
14
 
22
 
23
+
15
 name="$1"
24
 name="$1"
16
 instdir="$2"
25
 instdir="$2"
17
 
26
 
21
 loader="$instdir/loader.py"
30
 loader="$instdir/loader.py"
22
 conf="$instdir/conf.d/lodel2.ini"
31
 conf="$instdir/conf.d/lodel2.ini"
23
 
32
 
33
+if [ $1 = '-u' ]
34
+then
35
+	#Update instance
36
+	cp_loader
37
+	exit 0
38
+fi
39
+
24
 if [ -e "$instdir" ]
40
 if [ -e "$instdir" ]
25
 then
41
 then
26
 	echo "Abording... "$instdir" exists" 1>&2
42
 	echo "Abording... "$instdir" exists" 1>&2
34
 
50
 
35
 #cp -Rv $libdir/install/* $instdir
51
 #cp -Rv $libdir/install/* $instdir
36
 cp -Rv $libdir/install/conf.d $instdir/
52
 cp -Rv $libdir/install/conf.d $instdir/
37
-cp -Rv $libdir/install/loader.py $instdir/
38
 cp -Rv $libdir/examples/em_test.pickle $instdir/editorial_model.pickle
53
 cp -Rv $libdir/examples/em_test.pickle $instdir/editorial_model.pickle
39
 ln -sv $libdir/install/Makefile $instdir/Makefile
54
 ln -sv $libdir/install/Makefile $instdir/Makefile
40
 ln -sv $libdir/install/lodel_admin.py $instdir/lodel_admin.py
55
 ln -sv $libdir/install/lodel_admin.py $instdir/lodel_admin.py
41
 ln -sv $libdir/plugins $instdir/plugins
56
 ln -sv $libdir/plugins $instdir/plugins
42
-
43
-
44
-
45
-# Adding lib path to loader
46
-sed -i -E "s#^(LODEL2_LIB_ABS_PATH = )None#\1'$libdir'#" "$loader"
57
+cp_loader
47
 # Adding instance name to conf
58
 # Adding instance name to conf
48
 sed -i -E "s#^sitename = noname#sitename = $name#" "$conf"
59
 sed -i -E "s#^sitename = noname#sitename = $name#" "$conf"
49
 
60
 
61
+
50
 echo -e "\nInstance successfully created in $instdir"
62
 echo -e "\nInstance successfully created in $instdir"
51
 echo -e "============================\n"
63
 echo -e "============================\n"
52
 echo "Now you should edit files in '${instdir}/conf.d/' and then run : cd $instdir && make dyncode"
64
 echo "Now you should edit files in '${instdir}/conf.d/' and then run : cd $instdir && make dyncode"

BIN
tests/editorial_model.pickle View File


+ 2
- 2
tests/leapi/query/test_datasource.py View File

71
         self.assertEqual(call_args[0], cls)
71
         self.assertEqual(call_args[0], cls)
72
         self.assertEqual(
72
         self.assertEqual(
73
             sorted(call_args[1]),
73
             sorted(call_args[1]),
74
-            sorted([('lodel_id', '=', '1'), ('alias', '=', '2')]))
74
+            sorted([('lodel_id', '=', 1), ('alias', '=', '2')]))
75
         self.assertEqual(call_args[2], [])
75
         self.assertEqual(call_args[2], [])
76
         self.check_nocall(read = False, exclude = ['delete'])
76
         self.check_nocall(read = False, exclude = ['delete'])
77
         self.check_nocall(read = True)
77
         self.check_nocall(read = True)
87
         query.execute()
87
         query.execute()
88
         self.mockwrite.delete.assert_called_once_with(
88
         self.mockwrite.delete.assert_called_once_with(
89
             cls,
89
             cls,
90
-            [('lodel_id', '=', '1')],
90
+            [('lodel_id', '=', 1)],
91
             [(('alias', {cls: 'firstname'}), '=', 'foo')])
91
             [(('alias', {cls: 'firstname'}), '=', 'foo')])
92
         self.check_nocall(read = False, exclude = ['delete'])
92
         self.check_nocall(read = False, exclude = ['delete'])
93
         self.check_nocall(read = True)
93
         self.check_nocall(read = True)

+ 9
- 9
tests/leapi/query/test_filtered.py View File

3
 import tests.loader_utils
3
 import tests.loader_utils
4
 from tests.leapi.query.utils import dyncode_module as dyncode
4
 from tests.leapi.query.utils import dyncode_module as dyncode
5
 
5
 
6
-from lodel.leapi.exceptions import LeApiDataCheckError
6
+from lodel.leapi.exceptions import *
7
 from lodel.leapi.query import LeDeleteQuery, LeUpdateQuery, LeGetQuery
7
 from lodel.leapi.query import LeDeleteQuery, LeUpdateQuery, LeGetQuery
8
 
8
 
9
 class LeFilteredQueryTestCase(unittest.TestCase):
9
 class LeFilteredQueryTestCase(unittest.TestCase):
13
     def test_filters(self):
13
     def test_filters(self):
14
         """ Testing FilteredQuery filters handling """
14
         """ Testing FilteredQuery filters handling """
15
         test_datas = [  (   'lodel_id = 42',
15
         test_datas = [  (   'lodel_id = 42',
16
-                            (   [('lodel_id','=','42')],
16
+                            (   [('lodel_id','=',42)],
17
                                 [])),
17
                                 [])),
18
                         (   'lodel_id <= 42',
18
                         (   'lodel_id <= 42',
19
-                            (   [('lodel_id','<=','42')],
19
+                            (   [('lodel_id','<=',42)],
20
                                 [])),
20
                                 [])),
21
                         (   ['lodel_id <= 42'],
21
                         (   ['lodel_id <= 42'],
22
-                            (   [('lodel_id','<=','42')],
22
+                            (   [('lodel_id','<=',42)],
23
                                 [])),
23
                                 [])),
24
                         (   ('lodel_id <= 42',),
24
                         (   ('lodel_id <= 42',),
25
-                            (   [('lodel_id','<=','42')],
25
+                            (   [('lodel_id','<=',42)],
26
                                 [])),
26
                                 [])),
27
                         (   ['lodel_id <= 42','lodel_id >= 33'],
27
                         (   ['lodel_id <= 42','lodel_id >= 33'],
28
-                            (   [   ('lodel_id','<=','42'),
29
-                                    ('lodel_id', '>=','33')],
28
+                            (   [   ('lodel_id','<=',42),
29
+                                    ('lodel_id', '>=',33)],
30
                                 [])),
30
                                 [])),
31
         ]
31
         ]
32
         for q_class in self.q_classes:
32
         for q_class in self.q_classes:
53
         )
53
         )
54
         for invalid_filter in invalid_filters:
54
         for invalid_filter in invalid_filters:
55
             for q_class in self.q_classes:
55
             for q_class in self.q_classes:
56
-                with self.assertRaises( LeApiDataCheckError,
56
+                with self.assertRaises( LeApiDataCheckErrors,
57
                                         msg="for filter '%s'" % (invalid_filter,)):
57
                                         msg="for filter '%s'" % (invalid_filter,)):
58
                     q_class(dyncode.Publication, invalid_filter)
58
                     q_class(dyncode.Publication, invalid_filter)
59
             
59
             
70
                         'not in',
70
                         'not in',
71
                         'like',
71
                         'like',
72
                         'not like']
72
                         'not like']
73
-        values = (  '42',
73
+        values = (  42,
74
                     'not in',
74
                     'not in',
75
                     'in',
75
                     'in',
76
                     'like',
76
                     'like',

+ 1
- 1
tests/leapi/test_leobject.py View File

259
             mock_init.assert_called_once_with(
259
             mock_init.assert_called_once_with(
260
                 dyncode.Person,
260
                 dyncode.Person,
261
                 query_filters = ['lodel_id = 1'],
261
                 query_filters = ['lodel_id = 1'],
262
-                field_list = dyncode.Person.fieldnames(True),
262
+                field_list = None,
263
                 order = None, group = None, limit = None, offset = 0)
263
                 order = None, group = None, limit = None, offset = 0)
264
 
264
 
265
         with patch.object(
265
         with patch.object(

Loading…
Cancel
Save