Browse Source

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

prieto 8 years ago
parent
commit
b4a8e7ee99

+ 3
- 2
Makefile View File

@@ -1,3 +1,4 @@
1
+python=python3
1 2
 dyncode_filename='lodel/leapi/dyncode.py'
2 3
 
3 4
 all: tests doc dyncode
@@ -11,11 +12,11 @@ doc_graphviz:
11 12
 
12 13
 # Test em update ( examples/em_test.pickle )
13 14
 em_test:
14
-	python3 em_test.py
15
+	$(python) em_test.py
15 16
 
16 17
 # generate leapi dynamic code
17 18
 dyncode: clean_dyn em_test
18
-	python3 scripts/refreshdyn.py examples/em_test.pickle $(dyncode_filename) && echo -e "\n\nCode generated in $(dyncode_filename)"
19
+	$(python) scripts/refreshdyn.py examples/em_test.pickle $(dyncode_filename) && echo -e "\n\nCode generated in $(dyncode_filename)"
19 20
 
20 21
 # run tests
21 22
 tests:

+ 4
- 0
README.txt View File

@@ -1,6 +1,10 @@
1 1
 Local configuration :
2 2
 	First of all copy the settings.ini to settings_local.ini and replace values by correct path
3 3
 
4
+Dependencies :
5
+	with pip : see requierments.txt
6
+	debian stable : python3 python3-lxml python3-jinja2 python3-werkzeug python3-pymongo doxygen graphviz
7
+
4 8
 Doxygen documentation generation :
5 9
 	doxygen
6 10
 

+ 1
- 0
globconf.d/global.ini View File

@@ -8,3 +8,4 @@ emtranslator = picklefile
8 8
 dyncode = lodel/leapi/dyncode.py
9 9
 editormode = True
10 10
 groups = 
11
+

+ 3
- 1
install/Makefile View File

@@ -1,4 +1,6 @@
1
+python=python3
2
+
1 3
 all: dyncode
2 4
 
3 5
 dyncode:
4
-	python -c 'import lodel_admin; lodel_admin.refresh_dyncode()'
6
+	$(python) -c 'import lodel_admin; lodel_admin.refresh_dyncode()'

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

@@ -1,7 +1,8 @@
1 1
 [lodel2]
2 2
 debug = False
3
-plugins = dummy
4 3
 sitename = noname
4
+plugins_path = /foo/plugins
5
+plugins = dummy, webui
5 6
 
6 7
 [lodel2.logging.stderr]
7 8
 level = DEBUG

+ 8
- 0
install/conf.d/webui_plugin.ini View File

@@ -0,0 +1,8 @@
1
+[lodel2.webui]
2
+standalone=False
3
+#listen_address=127.0.0.1
4
+#listen_port=9090
5
+[lodel2.webui.sessions]
6
+directory=./sessions
7
+expiration=900
8
+file_template=lodel2_%s.sess

+ 7
- 1
install/loader.py View File

@@ -22,8 +22,14 @@ from lodel.settings.settings import Settings as settings
22 22
 settings('conf.d')
23 23
 from lodel.settings import Settings
24 24
 
25
+#Load plugins
26
+from lodel.plugin import Plugins
27
+Plugins.bootstrap()
25 28
 
26
-if __name__ == '__main__': # To allow running interactive python
29
+if __name__ == '__main__':
30
+    from lodel.plugin import LodelHook
31
+    LodelHook.call_hook('lodel2_loader_main', '__main__', None)
32
+    #Run interative python
27 33
     import code
28 34
     print("""
29 35
      Running interactive python in Lodel2 %s instance environment

run.py → install/run.py View File

@@ -1,15 +1,17 @@
1 1
 # -*- coding: utf-8 -*-
2
+import loader # Lodel2 loader
3
+
2 4
 import os
3 5
 from werkzeug.contrib.sessions import FilesystemSessionStore
4 6
 
7
+from lodel.settings import Settings
5 8
 from lodel.interface.web.router import get_controller
6 9
 from lodel.interface.web.lodelrequest import LodelRequest
7 10
 from lodel.utils.datetime import get_utc_timestamp
8 11
 
9
-# TODO Déplacer ces trois paramètres dans les settings
10
-SESSION_FILES_TEMPLATE = 'lodel_%s.sess'
11
-SESSION_FILES_BASE_DIR = 'tmp/sessions'
12
-SESSION_EXPIRATION_LIMIT = 900 # 15 min
12
+SESSION_FILES_BASE_DIR = Settings.webui.sessions.directory
13
+SESSION_FILES_TEMPLATE = Settings.webui.sessions.file_template
14
+SESSION_EXPIRATION_LIMIT = Settings.webui.sessions.expiration
13 15
 
14 16
 session_store = FilesystemSessionStore(path=SESSION_FILES_BASE_DIR, filename_template=SESSION_FILES_TEMPLATE)
15 17
 

+ 4
- 0
lodel/leapi/datahandlers/base_classes.py View File

@@ -203,6 +203,10 @@ class Reference(DataHandler):
203 203
     def back_reference(self):
204 204
         return copy.copy(self.__back_reference)
205 205
 
206
+    @property
207
+    def linked_classes(self):
208
+        return copy.copy(self.__allowed_classes)
209
+
206 210
     ##@brief Set the back reference for this field.
207 211
     def _set_back_reference(self, back_reference):
208 212
         self.__back_reference = back_reference

+ 17
- 0
lodel/leapi/leobject.py View File

@@ -118,6 +118,11 @@ class LeObject(object):
118 118
     @property
119 119
     def initialized(self):
120 120
         return not isinstance(self.__initialized, list)
121
+    
122
+    ##@return The uid field name
123
+    @classmethod
124
+    def uid_fieldname(cls):
125
+        return cls._uid
121 126
 
122 127
     ##@brief Return a list of fieldnames
123 128
     # @param include_ro bool : if True include read only field names
@@ -160,6 +165,18 @@ class LeObject(object):
160 165
     @classmethod
161 166
     def is_abstract(cls):
162 167
         return cls._abstract
168
+    
169
+    ##@brief Field data handler gettet
170
+    #@param fieldname str : The field name
171
+    #@return A datahandler instance
172
+    #@throw NameError if the field doesn't exist
173
+    @classmethod
174
+    def field(cls, fieldname):
175
+        try:
176
+            return cls._fields[field_uid]
177
+        except KeyError:
178
+            raise NameError("No field named '%s' in %s" % ( field_uid,
179
+                                                            cls.__name__))
163 180
 
164 181
     ##@brief Read only access to all datas
165 182
     # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance

+ 198
- 43
lodel/leapi/query.py View File

@@ -3,11 +3,13 @@
3 3
 import re
4 4
 from .leobject import LeObject, LeApiErrors, LeApiDataCheckError
5 5
 from lodel.plugin.hooks import LodelHook
6
+from lodel import logger
6 7
 
7 8
 class LeQueryError(Exception):
8 9
     ##@brief Instanciate a new exceptions handling multiple exceptions
9
-    # @param msg str : Exception message
10
-    # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
10
+    #@param msg str : Exception message
11
+    #@param exceptions dict : A list of data check Exception with concerned
12
+    # field (or stuff) as key
11 13
     def __init__(self, msg = "Unknow error", exceptions = None):
12 14
         self._msg = msg
13 15
         self._exceptions = dict() if exceptions is None else exceptions
@@ -17,7 +19,10 @@ class LeQueryError(Exception):
17 19
 
18 20
     def __str__(self):
19 21
         msg = self._msg
20
-        for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
22
+        if isinstance(self._exceptions, dict):
23
+            for_iter = self._exceptions.items()
24
+        else:
25
+            for_iter = enumerate(self.__exceptions)
21 26
         for obj, expt in for_iter:
22 27
             msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
23 28
                     expt_obj = obj,
@@ -36,7 +41,7 @@ class LeQuery(object):
36 41
     ##@brief Abstract constructor
37 42
     # @param target_class LeObject : class of object the query is about
38 43
     def __init__(self, target_class):
39
-        if hook_prefix is None:
44
+        if self._hook_prefix is None:
40 45
             raise NotImplementedError("Abstract class")
41 46
         if not issubclass(target_class, LeObject):
42 47
             raise TypeError("target class has to be a child class of LeObject")
@@ -49,9 +54,11 @@ class LeQuery(object):
49 54
     #
50 55
     # @note maybe the datasource in not an argument but should be determined
51 56
     #elsewhere
52
-    def execute(self, datasource, **datas = None):
57
+    def execute(self, datasource, datas = None):
53 58
         if len(datas) > 0:
54
-            self.__target_class.check_datas_value(datas, **self._data_check_args)
59
+            self.__target_class.check_datas_value(
60
+                                                    datas,
61
+                                                    **self._data_check_args)
55 62
             self.__target_class.prepare_datas() #not yet implemented
56 63
         if self._hook_prefix is None:
57 64
             raise NotImplementedError("Abstract method")
@@ -90,48 +97,196 @@ class LeFilteredQuery(LeQuery):
90 97
     # @param query_filters list : with a tuple (only one filter) or a list of tuple
91 98
     #   or a dict: {OP,list(filters)} with OP = 'OR' or 'AND
92 99
     #   For tuple (FIELD,OPERATOR,VALUE)
93
-    def __init__(self, target_class, query_filter):
100
+    def __init__(self, target_class, query_filters = None):
94 101
         super().__init__(target_class)
95 102
         ##@brief The query filter
96 103
         self.__query_filter = None
97 104
         self.set_query_filter(query_filter)
98 105
     
99
-    ##@brief Set the query filter for a query
100
-    def set_query_filter(self, query_filter):
101
-        #
102
-        #   Query filter check & prepare 
103
-        #   query_filters can be a tuple (only one filter), a list of tuple
104
-        #   or a dict: {OP,list(filters)} with OP = 'OR' or 'AND
105
-        #   For tuple (FIELD,OPERATOR,VALUE)
106
-        #   FIELD has to be in the field_names list of target class
107
-        #   OPERATOR in query_operator attribute
108
-        #   VALUE has to be a correct value for FIELD
106
+    ##@brief Add filter(s) to the query
107
+    #@param query_filter list|tuple|str : A single filter or a list of filters
108
+    #@see LeFilteredQuery._prepare_filters()
109
+    def filter(self, query_filter):
110
+        pass
111
+
112
+    ## @brief Prepare filters for datasource
113
+    # 
114
+    #A filter can be a string or a tuple with len = 3.
115
+    #
116
+    #This method divide filters in two categories :
117
+    #
118
+    #@par Simple filters
119
+    #
120
+    #Those filters concerns fields that represent object values (a title,
121
+    #the content, etc.) They are composed of three elements : FIELDNAME OP
122
+    # VALUE . Where :
123
+    #- FIELDNAME is the name of the field
124
+    #- OP is one of the authorized comparison operands ( see 
125
+    #@ref LeFilteredQuery.query_operators )
126
+    #- VALUE is... a value
127
+    #
128
+    #@par Relational filters
129
+    #
130
+    #Those filters concerns on reference fields ( see the corresponding
131
+    #abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
132
+    #The filter as quite the same composition than simple filters :
133
+    # FIELDNAME[.REF_FIELD] OP VALUE . Where :
134
+    #- FIELDNAME is the name of the reference field
135
+    #- REF_FIELD is an optionnal addon to the base field. It indicate on wich
136
+    #field of the referenced object the comparison as to be done. If no
137
+    #REF_FIELD is indicated the comparison will be done on identifier.
138
+    #
139
+    #@param cls
140
+    #@param filters_l list : This list of str or tuple (or both)
141
+    #@return a tuple(FILTERS, RELATIONNAL_FILTERS
142
+    #@todo move this doc in another place (a dedicated page ?)
143
+    @classmethod
144
+    def _prepare_filters(cls, filters_l):
145
+        filters = list()
146
+        res_filters = list()
147
+        rel_filters = list()
148
+        err_l = dict()
149
+        #Splitting in tuple if necessary
150
+        for fil in filters_l:
151
+            if len(fil) == 3 and not isinstance(fil, str):
152
+                filters.append(tuple(fil))
153
+            else:
154
+                filters.append(cls.split_filter(fil))
109 155
 
110
-        fieldnames = self.__target_class.fieldnames()
111
-        # Recursive method which checks filters
112
-        def check_tuple(tupl, fieldnames, target_class):
113
-            if isinstance(tupl, tuple):
114
-                if tupl[0] not in fieldnames:
115
-                    return False
116
-                if tupl[1] not in self.query_operators:
117
-                    return False
118
-                if not isinstance(tupl[2], target_class.datahandler(tupl[0])):
119
-                    return False
120
-                return True
121
-            elif isinstance(tupl,dict):
122
-                return check_tuple(tupl[1])
123
-            elif isinstance(tupl,list):
124
-                for tup in tupl:
125
-                    return check_tuple(tup)
126
-            else: 
127
-                raise TypeError("Wrong filters for query")
156
+        for field, operator, value in filters:
157
+            # Spliting field name to be able to detect a relational field
158
+            field_spl = field.split('.')
159
+            if len(field_spl) == 2:
160
+                field, ref_field = field_spl
161
+            elif len(field_spl) == 1:
162
+                ref_field = None
163
+            else:
164
+                err_l[field] = NameError(   "'%s' is not a valid relational \
165
+field name" % fieldname)
166
+                continue   
167
+            # Checking field against target_class
168
+            ret = self.__check_field(self.__target_class, field)
169
+            if isinstance(ret, Exception):
170
+                err_l[field] = ret
171
+                continue
172
+            # Check that the field is relational if ref_field given
173
+            if ref_field is not None and not cls.field(field).is_reference():
174
+                # inconsistency
175
+                err_l[field] = NameError(   "The field '%s' in %s is not\
176
+a relational field, but %s.%s was present in the filter"
177
+                                            % ( field,
178
+                                                field,
179
+                                                ref_field))
180
+            # Prepare relational field
181
+            if cls.field(field).is_reference():
182
+                ret = cls._prepare_relational_fields(field, ref_field)
183
+                if isinstance(ret, Exception):
184
+                    err_l[field] = ret
185
+                else:
186
+                    rel_filters.append((ret, operator, value))
187
+            else:
188
+                res_filters.append((field,operator, value))
189
+        
190
+        if len(err_l) > 0:
191
+            raise LeApiDataCheckError(
192
+                                        "Error while preparing filters : ",
193
+                                        err_l)
194
+        return (res_filters, rel_filters)
195
+
196
+    ## @brief Check and split a query filter
197
+    # @note The query_filter format is "FIELD OPERATOR VALUE"
198
+    # @param query_filter str : A query_filter string
199
+    # @param cls
200
+    # @return a tuple (FIELD, OPERATOR, VALUE)
201
+    @classmethod
202
+    def split_filter(cls, query_filter):
203
+        if cls._query_re is None:
204
+            cls.__compile_query_re()
205
+        matches = cls._query_re.match(query_filter)
206
+        if not matches:
207
+            raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
208
+        result = (matches.group('field'), re.sub(r'\s', ' ', matches.group('operator'), count=0), matches.group('value').strip())
209
+        for r in result:
210
+            if len(r) == 0:
211
+                raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
212
+        return result
213
+
214
+    ## @brief Compile the regex for query_filter processing
215
+    # @note Set _LeObject._query_re
216
+    @classmethod
217
+    def __compile_query_re(cls):
218
+        op_re_piece = '(?P<operator>(%s)'%cls._query_operators[0].replace(' ', '\s')
219
+        for operator in cls._query_operators[1:]:
220
+            op_re_piece += '|(%s)'%operator.replace(' ', '\s')
221
+        op_re_piece += ')'
222
+        cls._query_re = re.compile('^\s*(?P<field>(((superior)|(subordinate))\.)?[a-z_][a-z0-9\-_]*)\s*'+op_re_piece+'\s*(?P<value>[^<>=!].*)\s*$', flags=re.IGNORECASE)
223
+        pass
128 224
 
129
-        check_ok=check_tuple(query_filter, fieldnames, self.__target_class)
130
-        if check_ok:            
131
-            self.__query_filter = query_filter
132
-            
133
-		def execute(self, datasource, **datas = None):
134
-			super().execute(datasource, **datas)
225
+    @classmethod
226
+    def __check_field(cls, target_class, fieldname):
227
+        try:
228
+            target_class.field(fieldname)
229
+        except NameError:
230
+            tc_name = target_class.__name__
231
+            return ValueError("No such field '%s' in %s" % (    fieldname,
232
+                                                                tc_name))
233
+
234
+    ##@brief Prepare a relational filter
235
+    #
236
+    #Relational filters are composed of a tuple like the simple filters
237
+    #but the first element of this tuple is a tuple to :
238
+    #
239
+    #<code>( (FIELDNAME, {REF_CLASS: REF_FIELD}), OP, VALUE)</code>
240
+    # Where :
241
+    #- FIELDNAME is the field name is the target class
242
+    #- the second element is a dict with :
243
+    # - REF_CLASS as key. It's a LeObject child class
244
+    # - REF_FIELD as value. The name of the referenced field in the REF_CLASS
245
+    #
246
+    #Visibly the REF_FIELD value of the dict will vary only when
247
+    #no REF_FIELD is explicitly given in the filter string notation
248
+    #and REF_CLASSES has differents uid
249
+    #
250
+    #@par String notation examples
251
+    #<pre>contributeur IN (1,2,3,5)</pre> will be transformed into :
252
+    #<pre>(
253
+    #       (
254
+    #           contributeur, 
255
+    #           {
256
+    #               auteur: 'lodel_id',
257
+    #               traducteur: 'lodel_id'
258
+    #           } 
259
+    #       ),
260
+    #       ' IN ',
261
+    #       [ 1,2,3,5 ])</pre>
262
+    #@todo move the documentation to another place
263
+    #
264
+    #@param fieldname str : The relational field name
265
+    #@param ref_field str|None : The referenced field name (if None use
266
+    #uniq identifiers as referenced field
267
+    #@return a well formed relational filter tuple or an Exception instance
268
+    @classmethod
269
+    def __prepare_relational_fields(cls, fieldname, ref_field = None):
270
+        datahandler = self.__target_class.field(fieldname)
271
+        # now we are going to fetch the referenced class to see if the
272
+        # reference field is valid
273
+        ref_classes = datahandler.linked_classes
274
+        ref_dict = dict()
275
+        if ref_field is None:
276
+            for ref_class in ref_classes:
277
+                ref_dict[ref_class] = ref_class.uid_fieldname
278
+        else:
279
+            for ref_class in ref_classes:
280
+                if ref_field in ref_class.fieldnames(True):
281
+                    ref_dict[ref_class] = ref_field
282
+                else:
283
+                    logger.debug("Warning the class %s is not considered in \
284
+the relational filter %s" % ref_class.__name__)
285
+        if len(ref_dict) == 0:
286
+            return NameError(   "No field named '%s' in referenced objects"
287
+                                % ref_field)
288
+        return ( (fieldname, ref_dict), op, value)
289
+ 
135 290
 
136 291
 ##@brief A query to insert a new object
137 292
 class LeInsertQuery(LeQuery):
@@ -178,7 +333,7 @@ class LeUpdateQuery(LeFilteredQuery):
178 333
         l_uids=datasource.select(self.__target_class,list(self.__target_class.getuid()),query_filter,None, None, None, None, 0, False)
179 334
         # list of dict l_uids : _uid(s) of the objects to be updated, corresponding datas
180 335
         nb_updated = datasource.update(self.__target_class,l_uids, **datas)
181
-        if (nb_updated != len(l_uids):
336
+        if nb_updated != len(l_uids):
182 337
             raise LeQueryError("Number of updated items: %d is not as expected: %d " % (nb_updated, len(l_uids)))
183 338
         return nb_updated
184 339
     
@@ -206,7 +361,7 @@ class LeDeleteQuery(LeFilteredQuery):
206 361
         l_uids=datasource.select(self.__target_class,list(self.__target_class.getuid()),query_filter,None, None, None, None, 0, False)
207 362
         # list of dict l_uids : _uid(s) of the objects to be deleted
208 363
         nb_deleted = datasource.update(self.__target_class,l_uids, **datas)
209
-        if (nb_deleted != len(l_uids):
364
+        if nb_deleted != len(l_uids):
210 365
             raise LeQueryError("Number of deleted items %d is not as expected %d " % (nb_deleted, len(l_uids)))
211 366
         return nb_deleted
212 367
 

+ 3
- 4
lodel/plugin/plugins.py View File

@@ -87,12 +87,11 @@ class Plugins(object):
87 87
         
88 88
     ##@brief Bootstrap the Plugins class
89 89
     @classmethod
90
-    def bootstrap(cls, plugins_directories):
90
+    def bootstrap(cls):
91 91
         from lodel.settings import Settings
92
-        cls.start(Settings.plugins_path)
92
+        for plugin_name in Settings.plugins:
93
+            cls.load_plugin(plugin_name)
93 94
     
94
-    ##@brief Start the Plugins class by explicitly giving a plugin directory path
95
-    # @param plugins_directories list : List of path
96 95
     @classmethod
97 96
     def start(cls, plugins_directories):
98 97
         import inspect

+ 9
- 1
lodel/settings/settings_loader.py View File

@@ -5,6 +5,7 @@ import glob
5 5
 import copy
6 6
 
7 7
 from lodel.settings.utils import *
8
+from lodel.settings.validator import SettingsValidationError
8 9
 
9 10
    
10 11
 ##@brief Merges and loads configuration files
@@ -60,7 +61,14 @@ class SettingsLoader(object):
60 61
             sec=conf[section]
61 62
             if keyname in sec:
62 63
                 optionstr=sec[keyname]['value']
63
-                option= validator(sec[keyname]['value'])
64
+                try:
65
+                    option= validator(sec[keyname]['value'])
66
+                except Exception as e:
67
+                    raise SettingsValidationError(
68
+                                                    "For %s.%s : %s" % 
69
+                                                    (section, keyname,e)
70
+                    )
71
+
64 72
                 try:
65 73
                     del self.__conf_sv[section + ':' + keyname]
66 74
                 except KeyError: #allready fetched

+ 14
- 4
lodel/settings/validator.py View File

@@ -168,15 +168,20 @@ def directory_val(value):
168 168
 def loglevel_val(value):
169 169
     valids = ['DEBUG', 'INFO', 'SECURITY', 'ERROR', 'CRITICAL']
170 170
     if value.upper() not in valids:
171
-        raise SettingsValidationError("The value '%s' is not a valid loglevel")
171
+        raise SettingsValidationError(
172
+                "The value '%s' is not a valid loglevel" % value)
172 173
     return value.upper()
173 174
 
174 175
 def path_val(value):
175 176
     if not os.path.exists(value):
176
-        raise SettingsValidationError("The value '%s' is not a valid path")
177
+        raise SettingsValidationError(
178
+                "path '%s' doesn't exists" % value)
177 179
     return value
178 180
 
179
-def dummy_val(value): return value
181
+def none_val(value):
182
+    if value is None:
183
+        return None
184
+    raise SettingsValidationError("This settings cannot be set in configuration file")
180 185
 
181 186
 #
182 187
 #   Default validators registration
@@ -184,9 +189,14 @@ def dummy_val(value): return value
184 189
 
185 190
 SettingValidator.register_validator(
186 191
                                         'dummy',
187
-                                        dummy_val,
192
+                                        lambda value:value,
188 193
                                         'Validate anything')
189 194
 
195
+SettingValidator.register_validator(
196
+                                        'none',
197
+                                        none_val,
198
+                                        'Validate None')
199
+
190 200
 SettingValidator.register_validator(
191 201
                                         'strip',
192 202
                                         str.strip,

+ 2
- 2
lodel/template/loader.py View File

@@ -29,7 +29,7 @@ class TemplateLoader(object):
29 29
     #
30 30
     # @return str. String containing the HTML output of the processed templated
31 31
     def render_to_html(self, template_file, template_vars={}, template_extra=None):
32
-        loader = jinja2.FileSystemLoader(searchpath=self.search_path, followlinks=self.follow_links)
32
+        loader = jinja2.FileSystemLoader(searchpath=self.search_path)
33 33
         environment = jinja2.Environment(loader=loader) if self.is_cache_active else jinja2.Environment(loader=loader,
34 34
                                                                                                         cache_size=0)
35 35
         template = environment.get_template(template_file)
@@ -68,4 +68,4 @@ class TemplateLoader(object):
68 68
     # @param key str
69 69
     # @return bool
70 70
     def _is_allowed_template_key(self, key):
71
-        return False if key in self.__class__.__reserved_template_keys else True
71
+        return False if key in self.__class__.__reserved_template_keys else True

+ 0
- 0
plugins/webui/__init__.py View File


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

@@ -0,0 +1,22 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from lodel.settings.validator import SettingValidator
4
+
5
+CONFSPEC = {
6
+    'lodel2.webui': {
7
+        'standalone': ( False,
8
+                        SettingValidator('bool')),
9
+        'listen_address': ( '127.0.0.1',
10
+                            SettingValidator('dummy')),
11
+        'listen_port': (    '9090',
12
+                            SettingValidator('int')),
13
+    },
14
+    'lodel2.webui.sessions': {
15
+        'directory': (  '/tmp/lodel2_session',
16
+                        SettingValidator('path')),
17
+        'expiration': ( 900,
18
+                        SettingValidator('int')),
19
+        'file_template': (  'lodel2_%s.sess',
20
+                            SettingValidator('dummy')),
21
+    }
22
+}

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

@@ -0,0 +1,15 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import os
4
+from lodel.plugin import LodelHook
5
+from lodel.settings import Settings
6
+
7
+##@brief uwsgi startup demo
8
+@LodelHook('lodel2_loader_main')
9
+def uwsgi_fork(hook_name, caller, payload):
10
+    if Settings.webui.standalone:
11
+        cmd='uwsgi_python3 --http-socket {addr}:{port} --module run'
12
+        cmd = cmd.format(
13
+                    addr = Settings.webui.listen_address,
14
+                    port = Settings.webui.listen_port)
15
+        exit(os.system(cmd))

+ 3
- 2
requirements.txt View File

@@ -1,12 +1,13 @@
1 1
 astroid==1.4.4
2 2
 itsdangerous==0.24
3
-Jinja2==2.8
3
+Jinja2==2.7.3
4 4
 lazy-object-proxy==1.2.1
5 5
 logilab-common==1.2.0
6 6
 MarkupSafe==0.23
7 7
 pep8==1.6.2
8 8
 pylint==1.4.4
9
-pymongo==3.2.2
9
+pymongo==3.0.3
10 10
 six==1.10.0
11 11
 Werkzeug==0.11.4
12 12
 wrapt==1.10.6
13
+lxml==3.6.0

+ 12
- 1
scripts/create_instance.sh View File

@@ -29,8 +29,19 @@ fi
29 29
 
30 30
 echo "Creating lodel instance directory '$instdir'"
31 31
 mkdir -pv "$instdir"
32
+mkdir -pv "$instdir/sessions"
33
+chmod 700 "$instdir/sessions"
34
+
35
+#cp -Rv $libdir/install/* $instdir
36
+cp -Rv $libdir/install/conf.d $instdir/
37
+cp -Rv $libdir/install/loader.py $instdir/
38
+cp -Rv $libdir/install/editorial_model.pickle $instdir/
39
+ln -sv $libdir/install/Makefile $instdir/Makefile
40
+ln -sv $libdir/install/run.py $instdir/run.py
41
+ln -sv $libdir/install/lodel_admin.py $instdir/lodel_admin.py
42
+ln -sv $libdir/plugins $instdir/plugins
43
+
32 44
 
33
-cp -Rv $libdir/install/* $instdir
34 45
 
35 46
 # Adding lib path to loader
36 47
 sed -i -E "s#^(LODEL2_LIB_ABS_PATH = )None#\1'$libdir'#" "$loader"

Loading…
Cancel
Save