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,3 +20,7 @@ Instance operations :
20 20
 	make dyncode # Leapi dynamic code creation ( in leapi_dyncode.py in lodel2 instance root dir)
21 21
 	make init_db # Call migration handlers to tell them to init all needed databases. (note : this target has dyncode as dependencie)
22 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,6 +87,7 @@ person.new_field(   'firstname',
87 87
                         'fre': 'Prénom',
88 88
                     },
89 89
                     data_handler = 'varchar',
90
+                    group = base_group,
90 91
 )
91 92
 person.new_field(   'lastname',
92 93
                     display_name = {
@@ -94,6 +95,7 @@ person.new_field(   'lastname',
94 95
                         'fre': 'Nom de famille',
95 96
                     },
96 97
                     data_handler = 'varchar',
98
+                    group = base_group,
97 99
 )
98 100
 person.new_field(   'fullname',
99 101
                     display_name = {
@@ -115,6 +117,7 @@ person.new_field(   'alias',
115 117
                     allowed_classes = [person],
116 118
                     default = None,
117 119
                     nullable = True,
120
+                    group = base_group,
118 121
 )
119 122
 
120 123
 
@@ -324,14 +327,16 @@ index_name = index_abstract.new_field(
324 327
     display_name = {
325 328
         'eng': 'name',
326 329
         'fre': 'nom'},
327
-    data_handler = 'varchar')
330
+    data_handler = 'varchar',
331
+    group = index_group)
328 332
 
329 333
 index_value = index_abstract.new_field(
330 334
     'value',
331 335
     display_name = {
332 336
         'eng': 'value',
333 337
         'fre': 'valeur'},
334
-    data_handler = 'varchar')
338
+    data_handler = 'varchar',
339
+    group = index_group)
335 340
 
336 341
 text.new_field( 'indexes',
337 342
     display_name = {
@@ -341,7 +346,8 @@ text.new_field( 'indexes',
341 346
     back_reference = ('Indexabs', 'texts'),
342 347
     allowed_classes = [index_abstract],
343 348
     default = None,
344
-    nullable = True)
349
+    nullable = True,
350
+    group = index_group)
345 351
 
346 352
 index_abstract.new_field( 'texts',
347 353
     display_name = {
@@ -349,7 +355,8 @@ index_abstract.new_field( 'texts',
349 355
         'fre': 'Texte contenant cette index'},
350 356
     data_handler = 'list',
351 357
     back_reference = ('Text', 'indexes'),
352
-    allowed_classes = [text])
358
+    allowed_classes = [text],
359
+    group = index_group)
353 360
 
354 361
 index_theme = em.new_class(
355 362
     'indexTheme',
@@ -364,7 +371,8 @@ index_theme_theme = index_abstract.new_field(
364 371
     'theme',
365 372
     display_name = {
366 373
         'eng': 'theme'},
367
-    data_handler = 'varchar')
374
+    data_handler = 'varchar',
375
+    group = index_group)
368 376
 
369 377
 #em.save('xmlfile', filename = 'examples/em_test.xml')
370 378
 pickle_file = 'examples/em_test.pickle'

BIN
examples/em_test.pickle View File


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

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

+ 8
- 2
install/loader.py View File

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

+ 2
- 0
lodel/__init__.py View File

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

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

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

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

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

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

@@ -316,7 +316,7 @@ def load_class_xml(model, elem):
316 316
         
317 317
     fields = elem.find('fields')
318 318
     for field in fields:
319
-        emfield = load_field_xml(model, field)
319
+        emfield = load_field_xml(model, field, emclass)
320 320
         l_emfields = emclass.fields()
321 321
         incls = False
322 322
         for emf in l_emfields:
@@ -329,10 +329,11 @@ def load_class_xml(model, elem):
329 329
     return emclass
330 330
     
331 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 337
     uid = elem.find('uid').text
337 338
     if elem.find('display_name').text is None:
338 339
         name = None
@@ -354,16 +355,13 @@ def load_field_xml(model, elem):
354 355
         group = None
355 356
         
356 357
     dhdl = elem.find('datahandler_name')
358
+    dhdl_opts = {}
357 359
     if dhdl.text is not None:
358 360
         dhdl_opts = elem.find('datahandler_options')
359
-
360 361
         if dhdl_opts is not None:
361 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 366
     return emfield
369 367
 

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

@@ -110,8 +110,7 @@ class DataHandler(object):
110 110
                 return data_handler.default
111 111
         elif data_handler is not None and data_handler.nullable:
112 112
                 return None
113
-
114
-        return RuntimeError("Unable to construct data for field %s", fname)
113
+        return cur_value
115 114
 
116 115
     ##@brief Check datas consistency
117 116
     # @param emcomponent EmComponent : An EmComponent child class instance
@@ -184,6 +183,7 @@ class DataField(DataHandler):
184 183
 # References are fields that stores a reference to another
185 184
 # editorial object
186 185
 class Reference(DataHandler):
186
+    base_type="ref"
187 187
 
188 188
     ##@brief Instanciation
189 189
     # @param allowed_classes list | None : list of allowed em classes if None no restriction
@@ -249,19 +249,22 @@ class SingleRef(Reference):
249 249
 ##@brief This class represent a data_handler for multiple references to another object
250 250
 #
251 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 253
 class MultipleRef(Reference):
254 254
     
255 255
     ##
256 256
     # @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
257 257
     def __init__(self, max_item = None, **kwargs):
258
+        self.max_item = max_item
258 259
         super().__init__(**kwargs)
259 260
 
260 261
         
261 262
     def _check_data_value(self, value):
263
+        value = value.split(',')
262 264
         if self.max_item is not None:
263 265
             if self.max_item < len(value):
264 266
                 return None, FieldValidationError("To many items")
267
+        return value, None
265 268
 
266 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,9 +72,6 @@ class UniqID(Integer):
72 72
         kwargs['internal'] = 'automatic'
73 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 75
     def construct_data(self, emcomponent, fname, datas, cur_value):
79 76
         if cur_value is None:
80 77
             #Ask datasource to provide a new uniqID
@@ -114,3 +111,6 @@ class Concat(FormatString):
114 111
         super().__init__(
115 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,10 +20,9 @@ class List(MultipleRef):
20 20
     # @param value *
21 21
     # @return tuple(value, exception)
22 22
     def _check_data_value(self, value):
23
-        val, expt = super()._check_data_value()
23
+        val, expt = super()._check_data_value(value)
24 24
         if not isinstance(expt, Exception):
25 25
             val = list(val)
26
-        val, expt = super()._check_data_value(value.values())
27 26
         return val, expt
28 27
 
29 28
 
@@ -41,10 +40,9 @@ class Set(MultipleRef):
41 40
     # @param value *
42 41
     # @return tuple(value, exception)
43 42
     def _check_data_value(self, value):
44
-        val, expt = super()._check_data_value()
43
+        val, expt = super()._check_data_value(value)
45 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 46
         return val, expt
49 47
 
50 48
 
@@ -62,9 +60,9 @@ class Map(MultipleRef):
62 60
     # @param value *
63 61
     # @return tuple(value, exception)
64 62
     def _check_data_value(self, value):
63
+        val, expt = super()._check_data_value(value)
65 64
         if not isinstance(value, dict):
66 65
             return None, FieldValidationError("Values for dict fields should be dict")
67
-        val, expt = super()._check_data_value(value.values())
68 66
         return (
69 67
                 None if isinstance(expt, Exception) else value,
70 68
                 expt)

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

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

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

@@ -180,8 +180,11 @@ class LeObject(object):
180 180
                                                             cls.__name__))
181 181
     ##@return A dict with fieldname as key and datahandler as instance
182 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 189
     ##@brief Return the list of parents classes
187 190
     #
@@ -223,7 +226,7 @@ class LeObject(object):
223 226
         uid_handlers = set( cls._fields[name] for name in cls._uid )
224 227
         for pcls in cls.hierarch()[1:]:
225 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 230
                 or puid_handlers != uid_handlers:
228 231
                 break
229 232
             prev = pcls
@@ -242,14 +245,24 @@ class LeObject(object):
242 245
             ro_ds, rw_ds = cls._datasource_name
243 246
         #Read only datasource initialisation
244 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 256
         #Read write datasource initialisation
249 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 268
     ##@brief Replace the _datasource attribute value by a datasource instance
@@ -586,7 +599,6 @@ construction and consitency when datas are not complete\n")
586 599
         query_filter = list()
587 600
         for uid in uids:
588 601
             query_filter.append((uid, '=', self.data(uid)))
589
-        
590 602
         try:
591 603
             query = LeUpdateQuery(self.__class__, query_filter)
592 604
         except Exception as err:
@@ -648,9 +660,7 @@ construction and consitency when datas are not complete\n")
648 660
     #@return a list of items (lists of (fieldname, fieldvalue))
649 661
     @classmethod
650 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 664
             for uid in [ uidname
655 665
                 for uidname in cls.uid_fieldname()
656 666
                 if uidname not in field_list ]:

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

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

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

@@ -7,6 +7,7 @@ import copy
7 7
 from importlib.machinery import SourceFileLoader, SourcelessFileLoader
8 8
 
9 9
 import plugins
10
+from .exceptions import *
10 11
 
11 12
 ## @package lodel.plugins Lodel2 plugins management
12 13
 #
@@ -25,8 +26,6 @@ LOADER_FILENAME_VARNAME = '__loader__'
25 26
 PLUGIN_DEPS_VARNAME = '__plugin_deps__'
26 27
 ACTIVATE_METHOD_NAME = '_activate'
27 28
 
28
-class PluginError(Exception):
29
-    pass
30 29
 
31 30
 ##@brief Handle plugins
32 31
 #
@@ -119,7 +118,14 @@ class Plugin(object):
119 118
     #@throw PluginError if the filename was not valid
120 119
     def _import_from_init_var(self, varname):
121 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 129
         #Path are not allowed
124 130
         if filename != os.path.basename(filename):
125 131
             msg = "Invalid {varname} content : '{fname}' for plugin {name}"
@@ -213,12 +219,8 @@ class Plugin(object):
213 219
         #Loading the plugin
214 220
         try:
215 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 224
         except ImportError as e:
223 225
             msg = "Broken plugin {plugin} : {expt}"
224 226
             msg = msg.format(

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

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

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

@@ -13,8 +13,8 @@ from lodel import logger
13 13
 from lodel.leapi.leobject import CLASS_ID_FIELDNAME
14 14
 
15 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 19
 class MongoDbDataSourceError(Exception):
20 20
     pass
@@ -75,7 +75,7 @@ class MongoDbDatasource(object):
75 75
     #@warning multiple UID broken by this method
76 76
     #@return an integer
77 77
     def new_numeric_id(self, emcomp):
78
-        target = emcomp.uid_source()
78
+        target = emcomp #.uid_source()
79 79
         tuid = target._uid[0] # Multiple UID broken here
80 80
         results = self.select(
81 81
             target, field_list = [tuid], filters = [], 
@@ -130,16 +130,26 @@ class MongoDbDatasource(object):
130 130
 
131 131
         query_filters = self.__process_filters(
132 132
             target, filters, relational_filters)
133
+        
133 134
         query_result_ordering = None
134 135
         if order is not None:
135 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 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 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 153
         else:
144 154
             pipeline = list()
145 155
             unwinding_list = list()
@@ -156,7 +166,7 @@ class MongoDbDatasource(object):
156 166
             sorting_list.extends(query_result_ordering)
157 167
 
158 168
             pipeline.append({'$match': query_filters})
159
-            if results_field_list is not None:
169
+            if field_list is not None:
160 170
                 pipeline.append({
161 171
                     '$project': SON([{field_name: 1}
162 172
                     for field_name in field_list])})
@@ -180,7 +190,7 @@ class MongoDbDatasource(object):
180 190
     #@param relational_filters list : List of relational filters
181 191
     #@return int : number of deleted records
182 192
     def delete(self, target, filters, relational_filters):
183
-        if target.is_asbtract():
193
+        if target.is_abstract():
184 194
             #Deletion with abstract LeObject as target (reccursiv calls)
185 195
             return self.__act_on_abstract(target, filters,
186 196
                 relational_filters, self.delete)
@@ -197,15 +207,15 @@ class MongoDbDatasource(object):
197 207
     #@param upd_datas dict : datas to update (new values)
198 208
     #@return int : Number of updated records
199 209
     def update(self, target, filters, relational_filters, upd_datas):
200
-        if target.is_asbtract():
210
+        if target.is_abstract():
201 211
             #Update using abstract LeObject as target (reccursiv calls)
202 212
             return self.__act_on_abstract(target, filters,
203 213
                 relational_filters, self.update, upd_datas = upd_datas)
204 214
         #Non abstract beahavior
205 215
         mongo_filters = self.__process_filters(
206 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 220
     ## @brief Inserts a record in a given collection
211 221
     # @param target Emclass : class of the object to insert
@@ -252,7 +262,8 @@ class MongoDbDatasource(object):
252 262
                         fname, op, val))
253 263
                     del(new_filters[i])
254 264
             new_filters.append(
255
-                (CLASS_ID_FIELDNAME, '=', target_child.__name__))
265
+                (CLASS_ID_FIELDNAME, '=',
266
+                    collection_name(target_child.__name__)))
256 267
             result += act(
257 268
                 target = target_child,
258 269
                 filters = new_filters,
@@ -348,7 +359,7 @@ class MongoDbDatasource(object):
348 359
             if '$in' in res[fname]:
349 360
                 #WARNING we allready have a IN on this field, doing dedup
350 361
                 #from result
351
-                deduped = set(res[fname]['$in']) & subq
362
+                deduped = set(res[fname]['$in']) & subq_results
352 363
                 if len(deduped) == 0:
353 364
                     del(res[fname]['$in'])
354 365
                 else:
@@ -419,8 +430,8 @@ class MongoDbDatasource(object):
419 430
                 #here we are filling a dict with leobject as index but
420 431
                 #we are doing a UNIQ on collection name
421 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 435
                     rfilters[fname][leobject] = dict()
425 436
                 #Fecthing the collection's representative leobject
426 437
                 repr_leo = leo_collname[cur_collname]
@@ -436,17 +447,33 @@ class MongoDbDatasource(object):
436 447
     @classmethod
437 448
     def __filters2mongo(cls, filters):
438 449
         res = dict()
450
+        eq_fieldname = [] #Stores field with equal comparison OP
439 451
         for fieldname, op, value in filters:
440 452
             oop = op
441 453
             ovalue = value
442 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 467
             if fieldname not in res:
444 468
                 res[fieldname] = dict()
445 469
             if op in res[fieldname]:
446 470
                 logger.warning("Dropping condition : '%s %s %s'" % (
447 471
                     fieldname, op, value))
448 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 477
         return res
451 478
 
452 479
 

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

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

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

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

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

@@ -0,0 +1,51 @@
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,2 +1,5 @@
1 1
 from .base import *
2 2
 from .admin import *
3
+from .document import *
4
+from .listing import *
5
+

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

@@ -1,5 +1,135 @@
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 130
 def admin(request):
3 131
     return get_response('admin/admin.html')
4 132
 
133
+        
134
+            
5 135
 

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

@@ -5,13 +5,27 @@ from ..template.loader import TemplateLoader
5 5
 
6 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 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 19
     response.status_code = status_code
13 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 30
 def index(request):
17 31
     return get_response('index/index.html')
@@ -22,10 +36,14 @@ def not_found(request):
22 36
 
23 37
 
24 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

@@ -0,0 +1,6 @@
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

@@ -0,0 +1,19 @@
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,19 +3,15 @@ import re
3 3
 
4 4
 from .controllers import *
5 5
 from .urls import urls
6
+from ..main import root_url
6 7
 from lodel.settings import Settings
7 8
 
8
-
9 9
 def format_url_rule(url_rule):
10
-    if url_rule == '^$':
11
-        return "^%s$" % Settings.sitename
12
-
13
-    formatted_rule = ''
14 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 17
 def get_controller(request):
@@ -26,9 +22,10 @@ def get_controller(request):
26 22
 
27 23
     # Returning the right controller to call
28 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 29
             return callback
33 30
 
34 31
     return not_found

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

@@ -1,3 +1,8 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3 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,9 +2,12 @@
2 2
 import jinja2
3 3
 import os
4 4
 
5
-import settings
5
+from lodel.settings import Settings
6
+import leapi_dyncode
7
+
6 8
 from .api import api_lodel_templates
7 9
 from .exceptions.not_allowed_custom_api_key_error import NotAllowedCustomAPIKeyError
10
+from ...main import root_url
8 11
 
9 12
 from ...main import PLUGIN_PATH
10 13
 TEMPLATE_PATH = os.path.realpath(os.path.join(PLUGIN_PATH, 'templates/'))
@@ -39,6 +42,11 @@ class TemplateLoader(object):
39 42
         # lodel2 default api is loaded
40 43
         # TODO change this if needed
41 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 51
         # Extra modules are loaded
44 52
         if template_extra is not None:

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

@@ -1,9 +1,19 @@
1
+# -*- coding: utf-8 -*-
1 2
 from .controllers import *
2 3
 
3 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,12 +6,25 @@ from lodel.settings import Settings
6 6
 
7 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 15
 ##@brief uwsgi startup demo
10 16
 @LodelHook('lodel2_loader_main')
11 17
 def uwsgi_fork(hook_name, caller, payload):
18
+    from lodel.plugin.plugins import Plugin
19
+    Plugin.from_name('users')
20
+
12 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 23
         cmd = cmd.format(
15 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 30
         exit(os.system(cmd))

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

@@ -3,11 +3,14 @@ import loader # Lodel2 loader
3 3
 
4 4
 import os
5 5
 from werkzeug.contrib.sessions import FilesystemSessionStore
6
+from werkzeug.wrappers import Response
6 7
 
7 8
 from lodel.settings import Settings
8 9
 from .interface.router import get_controller
9 10
 from .interface.lodelrequest import LodelRequest
11
+from .exceptions import *
10 12
 from lodel.utils.datetime import get_utc_timestamp
13
+from lodel.plugin.hooks import LodelHook
11 14
 
12 15
 SESSION_FILES_BASE_DIR = Settings.webui.sessions.directory
13 16
 SESSION_FILES_TEMPLATE = Settings.webui.sessions.file_template
@@ -15,6 +18,8 @@ SESSION_EXPIRATION_LIMIT = Settings.webui.sessions.expiration
15 18
 
16 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 24
 # TODO déplacer dans un module "sessions.py"
20 25
 def delete_old_session_files(timestamp_now):
@@ -53,11 +58,23 @@ def application(env, start_response):
53 58
             request.session = session_store.new()
54 59
             request.session['user_context'] = None
55 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 74
     if request.session.should_save:
60 75
         session_store.save(request.session)
61 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,3 +1,10 @@
1 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

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,19 @@
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

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

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

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,5 @@
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

@@ -0,0 +1,9 @@
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,3 +1,9 @@
1 1
 {% extends "base.html" %}
2 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

@@ -0,0 +1,16 @@
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

@@ -0,0 +1,30 @@
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

@@ -0,0 +1,21 @@
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,6 +1,16 @@
1
+{% import "components/components.html" as components %}
2
+
1 3
 <html>
2 4
 <head></head>
3 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 14
     <form action="http://localhost:9090/admin?r=1&rand[]=7&rand[]=5" method="POST" enctype="multipart/form-data">
5 15
         <input type="text" name="re[]" value="3"><br />
6 16
         <input type="text" name="re[]" value="1"><br />

+ 19
- 7
scripts/create_instance.sh View File

@@ -1,10 +1,18 @@
1 1
 #!/bin/bash
2 2
 
3 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 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 16
 if [ $# -lt 2 ]
9 17
 then
10 18
 	echo "Not enough arguments" 1>&2
@@ -12,6 +20,7 @@ then
12 20
 fi
13 21
 
14 22
 
23
+
15 24
 name="$1"
16 25
 instdir="$2"
17 26
 
@@ -21,6 +30,13 @@ libdir="${libdir:=$(realpath $(dirname $0)/..)}/"
21 30
 loader="$instdir/loader.py"
22 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 40
 if [ -e "$instdir" ]
25 41
 then
26 42
 	echo "Abording... "$instdir" exists" 1>&2
@@ -34,19 +50,15 @@ chmod 700 "$instdir/sessions"
34 50
 
35 51
 #cp -Rv $libdir/install/* $instdir
36 52
 cp -Rv $libdir/install/conf.d $instdir/
37
-cp -Rv $libdir/install/loader.py $instdir/
38 53
 cp -Rv $libdir/examples/em_test.pickle $instdir/editorial_model.pickle
39 54
 ln -sv $libdir/install/Makefile $instdir/Makefile
40 55
 ln -sv $libdir/install/lodel_admin.py $instdir/lodel_admin.py
41 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 58
 # Adding instance name to conf
48 59
 sed -i -E "s#^sitename = noname#sitename = $name#" "$conf"
49 60
 
61
+
50 62
 echo -e "\nInstance successfully created in $instdir"
51 63
 echo -e "============================\n"
52 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,7 +71,7 @@ class LeQueryDatasourceTestCase(unittest.TestCase):
71 71
         self.assertEqual(call_args[0], cls)
72 72
         self.assertEqual(
73 73
             sorted(call_args[1]),
74
-            sorted([('lodel_id', '=', '1'), ('alias', '=', '2')]))
74
+            sorted([('lodel_id', '=', 1), ('alias', '=', '2')]))
75 75
         self.assertEqual(call_args[2], [])
76 76
         self.check_nocall(read = False, exclude = ['delete'])
77 77
         self.check_nocall(read = True)
@@ -87,7 +87,7 @@ class LeQueryDatasourceTestCase(unittest.TestCase):
87 87
         query.execute()
88 88
         self.mockwrite.delete.assert_called_once_with(
89 89
             cls,
90
-            [('lodel_id', '=', '1')],
90
+            [('lodel_id', '=', 1)],
91 91
             [(('alias', {cls: 'firstname'}), '=', 'foo')])
92 92
         self.check_nocall(read = False, exclude = ['delete'])
93 93
         self.check_nocall(read = True)

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

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

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

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

Loading…
Cancel
Save