1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-09-14 13:50:28 +02:00

Compare commits

...

114 commits

Author SHA1 Message Date
Yann
a03f6f58ec Deleted debug print in lodel.context 2017-04-21 15:25:09 +02:00
Yann
745446f6e3 Bugfix in site loading in multisite
The dyncode has to be boostraped after plugin preload and before plugin
full load (the plugins_loaded hook triggers dyncode datasource init)
2017-04-21 15:23:28 +02:00
Yann
366f56059f LodelContext enhancement
Now a static dict is stored designed to stores imported module by context
2017-04-21 15:22:34 +02:00
Yann
a9c457d38f Bugfix in model groups activation 2017-04-21 12:35:04 +02:00
Yann
71213ef58e Bugfix on multisite dyncode generation
The handled sites groups were not read from settings
2017-04-21 12:14:17 +02:00
Yann
6d8d3fd021 Various bugfix & enhancement
Better checks for datasources initialisation
The dyncode were not bootstraped by bootstrap.site_load() function
2017-04-21 11:52:56 +02:00
Yann
3c41d40072 Bugfix in multisite template
The both em pickle files were the same
2017-04-21 11:51:52 +02:00
Yann
e17c4f4468 Enhancement in webui router : replacing imports
using LodelContext.expose_modules instead of imports
2017-04-21 11:05:17 +02:00
Yann
ac75d1cd71 Bugfix on multisite web run
Now able to run multisite and handled sites. Some addaptations are needed
because handled childs always return 404
2017-04-21 10:28:13 +02:00
Yann
56cd806ceb Bugfix in various loader & bootstrap functions
Now we are able to generate dyncode for lodelsites and handled sites (
tested with only 1 handled site) using lodel_admin.py dyncode -a
2017-04-21 10:13:52 +02:00
Yann
bdb7007525 Bugfix on multisite run process 2017-04-21 09:28:31 +02:00
Yann
ff1baa4467 Small bugfix on lodel_admin.py dyncode
Now it seems to work when no handled sites registered
2017-04-21 09:14:39 +02:00
Yann
10332f3f87 Bugfix in bootstrap get_handled_sites_name()
Was missing : full plugin loading & hooks calls
2017-04-20 16:29:57 +02:00
Yann
f1eb6ac41a Bugfix in hanlded sites gathering 2017-04-20 15:45:35 +02:00
Yann
f620b4e31b Debuging lodel_admin dyncode action
Now following commands seems to work (on default multisite template) :
python3 lodel_admin.py dyncode
python3 lodel_admin.py dyncode -m sites_em.pickle -o sites_leapi_dyncode.py
2017-04-20 14:45:08 +02:00
Yann
82a6ab95c3 Bugfix in bootstrap lodelsites preloading 2017-04-20 14:29:12 +02:00
Yann
773f6cdd06 Bugfix in name guess in bootstrap module 2017-04-20 14:19:12 +02:00
Roland Haroutiounian
ceee53c5bb Import issue fix 2017-04-05 14:28:21 +02:00
Yann
ffd4ef21d5 Begin to fix some boostraping problems
Moved some function from plugin specific code to the bootstrap module
+ some bugfixes
2017-03-31 17:39:45 +02:00
Yann
3e3b4740da Small bugfix & enhancement 2017-03-31 11:22:44 +02:00
Yann
286e3ffe10 Updated builconf with lodel package path 2017-03-31 11:22:15 +02:00
Yann
97b59379f4 Bugfixing lodelsites datasource 2017-03-31 10:25:52 +02:00
Yann
689d6556c3 Updates lodelsites conf template 2017-03-31 10:18:37 +02:00
Yann
a567abc187 Implements inconsistency detection between db & fs
In multisite loader utils
2017-03-24 17:39:11 +01:00
Yann
b5f9f97031 Bugfix & compatibility for webui
In this state I was able to insert a new site. But only the mongodb datasource
seems to have receive the query (inserted in DB but not created on FS).
2017-03-24 17:25:56 +01:00
Yann
4cd5be0147 Fixes #204 2017-03-24 17:07:11 +01:00
Yann
2f6cdd279d Add a missing file 2017-03-24 15:01:53 +01:00
Yann
47fa14e566 Merge branch 'master' into lodelsites-dev
Conflicts:
	lodel/editorial_model/components.py
	lodel/editorial_model/model.py
	lodel/plugin/plugins.py
	lodel/plugins/Makefile.am
	lodel/plugins/webui/templates/admin/editable_component.html
	lodel/validator/validator.py
Revert:
	lodel/validator/validator.py 3d51695ea6
		The hook HAS TO be registered even if not value !
2017-03-24 14:45:44 +01:00
Yann
c3aa0aa85c Updated em pickle representation 2017-03-23 15:33:23 +01:00
Yann
cad6962fea Bugfix in webui
Deleted hardcoded Object class name + adding support for varchar & text
in editable_component template
2017-03-23 14:53:03 +01:00
Yann
236861083f Adapting plugins to implements a multisite web interface 2017-03-23 14:25:54 +01:00
Yann
5467df8184 Fixing multisite loader
Now it seems to work. Tested using :
dummy datasource, no handled sites, ipython ui
2017-03-23 09:30:57 +01:00
Yann
95305a88ba Add a small workarround for sicover plugin in multisite
Conditionnal plugins path given the context type (mono or multi site)
2017-03-23 09:14:15 +01:00
Yann
d3b88c06b2 Begin LodelContext addaptation in order to support dyncodes
Now the directory tree looks like :
.sites_context/lodelsites/NAME/__init__.py
.sites_context/lodelsites/NAME/leapi_dyncode.py
.sites_context/lodelsites/NAME/lodel/ <- symlink
2017-03-22 17:36:28 +01:00
Yann
8006bfb080 Now lodel_admin.py dyncode seems functionnal on multisite
Only tested for lodelsites instance
2017-03-22 17:35:46 +01:00
Yann
63f6143003 Typo fixing 2017-03-22 17:33:24 +01:00
Yann
f2a8578981 Bugfixes in dyncode core_script 2017-03-22 15:24:24 +01:00
Yann
ee6db4f895 Bugfix about pickle translator & LodelContext
The is_instance test on classes loaded from a pickle file is not
relevant :
lodel.editorial_model.components.EmClass != lodelsites.__loader__.editorial_model.components.EmClass
2017-03-22 15:18:46 +01:00
Yann
6e4942c6a9 Bugfix on lodeladmin allowing to run dyncode update
There is a problem left : when dyncode runs an error is triggered telling
that no active clases were found
2017-03-22 15:06:31 +01:00
Yann
f9ade94a06 Merged server.conf.d and lodelsites_conf.d for lodelsites
Now both __loader__ and lodelsites contexts are using the same configuration
files and the same confspecs.
2017-03-22 14:18:03 +01:00
Yann
e877adf9c9 Seems to fixes #250 and fixes #316 2017-03-22 11:58:41 +01:00
Yann
c4b37e151a Bugfix on monosite deployment
Now using mass_deploy and slim seems to work for a monosite
2017-03-22 11:37:13 +01:00
Yann
52c8be3398 Add the multisite_install_model dir 2017-03-07 12:09:30 +01:00
Yann
373772cf01 Updated lodel_admin.py script + started to write an updateDyncode core script 2017-03-07 12:08:08 +01:00
Yann
5eef806aa4 Settings exception & error enhancement 2017-03-07 12:07:23 +01:00
Yann
9a011fc615 Update create_multisite script 2017-03-07 12:06:31 +01:00
Yann
4135760ee4 An attemp to factorise loaders into a bootstrap module 2017-03-07 12:05:30 +01:00
Yann
ccbb05f5d0 Enhancement in create_multisite.sh script
Now create the full FS structure associated with a multisite instance
2017-03-02 11:38:35 +01:00
Yann
3e8b6bc30d Reverting AC_INIT variable
Changing lodel2 to lodel again. Lodel2 was breaking the lodel package naming
it lodel2 leading to import failures when attempting to import lodel
2017-03-01 16:55:16 +01:00
Yann
3bff03424c Small bugfix in lodelcontext 2017-03-01 15:08:16 +01:00
Yann
6cb55301cc Enhancement in plugins messages
The plugin and the settings classes are not able to use the logger in
early bootstrap stages. In order to see messages at thoses stages
we replace the logger by warnings.warn()
2017-03-01 15:07:15 +01:00
Yann
e10d8eb3bc Starting lodel_admin.py enhancement
But a large majority of works remain !
2017-03-01 15:05:03 +01:00
Yann
83b54cd523 Small bugfixes in lodelsites datasource
Adaptation to the new datahandlers
2017-03-01 15:02:28 +01:00
Yann
5aa1ac55cf Enhancement in tests runner
Now we set a PYTHONPATH variable instead of hacking inside python code
2017-03-01 15:01:27 +01:00
Yann
afea7e74e2 Small Makefile.am changes/bugfixes 2017-03-01 14:59:46 +01:00
Yann
4052440dab [dirty] Small bugfix assuming that some files are missing in previous commit 2017-03-01 14:58:55 +01:00
Quentin Bonaventure
46626b8830 Little forgotten flag... 2017-02-24 12:26:47 +01:00
Quentin Bonaventure
0fc3503a12 Replaced a handful of mkdir -p by $(MKDIR_P) 2017-02-24 12:01:50 +01:00
Quentin Bonaventure
e2c39e7b96 Fixed make deb failures (#250) 2017-02-24 11:55:05 +01:00
Quentin Bonaventure
87de9d653e Autotools fix for deb target
Recent changes broke the 'deb' target building process. Now fixed and
back at work !
2017-02-24 09:20:27 +01:00
Quentin Bonaventure
e640ff96e7 Autotools propagation of multisite variables
Better access to a few variables into the .am files. Allows for single
point of definition of variables required accross various .am.
For concerns separation, may probably switch from Makefile-common.am.inc
to Makefile-multisite.am.inc.
2017-02-23 11:39:55 +01:00
Quentin Bonaventure
cf8a1f488f Little typo in dir name
names was set to "sites_datas" isntead of "sites_data"...
2017-02-23 11:00:11 +01:00
Quentin Bonaventure quentin.bonaventure@openedition.org
98c72f5d9d Merge branch 'lodelsites-dev' of git@git.labocleo.org:lodel2 into lodelsites-dev 2017-02-23 10:54:25 +01:00
Quentin Bonaventure
1e15deaec3 Multisite creation
Allows creating multisite instances, along with its directories creation
into /var/lodel2
2017-02-23 10:54:17 +01:00
Yann
26f586ad84 Bugfix + forgotten file for lodelsites implementation 2017-02-23 09:49:48 +01:00
Yann
15e97bf2bd Bugfix in plugin Validator
Now an empty string is replaced by None
2017-02-23 09:45:00 +01:00
Yann
57c21f7f16 Merge branch 'master' into lodelsites-dev
In order to have settings_loader #308 bugfix

Conflicts:
	lodel/editorial_model/model.py
	lodel/leapi/datahandlers/base_classes.py
	lodel/plugins/multisite/confspecs.py
2017-02-22 17:18:11 +01:00
Yann
97ab8b8ea3 [broken state] first implementation of multisite loader
It's only the begining, I have to make a commit in order to be able
to fix the settings on master
2017-02-22 16:24:22 +01:00
Yann
7d221b499e Deleted useless files 2017-02-22 14:30:26 +01:00
Yann
6c721a702c Autotools bugfixes 2017-02-10 14:14:42 +01:00
Yann
e68312098c Implements a model for create_multisite script 2017-02-10 11:09:45 +01:00
Quentin Bonaventure
2bd1d3cf48 Now creates lower case instance names 2017-02-09 17:00:30 +01:00
Yann
93b713edf0 Dirty workaround in plugins 2017-02-09 16:57:30 +01:00
Quentin Bonaventure quentin.bonaventure@openedition.org
a7874c34c8 Merge branch 'lodelsites-dev' of git@git.labocleo.org:lodel2 into lodelsites-dev 2017-02-09 16:45:56 +01:00
Quentin Bonaventure
cb236c6450 Removed dist-pkg/lodelsites creation 2017-02-09 16:45:47 +01:00
Yann
f4386d9b83 Bugfix in lodel.context 2017-02-09 16:43:59 +01:00
Yann
e621f4adc5 Small bugfixes allowing deb pkg installation
Only syntax errors
2017-02-09 14:40:26 +01:00
Quentin Bonaventure
1ea521c971 Syntax error fix 2017-02-09 14:37:27 +01:00
Yann
816d99adf6 [untested] Implements dyncode update in lodelsites
Implements dyncode update for handled sites in lodelsites. solves #260
2017-02-09 14:21:27 +01:00
Yann
8acd34dfdd EditorialModel __set_active mod at runtime solves #281
Now we are able to control EditorialModel load process at runtime
for lodelsites handled sites dyncode generation
2017-02-09 13:52:37 +01:00
Quentin Bonaventure quentin.bonaventure@openedition.org
4c27a6bf87 Merge branch 'lodelsites-dev' of git@git.labocleo.org:lodel2 into lodelsites-dev 2017-02-09 08:47:04 +01:00
Quentin Bonaventure
ffd4e1d2b3 buildconf.py.am.in !? 2017-02-09 08:46:53 +01:00
Yann
c117f97c6e Typo fixing in multisite confspec 2017-02-08 17:40:36 +01:00
Quentin Bonaventure
aadfdac24b Added LODEL_PKG_PATH const 2017-02-08 17:17:03 +01:00
quentin
6586fbebfc Merge branch 'lodelsites-dev' of git@git.labocleo.org:lodel2 into lodelsites-dev
Conflicts:
	lodel/buildconf.py.am.in
2017-02-08 17:14:58 +01:00
Quentin Bonaventure
c6ec0aba2a Modifies lodel site install paths 2017-02-08 17:09:48 +01:00
Yann
68cd6a0c7d Updated confspec for multisite plugins
Introduce a new dependencie between lodelContext & multisite plugin
2017-02-08 17:03:00 +01:00
Yann
ac1d704791 Enhancement in context handling
Now the context manager handles lodelsites paths
2017-02-08 16:16:47 +01:00
Yann
0b337fd4b5 Modification of exposure process in LodelContext
Thoses modification has two goals :
- make lodelcontext fit with the new FS organisation implemented by the
lodelsites datasource
- make the dyncode exposure more "flexible"

Note : no solution implemented for MONOSITE
2017-02-08 15:38:54 +01:00
quentin
6953362a6b Merge branch 'lodelsites-dev' of git@git.labocleo.org:lodel2 into lodelsites-dev
Conflicts:
	lodel/plugins/lodelsite_datasource/fs_utils.py
2017-02-08 14:31:14 +01:00
Yann
6c642e3a41 Modified some constants in buildconf & in lodesites fs_utils 2017-02-08 14:26:16 +01:00
Quentin Bonaventure
d3bf43f3ff fixed modules exposed and paths 2017-02-08 14:25:31 +01:00
Yann
9df0443a8b [broken] Starting multisite loader rewriting refs #235
At this point the loader in lodel/plugins/multisite/run.py is theorically
complete & correct. But some features are missing in order to be able
to test & debug it ( refs #275 ).
2017-02-03 17:26:28 +01:00
Quentin Bonaventure quentin.bonaventure@openedition.org
d8b95da6f8 Merge branch 'lodelsites-dev' of git@git.labocleo.org:lodel2 into lodelsites-dev 2017-02-03 16:16:48 +01:00
Quentin Bonaventure
738fff7494 Fixed Lodelsites path in fs_utils 2017-02-03 16:16:40 +01:00
Yann
f2394ba7e1 Implement the translator for external EM 2017-02-03 14:19:58 +01:00
Yann
3ede546be4 Merge branch 'lodelsites-dev' of git.labocleo.org:lodel2 into lodelsites-dev
Conflicts:
	lodel/plugins/lodelsite_datasource/fs_utils.py
2017-02-03 13:53:24 +01:00
Yann
d5cf4cf668 Small bugfixes + pep8 2017-02-03 13:51:59 +01:00
Yann
f6001dbd20 Add a numeric PK for lodelsite EmClass. solves #255 2017-02-03 13:50:40 +01:00
Quentin Bonaventure
d0ff939544 bugfix on fs_utils.py related to Bug #253 2017-02-03 13:42:25 +01:00
Quentin Bonaventure
470dbfbca0 [unimportant] English corrections 2017-02-03 12:23:19 +01:00
Quentin Bonaventure
1c3edfba98 [Unimportant] english corrections 2017-02-03 12:17:08 +01:00
Quentin Bonaventure
3619f66a48 Fixed another typo caused b the first one 2017-02-03 11:55:40 +01:00
Quentin Bonaventure
bc9a0a0e6b Typo correction 2017-02-03 11:36:39 +01:00
Yann
d202a55773 Bugfixes in LodelSiteDatasource
Now we are able to insert a lodelsite
2017-02-02 16:41:05 +01:00
Quentin Bonaventure
a33f1daaeb Added basic User class to the EM 2017-02-02 15:11:43 +01:00
Yann
695ce80fc5 Merge branch 'master' into lodelsites-dev 2017-02-02 12:02:08 +01:00
Quentin Bonaventure
8a8a624cb7 Correction of lodelsites plugin scripts 2017-02-02 11:51:26 +01:00
Quentin Bonaventure
a3e048dcd3 Fix of the fix of base_class 2017-02-02 11:45:11 +01:00
Quentin Bonaventure
a3eb448093 Fix on base_classes 2017-02-02 11:44:15 +01:00
Yann
df2c6810a6 [broken] First LodelSiteDatasource implementation
no debug has been made on those piece of code
2017-02-02 11:32:38 +01:00
Quentin Bonaventure
819e9f62cf Added check against primary_key and uniq
As a primary_key field has to be unique, added a check at DataHandler
instanciation to make sure a field set as a pk is not set as non-unique
2017-02-02 11:12:30 +01:00
Quentin Bonaventure
5f304cd64e Switched shortname to primary_key
Modified the shortname's DH attribute value from uniq to primary_key
2017-02-02 11:02:13 +01:00
Quentin Bonaventure
94e7cf412a Changed shortname DH
Switched the shortname field's DH from Varchar to Regex
2017-02-02 10:44:37 +01:00
59 changed files with 2093 additions and 401 deletions

2
Makefile-common.am.inc Normal file
View file

@ -0,0 +1,2 @@
lodel2_multisite_data_dirname=sites_data
lodel2_multisite_ctx_dirname=.sites_contexts/lodelsites

View file

@ -1,5 +1,5 @@
SUBDIRS = lodel progs lodelsites
EXTRA_DIST = runtest examples tests debian
SUBDIRS = lodel progs
EXTRA_DIST = runtest examples tests debian Makefile-common.am.inc
CLEANFILES = runtest
lodel2_localstate_DATA =
@ -33,7 +33,7 @@ runtest: ./runtest.sh
#Adding logdir creation on install
install-data-hook:
mkdir -p ${DESTDIR}$(lodel2_localstatedir); mkdir -p ${DESTDIR}$(docdir); cp README ${DESTDIR}$(docdir);
$(MKDIR_P) ${DESTDIR}$(lodel2_localstatedir); $(MKDIR_P) ${DESTDIR}$(docdir); cp README ${DESTDIR}$(docdir);
#Making debian package
deb: dist
@ -72,10 +72,9 @@ cleandoc:
#other cleans
distclean-local:
-rm -vR debian_package ${PACKAGE}-${VERSION}.tar.gz
-find . -name Makefile.in -delete
-rm -vR debian_package/${PACKAGE}-${VERSION}.tar.gz
gitclean: distclean cleandoc
-rm -vR autom4te.cache/ aclocal.m4 install-sh missing py-compile configure; find ./ -name Makefile.in |xargs rm -v
-rm -vR debian_package autom4te.cache/ aclocal.m4 install-sh missing py-compile configure; find ./ -name Makefile.in |xargs rm -v
.PHONY: cleandoc tests doc

View file

@ -17,8 +17,7 @@ AC_CONFIG_FILES([Makefile \
lodel/utils/Makefile \
progs/Makefile \
progs/slim/Makefile \
lodel/plugins/Makefile \
lodelsites/Makefile \
lodel/plugins/Makefile
])
@ -28,6 +27,8 @@ AC_CONFIG_FILES([Makefile \
#pyconfigure macros are prefixed by PC_
###
AC_PROG_LN_S
AC_PROG_MKDIR_P
m4_include([m4/python.m4]) # Patched version of aclocal python.m4 file
m4_include([m4/python_pyconfigure.m4]) #Renamed because we use a patched version of aclocal python.m4 file
m4_define(python_min_ver, 3.4.2)
@ -57,6 +58,7 @@ PC_PYTHON_CHECK_MODULE_VERSION([pymongo], [2.7], [2.8], ,
AC_SUBST([PYMONGO], 'False')
)
#Documentation checks
AC_SUBST([DOCOK], 'OK')
AC_CHECK_PROGS([DOXYGEN], [doxygen])

Binary file not shown.

View file

@ -1,3 +1,5 @@
include $(top_builddir)/Makefile-common.am.inc
SUBDIRS=auth editorial_model leapi plugin settings utils plugins validator mlnamedobject
EXTRA_DIST = plugins
lodel_PYTHON = *.py
@ -14,11 +16,14 @@ gitclean:
logdir = $(localstatedir)/log/lodel2/
do_subst = sed -e 's,\[@\]LODEL2_VARDIR\[@\],$(localstatedir)/lodel2/,g' \
-e 's,\[@\]LODEL2_LOGDIR\[@\],$(logdir),g'
-e 's,\[@\]LODEL2_LOGDIR\[@\],$(logdir),g' \
-e 's,\[@\]LODEL2_LODEL_PKG\[@\],$(lodeldir),g' \
-e 's,\[@\]MULTISITE_DATA_DIRNAME\[@\],$(lodel2_multisite_data_dirname),g' \
-e 's,\[@\]MULTISITE_CTX_DIRNAME\[@\],$(lodel2_multisite_ctx_dirname),g'
install-data-hook:
-mkdir -p ${DESTDIR}$(logdir)
$(MKDIR_P) ${DESTDIR}$(logdir)
uninstall-hook:
-rmdir ${DESTDIR}$(logdir)

281
lodel/bootstrap.py Normal file
View file

@ -0,0 +1,281 @@
##@brief Contains functions allowing to bootstrap a lodel instance
#
#Typically a bootstrap process consist of settings preload ,
#plugins start and finally settings loading
#
#Assertions that are made in this file :
#- cwd is an instance directory
#- nothing as been done yet (no import, no context etc)
#
import warnings
import os, os.path
import lodel.buildconf
from lodel.context import LodelContext
##@brief Hardcoded configuration dirnames given an instance type
#
#First item is for monosite the second for multisite
#First item for multisite is server conf the second the lodelsites instance
#conf
#
#@note obsolete ! We are preparing a merge of server_conf.d and
#lodelsites.conf.d
CONFS_DIRNAMES = [
'conf.d',
('server_conf.d', lodel.buildconf.LODELSITE_CONFDIR)]
##@brief Test if current instance is monosite or multisite
#@return if monosite return True else False
def _monosite_test():
return os.path.isdir('./conf.d')
##@brief Initialize the context class taking care of the instance type (MONO
#or MULTI site)
def _context_initialisation():
LodelContext.init(
LodelContext.MONOSITE if _monosite_test() else LodelContext.MULTISITE)
##@brief Return confdir name given instance type and context type
#@param ctx_type : see @ref boostrap()
#@return a configuration directory
#@todo return abspath
def _get_confdir(ctx_type):
if _monosite_test():
return CONFS_DIRNAMES[0]
return CONFS_DIRNAMES[1][1]
#elif ctx_type == '__loader__':
# return CONFS_DIRNAMES[1][0]
#elif ctx_type == 'lodelsites':
# return CONFS_DIRNAMES[1][1]
#raise ValueError("ctx_type is not one of '__loader__' nor 'lodelsites' \
#authorized values")
##@brief Return confspec associated with current context & context type
#@param ctx_type str
#@todo delete the argument
#@todo delete this function
#@return None (for default confspecs) or a confspecs dict
def _get_confspec(ctx_type):
if not _monosite_test():
LodelContext.expose_modules(globals(), {
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
return multisite_confspecs.LODEL2_CONFSPECS
return None
##@brief After calling this function you should use your instance as it
#
#@param ctx_type str : ONLY FOR MULTISITE specify wich multisite context to
#bootstrap. The two choices are '__loader__' and 'lodelsites'. Default is
#__loader__
def bootstrap(ctx_type = None):
_context_initialisation()
monosite = _monosite_test()
if ctx_type is None:
#Default value
if not _monosite_test():
ctx_type = '__loader__'
elif monosite:
raise RuntimeError("Not allowed to give a value for ctx_type in a \
MONOSITE instance")
elif ctx_type not in ['__loader__', 'lodelsites']:
raise ValueError("ctx_type is not one of '__loader__' nor \
'lodelsites' authorized values")
custom_confspecs = _get_confspec(ctx_type)
confdir = _get_confdir(ctx_type)
if not os.path.isdir(confdir):
warnings.warn("Bootstraping seems to fail : unable to find confdir \
: %s. Attempt to continue using default values" % confdir)
LodelContext.expose_modules(globals(), {
'lodel.settings.settings': [('Settings', 'settings_loader')],
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
if ctx_type is not None:
settings_loader(confdir, custom_confspecs, True) #Append specs
else:
settings_loader(confdir, custom_confspecs)
del(globals()['settings_loader'])
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings']})
##@brief Preload a site
#
#Apply a common (as MONOSITE) loading process to a site :
#1. Conf preload
#2. Plugins preload
#3. Conf loading
#
#4. starting plugins & hooks
#@warning At this point we need a uniq identifier for the site (using it
#as key for contexts & FAST_APP_EXPOSAL_CACHE). To achieve this we use
#the data_path basename. It should works for handled sites and for the
#lodelsites instance
#@warning may only work for handled sites in a multisite context
#@param data_path str : path to the datas directory (containing the confdir)
#@param confdir_basename str : the basename of the site confdir
#@param lodelsites_instance bool : if true we are loading the lodelsites
#instance of the multisite (allow to load the good confspecs)
#
#@todo For now the interface plugin name for sites is hardcoded (set to
#webui). It HAS TO be loaded from settings. But it is a bit complicated,
#we have to get the plugin's module name abstracted from context :
#lodel.something but if we ask directly to Plugin class the module name
#it will return something like : lodelsites.sitename.something...
#
#@todo there is a quick & dirty workarround with comments saying that it
#avoid context escape via hooks. We have to understand why and how and then
#replace the workarround by a real solution !
#@todo check if it works with monosite context !
#@todo change data_path argument to sitename and determine datapath from it
def site_preload(data_path, confdir_basename = 'conf.d', lodelsites_instance = False):
#args check
if confdir_basename != os.path.basename(confdir_basename):
LodelFatalError('Bad argument given to site_load(). This really \
sux !')
#Determining uniq sitename from data_path
data_path = data_path.rstrip('/')
if lodelsites_instance:
data_path = os.path.dirname(data_path).rstrip('/')
ctx_name = os.path.basename(data_path)
if not os.path.exists(data_path) or not os.path.isdir(data_path):
LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelFatalError']})
raise LodelFatalError("A site named '%s' was found in the DB but not on the FS (expected to found it in '%s'!!!" % (os.path.basename(data_path), data_path))
#Immediately switching to the context
LodelContext.new(ctx_name)
LodelContext.set(ctx_name)
confdir = os.path.join(data_path, confdir_basename)
#Loading settings for current site
LodelContext.expose_modules(globals(), {
'lodel.settings.settings': [('Settings', 'settings_preloader')]})
if settings_preloader.started():
msg = 'Settings seems to be allready started for "%s". \
This should not append !' % ctx_name
#switch back to loader context in order to log & raise
LodelContext.set(None)
logger.critical(msg)
raise LodelFatalError(msg)
if lodelsites_instance:
#fetching custom confspec
custom_confspec = _get_confspec("dummy_argument_is_obsolete")
settings_preloader(os.path.join('./', confdir),
custom_confspec, True)
else:
settings_preloader(os.path.join('./', confdir))
LodelContext.set(None)
return
##@brief Load all plugins in a context
def site_load_plugins():
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook'],
'lodel.plugin.plugins': ['Plugin']})
Plugin.load_all()
LodelHook.call_hook('lodel2_bootstraped', 'bootstrap', None)
del(globals()['LodelHook'])
del(globals()['Plugin'])
##@brief End a site loading process (load plugins & hooks)
#@param data_path str : site data path (used to extract the sitename !!)
#@todo change data_path argument to sitename
def site_load(data_path):
ctx_name = os.path.basename(data_path)
LodelContext.set(ctx_name)
dyncode_bootstraping()
LodelContext.expose_modules(globals(), {
'lodel.plugin': ['Plugin', 'LodelHook'],
'lodel.logger': 'logger',
'lodel.plugin.core_hooks': 'core_hooks',
'lodel.plugin.core_scripts': 'core_scripts'
})
Plugin.load_all() #Then all plugins & hooks are loaded
#triggering dyncode datasource instanciations
LodelHook.call_hook('lodel2_plugins_loaded', 'bootstrap', None)
#triggering boostrapped hook
LodelHook.call_hook('lodel2_bootstraped', 'bootstrap', None)
#Populating FAST_APP_EXPOSAL_CACHE with wsgi application function
#
#WARNING !!!! Hardcoded interface name ! Here we have to find the
#interface plugin name in order to populate the cache properly
app = LodelContext.module('lodel.plugins.webui.run')
#a dirty & quick attempt to fix context unwanted exite via
#hooks
for name in ( 'Plugin', 'LodelHook', 'logger', 'core_hooks',
'core_scripts'):
del(globals()[name])
#HAS TO be populated in __loader__ context
LodelContext.set(None)
LodelContext.expose_modules(globals(), {
'lodel.plugins.multisite.loader_utils': ['FAST_APP_EXPOSAL_CACHE']})
FAST_APP_EXPOSAL_CACHE[ctx_name] = app
#site fully loaded, switching back to loader context
LodelContext.set(None)
##@brief Fetch handled sites name
#@note Have to be called in __loader__ context. After function call the
#loaded context will remain __loader__
#@note bootstrap the dyncode and load all plugins
#@warning assert that a full __loader__ context is ready and that the
#multisite context is preloaded too
#@warning hardcoded Lodelsite leo name and shortname fieldname
#@todo attempt to delete hardcoded leo name
#@todo attempt to delete hardcoded fieldname
def get_handled_sites_name():
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings'],
'lodel.exceptions': ['LodelException']})
lodelsites_name = Settings.sitename
LodelContext.set(lodelsites_name)
#Loading plugins
try:
#This is a bit special but the loading process begin to be a bit
#messy... Some plugins can ask for the dyncode (in order to make
#checks for example ?). In that case the site_load_plugins() will
#call first the LodelContext.expose_dyncode() method and will
#raise first (it appends when dummy_plugin is activated)
site_load_plugins()
dyncode_bootstraping()
except ImportError:
raise LodelException("dyncode not yet imported ! Probably not \
generated yet")
LodelContext.expose_dyncode(globals(), 'leapi_dyncode')
lodelsite_leo = leapi_dyncode.Lodelsite #hardcoded leo name
LodelContext.expose_modules(globals(), {
'lodel.leapi.query': ['LeGetQuery'],
})
handled_sites = LeGetQuery(lodelsite_leo, query_filters = [],
field_list = ['shortname']).execute()
if handled_sites is None:
LodelContext.set(None)
return []
res = [ s['shortname'] for s in handled_sites]
del(globals()['LeGetQuery'])
del(globals()['Settings'])
LodelContext.set(None)
return res
##@brief This function handles dyncode first exposure and bootstraping
#
#Call LodelContext.expose_dyncode() and then call the
#lodel2_dyncode_bootstraped hook in the current context
#@note Tries to avoid side effects by deleting all exposed modules
#@todo maybe some checks are usefull (is it allowed to expose dyncode in a
#__loader__ context ?)
def dyncode_bootstraping():
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook'],
})
LodelContext.expose_dyncode(globals(), 'leapi_dyncode')
LodelHook.call_hook('lodel2_dyncode_bootstraped', 'boostrap', None)
del(globals()['leapi_dyncode'])
del(globals()['LodelHook'])

View file

@ -7,3 +7,11 @@ WEBUI=@WEBUI@
#Populated by make
LODEL2VARDIR="[@]LODEL2_VARDIR[@]"
LODEL2LOGDIR="[@]LODEL2_LOGDIR[@]"
LODEL_PKG_PATH = "[@]LODEL2_LODEL_PKG[@]"
MULTISITE_DATADIR = "[@]MULTISITE_DATA_DIRNAME[@]"
MULTISITE_CONTEXTDIR = "[@]MULTISITE_CTX_DIRNAME[@]"
MULTISITE_DYNCODE_MODULENAME = 'leapi_dyncode'
##@todo to be generated via autotools
LODELSITE_CONFDIR = 'lodelsites.conf.d'

View file

@ -16,11 +16,13 @@ if 'lodel' not in sys.modules:
else:
globals()['lodel'] = sys.modules['lodel']
if 'lodelsites' not in sys.modules:
import lodelsites
else:
if 'lodelsites' in sys.modules:
#This should be true since LodelContext init method is called
#for a MULTISITE context handling
globals()['lodelsites'] = sys.modules['lodelsites']
from lodel import buildconf
##@brief Name of the package that will contains all the virtual lodel
#packages
CTX_PKG = "lodelsites"
@ -45,7 +47,8 @@ class ContextModuleError(ContextError):
pass
def dir_for_context(site_identifier):
return os.path.join(lodelsites.__path__[0], site_identifier)
_, ctx_path = LodelContext.lodelsites_paths()
return os.path.join(os.path.join(ctx_path, site_identifier))
##@brief Designed to permit dynamic packages creation from the lodel package
@ -60,6 +63,7 @@ def dir_for_context(site_identifier):
#to represent the lodel package os the FS and then we make python import
#files from the symlink.
#
#
#@note Current implementation is far from perfection. In fact no deletion
#mechanisms is written and the virtual package cannot be a subpackage of
#the lodel package for the moment...
@ -68,22 +72,68 @@ def dir_for_context(site_identifier):
#lodelsites/SITENAME/ folder
class LodelMetaPathFinder(importlib.abc.MetaPathFinder):
##@brief implements the find_spec method of MetaPathFinder
#
#@param fullname str : module fullname
#@param path str : with be the value of __path__ of the parent package
#@param target module : is a module object that the finder may use to
#make a more educated guess about what spec to return
#@see https://docs.python.org/3/library/importlib.html#importlib.abc.MetaPathFinder
def find_spec(fullname, path, target = None):
if fullname.startswith(CTX_PKG):
if fullname.startswith(CTX_PKG+'.'):
spl = fullname.split('.')
site_identifier = spl[1]
#creating a symlink to represent the lodel site package
mod_path = dir_for_context(site_identifier)
if not os.path.exists(mod_path):
os.symlink(lodel.__path__[0], mod_path, True)
os.mkdir(mod_path)
fd = open(os.path.join(mod_path, '__init__.py'), 'w+')
fd.close()
lodel_pkg_path = os.path.join(mod_path, 'lodel')
if not os.path.exists(lodel_pkg_path):
os.symlink(src = lodel.__path__[0], dst = lodel_pkg_path,
target_is_directory = True)
#Cache invalidation after we "created" the new package
#importlib.invalidate_caches()
return None
#def invalidate_caches(): pass
##@brief Class designed to handle context switching and virtual module
#exposure
#
#The main entrypoint of this class is the expose_module method. A kind of
#equivalent of the various import X [as Y], from X import Y [as Z] etc.
#existing in Python.
#The expose_module method add a preffix to the module fullname in order
#to make it reconizable by the LodelMetaPathfinder::find_spec() method.
#All module names are translated before import. The preffix is set at
#__init__ call in __pkg_name. The resulting name is __pkg_name + fullname
#
#@par examples
#When asking for lodel.leapi.leobject :
#- in MONOSITE resulting module will be lodel.leapi.leobject
#- in MULTISITE resulting module name will be
#lodelsites.SITE_ID.lodel.leapi.leobject
#
#The lodelsites package will be a subdir of buildconf.MULTISITE_CONTEXTDIR
#that will be itself added to sys.path in order to be able to import
#lodelsites
#
#@par Notes about dyncode exposure
#In MULTISITE mode the dyncode will be stored as a python module in
#buildconf.MULTISITE_CONTEXTDIR/SITE_ID/leapi_dyncode.py . The dyncode
#exposale process in MULTISITE mode is simply done by asking LodelContext
#to expose a module named leapi_dyncode. The LodelContext::_translate()
#method is able to produce a correct name for this module.
#In MONOSITE mode the dyncode will be stored as a python module in
#the site directory. In this case the _translate method will do the same
#transformation than for the others modules. But in MONOSITE mode the
#module preffix is empty. Resulting in import leapi_dyncode. This will
#work asserting that cwd in MONOSITE mode is the instance directory.
#
#
#
#@note a dedicated context named LOAD_CTX is used as context for the
#loading process
class LodelContext(object):
@ -102,7 +152,20 @@ class LodelContext(object):
##@brief Flag indicating if the classe is initialized
__initialized = False
##@brief Stores path used by MULTISITE instance
#
#This variable is a tuple with 2 elements (in this order):
#- lodelsites datadir (ex: /var/lodel2/MULTISITE_NAME/datadir/)
#- lodelsites contextdir (ex: /varL/lodel2/MULTISITE_NAME/.ctx/lodelsites)
__lodelsites_paths = None
##@brief Stores allready imported modules indexed on context name
#
#The dict contains context name as key and dict as values.
#Value dict has module fullname as key and module object as value
__imports_cache = dict()
##@brief Create a new context
#@see LodelContext.new()
def __init__(self, site_id, instance_path = None):
@ -150,10 +213,14 @@ a context without a path......")
else:
self.__instance_path = os.path.realpath(instance_path)
#Importing the site package to trigger its creation
self.__package = importlib.import_module(self.__pkg_name)
self.__package = importlib.import_module(
self.__pkg_name)
self.__class__._contexts[site_id] = self
#Designed to be use by with statement
self.__previous_ctx = None
def __repr__(self):
return '<LodelContext name="%s">' % self.__id
##@brief Expose a module from the context
#@param globs globals : globals where we have to expose the module
@ -172,7 +239,7 @@ length == 2 but got : %s" % spec)
##@brief Return a module from current context
def get_module(self, fullname):
fullname = self._translate(fullname)
module = importlib.import_module(fullname)
module = self._import_module(fullname)
return module
@ -194,6 +261,11 @@ site_id set to None when we are in MULTISITE beahavior")
raise ContextError("Cannot have a context with \
site_id set when we are in MONOSITE beahavior")
##@return identifier for current context
@classmethod
def current_id(cls):
return cls._current.__id
##@return True if the class is in MULTISITE mode
@classmethod
def multisite(cls):
@ -208,20 +280,32 @@ site_id set to None when we are in MULTISITE beahavior")
return cls.get(target_ctx_id)
##@brief Set a context as active
#
#This method handle the context switching operations. Some static
#attributes are set at this step.
#@note if not in LOAD_CTX a sys.path update is done
#@warning Inconsistency with lodelsites_datasource, we build again the
#site context dir path using site_id. This information should come
#from only one source
#@param site_id str : site identifier (identify a context)
#@todo unify the generation of the site specific context dir path
@classmethod
def set(cls, site_id):
if cls._type == cls.MONOSITE:
raise ContextError("Context cannot be set in MONOSITE beahvior")
site_id = LOAD_CTX if site_id is None else site_id
if not cls.validate_identifier(site_id):
raise ContextError("Given context name is not a valide identifier \
: '%s'" % site_id)
if site_id not in cls._contexts:
raise ContextError("No context named '%s' found." % site_id)
if cls.current_id() != LOAD_CTX and site_id != LOAD_CTX:
raise ContextError("Not allowed to switch into a site context \
from another site context. You have to switch back to LOAD_CTX before")
wanted_ctx = cls._contexts[site_id]
if hasattr(wanted_ctx, '__instance_path'):
os.chdir(self.__instance_path) #May cause problems
os.chdir(self.__instance_path) #May cause problems and may be obsolete
cls._current = wanted_ctx
return cls._current
@ -247,7 +331,7 @@ site_id set to None when we are in MULTISITE beahavior")
return copy.copy(cls._current.__id)
##@brief Create a new context given a context name
##@brief Create a new context given a context name and switch in it
#
#@note It's just an alias to the LodelContext.__init__ method
#@param site_id str : context name
@ -285,6 +369,9 @@ site_id set to None when we are in MULTISITE beahavior")
##@brief Return a module from current context
#@param fullname str : module fullname
#@todo check if not globals are set when getting a module ! (if so
#checks all calls to this method to check that this assertion was not
#made)
@classmethod
def module(cls, fullname):
return cls.get().get_module(fullname)
@ -292,12 +379,13 @@ site_id set to None when we are in MULTISITE beahavior")
##@brief Expose leapi_dyncode module
@classmethod
def expose_dyncode(cls, globs, alias = 'leapi_dyncode'):
cls.get()._expose_dyncode(globs, alias)
cls.get().expose_modules(globs, { 'leapi_dyncode': alias })
##@brief Initialize the context manager
#
#@note Add the LodelMetaPathFinder class to sys.metapath if type is
#LodelContext.MULTISITE
#@note lodelsites package name is hardcoded and has to be
#@param type FLAG : takes value in LodelContext.MONOSITE or
#LodelContext.MULTISITE
@classmethod
@ -309,11 +397,25 @@ initialize it anymore")
raise ContextError("Invalid flag given : %s" % type)
cls._type = type
if cls._type == cls.MULTISITE:
#Woot hardcoded stuff with no idea of what it implies :-P
lodelsites_path = os.getcwd() #Same assert in the loader
cls.__lodelsites_paths = (
os.path.join(lodelsites_path, buildconf.MULTISITE_DATADIR),
os.path.join(lodelsites_path,
buildconf.MULTISITE_CONTEXTDIR))
#Now we are able to import lodelsites package
sys.path.append(os.path.dirname(cls.__lodelsites_paths[1]))
if 'lodelsites' not in sys.modules:
import lodelsites
globals()['lodelsites'] = sys.modules['lodelsites']
#End of Woot
cls._contexts = dict()
#Add custom MetaPathFinder allowing implementing custom imports
sys.meta_path = [LodelMetaPathFinder] + sys.meta_path
#Create and set __loader__ context
cls.new(LOAD_CTX)
ctx = cls.new(LOAD_CTX)
#DIRTY enforcing
cls._current = ctx
cls.set(LOAD_CTX)
else:
#Add a single context with no site_id
@ -330,7 +432,8 @@ initialize it anymore")
def context_dir(cls):
if cls._type == cls.MONOSITE:
return './'
return dir_for_context(cls._current.__id)
return os.path.join(cls.__lodelsites_paths[1],
cls._current.__id)
##@brief Validate a context identifier
@ -370,7 +473,7 @@ key '%s' with a different value : %s != %s" % (LodelContext.get_name(), alias, g
print("Context '%s' : A module exposure leads in a useless replacement for \
key '%s'" % (LodelContext.get_name(),alias))
globs[alias] = obj
##@brief Create a context from a path and returns the context name
#@param path str : the path from which we extract a sitename
#@return the site identifier
@ -386,8 +489,14 @@ MONOSITE mode")
"Unable to create a context named '%s'" % site_id)
cls.new(site_id, path)
return site_id
##@brief Return a tuple containing lodelsites datadir & contextdir (
#in this order)
@classmethod
def lodelsites_paths(cls):
if cls.__lodelsites_paths is None:
raise ContextError('No paths available')
return copy.copy(cls.__lodelsites_paths)
##@brief Utility method to expose a module with an alias name in globals
#@param globs globals() : concerned globals dict
@ -395,9 +504,23 @@ MONOSITE mode")
#@param alias str : alias name
@classmethod
def _expose_module(cls, globs, fullname, alias):
module = importlib.import_module(fullname)
module = cls._import_module(fullname)
cls.safe_exposure(globs, module, alias)
##@brief Utility method to import a module
#@param fullname str : module fullname
#@param reimport bool : if true import the module even if it was allready
#imported in this context
@classmethod
def _import_module(cls, fullname, reimport = False):
ctx_id = cls.current_id()
if ctx_id not in cls.__imports_cache:
cls.__imports_cache[ctx_id] = dict()
if reimport or fullname not in cls.__imports_cache[ctx_id]:
cls.__imports_cache[ctx_id][fullname] = importlib.import_module(
fullname)
return cls.__imports_cache[ctx_id][fullname]
##@brief Utility mehod to expose objects like in a from x import y,z
#form
#@param globs globals() : dict of globals
@ -406,7 +529,7 @@ MONOSITE mode")
@classmethod
def _expose_objects(cls, globs, fullname, objects):
errors = []
module = importlib.import_module(fullname)
module = cls._import_module(fullname)
for o_name in objects:
if isinstance(o_name, str):
alias = o_name
@ -420,27 +543,26 @@ MONOSITE mode")
msg = "Module %s does not have any of [%s] as attribute" % (
fullname, ','.join(errors))
raise ImportError(msg)
##@brief Implements LodelContext::expose_dyncode()
#@todo change hardcoded leapi_dyncode.py filename
def _expose_dyncode(self, globs, alias = 'leapi_dyncode'):
fullname = '%s.%s.dyncode' % (CTX_PKG, self.__id)
if fullname in sys.modules:
dyncode = sys.modules[fullname]
else:
path = os.path.join(self.__instance_path, 'leapi_dyncode.py')
sfl = importlib.machinery.SourceFileLoader(fullname, path)
dyncode = sfl.load_module()
self.safe_exposure(globs, dyncode, alias)
##@brief Translate a module fullname to the context equivalent
#
#Two transformation are possible :
#- we are importing a submodule of the lodel package : resulting module
#name will be : self.__pkg_name + module_fullname
#- we are importing the dyncode : resulting module name is :
#self.__pkg_name + dyncode_modulename
#@param module_fullname str : a module fullname
#@return The module name in the current context
def _translate(self, module_fullname):
if not module_fullname.startswith('lodel'):
raise ContextModuleError("Given module is not lodel or any \
submodule : '%s'" % module_fullname)
return module_fullname.replace('lodel', self.__pkg_name)
#This test should be obsolete now
if module_fullname.startswith('lodel') or \
module_fullname.startswith('leapi_dyncode'):
if self.multisite():
return self.__pkg_name +'.'+ module_fullname
else:
return module_fullname
raise ContextModuleError("Given module is not lodel nor dyncode \
or any submodule : '%s'" % module_fullname)
##@brief Implements the with statement behavior
#@see https://www.python.org/dev/peps/pep-0343/

View file

@ -54,6 +54,27 @@ class EmComponent(MlNamedObject):
m.update(bytes(data, 'utf-8'))
return int.from_bytes(m.digest(), byteorder='big')
##@brief Type test method
#@return bool
#@note Those methodes are implemented because we use picklefile to store
#EM -> the classes that are stored are not the same than the one loaded
#by LodelContext : lodel.editorial_model.components.EmClass !=
#lodelsites.INSTANCE_NAME.editorial_model.components.EmClass
@classmethod
def is_emclass(cls):
return False
##@brief Type test method
#@return bool
@classmethod
def is_emgroup(cls):
return False
##@brief Type test method
#@return bool
@classmethod
def is_emfield(cls):
return False
# @brief Handles editorial model objects classes
#@ingroup lodel2_em
@ -116,6 +137,11 @@ class EmClass(EmComponent):
data_handler='LeobjectSubclassIdentifier',
internal=True,
group=group)
##@todo delete me when support for pickle translator is dropped
@classmethod
def is_emclass(cls):
return True
# @brief Property that represents a dict of all fields
# (the EmField objects defined in this class and all their parents)
@ -251,6 +277,11 @@ class EmField(EmComponent):
else:
group.add_components([self])
##@todo delete me when support for pickle translator is dropped
@classmethod
def is_emfield(cls):
return True
# @brief Returns data_handler_name attribute
def get_data_handler_name(self):
return copy.copy(self.data_handler_name)
@ -302,6 +333,11 @@ class EmGroup(MlNamedObject):
raise ValueError("EmGroup expected in depends argument but %s found" % grp)
self.add_dependency(grp)
##@todo delete me when support for pickle translator is dropped
@classmethod
def is_emgroup(cls):
return True
# @brief Returns EmGroup dependencies
# @param recursive bool : if True returns all dependencies and their own dependencies
# @return a dict of EmGroup identified by uid

View file

@ -20,7 +20,16 @@ LodelContext.expose_modules(globals(), {
#@ingroup lodel2_em
class EditorialModel(MlNamedObject):
# @brief Create a new editorial model
##@brief if true we are in editor mode (all groups visible & loaded)
#@note Usually comes from settings but can be overriden at runtime.
#It as been implemented this way in order to allow to load EM
#in lodelsites context (to generate dyncode for handled sites)
_editormode = Settings.editorialmodel.editormode
##@brief When non in editor mode indicates the groups that we have to load
#in our EM
_groups = Settings.editorialmodel.groups
##@brief Create a new editorial model
# @param name MlString|str|dict : the editorial model name
# @param description MlString|str|dict : the editorial model description
def __init__(self, name, description=None, display_name=None, help_text=None):
@ -123,7 +132,20 @@ class EditorialModel(MlNamedObject):
res.append(cls)
return set(res)
# @brief EmGroup getter
##@brief Allow to override class attributes _editormode and
#_groups in order to control groups exposure
#
#A typicall usage of this method is for lodelsites when updating
#dyncode for handled sites. We have to set both attribute in order to
#load the EM as wanted by the handled site.
@classmethod
def _override_settings(cls, editormode = False, groups = []):
logger.warning("EM settings overriding ! If not in lodelsites \
context this is a REALLY BAD idea !")
cls.__editormode = editormode
cls.__groups = groups
##@brief EmGroup getter
# @param uid None | str : give this argument to get a specific EmGroup
# @return if uid is given returns an EmGroup else returns an EmGroup iterator
def groups(self, uid=None):
@ -141,7 +163,7 @@ class EditorialModel(MlNamedObject):
# @brief Update the EditorialModel.__active_groups and
# EditorialModel.__active_classes attibutes
def __set_actives(self):
if Settings.editorialmodel.editormode:
if self._editormode:
logger.warning("All EM groups active because editormode in ON")
# all groups & classes actives because we are in editor mode
self.__active_groups = self.__groups
@ -150,13 +172,13 @@ class EditorialModel(MlNamedObject):
# determine groups first
self.__active_groups = dict()
self.__active_classes = dict()
for agrp in Settings.editorialmodel.groups:
for agrp in self.__groups:
if agrp not in self.__groups:
raise SettingsError('Invalid group found in settings : %s' % agrp)
logger.debug("Set group '%s' as active" % agrp)
grp = self.__groups[agrp]
self.__active_groups[grp.uid] = grp
for acls in [cls for cls in grp.components() if isinstance(cls, EmClass)]:
for acls in [cls for cls in grp.components() if cls.is_emclass()]:
self.__active_classes[acls.uid] = acls
if len(self.__active_groups) == 0:
raise RuntimeError("No groups activated, abording...")
@ -238,9 +260,8 @@ class EditorialModel(MlNamedObject):
# @brief Raise an error if lodel is not in EM edition mode
@staticmethod
def raise_if_ro():
if not Settings.editorialmodel.editormode:
raise EditorialModelError(
"Lodel in not in EM editor mode. The EM is in read only state")
if not self._editormode:
raise EditorialModelError("Lodel in not in EM editor mode. The EM is in read only state")
# @brief Load a model
# @param translator module : The translator module to use

View file

@ -0,0 +1,13 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelFatalError'],
})
def save(model, filename= None):
raise LodelFatalError('Not allowed to save an EM. EM are handled \
externally')
def load(filename):
raise LodelFatalError('Not allowed to load an EM : EM are handled \
externally')

View file

@ -31,6 +31,9 @@ class LodelExceptions(LodelException):
class LodelFatalError(Exception):
pass
class LodelFatalErrors(LodelFatalError, LodelExceptions):
pass
##@brief Designed to be a catched exception.
#
#@note Designed to be raised in DataHandler

View file

@ -67,6 +67,7 @@ class DataHandler(MlNamedObject):
self.immutable = False
self.primary_key = False
self.internal = False
if 'default' in kwargs:
self.default, error = self.check_data_value(kwargs['default'])
if error:
@ -74,6 +75,12 @@ class DataHandler(MlNamedObject):
del kwargs['default']
for argname, argval in kwargs.items():
setattr(self, argname, argval)
if self.is_primary_key():
if 'uniq' in kwargs and not self.is_uniq():
message = "Parameter 'uniq' has to be set to True when 'primary_key' is not False"
raise LodelException(message)
self.uniq = True
self.check_options()
display_name = kwargs.get('display_name', MlString(self.display_name))
@ -111,6 +118,9 @@ class DataHandler(MlNamedObject):
def is_primary_key(self):
return self.primary_key
def is_uniq(self):
return self.uniq
# @brief checks if a fieldtype is internal
# @return bool

View file

@ -90,8 +90,6 @@ def get_classes(model):
# @brief Given an EmField returns the data_handler constructor suitable for dynamic code
# @param a EmField instance
# @return a string
def data_handler_constructor(emfield):
#dh_module_name = DataHandler.module_name(emfield.data_handler_name)+'.DataHandler'
get_handler_class_instr = 'DataField.from_name(%s)' % repr(emfield.data_handler_name)
@ -112,21 +110,25 @@ def data_handler_constructor(emfield):
# @brief Return a python repr of option values
# @param A value of any type which represents option
# @return a string
def forge_optval(optval):
if isinstance(optval, dict):
return '{' + (', '.join(['%s: %s' % (repr(name), forge_optval(val)) for name, val in optval.items()])) + '}'
if isinstance(optval, (set, list, tuple)):
return '[' + (', '.join([forge_optval(val) for val in optval])) + ']'
##@todo better class test & name retrieval possible using inspect or type
if hasattr(optval, '__class__'):
cls_name = optval.__class__.__name__
else:
cls_name = optval.__name__
if isinstance(optval, EmField):
if cls_name == 'EmField':
return "{leobject}.data_handler({fieldname})".format(
leobject=LeObject.name2objname(optval._emclass.uid),
fieldname=repr(optval.uid)
)
if isinstance(optval, EmClass):
elif cls_name == 'EmClass':
return LeObject.name2objname(optval.uid)
return repr(optval)

View file

@ -17,7 +17,7 @@ LodelContext.expose_modules(globals(), {
'LeApiQueryErrors'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'lodel.exceptions': ['LodelFatalError'],
'lodel.exceptions': ['LodelFatalError', 'LodelFatalErrors'],
'lodel.plugin.hooks': ['LodelHook'],
'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
@ -270,26 +270,31 @@ class LeObject(object):
rw_ds = ro_ds = cls._datasource_name
else:
ro_ds, rw_ds = cls._datasource_name
errors = []
# Read only datasource initialisation
cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
if cls._ro_datasource is None:
log_msg = "No read only datasource set for LeObject %s"
log_msg %= cls.__name__
logger.debug(log_msg)
else:
log_msg = "Read only datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
try:
cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
if cls._ro_datasource is not None:
log_msg = "Read only datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
except LodelFatalError as e:
errors.append(e)
# Read write datasource initialisation
cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
if cls._ro_datasource is None:
log_msg = "No read/write datasource set for LeObject %s"
log_msg %= cls.__name__
logger.debug(log_msg)
else:
log_msg = "Read/write datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
try:
cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
if cls._ro_datasource is not None:
log_msg = "Read/write datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
except LodelFatalError as e:
errors.append(e)
if len(errors) > 0:
raise LodelFatalErrors(msg = 'Unable to instanciate datasources \
for LeObject %s' % cls.__name__, exceptions = errors)
# @brief Return the uid of the current LeObject instance
#@return the uid value

View file

@ -3,8 +3,9 @@ import shutil
import tempfile
import os, os.path
from lodel.context import LodelContext
from lodel import buildconf
LodelContext.expose_modules(globals(), {
'lodel.plugin.scripts': 'lodel_script',
'lodel.plugin.scripts': ['LodelScript'],
'lodel.logger': 'logger'})
##@package lodel.plugin.core_scripts
@ -17,7 +18,7 @@ LodelContext.expose_modules(globals(), {
#@ingroup lodel2_plugins
#@ingroup lodel2_script
#
class ListPlugins(lodel_script.LodelScript):
class ListPlugins(LodelScript):
_action = 'plugins-list'
_description = "List all installed plugins"
@ -72,7 +73,7 @@ class ListPlugins(lodel_script.LodelScript):
##@brief Handle install & uninstall of lodel plugins
class PluginManager(lodel_script.LodelScript):
class PluginManager(LodelScript):
_action = 'plugins'
_description = "Install/Uninstall plugins"
@ -276,7 +277,7 @@ oprtions. Use -d --directory instead")
##@brief Implements lodel_admin.py **hooks-list** action
#@ingroup lodel2_script
#@ingroup lodel2_hooks
class ListHooks(lodel_script.LodelScript):
class ListHooks(LodelScript):
_action = 'hooks-list'
_description = 'Generate a list of registered hooks once instance started'
@ -301,3 +302,104 @@ class ListHooks(lodel_script.LodelScript):
priority = priority))
print("\n")
##@brief Implements lodel_admin **dyncode** action
#@ingroup lodel2_script
class RefreshDyncode(LodelScript):
_action = 'dyncode'
_description = 'Update the dynamic code according to EM and conf'
@classmethod
def argparser_config(cls, parser):
parser.add_argument('-m', '--em',
help='Specify the emfile to use for dyncode generation',
type=str, default='')
parser.add_argument('-o', '--dyncode',
help='Specify the filename where the dyncode should be written',
type=str, default='')
if LodelContext.multisite():
parser.add_argument('-a', '--all', action='store_true',
help="ONLY VALID FOR MULtisites ! Refresh lodelsites dyncode \
+ all handled sites dyncode")
return
##@todo think of a better method to determine if we are in mono or
#multisite instance
#@todo code factorisation to fetch handled sites list
#@todo fetch & use correct em_translator for handled sites
@classmethod
def run(cls, args):
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings'],
'lodel.editorial_model.model': ['EditorialModel'],
'lodel.leapi.lefactory': 'lefactory'})
em_translator = Settings.editorialmodel.emtranslator
model_file = args.em
if len(args.em.strip()) == 0:
#using the default em_file
model_file = Settings.editorialmodel.emfile
elif LodelContext.multisite() and args.all:
raise
#Creating dyncode
dyncode_file = args.dyncode
if len(args.dyncode.strip()) == 0:
#using dyncode filename from conf
dyncode_file = Settings.editorialmodel.dyncode
elif LodelContext.multisite() and args.all:
raise
if LodelContext.multisite() and args.all:
#NOTE the code bellow can be factorised with
#plugins/multisite/loader_utils.py
#Get the lodelsites instance name and the emfile path
LodelContext.set(None)
LodelContext.expose_modules(globals(), {
'lodel.settings':['Settings']})
lodelsites_name = Settings.sitename
emfile_path = Settings.lodelsites.sites_emfile
del(globals()['Settings']) #should be useless
#Get the list of handled sites name
LodelContext.set(None)
LodelContext.expose_modules(globals(), {
'lodel.bootstrap': ['get_handled_sites_name'],
})
handled_sites = get_handled_sites_name()
del(globals()['get_handled_sites_name'])
lodlesites_path = os.path.join(
os.path.join(buildconf.LODEL2VARDIR, lodelsites_name),
buildconf.MULTISITE_CONTEXTDIR)
if handled_sites is not None:
for sitename in handled_sites:
LodelContext.set(None)
#construct dyncode filename
##@todo fetch dyncode filename from handled site conf
dyncode_path = os.path.join(
os.path.join(lodlesites_path, sitename),
'leapi_dyncode.py') #BOO hardcoded dyncode file name
LodelContext.set(sitename)
cls.refresh_dyncode(emfile_path, dyncode_path,
em_translator)
LodelContext.set(None)
#Refresh only one dyncode
#if multisite it's the lodelsites dyncode
LodelContext.set(None)
cls.refresh_dyncode(model_file, dyncode_file, em_translator)
##@brief Refresh dyncode
#@warning you HAVE TO be in handled site context (in order to fetch groups
#from conf)
#@param model_file str : EM filename
#@param dyncode_file str : dyncode output filename
#@param em_translator str : translator name
@classmethod
def refresh_dyncode(cls, model_file, dyncode_file, em_translator):
#Model loaded
LodelContext.expose_modules(globals(), {
'lodel.settings':['Settings']})
model = EditorialModel.load(em_translator, filename = model_file)
dyncode_content = lefactory.dyncode_from_em(model)
with open(dyncode_file, 'w+') as dfp:
dfp.write(dyncode_content)
print("Dyncode written in %s from em %s" % (dyncode_file, model_file))
del(globals()['Settings'])

View file

@ -1,11 +1,13 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.hooks': ['LodelHook'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'LodelScriptError', 'DatasourcePluginError'],
'lodel.validator.validator': ['Validator'],
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
'LodelFatalError', 'DataNoneValid', 'FieldValidationError'],
})
_glob_typename = 'datasource'
@ -41,8 +43,8 @@ reimplemented by plugin datasource child class")
#@param offset int: used with limit to choose the start record
#@param instanciate bool : If true, the records are returned as instances, else they are returned as dict
#@return list
def select(self, target, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0,
instanciate=True):
def select(self, target, field_list, filters, relational_filters=None,
order=None, group=None, limit=None, offset=0, instanciate=True):
self._abs_err()
##@brief Deletes records according to given filters
@ -138,7 +140,11 @@ lodel.plugin.datasource_plugin.AbstractDatasource" % (self.name))
plugin_name, ds_identifier = cls.plugin_name(ds_name, ro)
ds_conf = cls._get_ds_connection_conf(ds_identifier, plugin_name)
ds_cls = cls.get_datasource(plugin_name)
return ds_cls(**ds_conf)
res = ds_cls(**ds_conf)
if res is None:
raise LodelFatalError("Unable to instanciate a datasource from \
name : '%s'" % ds_name)
return res
##@brief Return an initialized MigrationHandler instance
#@param ds_name str : The datasource name

View file

@ -5,6 +5,7 @@ import os.path
import importlib
import copy
import json
from warnings import warn #Because the logger is not able to work at some step
from importlib.machinery import SourceFileLoader
from lodel.context import LodelContext
@ -63,7 +64,14 @@ PLUGIN_DEPS_VARNAME = '__plugin_deps__'
##@brief Name of the optionnal activate method
ACTIVATE_METHOD_NAME = '_activate'
##@brief Default & failover value for plugins path list
PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'plugins')
if LodelContext.multisite():
#When in multisite mode we fetch plugins list from context dir in the
#lodel pkg symlink
PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'lodel/plugins')
else:
#When in monosite mode, for the moment, the plugins list is fetched from
#a symlink in the instance dir
PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'plugins')
##@brief List storing the mandatory variables expected in a plugin __init__.py
#file
@ -431,10 +439,13 @@ name differ from the one found in plugin's init file"
##@brief Return associated module name
def module_name(self):
path_array = self.path.split('/')
if 'plugins' not in self.path:
#Breaks deeper hierarchy for the moment, just a quick & dirty workaround
return "lodel.plugins."+os.path.basename(self.path)
#if not self.path.startswith('./plugins'):
if 'plugins' not in self.path: # startswith != in
raise PluginError("Bad path for plugin %s : %s" % (
self.name, self.path))
path_array = self.path.split('/')
return '.'.join(['lodel'] + path_array[path_array.index('plugins'):])
##@brief Check dependencies of plugin
@ -754,14 +765,16 @@ name differ from the one found in plugin's init file"
except PluginError as e:
log_msg += "unable to load '%s'. Exception raised : %s"
log_msg %= (INIT_FILENAME, e)
logger.debug(log_msg)
logger.warning(log_msg)
warn(log_msg)
return False
#Checking mandatory init module variables
for attr_name in MANDATORY_VARNAMES:
if not hasattr(initmod,attr_name):
log_msg += " mandatory variable '%s' not found in '%s'"
log_msg %= (attr_name, INIT_FILENAME)
logger.debug(log_msg)
logger.warning(log_msg)
warn(log_msg)
return False
#Fetching plugin's version
try:

View file

@ -4,14 +4,14 @@ pluginsdir=$(pkgpythondir)/plugins
lodel2confdir=$(sysconfdir)/lodel2
install-data-hook:
mkdir -p ${DESTDIR}$(pluginsdir); cp -R * ${DESTDIR}$(pluginsdir) && rm ${DESTDIR}$(pluginsdir)/Makefile* ;\
mkdir -p ${DESTDIR}$(lodel2confdir); \
if [ ! -d ${DESTDIR}$(lodel2confdir)/plugins ]; \
then \
ln -rs -t ${DESTDIR}$(lodel2confdir) $(pluginsdir); \
fi
-$(MKDIR_P) ${DESTDIR}$(pluginsdir);\
cp -R * ${DESTDIR}$(pluginsdir);\
rm -v ${DESTDIR}$(pluginsdir)/Makefile*;
install-exec-hook:
- $(MKDIR_P) ${DESTDIR}$(lodel2confdir);\
$(LN_S) -rt ${DESTDIR}$(lodel2confdir) ${DESTDIR}$(pluginsdir)
uninstall-hook:
-rm -R ${DESTDIR}$(pluginsdir)
-rm -R $(lodel2confdir)
-rm -R ${DESTDIR}$(lodel2confdir)

View file

@ -0,0 +1,49 @@
##@brief Plugin designed to handle FS site creation
#
#This plugin has a strong coupling with the multisite EM. It is
#designed to handle only 1 EmClass : Lodelsite .
#This class must have some mandatory fields :
# - shortname
# - extensions : list of loaded plugins
# - em_groups : selected Em Group from base Em
#
# Every other fields are ignored by this datasource and forwarded to
#childs datasource.
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator'],
'lodel.logger': 'logger'})
from .specs import check
__plugin_type__ = 'datasource'
__plugin_name__ = "lodelsite_datasource"
__version__ = '0.0.1'
__loader__ = 'main.py'
__plugin_deps__ = []
CONFSPEC = {
'lodel2.datasource.lodelsite_datasource.*' : {
'db_datasource': ( None,
Validator('string')),
'db_datasource_ro': (None,
Validator('string', none_is_valid = True)),
}
}
##@brief Hardcoded checks that try to know if the EM fits the requierements
def _activate():
LodelContext.expose_dyncode(globals())
if 'Lodelsite' not in leapi_dyncode.dynclasses_dict:
logger.fatal('Unable to activate lodelsite_datasource. Expected a \
Lodelsite leapi object')
return False #or raise ?
LodelSite = leapi_dyncode.dynclasses_dict['Lodelsite']
res, reason = check(LodelSite)
if not res:
logger.fatal('The lodelsite EmClass has some missins mandatory \
fields : '+reason)
return res

View file

@ -0,0 +1,5 @@
class LodelSiteDatasourceError(Exception):
pass
class LodelSiteDatasourceInconsistency(LodelSiteDatasourceError):
pass

View file

@ -0,0 +1,218 @@
##@brief This module contains usefull functions to handle lodelsites on FS
import os
import os.path
import shutil
from lodel.context import LodelContext
from lodel import buildconf #No need to protect it in Contexts
LodelContext.expose_modules(globals(), {
'lodel.logger' : 'logger',
'lodel.plugin.datasource_plugin': ['AbstractDatasource', 'DatasourcePlugin'],
'lodel.exceptions': ['LodelFatalError'],
'lodel.settings': ['Settings']})
from .exceptions import *
LODELSITE_DATA_PATH, LODELSITE_CONTEXTS_PATH = LodelContext.lodelsites_paths()
##@brief Define directories architecture
#
#@note useless because a lot of those stuff would be hardcoded
LODELSITE_INSTALL_MODEL = {
'datas' : ['uploads', 'conf.d'], #For datadir
'ctx': ['leapi_dyncode'] #for context dir
}
CONF_MODELS = os.path.join(os.path.dirname(__file__),'model_conf.d/')
CONF_AUTOCONF_FILENAME = 'lodelsites_autoconfig.ini'
##@brief Util function that returns both datadir and contextdir paths
#@param name str : the site shortname
#@return a tuple (datadir, contextdir)
def name2path(name):
return (os.path.join(LODELSITE_DATA_PATH, name),
os.path.join(LODELSITE_CONTEXTS_PATH, name))
##@brief Util function that indicates if a site exists or not
#
#This function only checks that both paths returned by name2path are
#existing directories
#@param name str : site shortname
#@return a bool
#@throws LodelSiteDatasourceInconsistency if inconsistency detected on FS
def site_exists(name):
paths = name2path(name)
if name == Settings.sitename:
msg = 'Site shortname "%s" is conflicting with the Lodelsites instance name' % name
raise LodelSiteDatasourceError(msg)
for path in paths:
if os.path.isfile(path):
msg = 'Will trying to determine if a lodesite "%s" exists we \
found that "%s" is a file, but a directory was expected' % (name, path)
raise LodelSiteDatasourceInconsistency(msg)
res = [False, False]
res = [os.path.isdir(paths[0]),
os.path.isdir(paths[1])]
if res[0] != res[1]:
msg = 'Inconsistency detected on filesystem will trying to determine \
wether a lodelsite exists or not : '
if res[0]:
msg += 'datadir was found but no contextdir'
else:
msg += 'contextdir found but no datadir'
raise LodelSiteDatasourceInconsistency(msg)
return res[0]
##@brief Create sites directory on filesystem
#@param name str : site shortname
#@return None
#@throws LodelSiteDatasourceError if something fails
#@throws LodelSiteDatasourceError if the site already exists
#@todo make uploads directory name configurable
def site_directories_creation(name):
if site_exists(name):
raise LodelSiteDatasourceError('This site identified by "%s" \
already exists' % name)
data_path, ctx_path = name2path(name)
#Starting by creating both directories
#Datadir
try:
os.mkdir(data_path)
except FileExistsError:
logger.critical('This should never happen ! We just checked that this \
directory does not exist. BAILOUT !')
raise LodelFatalError('Unable to create data directory for lodelsite \
"%s", file exists')
except Exception as e:
raise LodelFatalError('Unable to create data directory for lodelsite \
"%s" : %s' % (name,e))
#Context dir
try:
os.mkdir(ctx_path)
except FileExistsError:
logger.critical('This should never happen ! We just checked that this \
directory does not exist. BAILOUT !')
raise LodelFatalError('Unable to create context directory for \
lodelsite "%s", file exists' % name)
except Exception as e:
raise LodelFatalError('Unable to create context directory for \
lodelsite "%s" : %s' % (name, e))
#Creates lodel site data subdirectories
for data_subdirectory in LODELSITE_INSTALL_MODEL['datas']:
to_create = os.path.join(data_path, data_subdirectory)
try:
os.mkdir(to_create)
except FileExistsError:
logger.critical('This should never happen ! We just checked that this \
directory does not exist. BAILOUT !')
except Exception as e:
raise LodelFatalError('Unable to create %s directory for \
lodelsite "%s" : %s' % (d,name, e))
def site_lodelpkg_link_creation(name):
dst_path = os.path.join(name2path(name)[1], 'lodel')
src_path = buildconf.LODEL_PKG_PATH
try:
os.symlink(src_path, dst_path)
except FileExistsError:
logger.critical('This should never happen ! We just checked that this \
directory does not exist. BAILOUT !')
raise LodelFatalError('Unable to create lodel package symlink for \
lodelsite "%s", link exists in dir %s' % (name, directory))
except Exception as e:
raise LodelFatalError('Unable to create lodel package symlink for \
lodelsite "%s" : %s' % (name, e))
def site_context_init_creation(name):
directory = name2path(name)[1]
try:
open(os.path.join(directory, '__init__.py'), 'w+').close()
except FileExistsError:
logger.critical('This should never happen ! We just checked that this \
directory does not exist. BAILOUT !')
raise LodelFatalError('Unable to create python __init__ file for \
lodelsite context "%s", file exists in dir %s' % (name, directory))
except Exception as e:
raise LodelFatalError('Unable to create python _init__ file for \
lodelsite "%s" : %s' % (name, e))
##@brief Generate conffile containing informations set by lodelsites EM
#
#@param sitename str : site shortname
#@param em_groups list : list of str -> selected em_groups
#@param extensions list : list of str -> activated extensions
#@return config file content
def generate_conf(sitename, groups, extensions):
tpl = """
[lodel2]
sitename = {sitename}
extensions = {extensions}
[lodel2.editorialmodel]
groups = {em_groups}
"""
return tpl.format(
sitename = sitename,
extensions = ', '.join(extensions),
em_groups = ', '.join(groups))
##@brief Delete generated conf and generate a new one
#@param sitename str : site shortname
#@param em_groups list : list of str -> selected em_groups
#@param extensions list : list of str -> activated extensions
#@return None
def update_conf(sitename, groups, extensions):
data_path, _ = name2path(sitename)
conf_dir = os.path.join(data_path, 'conf.d') #Booo hardcoded
autoconf = os.path.join(conf_dir, CONF_AUTOCONF_FILENAME)
try:
os.unlink(autoconf)
except Exception as e:
#Dropping error on deletion
logger.warning('Unable to delete generated conf %s when trying to \
update it %s' % (autoconf, e))
with open(autoconf, 'w+') as cfp:
cfp.write(generate_conf(sitename, groups, extensions))
logger.info('Generated configuration file update for %s' % sitename)
##@brief Copies conffile from model and generates a conffile from given info
#@param sitename str : site shortname
#@param em_groups list : list of str -> selected em_groups
#@param extensions list : list of str -> activated extensions
#@return None
def make_confs(sitename, groups, extensions):
data_path, _ = name2path(sitename)
conf_dir = os.path.join(data_path, 'conf.d') #Booo hardcoded
#Config copy & forge
for conffile in os.listdir(CONF_MODELS):
if conffile.endswith('.ini'):
conffile = os.path.join(CONF_MODELS, conffile)
target_conffile = os.path.join(conf_dir, conffile)
logger.info(
"Copying conffile %s -> %s" % (conffile, target_conffile))
shutil.copyfile(conffile, target_conffile)
#Creating generated conf
autoconf = os.path.join(conf_dir, CONF_AUTOCONF_FILENAME)
with open(autoconf, 'w+') as cfp:
cfp.write(generate_conf(sitename, groups, extensions))
logger.info("Configuration file %s generated" % (autoconf))
##@brief Deletes all files related to a site
#@warning can lead to dirty bugs if the site is running...
def purge(sitename):
for todel in name2path(sitename):
try:
shutil.rmtree(todel)
except Exception as e:
logger.error('Unable to delete "%s" folder' % todel)
logger.info('"%s" files are deleted' % sitename)

View file

@ -0,0 +1,164 @@
import os.path
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin.datasource_plugin': ['AbstractDatasource', 'DatasourcePlugin'],
'lodel.logger': 'logger',
'lodel.exceptions': ['LodelFatalError'],
'lodel.settings': 'Settings'})
from .fs_utils import *
from .sites_utils import update_dyncode
from .specs import check as check_leo
from .exceptions import *
##@brief This datasource handles site creation
#@note Filters evaluation is forwarded to child datasource using our select
#method. Not optimized but much simple
class Datasource(AbstractDatasource):
##@brief Constructor
#
#Handles child datasource instanciation
#@param db_datasource str : datasource identifier
#@param db_datasource_ro str : read only datasource identitifer
def __init__(self, db_datasource, db_datasource_ro = None):
if db_datasource_ro is None:
db_datasource_ro = db_datasource
self._child_ds = DatasourcePlugin.init_datasource(
db_datasource, False)
self._child_ds_ro = DatasourcePlugin.init_datasource(
db_datasource, True)
pass
##@brief Checks that given emcomponent is compatible with datasource
#behavior
#@warning 100% hardcoded checks on leo name fieldnames & types
#@param emcomp LeObject subclass (or instance)
#@throws LodelFatalError if not compatible
@staticmethod
def __assert_good_leo(leo):
res, reason = check_leo(leo)
if not res:
msg = 'Bad leo given : %s because %s' % (leo, reason)
logger.critical(msg)
raise LodelFatalError(msg)
##@brief Provide a new uniq numeric ID
#@param emcomp LeObject subclass (not instance) : To know on wich things we
#have to be uniq
#@return an integer
def new_numeric_id(self, emcomp):
self.__assert_good_leo(emcomp)
return self._child_ds.new_numeric_id(emcomp)
##@brief returns a selection of documents from the datasource
#@note Simply forwarded to ro child datasource
#@param target Emclass
#@param field_list list
#@param filters list : List of filters
#@param rel_filters list : List of relational filters
#@param order list : List of column to order. ex: order = [('title', 'ASC'),]
#@param group list : List of tupple representing the column to group together. ex: group = [('title', 'ASC'),]
#@param limit int : Number of records to be returned
#@param offset int: used with limit to choose the start record
#@param instanciate bool : If true, the records are returned as instances, else they are returned as dict
#@return list
def select(self, target, field_list, filters, relational_filters=None,
order=None, group=None, limit=None, offset=0, instanciate=True):
return self._child_ds_ro.select(
target, field_list, relational_filters, order, group, limit, offset)
##@brief Deletes records according to given filters
#@note lazy filters evaluation implementation : to evaluate filters &
#rel_filters we run self.select using them
#@param target Emclass : class of the record to delete
#@param filters list : List of filters
#@param relational_filters list : List of relational filters
#@return int : number of deleted records
def delete(self, target, filters, relational_filters):
shortnames = self.select(
target,
['shortname'],
filters,
relational_filters)
for shortname in [ item['shortname'] for item in shortnames]:
purge(shortname)
return self._child_ds.delete(target, filters, relational_filters)
## @brief updates records according to given filters
#
#@note lazy filters evaluation implementation : to evaluate filters &
#rel_filters we run self.select using them
#@note shortname updates are forbidden
#@param target Emclass : class of the object to update
#@param filters list : List of filters
#@param relational_filters list : List of relational filters
#@param upd_datas dict : datas to update (new values)
#@return int : Number of updated records
def update(self, target, filters, relational_filters, upd_datas):
if 'shortname' in upd_datas:
raise LodelSiteDatasourceError('Unable to update the \
shortname, it is a site identifier. The right way for doing so is to copy \
existing site with a new name')
datas = self.select(
target,
['shortname', 'em_groups', 'extensions'],
filters,
relational_filters)
for data in datas:
if 'em_groups' in upd_datas:
#groups update, dyncode refresh
groups = upd_datas['em_groups']
update_dyncode(data['shortname'], upd_datas['em_groups'])
else:
groups = data['em_groups']
if 'extensions' in upd_datas:
extensions = upd_datas['extensions']
else:
extensions = data['extensions']
update_conf(**{
'sitename': data['shortname'],
'em_groups': groups,
'extensions': extensions})
return self._child_ds.update(
target, filters, relational_filters, upd_datas)
##@brief Inserts a record in a given collection
#@param target Emclass : class of the object to insert
#@param new_datas dict : datas to insert
#@return the inserted uid
def insert(self, target, new_datas):
self.__assert_good_leo(target)
if site_exists(new_datas['shortname']):
raise LodelSiteDatasourceError('A site with "%s" as shortname \
already exists' % (new_datas['shortname']))
site_directories_creation(new_datas['shortname'])
site_lodelpkg_link_creation(new_datas['shortname'])
site_context_init_creation(new_datas['shortname'])
generate_conf(
new_datas['shortname'],
new_datas['em_groups'],
new_datas['extensions'])
ret = self._child_ds.insert(target, new_datas)
update_dyncode(new_datas['shortname'], new_datas['em_groups'])
return ret
## @brief Inserts a list of records in a given collection
#@note Here is a simple implementation : a for loop triggering
#insert() calls
# @param target Emclass : class of the objects inserted
# @param datas_list list : list of dict
# @return list : list of the inserted records' ids
def insert_multi(self, target, datas_list):
res = []
for new_datas in datas_list:
res.append(self.insert(target, datas))
return res

View file

@ -0,0 +1,9 @@
[lodel2]
debug=False
[lodel2.logging.stderr]
level = INFO
filename = -
context = True

View file

@ -0,0 +1,42 @@
##
#@note in itself thos method should be implemented as custom methods
#for Lodelsite leobject. The current state of the project do not allow
#to do this easily. So you will found them here for the moment....
import os.path
from lodel import buildconf #safe even outside any context
from lodel.context import LodelContext
##@brief Update the leapi_dyncode.py file for a given site
#
#Follows expectation of LodelContext :
#The leapi_dyncode.py file will be stored in site's context dir
#@param site_name str : site shortname
#@param em_groups list : list of em groups to enable in the dyncode
#@return Nothing
def update_dyncode(site_name, em_groups):
_, ctx_path = LodelContext.lodelsites_paths()
dyncode_path = os.path.join(os.path.join(ctx_path, site_name),
buildconf.MULTISITE_DYNCODE_MODULENAME+'.py')
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.settings': ['Settings'],
'lodel.editorial_model.model': ['EditorialModel'],
'lodel.leapi.lefactory': 'lefactory'})
EditorialModel._override_settings(False, em_groups)
model = EditorialModel.load(
Settings.lodelsites.sites_emtranslator,
filename = Settings.lodelsites.sites_emfile)
logger.info('EditorialModel loaded for handled site %s' % site_name)
dyncode = lefactory.dyncode_from_em(model)
with open(dyncode_path, 'w+') as dfp:
dfp.write(dyncode)
EditorialModel._override_settings() #Restoring safe values
logger.info('Dyncode generated for handled site %s' % site_name)
logger.debug('Dyncode for %s contains those groups : %s' % (
site_name, em_groups))

View file

@ -0,0 +1,22 @@
##@brief Contains some constants in order to check that a LeObject child
#is compatible with the datasource
#
#@note isolated in order to be usable by __init__.py and main.py
LEO_NAME = 'Lodelsite'
MANDATORY_FIELDNAMES = [ 'shortname', 'extensions', 'em_groups' ]
##@brief Checks that given emcomponent is compatible with datasource
#behavior
#@warning 100% hardcoded checks on leo name fieldnames & types
#@param emcomp LeObject subclass (or instance)
#@return a tuple (bool, reason_str)
def check(leo):
if not hasattr(leo, '__name__'):
leo = leo.__class__
if leo.__name__ != LEO_NAME:
return (False, 'bad name')
missings = set(MANDATORY_FIELDNAMES) - set(leo.fieldnames())
if len(missings) > 0:
return (False, 'missing fields : ' + (', '.join(missings)))
return (True, 'ok')

View file

@ -1,3 +1,22 @@
##@brief
#
#@par Notes on FS organisation
#
#The plan is as follow :
#
#The autotools deploiment chain or the debian package will create a var
#folder dedicated for lodel2 ( for example /var/lodel2 ). This folder will
#contains folder named as the lodesites instances (lodelsites is a lodel site
#that handles lodel site as content). We will call this folder multisite
#folder or lodelsites folder.
#The multisite folder contains 2 settings folders :
#- lodelsites.conf.d : the lodelsites configuration
#- server.conf.d : the multisite process configuration
#- datas : a folder containing datas for each site handled by the lodelsites
#- .contexts : a folder containing context stuff for each site handlers by
#the lodelsites ( the lodel package symlink + dyncode.py)
#
from lodel.context import LodelContext, ContextError
try:
LodelContext.expose_modules(globals(), {

View file

@ -2,33 +2,19 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
#Define a minimal confspec used by multisite loader
#Define a minimal confspec used by multisite loader and by lodelsites
#instance
LODEL2_CONFSPECS = {
'lodel2': {
'debug': (True, Validator('bool'))
'lodel2.lodelsites': {
#'name': (None,
# Validator('string', none_is_valid = False)), #replaced by lodel2.name
'lodelsites_emfile': (None,
Validator('string', none_is_valid = False)), #Bad validator
'lodelsites_emtranslator': ('picklefile',
Validator('strip', none_is_valid = False)), #Bad validator
'sites_emfile': (None,
Validator('string', none_is_valid = False)), #Bad validator
'sites_emtranslator': ('picklefile',
Validator('string', none_is_valid = False)), #Bad validator
},
'lodel2.server': {
'listen_address': ('127.0.0.1', Validator('dummy')),
#'listen_address': ('', Validator('ip')), #<-- not implemented
'listen_port': ( 1337, Validator('int')),
'uwsgi_workers': (8, Validator('int')),
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
'virtualenv': (None, Validator('path', none_is_valid = True)),
},
'lodel2.logging.*' : {
'level': ( 'ERROR',
Validator('loglevel')),
'context': ( False,
Validator('bool')),
'filename': ( None,
Validator('errfile', none_is_valid = True)),
'backupcount': ( 10,
Validator('int', none_is_valid = False)),
'maxbytes': ( 1024*10,
Validator('int', none_is_valid = False)),
},
'lodel2.datasources.*': {
'read_only': (False, Validator('bool')),
'identifier': ( None, Validator('string')),
}
}

View file

@ -4,11 +4,28 @@ import sys
import shlex
import warnings
##@brief Preloader for a multisite process
#
#This loader is a kind of fake loader. In fact it only read configurations
#for the multisite instance and then run a UWSGI process that will run
#the run.py file.
#
#If you want to see the "real" multisite loading process see
#@ref lodel/plugins/multisite/run.py file
#
#@par Implementation details
#Here we have to bootstrap a minimal __loader__ context in order
#to be able to load the settings
#
#This file (once bootstraped) start a new process for uWSGI. uWSGI then
#run lodel.plugins.multisite.run.application function
#@note the uwsgi process in started using the execl function when UWSGI
#will exit this process will stop too
#
from lodel import buildconf
from lodel import bootstrap
bootstrap.bootstrap('__loader__')
try:
from lodel.context import LodelContext
except ImportError:
@ -16,54 +33,28 @@ except ImportError:
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from lodel.context import LodelContext
from lodel import buildconf
LodelContext.init(LodelContext.MULTISITE)
LodelContext.set(None) #Loading context creation
#Multisite instance settings loading
CONFDIR = os.path.join(os.getcwd(), 'conf.d')
if not os.path.isdir(CONFDIR):
warnings.warn('%s do not exists, default settings used' % CONFDIR)
LodelContext.expose_modules(globals(), {
'lodel.settings.settings': [('Settings', 'settings')],
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
if not settings.started():
settings('./conf.d', multisite_confspecs.LODEL2_CONFSPECS)
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings']})
'lodel.plugin.hooks': ['LodelHook'],
})
##@brief Starts uwsgi in background using settings
def uwsgi_fork():
sockfile = os.path.join(buildconf.LODEL2VARDIR, 'uwsgi_sockets/')
if not os.path.isdir(sockfile):
os.mkdir(sockfile)
sockfile = os.path.join(sockfile, 'uwsgi_lodel2_multisite.sock')
logfile = os.path.join(
buildconf.LODEL2LOGDIR, 'uwsgi_lodel2_multisite.log')
cmd='{uwsgi} --plugin python3 --http-socket {addr}:{port} --module \
lodel.plugins.multisite.run --socket {sockfile} --logto {logfile} -p {uwsgiworkers}'
cmd = cmd.format(
addr = Settings.server.listen_address,
port = Settings.server.listen_port,
uwsgi= Settings.server.uwsgicmd,
sockfile=sockfile,
logfile = logfile,
uwsgiworkers = Settings.server.uwsgi_workers)
if Settings.server.virtualenv is not None:
cmd += " --virtualenv %s" % Settings.webui.virtualenv
#If an interface that execl to run was loaded it will be run by following
#hook
LodelHook.call_hook('multisite_execl_interface', '__main__', None)
try:
args = shlex.split(cmd)
print("Running %s" % cmd)
exit(os.execl(args[0], *args))
except Exception as e:
print("Webui plugin uwsgi execl fails cmd was '%s' error : " % cmd,
e, file=sys.stderr)
exit(1)
#Nothing appened, running default ipython interface
#The interface as to be run by execl to go out of this partial context
PYTHON_EXC = '/usr/bin/python3'
RUNNER_FILE = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'run.py')
cmd = '%s "%s"' % (PYTHON_EXC, RUNNER_FILE)
try:
args = shlex.split(cmd)
print("\n\nEND LOADER MULTISITE, execl\n\n")
exit(os.execl(args[0], *args))
except Exception as e:
print("Multisite std interface execl fails. Command was : '%s' error \
: %s" % (cmd, e), file=sys.stderr)
exit(1)
if __name__ == '__main__':
uwsgi_fork()

View file

@ -0,0 +1,122 @@
import os
import os.path
##@brief A cache allowing a fast application exposure
#
#This dict contains reference on interface module of each handled site in
#order to quickly call the application (PEP 3333) function of concerned site
FAST_APP_EXPOSAL_CACHE = dict()
try:
from lodel.context import LodelContext
except ImportError:
LODEL_BASE_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from lodel.context import LodelContext, ContextError
import lodel.buildconf
##@brief basename of lodelsites site conf folder
LODELSITES_CONFD = lodel.buildconf.LODELSITE_CONFDIR
##@brief Stores the main function of a multisite loader
##@brief Function designed to bootstrap a multisite runner
#
#Handles lodelsites site loading, handled site list fecth & load
#@note called at end of file
#
#@todo evaluate if it is safe to assume that lodelsites_datapath = os.getcwd()
#@todo get rid of hardcoded stuff (like shortname fieldname)
#@todo use the dyncode getter when it will be available (replaced by
#the string SUPERDYNCODE_ACCESSOR.Lodelsite for the moment)
#@todo remove hardcoded app name (need a better abstraction or a really
#generic multiinstance runner)
#@return lodelsites instance name
def main(handled_sites_may_not_load = False):
#Set current context to reserved loader context
from lodel import bootstrap
bootstrap.bootstrap('__loader__')
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings']})
lodelsites_name = Settings.sitename
lodelsites_datapath = os.path.join(
os.path.join(lodel.buildconf.LODEL2VARDIR, lodelsites_name),
lodel.buildconf.MULTISITE_DATADIR)
del(globals()['Settings'])
#bootstraping the lodelsites instance
LodelContext.new(lodelsites_name)
LodelContext.set(lodelsites_name)
#in lodelsites context
LodelContext.expose_modules(globals(), {
'lodel.settings.settings': [('Settings', 'settings_loader')],
'lodel.plugins.multisite.confspecs': 'multisite_confspecs',
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
settings_loader(lodel.buildconf.LODELSITE_CONFDIR,
multisite_confspecs.LODEL2_CONFSPECS, True)
del(globals()['settings_loader'])
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings']})
LodelContext.expose_dyncode(globals())
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.plugin.hooks': ['LodelHook'],
'lodel.plugin': ['Plugin']})
Plugin.load_all()
LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
lodelsite_leo = leapi_dyncode.Lodelsite #hardcoded leo name
LodelContext.expose_modules(globals(), {
'lodel.leapi.query': ['LeGetQuery'],
})
#the line bellow you will find another harcoded thing : the shortname
#fieldname for a lodelsite
handled_sites = LeGetQuery(lodelsite_leo, query_filters = [],
field_list = ['shortname']).execute()
#Now that we have the handled sitenames list we can go back to
#loader context and clean it
if handled_sites is not None:
LodelContext.set(None)
LodelContext.expose_modules(globals(), {
'lodel.bootstrap': ['site_preload', 'site_load']})
for mname in ['LeGetQuery', 'Settings', 'LodelHook', 'Plugin', 'logger']:
del(globals()[mname])
#Loading handled sites
for handled_sitename in [s['shortname'] for s in handled_sites]:
datapath = os.path.join(lodelsites_datapath, handled_sitename)
try:
site_preload(datapath) #using default conf.d configuration dirname
site_load(datapath)
#
# HARDCODED APP NAME
#
populate_fast_app_cache(datapath, 'lodel.plugins.webui.run')
except Exception as e:
LodelContext.set(None)
LodelContext.set(lodelsites_name)
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings'],
'lodel.logger': 'logger'})
if Settings.debug or handled_sites_may_not_load:
logger.critical("Unable to load site %s : %s" % (
e, handled_sitename))
else:
raise e
else:
logger.warning("No handled sites !")
LodelContext.set(None)
return lodelsites_name
##@brief Add an app to FAST_APP_EXPOSAL_CACHE
#@param data_path str : instance data_path (used to extract the sitename !)
#@param app_name str : application name (like lodel.plugins.webui.run)
def populate_fast_app_cache(data_path, app_name):
ctx_name = os.path.basename(data_path)
LodelContext.set(ctx_name)
FAST_APP_EXPOSAL_CACHE[ctx_name] = LodelContext.module(app_name)
LodelContext.set(None)

View file

@ -1,18 +1,6 @@
# -*- coding: utf-8 -*-
import os
import os.path
import warnings
#This file expose common function to process a wsgi request and the
#uWSGI application callback
#preloading all instances
FAST_APP_EXPOSAL_CACHE = dict()
LODEL2_INSTANCES_DIR = '.'
EXCLUDE_DIR = {'conf.d', '__pycache__'}
"""
try:
from lodel.context import LodelContext
except ImportError:
@ -20,114 +8,29 @@ except ImportError:
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from lodel.context import LodelContext, ContextError
LodelContext.init(LodelContext.MULTISITE)
LodelContext.set(None) #Loading context creation
import lodel.buildconf #safe even outside contexts
"""
#Multisite instance settings loading
CONFDIR = os.path.join(os.getcwd(), 'conf.d')
if not os.path.isdir(CONFDIR):
warnings.warn('%s do not exists, default settings used' % CONFDIR)
from lodel.plugins.multisite.loader_utils import main, FAST_APP_EXPOSAL_CACHE
from lodel.bootstrap import site_load
from lodel.context import LodelContext
lodelsites_name = main() #multisite bootstraping
#Switching back to lodelsites context in order to trigger hooks
LodelContext.set(lodelsites_name)
LodelContext.expose_modules(globals(), {
'lodel.settings.settings': [('Settings', 'settings')],
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
if not settings.started():
settings('./conf.d', multisite_confspecs.LODEL2_CONFSPECS)
'lodel.plugin.hooks': ['LodelHook']})
LodelHook.call_hook('lodel2_loader_main', '__main__', None)
LodelContext.set(None)
#If a hook is registered in lodelsites context for lodel2_loader_main
#this function never returns
#Fetching insrtance list from subdirectories
lodelsites_list = [ os.path.realpath(os.path.join(LODEL2_INSTANCES_DIR,sitename))
for sitename in os.listdir(LODEL2_INSTANCES_DIR)
if os.path.isdir(sitename) and sitename not in EXCLUDE_DIR]
#Bootstraping instances
for lodelsite_path in lodelsites_list:
ctx_name = LodelContext.from_path(lodelsite_path)
#Switch to new context
LodelContext.set(ctx_name)
os.chdir(lodelsite_path)
# Loading settings
LodelContext.expose_modules(globals(), {
'lodel.settings.settings': [('Settings', 'settings')]})
if not settings.started():
settings('./conf.d')
LodelContext.expose_modules(globals(), {'lodel.settings': ['Settings']})
# Loading hooks & plugins
LodelContext.expose_modules(globals(), {
'lodel.plugin': ['LodelHook'],
'lodel.plugin.core_hooks': 'core_hooks',
'lodel.plugin.core_scripts': 'core_scripts'
})
#Load plugins
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.plugin': ['Plugin']})
logger.debug("Loader.start() called")
Plugin.load_all()
#Import & expose dyncode
LodelContext.expose_dyncode(globals())
#Next hook triggers dyncode datasource instanciations
LodelHook.call_hook('lodel2_plugins_loaded', '__main__', None)
#Next hook triggers call of interface's main loop
LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
#FAST_APP_EXPOSAL_CACHE populate
FAST_APP_EXPOSAL_CACHE[ctx_name] = LodelContext.module(
'lodel.plugins.webui.run')
LodelContext
#a dirty & quick attempt to fix context unwanted exite via
#hooks
for name in ( 'LodelHook', 'core_hooks', 'core_scripts',
'Settings', 'settings', 'logger', 'Plugin'):
del(globals()[name])
#switch back to loader context
LodelContext.set(None)
#
# From here lodel2 multisite instances are loaded and ready to run
#
##@brief Utility function to return quickly an error
def http_error(env, start_response, status = '500 internal server error', \
extra = None):
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
msg = status
if extra is not None:
msg = extra
return [msg.encode('utf-8')]
##@brief utility function to extract site id from an url
#@param url str :
def site_id_from_url(url):
res = ''
for c in url[1:]:
if c == '/':
break
res += c
if len(res) == 0:
return None
return res
##@brief This method is run in a child process by the handler
def application(env, start_response):
#Attempt to load a context
site_id = site_id_from_url(env['PATH_INFO'])
if site_id is None:
#It can be nice to provide a list of instances here
return http_error(env, start_response, '404 Not Found')
try:
LodelContext.set(site_id)
#We are in the good context
except ContextError as e:
print(e)
return http_error(env, start_response, '404 Not found',
"No site named '%s'" % site_id)
#Calling webui
return FAST_APP_EXPOSAL_CACHE[site_id].application(env, start_response)
#LodelContext.expose_modules(globals(), {
# 'lodel.plugins.webui.run': ['application']})
#return application(env, start_response)
import code
print("""
Running interactive python in Lodel2 multisite instance env.
Note : you are in LOAD_CTX environnment.
use lodel.context.Lodelcontext.set(CONTEXT_NAME) to switch
""")
code.interact(local=locals())

View file

@ -0,0 +1,55 @@
##@brief
#
#Lodel2 multisite uwsgi plugin
from lodel.context import LodelContext, ContextError
try:
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
__plugin_type__ = 'ui' #Warning !!! It's a MULTISITE interface
__plugin_name__ = 'multisite_web'
__version__ = '0.0.1'
__loader__ = 'main.py'
#__plugin_deps__ = ['multisite']
CONFSPEC = {
'lodel2.server': {
'listen_address': ('127.0.0.1', Validator('dummy')),
#'listen_address': ('', Validator('ip')), #<-- not implemented
'listen_port': ( 1337, Validator('int')),
'uwsgi_workers': (8, Validator('int')),
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
'virtualenv': (None, Validator('path', none_is_valid = True)),
},
#A copy from webui confspec
##@todo automatic merging of webuig confspecs
'lodel2.webui': {
'standalone': ( 'False',
Validator('string')),
'listen_address': ( '127.0.0.1',
Validator('dummy')),
'listen_port': ( '9090',
Validator('int')),
'static_url': ( 'http://127.0.0.1/static/',
Validator('regex', pattern = r'^https?://[^/].*$')),
'virtualenv': (None,
Validator('path', none_is_valid=True)),
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', Validator('dummy')),
'cookie_session_id': ('lodel', Validator('dummy')),
'uwsgi_workers': (2, Validator('int'))
},
'lodel2.webui.sessions': {
'directory': ( '/tmp',
Validator('path')),
'expiration': ( 900,
Validator('int')),
'file_template': ( 'lodel2_%s.sess',
Validator('dummy')),
}
}
except ContextError:
#This case append when uwsgi call the run.py directly
pass

View file

@ -0,0 +1 @@
../multisite/loader_utils.py

View file

@ -0,0 +1,50 @@
import os
import shlex
from lodel.context import LodelContext
import lodel.buildconf
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook'],
})
##@brief Starts uwsgi in background using settings
@LodelHook('lodel2_loader_main')
def uwsgi_fork(hook_name, caller, payload):
sockfile = os.path.join(lodel.buildconf.LODEL2VARDIR, 'uwsgi_sockets/')
if not os.path.isdir(sockfile):
os.mkdir(sockfile)
sockfile = os.path.join(sockfile, 'uwsgi_lodel2_multisite.sock')
logfile = os.path.join(
lodel.buildconf.LODEL2LOGDIR, 'uwsgi_lodel2_multisite.log')
#Switching to __loader__ context
print("PASS1")
LodelContext.set(None)
print("PASS2")
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings']})
print("PASS3")
cmd='{uwsgi} --plugin python3 --http-socket {addr}:{port} --module \
lodel.plugins.multisite_web.run --socket {sockfile} --logto {logfile} -p {uwsgiworkers}'
cmd = cmd.format(
addr = Settings.server.listen_address,
port = Settings.server.listen_port,
uwsgi= Settings.server.uwsgicmd,
sockfile=sockfile,
logfile = logfile,
uwsgiworkers = Settings.server.uwsgi_workers)
if Settings.server.virtualenv is not None:
cmd += " --virtualenv %s" % Settings.webui.virtualenv
print("PASS4")
try:
args = shlex.split(cmd)
print("Running %s" % cmd)
exit(os.execl(args[0], *args))
except Exception as e:
print("Webui plugin uwsgi execl fails cmd was '%s' error : " % cmd,
e, file=sys.stderr)
exit(1)

View file

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
import os
import os.path
import warnings
##@brief File designed to be executed by an UWSGI process in order to
#run a multisite instance process
#
#@warning In this module we have to be VERY carrefull about module exposure !
#in fact here we will go through every context at least once and the globals
#will be shared all among the module. So ANY exposed module will be accessible
#for a short time outside its context !! A good way be protected about that
#is to del(globals()[exposed_module_name]) after use. But it's really not
#sure that this way of doing is a real safe protection !
#
#@par Expected context when called
#- cwd has to be the lodelsites directory (the directory containing the
#conf.d folder)
#
#This file is divided in two blocks :
#
#@par Run at load
#The main function will be called at import.
#This piece of code handles :
#- loading the lodelsites site (the site that handles sites ;) )
#- fetch the list of handled lodel sites
#- loading the whole list of lodel sites
#
#@par WSGI processing
#The other functions are here to handles WSGI request. The entry function
#is application(), following the PEP 3333 specifications
try:
from lodel.context import LodelContext
except ImportError:
LODEL_BASE_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from lodel.context import LodelContext, ContextError
import lodel.buildconf #safe even outside contexts
import lodel.plugins.multisite.loader_utils as loader_utils
lodelsites_name = loader_utils.main()
#FAST_APP_EXPOSAL_CACHE has to be loaded and used from __loader__ context
LodelContext.set(None)
LodelContext.expose_modules(globals(), {
'lodel.plugins.multisite.loader_utils': ['FAST_APP_EXPOSAL_CACHE']
})
LodelContext.set(lodelsites_name)
LodelContext.expose_modules(globals(), {
'lodel.bootstrap': ['site_load'],
})
FAST_APP_EXPOSAL_CACHE[lodelsites_name] = LodelContext.module(
'lodel.plugins.webui.run')
LodelContext.set(None)
##@brief Utility function to return quickly an error
def http_error(env, start_response, status = '500 internal server error', \
extra = None):
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
msg = status
if extra is not None:
msg = extra
return [msg.encode('utf-8')]
##@brief utility function to extract site id from an url
#@param url str :
def site_id_from_url(url):
res = ''
for c in url[1:]:
if c == '/':
break
res += c
if len(res) == 0:
return None
return res
##@brief This method is run in a child process by the handler
def application(env, start_response):
LodelContext.set(None) #Ensure context switching
#Attempt to load a context
site_id = site_id_from_url(env['PATH_INFO'])
if site_id is None:
#It can be nice to provide a list of instances here
return http_error(env, start_response, '404 Not Found')
try:
LodelContext.set(site_id)
#We are in the good context
except ContextError as e:
print(e)
return http_error(env, start_response, '404 Not found',
"No site named '%s'" % site_id)
#Calling webui
return FAST_APP_EXPOSAL_CACHE[site_id].application(env, start_response)
#LodelContext.expose_modules(globals(), {
# 'lodel.plugins.webui.run': ['application']})
#return application(env, start_response)

View file

@ -7,7 +7,7 @@ LodelContext.expose_modules(globals(), {
'lodel.leapi.exceptions': [],
'lodel.logger': 'logger',
'lodel.leapi.datahandlers.base_classes': ['MultipleRef'],
'lodel.leapi.exceptions': ['LeApiDataCheckErrors'],
'lodel.leapi.exceptions': ['LeApiDataCheckErrors', 'LeApiError'],
'lodel.exceptions': ['LodelExceptions']})
LodelContext.expose_dyncode(globals(), 'dyncode')
@ -43,7 +43,7 @@ def admin_update(request):
if not(datas is False):
if 'lodel_id' not in datas:
raise HttpException(400)
target_leo = dyncode.Object.name2class(datas['classname'])
target_leo = dyncode.dynclasses[0].name2class(datas['classname'])
leo = target_leo.get_from_uid(datas['lodel_id'])
if leo is None:
raise HttpException(404,
@ -66,7 +66,7 @@ def admin_update(request):
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
# classname = None
raise HttpException(400)
@ -109,7 +109,7 @@ def admin_create(request):
datas = process_form(request)
if not(datas is False):
target_leo = dyncode.Object.name2class(datas['classname'])
target_leo = dyncode.dynclasses[0].name2class(datas['classname'])
if 'lodel_id' in datas:
raise HttpException(400)
try:
@ -133,7 +133,7 @@ def admin_create(request):
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
classname = None
@ -158,7 +158,7 @@ def admin_delete(request):
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
# classname = None
raise HttpException(400)
@ -227,7 +227,7 @@ def admin_class(request):
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
classname = None
if classname is None or target_leo.is_abstract():
@ -246,7 +246,7 @@ def delete_in_class(request):
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
classname = None
if classname is None or target_leo.is_abstract():
@ -266,7 +266,7 @@ def search_object(request):
classname = request.POST['classname']
searchstring = request.POST['searchstring']
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
raise HttpException(400)
# TODO The get method must be implemented here
@ -286,7 +286,7 @@ def process_form(request):
raise HttpException(400)
res['classname'] = classname = request.form['classname']
try:
target_leo = dyncode.Object.name2class(classname)
target_leo = dyncode.dynclasses[0].name2class(classname)
except LeApiError:
logger.error(
"Received a form with an invalid leo name : '%s'" % classname)

View file

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {'lodel.logger': 'logger'})
LodelContext.expose_modules(globals(), {'lodel.logger': 'logger',
'lodel.leapi.exceptions': ['LeApiError']})
LodelContext.expose_dyncode(globals(), 'dyncode')
from .base import get_response
from .utils import *
from ...exceptions import *
##@brief These functions are called by the rules defined in ../urls.py

View file

@ -2,12 +2,13 @@
import re
from .controllers import *
from .urls import urls
from ..main import root_url
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings']})
'lodel.settings': ['Settings'],
'lodel.plugins.webui.main': ['root_url'],
'lodel.plugins.webui.interface.urls': ['urls'],
})
def format_url_rule(url_rule):
if url_rule.startswith('^'):

View file

@ -8,25 +8,25 @@
<input id="field_input_{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="checkbox" checked="{% if value %}checked{% endif %}" >
{% elif field.base_type == 'password' %}
<input id="{{fieldname}}" name="field_input_{{fieldname}}" class="form-control" type="password" value="{{sval}}" >
{% elif field.base_type == 'char' or field.base_type == 'int' %}
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{value}}" >
{% elif field.base_type == 'text' %}
<textarea class="form-control" name="field_input_{{fieldname}}">{{value}}</textarea>
{% elif field.base_type == 'datetime' %}
<input class="form-control" type="date" value="{{value}}" name="field_input_{{fieldname}}">
{% elif field.base_type == 'ref' %}
{% if value is iterable %}
{% set sval=value|join(',') %}
{% else %}
{% set sval = value %}
{% endif %}
{% if field.directly_editable %}
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}" >
{% set l_classe = field.allowed_classes %}
<p> Please enter uids to instances of {{ l_classe.__name__ }} separated by commas </p>
{% else %}
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}">
{% endif %}
{% elif field.base_type == 'char' or field.base_type == 'int' or field.base_type == 'varchar' %}
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{value}}" >
{% elif field.base_type == 'text' %}
<textarea class="form-control" name="field_input_{{fieldname}}">{{value}}</textarea>
{% elif field.base_type == 'datetime' %}
<input class="form-control" type="date" value="{{value}}" name="field_input_{{fieldname}}">
{% elif field.base_type == 'ref' %}
{% if value is iterable %}
{% set sval=value|join(',') %}
{% else %}
{% set sval = value %}
{% endif %}
{% if field.directly_editable %}
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}" >
{% set l_classe = field.allowed_classes %}
<p> Please enter uids to instances of {{ l_classe.__name__ }} separated by commas </p>
{% else %}
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}">
{% endif %}
{% else %}
Unsupported base type "{{field.base_type}}" <br>
{% endif %}

View file

@ -82,17 +82,25 @@ class Settings(object, metaclass=MetaSettings):
#  @brief Stores the singleton instance
instance = None
#  @brief Instanciate the Settings singleton
# @param conf_dir str : The configuration directory
##@brief Instanciate the Settings singleton
#@param conf_dir str : The configuration directory
#@param custom_confspecs None | dict : if given overwrite default lodel2
# confspecs
def __init__(self, conf_dir, custom_confspecs=None):
#@param append bool : if true do not replace LODEL2CONFSPECS by
#custom_confspecs, append them instead
#
#@todo append param and __custom_specs_append attributes are quick
#workarounds in order to be able to specialise LODEL2_CONF_SPECS for a
#multisite lodelsite. This should be done using the standard confspecs
#mechanism available using plugins
def __init__(self, conf_dir, custom_confspecs=None, append = False):
self.singleton_assert() # check that it is the only instance
Settings.instance = self
#  @brief Configuration specification
#
# Initialized by Settings.__bootstrap() method
self.__conf_specs = custom_confspecs
self.__conf_specs = custom_confspecs if not append else None
self.__conf_specs_append = custom_confspecs if append else None
#  @brief Stores the configurations in namedtuple tree
self.__confs = None
self.__conf_dir = conf_dir
@ -185,6 +193,8 @@ class Settings(object, metaclass=MetaSettings):
Plugin.start(plugin_list)
# Fetching conf specs from plugins
specs = [lodel2_specs]
if self.__conf_specs_append is not None:
specs.append(self.__conf_specs_append)
errors = list()
for plugin_name in plugin_list:
try:

View file

@ -25,6 +25,9 @@ class SettingsLoader(object):
# @param conf_path str : conf.d path
def __init__(self, conf_path):
self.__conf_path = conf_path
if not os.path.isdir(conf_path):
logger.warning("Configuration directory %s do not exists. Using \
default configuration values (cwd : %s)" % (conf_path, os.getcwd()))
self.__conf_sv = dict()
self.__conf = self.__merge()
# Stores errors
@ -168,6 +171,7 @@ class SettingsLoader(object):
key_id=key_id, \
filename =filename))
if len(err_l) > 0:
raise SettingsErrors(err_l)
raise SettingsErrors(err_l, 'Following errors were detected \
while loading confs from %s' % self.__conf_path)
else:
return

View file

@ -33,7 +33,8 @@ class SettingsErrors(Exception):
##@brief Instanciate an SettingsErrors
# @param exceptions list : list of SettingsError instance
def __init__(self, exceptions):
def __init__(self, exceptions, extra_msg = 'Errors :'):
self._extra_msg = extra_msg
for expt in exceptions:
if not isinstance(expt, SettingsError):
raise ValueError("The 'exceptions' argument has to be an array of <class SettingsError>, but a %s was found in the list" % type(expt))
@ -43,7 +44,7 @@ class SettingsErrors(Exception):
def __repr__(self): return str(self)
def __str__(self):
res = "Errors :\n"
res = "%s\n" % self._extra_msg
for expt in self.__exceptions:
res += "\t%s\n" % str(expt)
return res

View file

@ -348,7 +348,7 @@ def emfield_val(value):
@LodelHook('lodel2_dyncode_bootstraped')
def emfield_conf_check(hookname, caller, payload):
import leapi_dyncode as dyncode # <-- dirty & quick
LodelContext.expose_dyncode(globals(), 'dyncode')
classnames = {cls.__name__.lower(): cls for cls in dyncode.dynclasses}
if value[0].lower() not in classnames:
msg = "Following dynamic class do not exists in current EM : %s"
@ -362,35 +362,33 @@ def emfield_val(value):
# @brief Validator for plugin name & optionnaly type
#
# Able to check that the value is a plugin and if it is of a specific type
#@param value the plugin name
#@param ptype : plugin type
def plugin_validator(value, ptype=None):
if value:
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook']})
value = copy.copy(value.strip())
value = None if len(value) == 0 else value
@LodelHook('lodel2_dyncode_bootstraped')
def plugin_type_checker(hookname, caller, payload):
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook']})
value = copy.copy(value)
@LodelHook('lodel2_dyncode_bootstraped')
def plugin_type_checker(hookname, caller, payload):
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError']})
if value is None:
return
try:
plugin = Plugin.get(value)
except PluginError:
msg = "No plugin named %s found"
msg %= value
raise ValidationError(msg)
if plugin._type_conf_name.lower() != ptype.lower():
msg = "A plugin of type '%s' was expected but found a plugin \
named '%s' that is a '%s' plugin"
msg %= (ptype, value, plugin._type_conf_name)
raise ValidationError(msg)
return value
return None
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError']})
if value is None:
return
try:
plugin = Plugin.get(value)
except PluginError:
msg = "No plugin named %s found"
msg %= value
raise ValidationError(msg)
if plugin._type_conf_name.lower() != ptype.lower():
msg = "A plugin of type '%s' was expected but found a plugin \
named '%s' that is a '%s' plugin"
msg %= (ptype, value, plugin._type_conf_name)
raise ValidationError(msg)
return value
Validator.register_validator(
'plugin',
@ -406,7 +404,7 @@ Validator.register_validator(
# Lodel 2 configuration specification
#
# @brief Append a piece of confspec
##@brief Append a piece of confspec
#@note orig is modified during the process
#@param orig dict : the confspec to update
#@param section str : section name
@ -414,8 +412,6 @@ Validator.register_validator(
#@param validator Validator : the validator to use to check this configuration key's value
#@param default
#@return new confspec
def confspec_append(orig, section, key, validator, default):
if section not in orig:
orig[section] = dict()

View file

@ -1,3 +0,0 @@
lodelsites_PYTHON = *.py
lodelsitesdir=$(pkgpythondir)/../lodelsites/

View file

View file

@ -26,11 +26,12 @@ em_lodel_site = em.new_class(
)
em_lodel_site.new_field(
'name',
display_name = 'lodelSiteName',
help_text = 'Lodel site full name',
'id',
display_name = 'Site id',
help_text = 'Numeric uniq ID for a Lodel site',
group = base_group,
data_handler = 'varchar'
data_handler = 'uniqid',
internal = True
)
em_lodel_site.new_field(
@ -38,11 +39,20 @@ em_lodel_site.new_field(
display_text = 'lodelSiteShortName',
help_text = 'Lodel site short string identifier',
group = base_group,
data_handler = 'varchar',
max_length = 5,
data_handler = 'regex',
max_length = 16,
regex = '^[0-9a-z_-]+$',
uniq = True
)
em_lodel_site.new_field(
'name',
display_name = 'lodelSiteName',
help_text = 'Lodel site full name',
group = base_group,
data_handler = 'varchar'
)
em_lodel_site.new_field(
'extensions',
display_text = 'lodeSiteExtensions',
@ -60,6 +70,41 @@ em_lodel_site.new_field(
data_handler = 'text',
)
#
#This class & fields becomes mandatory when the auth process was integrated
#into lodel2 engine
#
user_group = em.new_group(
'users',
display_name = 'Lodel users',
help_text = 'Group that handle users en perm'
)
user = em.new_class(
'User',
group = user_group
)
user.new_field(
'login',
display_name = 'User login',
help_text = 'login',
group = user_group,
data_handler = 'varchar',
primary_key = True
)
user.new_field(
'password',
display_name = 'Password',
help_text = 'User password',
group = user_group,
data_handler = 'password',
)
pickle_file_path = 'examples/lodelsites_em.pickle'
xml_file_path = 'examples/lodelsites_em.xml'

View file

@ -1 +1 @@
lodel/plugins/
lodel/plugins

View file

@ -1,17 +1,30 @@
include $(top_builddir)/Makefile-common.am.inc
SUBDIRS=slim
lodel2_scripts_dir = $(datadir)/lodel2/scripts
lodel2_scripts__DATA = create_instance mass_deploy
CLEANFILES = create_instance mass_deploy mass_deploy.cfg
EXTRA_DIST = create_instance.sh mass_deploy.sh
lodel2_scripts__DATA = create_instance create_multisite mass_deploy
CLEANFILES = create_instance create_multisite mass_deploy mass_deploy.cfg
EXTRA_DIST = create_instance.sh create_multisite.sh mass_deploy.sh
lodel2conf_DATA = mass_deploy.cfg
lodel2confdir=$(sysconfdir)/lodel2
lodel2statedir=$(localstatedir)/lodel2
lodel2datadir = $(datadir)/lodel2
lodel_libdir=$(pkgpythondir)/
install_model_dir = $(lodel2datadir)/install_model
multisite_install_model_dir = $(lodel2datadir)/multisite_install_model
do_subst = sed -e 's,\[@\]PKGPYTHONDIR\[@\],$(pkgpythondir),g' \
do_subst = sed -e 's,\[@\]PKGPYTHONDIR\[@\],$(pkgpythondir),g' \
-e 's,\[@\]LODEL2_LIBDIR\[@\],$(lodel_libdir),g' \
-e 's,\[@\]LODEL2_DATADIR\[@\],$(lodel2datadir),g' \
-e 's,\[@\]LODEL2_PROGSDIR\[@\],$(lodel2_scripts_dir),g' \
-e 's,\[@\]LODEL2_CONFDIR\[@\],$(lodel2confdir),g'
-e 's,\[@\]LODEL2_CONFDIR\[@\],$(lodel2confdir),g' \
-e 's,\[@\]LODEL2_VARDIR\[@\],$(lodel2statedir),g' \
-e 's,\[@\]INSTALLMODEL_DIR\[@\],$(install_model_dir),g' \
-e 's,\[@\]MULTISITE_INSTALLMODEL_DIR\[@\],$(multisite_install_model_dir),g' \
-e 's,\[@\]LODELSITES_DATA_DIRNAME\[@\],$(lodel2_multisite_data_dirname),g' \
-e 's,\[@\]LODELSITES_CTX_DIRNAME\[@\],$(lodel2_multisite_ctx_dirname),g'
#There is clearly a way to factorise those rules
mass_deploy: mass_deploy.sh
@ -25,6 +38,10 @@ create_instance: create_instance.sh
$(do_subst) < $(srcdir)/create_instance.sh > create_instance
chmod +x create_instance
create_multisite: create_multisite.sh
$(do_subst) < $(srcdir)/create_multisite.sh > create_multisite
chmod +x create_multisite
install-data-hook:
chmod +x ${DESTDIR}$(datadir)/lodel2/scripts/*

84
progs/create_multisite.sh Normal file
View file

@ -0,0 +1,84 @@
#!/bin/bash
usage() {
cat <<HELP_USAGE
Usage: $(basename $0) lodelsites_name [instance_dir] [install_path] [install_tpl]
lodelsites_name: name of the lodelsites instance, only lower case alphas, numerics, -, _, are allowed
instance_dir: path where the data and context folders will be set (defaults to /var/lodel2)
Options:
-h, --help give this help list
HELP_USAGE
exit 1
}
conf_file_subst() {
#Make substitution in file given as argument
#$1 filename
#$2 instance_name
sed -i "s/@@@INSTANCE_NAME@@@/$2/g" $1
}
[ $# -lt 1 -o $# -gt 4 ] && usage
name="$1"
install_dir=$2
install_dir=${install_dir:=[@]LODEL2_VARDIR[@]}
install_tpl="$3"
install_tpl=${install_tpl:=[@]MULTISITE_INSTALLMODEL_DIR[@]}
context_dir_name="[@]LODELSITES_CTX_DIRNAME[@]"
datas_dir_name="[@]LODELSITES_DATA_DIRNAME[@]"
libdir="[@]LODEL2_LIBDIR[@]"
#Inconsistency with the fact that we can give the install template in argument
mono_model=[@]INSTALLMODEL_DIR[@]
if [[ ! "$name" =~ ^[abcdefghijklmnopqrstuvwxyz0-9_-]+$ ]]
then
echo -e "Only lowercase alpha, numerics, '-', '_', are allowed in name\n" >&2
usage
fi
if [ -d "$install_dir/$name" ]
then
echo "A multisite install $name already exists\n" >&2
usage
fi
instance_dir="$install_dir/$name"
lodelsites_ctx_dir="$instance_dir/$context_dir_name"
lodelsites_data_dir="$instance_dir/$datas_dir_name"
echo "Creating multisite instance directory from template"
mkdir -pv $instance_dir
#Copy both EM for lodelsites and handled sites
cp -Rv $install_tpl/*.pickle "$instance_dir"
#Creating both conf directory
for dname in "lodelsites.conf.d" "server_conf.d"
do
cp -Rv $install_tpl/$dname $instance_dir
#make subst in configuration files
for confname in $instance_dir/$dname/*
do
conf_file_subst $confname $name
done
done
#Using monosite files to populate the instance directory
for fname in "Makefile" "lodel_admin.py"
do
ln -vs $mono_model/$fname $instance_dir/$fname
done
#Dirty ln -s of plugins libdir
##@todo find another way to handles plugins in instances
ln -vs $libdir/plugins $instance_dir/plugins
#Dirty again :/
ln -vs $libdir/plugins/multisite/loader.py $instance_dir/
mkdir -pv "$lodelsites_ctx_dir"
mkdir -pv "$lodelsites_data_dir"
echo "Creating $lodelsites_ctx_dir/__init__.py"
> "$lodelsites_ctx_dir/__init__.py"
echo "Multisite $name created in $install_dir"
echo -e "Now you can :\ncd $instance_dir && make dyncode"

View file

@ -55,6 +55,9 @@ else
random_name="$fixed_name"
fi
random_name=${random_name,,}
#Check for the presence of mongo and its conf
if hash mongo 2>/dev/null
then
@ -98,7 +101,7 @@ then
echo "You didn't answer 'Y' (case matters), exiting"
fi
exit
elif echo $1 | grep -E "[A-Za-z]" &>/dev/null
elif ! echo $1 | grep -E "[0-9]" &>/dev/null
then
usage
exit 0

View file

@ -1,6 +1,6 @@
bin_SCRIPTS = slim
CLEANFILES = $(bin_SCRIPTS) slim_instances.json slim_instances_pid.json
EXTRA_DIST = slim.py install_model emfile.pickle
EXTRA_DIST = slim.py install_model multisite_install_model emfile.pickle
lodel2_scripts_dir = $(datadir)/lodel2/scripts
@ -14,6 +14,16 @@ install_model__DATA = install_model/loader.py \
install_model/lodel_admin.py \
install_model/Makefile
multisite_install_model_dir = $(lodel2datadir)/multisite_install_model
multisite_install_model__DATA = multisite_install_model/Makefile \
install_model/lodel_admin.py \
multisite_install_model/sites_em.pickle \
multisite_install_model/editorial_model.pickle
multisite_lodelsites_conf_dir = $(multisite_install_model_dir)/lodelsites.conf.d
multisite_lodelsites_conf__DATA = multisite_install_model/lodelsites.conf.d/lodel2.ini
slim_conf_model_dir = $(install_model_dir)/conf.d/
slim_conf_model__DATA = install_model/conf.d/lodel2.ini
@ -28,6 +38,7 @@ slim_instances_pid.json:
do_subst = sed -e 's,\[@\]LODEL2_PROGSDIR\[@\],$(lodel2_scripts_dir),g' \
-e 's,\[@\]SLIM_DATADIR\[@\],$(slimdatadir),g' \
-e 's,\[@\]INSTALLMODEL_DIR\[@\],$(install_model_dir),g' \
-e 's,\[@\]MULTISITE_INSTALLMODEL_DIR\[@\],$(multisite_install_model_dir),g' \
-e 's,\[@\]SLIM_VAR_DIR\[@\],$(slim_var_dir),g' \
-e 's,\[@\]PKGPYTHONDIR\[@\],$(pkgpythondir),g'
@ -37,4 +48,5 @@ slim: slim.py
install-data-hook:
chmod +x ${DESTDIR}$(install_model_dir)/lodel_admin.py
chmod +x ${DESTDIR}$(multisite_install_model_dir)/lodel_admin.py

View file

@ -15,10 +15,14 @@ import argparse
#
#In order to solve this problem the _simlink_hack() function delete the
#LODEL2LIB_FOLDER/install entry from sys.path
#@note This problem will be solved once lodel lib dir will be in
#/usr/lib/python3/lodel
#@todo delete me
def _simlink_hack():
sys.path[0] = os.getcwd()
sys.path[0] = os.getcwd() #In order to override default ./ path
##@brief Return true if we are in a monosite instance
#@todo find a better way to achieve this
def _monosite_test():
return os.path.isdir('./conf.d')
## @brief Utility method to generate python code given an emfile and a
# translator
@ -28,7 +32,7 @@ def _simlink_hack():
def generate_dyncode(model_file, translator):
from lodel.editorial_model.model import EditorialModel
from lodel.leapi import lefactory
model = EditorialModel.load(translator, filename = model_file)
dyncode = lefactory.dyncode_from_em(model)
return dyncode
@ -49,8 +53,16 @@ def create_dyncode(model_file, translator, output_filename):
## @brief Refresh dynamic leapi code from settings
#@todo make it compatible with multisite : create a "real" progs that takes
#conf path in args
def refresh_dyncode():
import loader
#import loader
from lodel.context import LodelContext
LodelContext.init()
from lodel.settings.settings import Settings as settings_loader
CONFDIR = os.path.join(os.getcwd(), 'conf.d')
settings_loader(CONFDIR)
from lodel.settings import Settings
# EditorialModel update/refresh
@ -119,7 +131,7 @@ def update_plugin_discover_cache(path_list = None):
os.environ['LODEL2_NO_SETTINGS_LOAD'] = 'True'
import loader
from lodel.plugin.plugins import Plugin
res = Plugin.discover(path_list)
res = Plugin.discover()
print("Plugin discover result in %s :\n" % res['path_list'])
for pname, pinfos in res['plugins'].items():
print("\t- %s %s -> %s" % (
@ -127,7 +139,46 @@ def update_plugin_discover_cache(path_list = None):
if __name__ == '__main__':
_simlink_hack()
import loader
loader.start()
from lodel.plugin.scripts import main_run
import lodel.buildconf
from lodel import bootstrap
from lodel.context import LodelContext
#to deleted when we known wich action need what kind of MULTISITE API
if not bootstrap._monosite_test():
bootstrap.bootstrap('lodelsites')
LodelContext.set(None)
#lodelsites instance preloading
#THIS CODE SHOULD BE IN AN UTIL FUNCTION
LodelContext.expose_modules(globals(), {
'lodel.bootstrap': ['get_handled_sites_name', 'site_preload'],
'lodel.settings': ['Settings']})
lodelsites_name = Settings.sitename
lodelsites_datapath = os.path.join(
os.path.join(lodel.buildconf.LODEL2VARDIR, lodelsites_name),
lodel.buildconf.MULTISITE_DATADIR)
del(globals()['Settings'])
#preloading lodelsites instance
site_preload(lodelsites_datapath, 'lodelsites.conf.d', True)
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.exceptions': ['LodelException']})
#preloading all handled sites
try:
handled_sites = get_handled_sites_name()
except LodelException:
handled_sites = []
logger.warning("Unable to fetch handled site names without a \
leapi_dyncode. Abording handled sites preloading")
del(globals()['logger'])
del(globals()['LodelException'])
for sitename in handled_sites:
datapath = os.path.join(lodelsites_datapath, sitename)
site_preload(datapath)
else:
bootstrap.bootstrap()
#from lodel.plugin.scripts import main_run
LodelContext.expose_modules(globals(),
{'lodel.plugin.scripts': ['main_run'],
'lodel.plugin.core_scripts': 'core_scripts'})
main_run()
exit()

View file

@ -0,0 +1,15 @@
python=python3
all: dyncode
dyncode:
$(python) -c 'import lodel_admin; lodel_admin.refresh_dyncode()'
init_db:
$(python) -c 'import lodel_admin; lodel_admin.init_all_dbs()'
list_hooks: dyncode
$(python) -c 'import lodel_admin; lodel_admin.list_registered_hooks()'
refresh_plugins:
$(python) -c 'import lodel_admin; lodel_admin.update_plugin_discover_cache()'

View file

@ -0,0 +1,64 @@
[lodel2]
debug = False
sitename = @@@INSTANCE_NAME@@@
datasource_connectors = dummy_datasource lodelsite_datasource
session_handler = filesystem_session
interface = multisite_web
[lodel2.auth]
login_classfield = lodelsite.shortname
pass_classfield = lodelsite.name
[lodel2.editorialmodel]
groups = base_group
emfile = editorial_model.pickle
#emtranslator = externaly_handled
dyncode = leapi_dyncode.py
[lodel2.lodelsites]
sites_emfile = sites_em.pickle
lodelsites_emfile = editorial_model.pickle
[lodel2.datasource.lodelsite_datasource.default]
db_datasource = mongodb_datasource.default
[lodel2.datasources.mongo]
identifier = mongodb_datasource.default
[lodel2.datasources.default]
identifier = lodelsite_datasource.default
[lodel2.datasources.dummy2]
identifier = lodelsite_datasource.default
[lodel2.datasource.dummy_datasource.default]
dummy =
#Example using a real datasource
#
#[lodel2.lodelsites]
#sites_emfile = sites_em.pickle
#lodelsites_emfile = editorial_model.pickle
#
#[lodel2.datasource.lodelsite_datasource.default]
#db_datasource = mongo
#
#[lodel2.datasources.default]
#identifier = mongodb_datasource.default
#
#[lodel2.datasources.dummy2]
#identifier = mongodb_datasource.default
#
#[lodel2.datasource.dummy_datasource.default]
#dummy =
#
#[lodel2.datasource.mongodb_datasource.default]
#host = HOSTNAME
#username = USERNAME
#password = PASSWORD
#db_name = DBNAME
[lodel2.logging.default]
context = True
filename = -
level = DEBUG

Binary file not shown.

View file

@ -16,28 +16,34 @@ from lodel import buildconf
logging.basicConfig(level=logging.INFO)
INSTANCES_ABSPATH="/tmp/lodel2_instances"
INSTANCES_ABSPATH="/var/lodel2"
LODEL2_INSTALLDIR="/usr/lib/python3/dist-packages"
CONFFILE='conf.d/lodel2.ini'
try:
STORE_FILE = os.path.join("[@]SLIM_VAR_DIR[@]", 'slim_instances.json')
PID_FILE = os.path.join("[@]SLIM_VAR_DIR[@]", 'slim_instances_pid.json')
CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_instance')
MONOSITE_CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_instance')
MULTISITE_CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_multisite')
INSTALL_TPL = "[@]INSTALLMODEL_DIR[@]"
MULTISITE_INSTALL_TPL = "[@]MULTISITE_INSTALLMODEL_DIR[@]"
EMFILE = os.path.join("[@]SLIM_DATADIR[@]", 'emfile.pickle')
except SyntaxError:
STORE_FILE='./instances.json'
PID_FILE = './slim_instances_pid.json'
CREATION_SCRIPT='../scripts/create_instance.sh'
MONOSITE_CREATION_SCRIPT='../scripts/create_instance.sh'
MULTISITE_CREATION_SCRIPT='../scripts/create_multisite.sh'
INSTALL_TPL = './slim_ressources/slim_install_model'
MULTISITE_INSTALL_TPL = './slim_ressources/slim_multisite_install_model'
EMFILE = './slim_ressources/emfile.pickle'
CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), CREATION_SCRIPT)
MONOSITE_CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), MONOSITE_CREATION_SCRIPT)
MULTISITE_CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), MULTISITE_CREATION_SCRIPT)
STORE_FILE=os.path.join(os.path.dirname(__file__), STORE_FILE)
INSTALL_TPL=os.path.join(os.path.dirname(__file__), INSTALL_TPL)
MULTISITE_INSTALL_TPL=os.path.join(os.path.dirname(__file__), MULTISITE_INSTALL_TPL)
EMFILE=os.path.join(os.path.dirname(__file__), EMFILE)
@ -202,13 +208,22 @@ def new_instance(name):
if not os.path.isdir(INSTANCES_ABSPATH):
logging.info("Instances directory '%s' don't exists, creating it")
os.mkdir(INSTANCES_ABSPATH)
instance_path = os.path.join(INSTANCES_ABSPATH, name)
if args.multisite:
creation_script = MULTISITE_CREATION_SCRIPT
install_tpl = MULTISITE_INSTALL_TPL
instance_path = INSTANCES_ABSPATH
else:
creation_script = MONOSITE_CREATION_SCRIPT
install_tpl = INSTALL_TPL
instance_path = os.path.join(INSTANCES_ABSPATH, name)
creation_cmd = '{script} "{name}" "{path}" "{install_tpl}" \
"{emfile}"'.format(
script = CREATION_SCRIPT,
script = creation_script,
name = name,
path = instance_path,
install_tpl = INSTALL_TPL,
install_tpl = install_tpl,
emfile = EMFILE)
res = os.system(creation_cmd)
if res != 0:
@ -444,6 +459,7 @@ server {
"""
print(ret)
##@brief Returns instanciated parser
def get_parser():
parser = argparse.ArgumentParser(
@ -491,6 +507,12 @@ def get_parser():
actions.add_argument('--nginx-conf', action='store_const',
default = False, const=True,
help="Output a conf for nginx given selected instances")
actions.add_argument(
'--multisite',
default=False,
const=True,
action='store_const',
help="Creates the instance as a multisite one")
startstop.add_argument('--stop', action='store_const',
default=False, const=True, help="Stop instances")
@ -555,6 +577,7 @@ if __name__ == '__main__':
print("\nAn instance name expected when creating an instance !",
file=sys.stderr)
exit(1)
for name in args.name:
new_instance(name)
elif args.purge:

View file

@ -62,7 +62,8 @@ else
mkdir $logdir
fi
PYTHON='env python3'
export PYTHONPATH="$PYTHONPATH:$(pwd)"
PYTHON="env python3"
ret_status=0
$PYTHON ./nocontext_tests.py $logdir $@ || ret_status=1

View file

@ -45,6 +45,8 @@
#
# https://docs.python.org/3.4/library/unittest.html
#
export PYTHONPATH="$(pwd)/$(dirname $0)/:$PYTHONPATH"
if test ! -f lodel/buildconf.py
then
echo "You have to build the project before running the tests"
@ -52,7 +54,7 @@ then
exit 1
fi
PYTHON='env python3'
PYTHON="env python3"
testdir=$(mktemp -td "lodel2_unittest_instance_XXXXXXXX")
install_model_dir="[@]INSTALLMODEL_DIR[@]"
if [ ! -d "$install_model_dir" ]