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
342 changed files with 3860 additions and 12786 deletions

141
COPYING
View file

@ -1,5 +1,5 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@ -7,15 +7,17 @@
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
@ -60,7 +72,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@ -633,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
GNU General Public License for more details.
You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View file

@ -1,17 +0,0 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3,
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

View file

@ -1,661 +0,0 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

View file

@ -1,651 +0,0 @@
GNU Affero General Public License
=================================
_Version 3, 19 November 2007_
_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
## Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: **(1)** assert copyright on the software, and **(2)** offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions
“This License” refers to version 3 of the GNU Affero General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this
License. Each licensee is addressed as “you”. “Licensees” and
“recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a “modified version” of the
earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based
on the Program.
To “propagate” a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices”
to the extent that it includes a convenient and prominently visible
feature that **(1)** displays an appropriate copyright notice, and **(2)**
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code
The “source code” for a work means the preferred form of the work
for making modifications to it. “Object code” means any non-source
form of a work.
A “Standard Interface” means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other
than the work as a whole, that **(a)** is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and **(b)** serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
“Major Component”, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
### 2. Basic Permissions
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
### 4. Conveying Verbatim Copies
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
* **a)** The work must carry prominent notices stating that you modified
it, and giving a relevant date.
* **b)** The work must carry prominent notices stating that it is
released under this License and any conditions added under section 7.
This requirement modifies the requirement in section 4 to
“keep intact all notices”.
* **c)** You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
* **d)** If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
“aggregate” if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
* **a)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
* **b)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either **(1)** a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or **(2)** access to copy the
Corresponding Source from a network server at no charge.
* **c)** Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
* **d)** Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
* **e)** Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A “User Product” is either **(1)** a “consumer product”, which means any
tangible personal property which is normally used for personal, family,
or household purposes, or **(2)** anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, “normally used” refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
“Installation Information” for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms
“Additional permissions” are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
* **a)** Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
* **b)** Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
* **c)** Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
* **d)** Limiting the use for publicity purposes of names of licensors or
authors of the material; or
* **e)** Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
* **f)** Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered “further
restrictions” within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
### 8. Termination
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated **(a)**
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and **(b)** permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents
A “contributor” is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, “control” includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To “grant” such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either **(1)** cause the Corresponding Source to be so
available, or **(2)** arrange to deprive yourself of the benefit of the
patent license for this particular work, or **(3)** arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. “Knowingly relying” means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is “discriminatory” if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license **(a)** in connection with copies of the covered work
conveyed by you (or copies made from those copies), or **(b)** primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
### 13. Remote Network Interaction; Use with the GNU General Public License
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
### 14. Revised Versions of this License
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License “or any later version” applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
### 16. Limitation of Liability
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
_END OF TERMS AND CONDITIONS_
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a “Source” link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a “copyright disclaimer” for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
&lt;<http://www.gnu.org/licenses/>&gt;.

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,25 +1,5 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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 =
@ -53,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
@ -92,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

@ -1,22 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3,
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#!/bin/bash
autoreconf -fvi

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
AC_INIT([lodel], [0.1], [contact@openedition.org])
AM_INIT_AUTOMAKE
@ -37,8 +17,7 @@ AC_CONFIG_FILES([Makefile \
lodel/utils/Makefile \
progs/Makefile \
progs/slim/Makefile \
lodel/plugins/Makefile \
lodelsites/Makefile \
lodel/plugins/Makefile
])
@ -48,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)
@ -77,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.

Binary file not shown.

Binary file not shown.

14
doc/img/graphviz/Makefile Normal file
View file

@ -0,0 +1,14 @@
dotfiles := $(wildcard *.dot)
images := $(patsubst %.dot,%.png,$(wildcard *.dot))
all: $(images)
%.png: %.dot
dot -Tpng $< > $@
.PHONY: clean distclean
clean:
-rm $(images)
distclean: clean

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

View file

@ -1,24 +1,5 @@
#!/usr/bin/python3
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
import sys
import os, os.path

View file

@ -1,23 +1,5 @@
#!/usr/bin/python3
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.init()

Binary file not shown.

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
[lodel2]
debug = False
sitename = noname

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
include $(top_builddir)/Makefile-common.am.inc
SUBDIRS=auth editorial_model leapi plugin settings utils plugins validator mlnamedobject
EXTRA_DIST = plugins
@ -34,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)

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
dyncode = None
@ -25,7 +7,7 @@ dyncode = None
# @par Lodel2 boot sequence
# see @ref install/loader.py
# 1. lodel package is imported
# 2. settings are started
# 2. settings are started
# 3. plugins are pre-loaded from conf to load plugins configuration specs
# 4. settings are loaded from conf and checked
# 3. plugins are loaded (hooks are registered etc)
@ -79,11 +61,11 @@ dyncode = None
#
#Autotools provide a way to distribute a software on Posix platforms.
#
#@section lodel2_autotools_howto How to use them
#@section lodel2_autotools_howto Howto use them
#
#Basically you have to run :
#- <code>./bootstrap.sh</code> to generate the configure script( run
#approximatly <code>aclocal; autoconf; automake</code> or
#- <code>./bootstrap.sh</code> to generate the configure script( run
#approximatly <code>aclocal; autoconf; automake</code> or
#<code>autoreconf</code> )
#- <code>./configure</code> to generate Makefile s
#- <code>make</code> to build lodel2 (actually to generate lodel/buildconf.py
@ -93,13 +75,13 @@ dyncode = None
#- automake targets
# - **all** compile the sources (don't do a lot for a script langage)
# - **clean** delete compiled files (don't do a lot for a script langage)
# - **distclean** enhanced comportment compared to default (delete compiled
# - **distclean** enhanced comportment compared to default (delete compiled
#files and generated binary). Here it deletes everything generated by
#<code>./bootstrap.sh && ./configure && make</code>
# - **install** Install lodel2 (for the moment copy the lodel dir in
# - **install** Install lodel2 (for the moment copy the lodel dir in
#the good path (configurable when running ./configure ) )
# - **uninstall** Remove installed files
#- lodel2 specific targets
#- lodel2 specific targets
# - **tests**, **check** and **checks** are aliases for running tests
# - **doc** generate the doxygen documentation
# - **em_test** refresh the example/em_test.pickle file using em_test.py
@ -109,17 +91,17 @@ dyncode = None
#@section lodel2_autotools_why Why using autotools
#
#Python has a lot of packaging and distributing solutions, but none of them
#is as convinent, complete, portable as GNU autotools. For example setup.py
#with distutils has no uninstall target; pip, wheel, easy-install etc brokes
#is as convinent, complete, portable as GNU autotools. For example setup.py
#with distutils has no uninstall target; pip, wheel, easy-install etc brokes
#totally your distribution packaging/upgrade system etc.
#
#Autotools are portable, integrated by debian packaging system ( see
#Autotools are portable, integrated by debian packaging system ( see
#checkinstall) and can support multi langages.
#
#@section lodel2_autotools_whatfor For doing what
#
#Autotools are here to allow distributing and installing Lodel2 on
#Posix OS.
#Autotools are here to allow distributing and installing Lodel2 on
#Posix OS.
#
#The distribution mechanisms handles dependencies checking (NO AUTO INSTALL
#WITH BINARY BLOBS !!!), compiling .py files to pyc and pyo, and copy all files
@ -128,12 +110,12 @@ dyncode = None
#@section lodel2_autotools_how How autotools are integrated
#
#What we call autotools is in fact a lot of software. In our case we use only
#both of them :
#both of them :
#<a href="https://www.gnu.org/software/autoconf/autoconf.html#documentation">
#autoconf</a> and
#autoconf</a> and
#<a href="https://www.gnu.org/software/automake/#documentation">automake</a>
#
#A python file is generated ( lodel/buildconf.py from
#A python file is generated ( lodel/buildconf.py from
#@ref lodel/buildconf.py.in) containing various informations gathered during
#the build process (for example the presence of pymongo or the precense of
#the dependency needed by webui etc.)
@ -161,28 +143,28 @@ dyncode = None
#Automake job is to transform the Makefile.am files into Makefile.in files.
#It handles all target creation for build, clean, install, uninstall etc.
#
#@section lodel2_autotools_problems Encountered problems
#@section lodel2_autotools_problems Encontered problems
#
#@ref lodel2_autoconf "Autoconf" use macro "written in m4" (not sure if m4
#is the macro langage). We use two macros sources : automake default python
#is the macro langage). We use two macros sources : automake default python
#support & pyconfigure automake macros.
#
#Those macros are broken with python3 (see
#Those macros are broken with python3 (see
#<a href="https://bugs.launchpad.net/ubuntu/+source/python3-defaults/+bug/1408092">
#the python3 sysconfig bug with debian OS</a> ). There is patched version of
#these macro in the m4 directory (and the associated patches :
#@ref m4/python.m4.patch "for automake python macros patch" and
#the python3 sysconfig bug with debian OS</a> ). There is patched version of
#these macro in the m4 directory (and the associated patches :
#@ref m4/python.m4.patch "for automake python macros patch" and
#@ref m4/python_pyconfigure.m4.patch "for pyconfigure python macros patch")
##@file m4/python.m4.patch
#@ingroup lodel2_deployment
#@brief Patch of automake python macro to solve a bug in pythondir retrieval
#@brief Patch of automake python macro to solve a bug in pythondir retrieval
#on debian
#@see https://bugs.launchpad.net/ubuntu/+source/python3-defaults/+bug/1408092
##@file m4/python_pyconfigure.m4.patch
#@ingroup lodel2_deployment
#@brief Patch to solve a bug in pyconfigure ac macros in pythondir retrieval
#@brief Patch to solve a bug in pyconfigure ac macros in pythondir retrieval
#on debian
#@see https://bugs.launchpad.net/ubuntu/+source/python3-defaults/+bug/1408092
@ -207,3 +189,4 @@ dyncode = None
##@file lodel/Makefile.am
#@brief Makefile model for autotools
#@ingroup lodel2_deployment

View file

@ -1,23 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
SUBDIRS=
auth_PYTHON = *.py
authdir=$(pkgpythondir)/auth

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##@package lodel.auth Package handling authentication on Lodel2
#
#The authentication mechanism is divided in multiple pieces :

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- Coding: utf-8 -*-
import copy
import sys
@ -32,7 +14,7 @@ LodelContext.expose_modules(globals(), {
'ClientPermissionDenied', 'ClientAuthenticationError'],
'lodel.leapi.query': ['LeGetQuery'], })
## @brief Client metaclass designed to implements container accessor on
# @brief Client metaclass designed to implements container accessor on
# Client Class
#
#@todo Maybe we can delete this metaclass....
@ -58,14 +40,14 @@ class ClientMetaclass(type):
def __str__(self):
return str(self._instance)
## @brief Abstract singleton class designed to handle client informations
# @brief Abstract singleton class designed to handle client informations
#
# This class is designed to handle client authentication and sessions
class Client(object, metaclass=ClientMetaclass):
## @brief Singleton instance
# @brief Singleton instance
_instance = None
# @brief List of dict that stores field ref for login and password
#
@ -81,11 +63,11 @@ class Client(object, metaclass=ClientMetaclass):
# - password typle contains (LeObjectChild, FieldName)
_infos_fields = None
## @brief Constant that stores the session key that stores authentication
# @brief Constant that stores the session key that stores authentication
# informations
_AUTH_DATANAME = '__auth_user_infos'
## @brief Constructor
# @brief Constructor
#@param session_token mixed : Session token provided by client to interface
def __init__(self, session_token=None):
logger.debug(session_token)
@ -99,13 +81,13 @@ class Client(object, metaclass=ClientMetaclass):
del(old)
logger.debug("Replacing old Client instance by a new one")
else:
## first instanciation, fetching settings
# first instanciation, fetching settings
self.fetch_settings()
# @brief Stores infos for authenticated users (None == anonymous)
self.__user = None
## @brief Stores the session handler
# @brief Stores the session handler
Client._instance = self
## @brief Stores LodelSession instance
# @brief Stores LodelSession instance
self.__data = dict()
if session_token is not None:
self.__data = SessionHandler.restore(session_token)
@ -116,14 +98,14 @@ class Client(object, metaclass=ClientMetaclass):
def __del__(self):
del(self.__session_token)
del(self.__data)
## @brief Returns session
# @brief Returns session
#@ returns the dict which stores session
@classmethod
def data(cls):
return cls._instance.__data
## @brief Returns the user's information contained in the session's data
# @brief Returns the user's information contained in the session's data
@classmethod
def user(cls):
if '__auth_user_infos' in cls._instance.__data:
@ -131,18 +113,18 @@ class Client(object, metaclass=ClientMetaclass):
else:
return None
## @brief Returns the session's token
# @brief Returns the session's token
@classmethod
def get_session_token(cls):
return cls._instance.__session_token
## @brief Set the session's token
# @brief Set the session's token
#@param the value of the token
@classmethod
def set_session_token(cls, value):
cls._instance.__session_token = value
## @brief Try to authenticate a user with a login and a password
# @brief Try to authenticate a user with a login and a password
#@param login str : provided login
#@param password str : provided password (hash)
#@warning brokes composed UID
@ -178,7 +160,7 @@ class Client(object, metaclass=ClientMetaclass):
if self.is_anonymous():
self.authentication_failure() # Security logging
## @brief Attempt to restore a session given a session token
# @brief Attempt to restore a session given a session token
#@param token mixed : a session token
#@return Session data (a dict)
#@throw ClientAuthenticationFailure if token is not valid or not
@ -196,14 +178,14 @@ a session is already started !!!")
logger.warning("Session restoring failed")
return copy.copy(cls._instance.data)
## @brief Returns the current session token or None
# @brief Returns the current session token or None
#@return A session token or None
@classmethod
def session_token(cls):
cls._assert_instance()
return cls._instance.__session_token
## @brief Deletes current session
# @brief Deletes current session
@classmethod
def destroy(cls):
cls._assert_instance()
@ -211,7 +193,7 @@ a session is already started !!!")
cls._instance.__session_token = None
cls._instance.__data = dict()
## @brief Deletes current client and saves its session
# @brief Deletes current client and saves its session
@classmethod
def clean(cls):
if cls._instance.__session_token is not None:
@ -220,34 +202,34 @@ a session is already started !!!")
del(Client._instance)
Client._instance = None
## @brief Tests if a client is anonymous or logged in
# @brief Tests if a client is anonymous or logged in
#@return True if client is anonymous
@classmethod
def is_anonymous(cls):
return Client._instance.user() is None
## @brief Method to be called on authentication failure
# @brief Method to be called on authentication failure
#@throw ClientAuthenticationFailure
#@throw LodelFatalError if no Client child instance is found
@classmethod
def authentication_failure(cls):
cls._generic_error(ClientAuthenticationFailure)
## @brief Method to be called on authentication error
# @brief Method to be called on authentication error
#@throw ClientAuthenticationError
#@throw LodelFatalError if no Client child instance is found
@classmethod
def authentication_error(cls, msg="Unknow error"):
cls._generic_error(ClientAuthenticationError, msg)
## @brief Method to be called on permission denied error
# @brief Method to be called on permission denied error
#@throw ClientPermissionDenied
#@throw LodelFatalError if no Client child instance is found
@classmethod
def permission_denied_error(cls, msg=""):
cls._generic_error(ClientPermissionDenied, msg)
## @brief Generic error method
# @brief Generic error method
#@see Client::authentication_failure() Client::authentication_error()
# Client::permission_denied_error()
#@throw LodelFatalError if no Client child instance is found
@ -256,14 +238,14 @@ a session is already started !!!")
cls._assert_instance()
raise expt(Client._instance, msg)
## @brief Asserts that an instance of Client child class exists
# @brief Asserts that an instance of Client child class exists
#@throw LodelFataError if no instance of Client child class is found
@classmethod
def _assert_instance(cls):
if Client._instance is None:
raise LodelFatalError("No client instance found. Abording.")
## @brief Class method that fetches conf
# @brief Class method that fetches conf
#
# This method populates Client._infos_fields . This attribute stores
# informations on login and password location (LeApi object & field)
@ -300,7 +282,7 @@ login EmClass '%s' and password EmClass '%s'. Abording..." % (
cls._infos_fields.append(
{'login': res_infos[0], 'password': res_infos[1]})
## @brief Sets a user as authenticated and starts a new session
# @brief Sets a user as authenticated and starts a new session
#@param leo LeObject child class : the LeObject the user is stored in
#@param uid str : uniq id (in leo)
#@return None

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# @package lodel.auth.exceptions
# @brief Defines the specific exceptions used in the authentication process

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

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##@brief Stores constant evaluated at build time
#@note Populated by ./configure
#@warning This file HAS TO be safely importable outside any lodel context
@ -27,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

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import importlib
import importlib.machinery
import importlib.abc
@ -36,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"
@ -65,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
@ -80,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...
@ -88,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):
@ -122,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):
@ -170,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
@ -192,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
@ -214,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):
@ -228,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
@ -267,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
@ -305,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)
@ -312,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
@ -329,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
@ -350,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
@ -390,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
@ -406,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
@ -415,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
@ -426,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
@ -440,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

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
SUBDIRS=translator
em_PYTHON=*.py

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##@defgroup lodel2_em Editorial Model
#@brief Data organisation description

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
# @package lodel.editorial_model.components
#@brief Defines all @ref lodel2_em "EM" components
@ -35,7 +17,7 @@ LodelContext.expose_modules(globals(), {
'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'],
'lodel.leapi.leobject': ['CLASS_ID_FIELDNAME']})
## @brief Abstract class to represent editorial model components
# @brief Abstract class to represent editorial model components
# @see EmClass EmField
# @todo forbid '.' in uid
#@ingroup lodel2_em
@ -43,7 +25,7 @@ LodelContext.expose_modules(globals(), {
class EmComponent(MlNamedObject):
## @brief Instanciate an EmComponent
# @brief Instanciate an EmComponent
# @param uid str : uniq identifier
# @param display_name MlString|str|dict : component display_name
# @param help_text MlString|str|dict : help_text
@ -54,13 +36,13 @@ class EmComponent(MlNamedObject):
self.group = group
super().__init__(display_name, help_text)
## @brief Returns the display_name of the component if it is not None, its uid else
# @brief Returns the display_name of the component if it is not None, its uid else
def __str__(self):
if self.display_name is None:
return str(self.uid)
return str(self.display_name)
## @brief Returns a hash code for the component
# @brief Returns a hash code for the component
def d_hash(self):
m = hashlib.md5()
for data in (
@ -72,12 +54,33 @@ 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 Handles editorial model objects classes
##@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
class EmClass(EmComponent):
## @brief Instanciates a new EmClass
# @brief Instanciates a new EmClass
#@param uid str : uniq identifier
#@param display_name MlString|str|dict : component display_name
#@param abstract bool : set the class as asbtract if True
@ -112,7 +115,7 @@ class EmClass(EmComponent):
else:
parents = list()
self.parents = parents
## @brief Stores EmFields instances indexed by field uid
# @brief Stores EmFields instances indexed by field uid
self.__fields = dict()
self.group = group
@ -134,8 +137,13 @@ 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
# @brief Property that represents a dict of all fields
# (the EmField objects defined in this class and all their parents)
# @todo use Settings.editorialmodel.groups to determine which fields should be returned
@property
@ -146,12 +154,12 @@ class EmClass(EmComponent):
res.update(self.__fields)
return res
## @brief RO access to datasource attribute
# @brief RO access to datasource attribute
@property
def datasource(self):
return self.__datasource
## @brief Returns the list of all dependencies
# @brief Returns the list of all dependencies
#
# Recursive parents listing
@property
@ -164,7 +172,7 @@ class EmClass(EmComponent):
res |= parent.parents_recc
return res
## @brief EmField getter
# @brief EmField getter
# @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
# @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
# @return A list on EmFields instances (if uid is None) else return an EmField instance
@ -176,7 +184,7 @@ class EmClass(EmComponent):
except KeyError:
raise EditorialModelError("No such EmField '%s'" % uid)
## @brief Keeps in __fields only fields contained in active groups
# @brief Keeps in __fields only fields contained in active groups
def _set_active_fields(self, active_groups):
if not Settings.editorialmodel.editormode:
active_fields = []
@ -186,7 +194,7 @@ class EmClass(EmComponent):
self.__fields = {fname: fdh for fname, fdh in self.__fields.items()
if fdh in active_fields}
## @brief Adds a field to the EmClass
# @brief Adds a field to the EmClass
# @param emfield EmField : an EmField instance
# @warning do not add an EmField already in another class !
# @throw EditorialModelException if an EmField with same uid already in this EmClass (overwriting allowed from parents)
@ -205,7 +213,7 @@ class EmClass(EmComponent):
self.__fields[emfield.uid] = emfield
return emfield
## @brief Creates a new EmField and adds it to the EmClass
# @brief Creates a new EmField and adds it to the EmClass
# @param data_handler str : A DataHandler name
# @param uid str : the EmField uniq id
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
@ -238,11 +246,11 @@ class EmClass(EmComponent):
return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid))
## @brief Handles editorial model classes fields
# @brief Handles editorial model classes fields
#@ingroup lodel2_em
class EmField(EmComponent):
## @brief Instanciates a new EmField
# @brief Instanciates a new EmField
# @param uid str : uniq identifier
# @param display_name MlString|str|dict : field display_name
# @param data_handler str : A DataHandler name
@ -252,15 +260,15 @@ class EmField(EmComponent):
def __init__(self, uid, data_handler, em_class=None, display_name=None, help_text=None, group=None, **handler_kwargs):
from lodel.leapi.datahandlers.base_classes import DataHandler
super().__init__(uid, display_name, help_text, group)
## @brief The data handler name
# @brief The data handler name
self.data_handler_name = data_handler
## @brief The data handler class
# @brief The data handler class
self.data_handler_cls = DataHandler.from_name(data_handler)
## @brief The data handler instance associated with this EmField
# @brief The data handler instance associated with this EmField
self.data_handler_instance = self.data_handler_cls(**handler_kwargs)
## @brief Stores data handler instanciation options
# @brief Stores data handler instanciation options
self.data_handler_options = handler_kwargs
## @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
# @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
self._emclass = em_class
if self._emclass is None:
warnings.warn("No EmClass for field %s" % uid)
@ -269,11 +277,16 @@ class EmField(EmComponent):
else:
group.add_components([self])
## @brief Returns data_handler_name attribute
##@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)
## @brief Returns data_handler_cls attribute
# @brief Returns data_handler_cls attribute
def get_data_handler_cls(self):
return copy.copy(self.data_handler_cls)
@ -292,13 +305,13 @@ class EmField(EmComponent):
'utf-8')
).digest(), byteorder='big')
## @brief Handles functionnal group of EmComponents
# @brief Handles functionnal group of EmComponents
#@ingroup lodel2_em
class EmGroup(MlNamedObject):
## @brief Creates a new EmGroup
# @brief Creates a new EmGroup
# @note you should NEVER call the constructor yourself. Use Model.add_group instead
# @param uid str : Uniq identifier
# @param depends list : A list of EmGroup dependencies
@ -306,11 +319,11 @@ class EmGroup(MlNamedObject):
# @param help_text MlString|str :
def __init__(self, uid, depends=None, display_name=None, help_text=None):
self.uid = uid
## @brief Stores the list of groups that depends on this EmGroup indexed by uid
# @brief Stores the list of groups that depends on this EmGroup indexed by uid
self.required_by = dict()
## @brief Stores the list of dependencies (EmGroup) indexed by uid
# @brief Stores the list of dependencies (EmGroup) indexed by uid
self.require = dict()
## @brief Stores the list of EmComponent instances contained in this group
# @brief Stores the list of EmComponent instances contained in this group
self.__components = set()
super().__init__(display_name, help_text)
@ -320,7 +333,12 @@ class EmGroup(MlNamedObject):
raise ValueError("EmGroup expected in depends argument but %s found" % grp)
self.add_dependency(grp)
## @brief Returns EmGroup dependencies
##@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
def dependencies(self, recursive=False):
@ -336,7 +354,7 @@ class EmGroup(MlNamedObject):
res[new_dep.uid] = new_dep
return res
## @brief Returns EmGroup applicants
# @brief Returns EmGroup applicants
# @param recursive bool : if True returns all dependencies and their dependencies
# @returns a dict of EmGroup identified by uid
def applicants(self, recursive=False):
@ -352,12 +370,12 @@ class EmGroup(MlNamedObject):
res[new_app.uid] = new_app
return res
## @brief Returns EmGroup components
# @brief Returns EmGroup components
# @returns a copy of the set of components
def components(self):
return (self.__components).copy()
## @brief Returns EmGroup display_name
# @brief Returns EmGroup display_name
# @param lang str | None : If None returns default lang translation
# @returns None if display_name is None, a str for display_name else
def get_display_name(self, lang=None):
@ -366,7 +384,7 @@ class EmGroup(MlNamedObject):
return None
return name.get(lang)
## @brief Returns EmGroup help_text
# @brief Returns EmGroup help_text
# @param lang str | None : If None returns default lang translation
# @returns None if display_name is None, a str for display_name else
def get_help_text(self, lang=None):
@ -375,7 +393,7 @@ class EmGroup(MlNamedObject):
return None
return help.get(lang)
## @brief Adds components in a group
# @brief Adds components in a group
# @param components list : EmComponent instances list
def add_components(self, components):
assert_edit()
@ -390,7 +408,7 @@ class EmGroup(MlNamedObject):
"Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
self.__components |= set(components)
## @brief Add a dependency
# @brief Add a dependency
# @param em_group EmGroup|iterable : an EmGroup instance or list of instances
def add_dependency(self, grp):
assert_edit()
@ -408,7 +426,7 @@ class EmGroup(MlNamedObject):
self.require[grp.uid] = grp
grp.required_by[self.uid] = self
## @brief Add a applicant
# @brief Add a applicant
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
# Useless ???
def add_applicant(self, grp):
@ -427,17 +445,17 @@ class EmGroup(MlNamedObject):
self.required_by[grp.uid] = grp
grp.require[self.uid] = self
## @brief Search for circular dependency
# @brief Search for circular dependency
# @return True if circular dep found else False
def __circular_dependency(self, new_dep):
return self.uid in new_dep.dependencies(True)
## @brief Search for circular applicant
# @brief Search for circular applicant
# @return True if circular app found else False
def __circular_applicant(self, new_app):
return self.uid in new_app.applicants(True)
## @brief Fancy string representation of an EmGroup
# @brief Fancy string representation of an EmGroup
# @return a string
def __str__(self):
if self.display_name is None:
@ -445,7 +463,7 @@ class EmGroup(MlNamedObject):
else:
return self.display_name.get()
## @brief Computes a d-hash code for the EmGroup
# @brief Computes a d-hash code for the EmGroup
# @return a string
def d_hash(self):
@ -465,7 +483,7 @@ class EmGroup(MlNamedObject):
byteorder='big'
)
## @brief Complete string representation of an EmGroup
# @brief Complete string representation of an EmGroup
# @return a string
def __repr__(self):
return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]))

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
## @package lodel.editorial_model.exceptions
# This module contains the specific exceptions related to the EditorialModel Management.

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding:utf-8 -*-
import hashlib
import importlib
@ -34,19 +16,28 @@ LodelContext.expose_modules(globals(), {
'lodel.editorial_model.components': ['EmClass', 'EmField', 'EmGroup']})
## @brief Describe an editorial model
# @brief Describe an editorial model
#@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):
self.name = MlString(name)
self.description = MlString(description)
## @brief Stores all groups indexed by id
# @brief Stores all groups indexed by id
self.__groups = dict()
## @brief Stores all classes indexed by id
# @brief Stores all classes indexed by id
self.__classes = dict()
#  @brief Stores all activated groups indexed by id
self.__active_groups = dict()
@ -59,7 +50,7 @@ class EditorialModel(MlNamedObject):
help_text = description
super().__init__(display_name, help_text)
## @brief EmClass uids accessor
# @brief EmClass uids accessor
#@return a copy of the dict containing all emclasses of the model if uid is None
# else a copy the class with uid uid
def all_classes(self, uid=None):
@ -71,7 +62,7 @@ class EditorialModel(MlNamedObject):
except KeyError:
raise EditorialModelException("EmClass not found : '%s'" % uid)
## @brief EmClass uids accessor
# @brief EmClass uids accessor
#@return the dict containing all emclasses of the model if uid is None
# else the class with uid uid
def all_classes_ref(self, uid=None):
@ -83,12 +74,12 @@ class EditorialModel(MlNamedObject):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
## @brief active EmClass uids accessor
# @brief active EmClass uids accessor
#@return a list of active class uids
def active_classes_uids(self):
return list(self.__active_classes.keys())
## @brief EmGroups accessor
# @brief EmGroups accessor
#@return a copy of the dict of the model's group if uid is None
# else a copy of the group with uniq id uid
def all_groups(self, uid=None):
@ -100,7 +91,7 @@ class EditorialModel(MlNamedObject):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
## @brief EmGroups accessor
# @brief EmGroups accessor
#@return the dict of the model's group if uid is None
# else the group with uniq id uid
def all_groups_ref(self, uid=None):
@ -112,12 +103,12 @@ class EditorialModel(MlNamedObject):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
## @brief active EmClass uids accessor
# @brief active EmClass uids accessor
#@return a list of active group uids
def active_groups_uids(self):
return list(self.__active_groups.keys())
## @brief EmClass accessor
# @brief EmClass accessor
#@param uid None | str : give this argument to get a specific EmClass
#@return if uid is given returns an EmClass else returns an EmClass
# iterator
@ -130,7 +121,7 @@ class EditorialModel(MlNamedObject):
except KeyError:
raise EditorialModelException("EmClass not found : '%s'" % uid)
## @brief EmClass child list accessor
# @brief EmClass child list accessor
#@param uid str : the EmClass uid
#@return a set of EmClass
def get_class_childs(self, uid):
@ -141,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):
@ -151,15 +155,15 @@ class EditorialModel(MlNamedObject):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
## @brief Private getter for __groups or __classes
# @brief Private getter for __groups or __classes
# @see classes() groups()
def __elt_getter(self, elts, uid):
return list(elts.values()) if uid is None else elts[uid]
## @brief Update the EditorialModel.__active_groups and
# @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
@ -168,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...")
@ -183,7 +187,7 @@ class EditorialModel(MlNamedObject):
for clsname, acls in self.__active_classes.items():
acls._set_active_fields(self.__active_groups)
## @brief EmField getter
# @brief EmField getter
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
# @return Fals or an EmField instance
#
@ -204,7 +208,7 @@ class EditorialModel(MlNamedObject):
pass
return False
## @brief Add a class to the editorial model
# @brief Add a class to the editorial model
# @param emclass EmClass : the EmClass instance to add
# @return emclass
def add_class(self, emclass):
@ -216,7 +220,7 @@ class EditorialModel(MlNamedObject):
self.__classes[emclass.uid] = emclass
return emclass
## @brief Add a group to the editorial model
# @brief Add a group to the editorial model
# @param emgroup EmGroup : the EmGroup instance to add
# @return emgroup
def add_group(self, emgroup):
@ -228,7 +232,7 @@ class EditorialModel(MlNamedObject):
self.__groups[emgroup.uid] = emgroup
return emgroup
## @brief Add a new EmClass to the editorial model
# @brief Add a new EmClass to the editorial model
#@param uid str : EmClass uid
#@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() )
@ -236,7 +240,7 @@ class EditorialModel(MlNamedObject):
assert_edit()
return self.add_class(EmClass(uid, **kwargs))
## @brief Add a new EmGroup to the editorial model
# @brief Add a new EmGroup to the editorial model
#@param uid str : EmGroup uid
#@param *kwargs : EmGroup constructor keywords arguments (
# see @ref lodel.editorial_model.component.EmGroup.__init__() )
@ -244,7 +248,7 @@ class EditorialModel(MlNamedObject):
assert_edit()
return self.add_group(EmGroup(uid, **kwargs))
## @brief Save a model
# @brief Save a model
# @param translator module : The translator module to use
# @param **translator_args
def save(self, translator, **translator_kwargs):
@ -253,14 +257,13 @@ class EditorialModel(MlNamedObject):
translator = self.translator_from_name(translator)
return translator.save(self, **translator_kwargs)
## @brief Raise an error if lodel is not in EM edition mode
# @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
# @brief Load a model
# @param translator module : The translator module to use
# @param **translator_args
@classmethod
@ -271,7 +274,7 @@ class EditorialModel(MlNamedObject):
res.__set_actives()
return res
## @brief Return a translator module given a translator name
# @brief Return a translator module given a translator name
# @param translator_name str : The translator name
# @return the translator python module
# @throw NameError if the translator does not exists
@ -284,7 +287,7 @@ class EditorialModel(MlNamedObject):
raise NameError("No translator named %s")
return mod
## @brief Lodel hash
# @brief Lodel hash
def d_hash(self):
payload = "%s%s" % (
self.name,
@ -301,7 +304,7 @@ class EditorialModel(MlNamedObject):
byteorder='big'
)
## @brief Returns a list of all datahandlers
# @brief Returns a list of all datahandlers
# @return a list of all datahandlers
@staticmethod
def list_datahandlers():

View file

@ -1,22 +1,2 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
translator_PYTHON=*.py
translatordir=$(pkgpythondir)/editorial_model/translator

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.editorial_model.translator Editorial model translators
#
# This package is dedicated to the translation of an EditorialModel as several formats like pickle files or XML files. \n

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

@ -1,23 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
##@package lodel.editorial_model.translator.picklefile
# This module handles the file storage of an editorial model

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
import lxml
import os

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
class LodelException(Exception):
pass
@ -49,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

@ -1,24 +1,5 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
SUBDIRS=datahandlers
leapi_PYTHON= *.py
leapidir=$(pkgpythondir)/leapi

View file

@ -1,226 +1,208 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##@defgroup lodel2_leapi LeAPI
#@brief Lodel2 Editorial API
#
#Provide access to datas via objects as defined in the Editorial Model
## @defgroup lodel2_leapi LeAPI
# @brief Lodel2 Editorial API
#
# Provides access to any kind of data in Lodel2, using classes
# defined by the Editorial Model
## @package lodel.leapi
# @brief Lodel2 Editorial API
##@package lodel.leapi
#@brief Lodel2 Editorial API
#
# Defines an API to access to objects as described by
# @ref lodel.editorial_model "Editorial Model"
#@ref lodel.editorial_model "Editorial Model"
## @page lodel2_leapi_page LeAPI
# @ingroup lodel2_leapi
##@page lodel2_leapi_page LeAPI
#@ingroup lodel2_leapi
#
# @section lodel2_leapi_base LeAPI (Lodel Editorial API)
#
# LeAPI is an API that provides access to data as defined in an
# @ref lodel2_em "Editorial Model".
# LeAPI is an API that provide access to datas as defined in an
#@ref lodel2_em "Editorial Model".
#
# @subsection lodel2_leapi_gen LeAPI dynamic code & LeFactory
#@subsection lodel2_leapi_gen LeAPI dynamic code & LeFactory
#
# LeAPI's python code is programmaticaly generated by
# @ref lodel.leapi.lefactory "LeFactory", who generates
# @ref lodel.leapi.leobject.LeObject "LeObject" child classes.
#LeAPI python code is programmaticaly generated by
#@ref lodel.leapi.lefactory "LeFactory". LeFactory generates
#@ref lodel.leapi.leobject.LeObject "LeObject" child classes.
#
# @par Example
#
#@par Example
#<pre>
#from lodel.leapi.leobject import LeObject
#from lodel.leapi.datahandlers.base_classes import DataField
#from lodel.plugin.hooks import LodelHook
#
#class Abstract_Object(LeObject):
# _abstract = True
# _fields = None
# _uid = []
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
#class User(LeObject):
# _abstract = False
# _fields = None
# _uid = ['id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
#class Object(Abstract_Object):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
#class Entry(Object):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
#class Entitie(Object):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
#class Person(Object):
# _abstract = False
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#class Text(Entitie):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
#class Publication(Entitie):
# _abstract = False
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#Abstract_Object._set__fields({})
#Abstract_Object._child_classes = (Section, Text, Object, Entry, Collection, Text_Person, Entitie, Indextheme, Person, Indexabs, Publication, Subsection,)
#User._set__fields({
# 'firstname': DataField.from_name('varchar')(**{ 'internal': False }),
# 'lastname': DataField.from_name('varchar')(**{ 'internal': False }),
# 'classname': DataField.from_name('LeobjectSubclassIdentifier')(**{ 'internal': True }),
# 'login': DataField.from_name('varchar')(**{ 'internal': True, 'uniq': True }),
# 'id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'password': DataField.from_name('password')(**{ 'internal': False })})
#User._child_classes = tuple()
#Object._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
#Object._child_classes = (Section, Text, Entry, Collection, Text_Person, Entitie, Indextheme, Person, Indexabs, Publication, Subsection,)
#Entry._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
#Entry._child_classes = tuple()
#Entitie._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
#Entitie._child_classes = (Section, Text, Text_Person, Collection, Publication, Subsection,)
#Person._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'firstname': DataField.from_name('varchar')(**{ }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'linked_texts': DataField.from_name('list')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Text], 'back_reference': (Text, 'linked_persons') }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'lastname': DataField.from_name('varchar')(**{ }),
# 'fullname': DataField.from_name('Concat')(**{ 'field_list': ['firstname', 'lastname'], 'immutable': True }),
# 'classname': DataField.from_name('LeobjectSubclassIdentifier')(**{ 'internal': True }),
# 'alias': DataField.from_name('set')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Person] }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
#Person._child_classes = tuple()
#Text._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'subtitle': DataField.from_name('varchar')(**{ 'nullable': True, 'default': None }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'linked_persons': DataField.from_name('list')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Person], 'back_reference': (Person, 'linked_texts') }),
# 'indexes': DataField.from_name('list')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Indexabs], 'back_reference': (Indexabs, 'texts') }),
# 'title': DataField.from_name('varchar')(**{ 'nullable': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
#Text._child_classes = (Section, Subsection,)
#Publication._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'classname': DataField.from_name('LeobjectSubclassIdentifier')(**{ 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'collection': DataField.from_name('link')(**{ 'back_reference': (Collection, 'publications') }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
#Publication._child_classes = tuple()
#
#\# List of dynamically generated classes
#dynclasses = [Abstract_Object, User, Object, Entry, Entitie, Person, Indexabs, Text, Publication, Collection, Indextheme, Text_Person, Section, Subsection]
#\# Dict of dynamically generated classes indexed by name
#dynclasses_dict = {'Abstract_Object': Abstract_Object, 'User': User, 'Object': Object, 'Entry': Entry, 'Entitie': Entitie, 'Person': Person, 'Indexabs': Indexabs, 'Text': Text, 'Publication': Publication, 'Collection': Collection, 'Indextheme': Indextheme, 'Text_Person': Text_Person, 'Section': Section, 'Subsection': Subsection}
#
#
#\#\# \@brief Return a dynamically generated class given it's name
#\# \@param name str : The dynamic class name
#\# \@return False or a child class of LeObject
#def name2class(name):
# if name not in dynclasses_dict:
# return False
# return dynclasses_dict[name]
#
#
#\#\# \@brief Return a dynamically generated class given it's name
#\# \@note Case insensitive version of name2class
#\# \@param name str
#\# \@retrun False or a child class of LeObject
#def lowername2class(name):
# name = name.lower()
# new_dict = {k.lower():v for k,v in dynclasses_dict.items()}
# if name not in new_dict:
# return False
# return new_dict[name]
#
#
#\#\# \@brief Trigger dynclasses datasources initialisation
#@LodelHook("lodel2_plugins_loaded")
#def lodel2_dyncode_datasources_init(self, caller, payload):
# for cls in dynclasses:
# cls._init_datasources()
# from lodel.leapi.leobject import LeObject
# from lodel.leapi.datahandlers.base_classes import DataField
# from lodel.plugin.hooks import LodelHook
# LodelHook.call_hook("lodel2_dyncode_loaded", __name__, dynclasses)
#
# class Abstract_Object(LeObject):
# _abstract = True
# _fields = None
# _uid = []
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
# class User(LeObject):
# _abstract = False
# _fields = None
# _uid = ['id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
# class Object(Abstract_Object):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
# class Entry(Object):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
# class Entitie(Object):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
# class Person(Object):
# _abstract = False
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
# class Text(Entitie):
# _abstract = True
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
#
# class Publication(Entitie):
# _abstract = False
# _fields = None
# _uid = ['lodel_id']
# _ro_datasource = None
# _rw_datasource = None
# _datasource_name = 'default'
# _child_classes = None
#
# Abstract_Object._set__fields({})
# Abstract_Object._child_classes = (Section, Text, Object, Entry, Collection, Text_Person, Entitie, Indextheme, Person, Indexabs, Publication, Subsection,)
# User._set__fields({
# 'firstname': DataField.from_name('varchar')(**{ 'internal': False }),
# 'lastname': DataField.from_name('varchar')(**{ 'internal': False }),
# 'classname': DataField.from_name('LeobjectSubclassIdentifier')(**{ 'internal': True }),
# 'login': DataField.from_name('varchar')(**{ 'internal': True, 'uniq': True }),
# 'id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'password': DataField.from_name('password')(**{ 'internal': False })})
# User._child_classes = tuple()
# Object._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
# Object._child_classes = (Section, Text, Entry, Collection, Text_Person, Entitie, Indextheme, Person, Indexabs, Publication, Subsection,)
# Entry._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
# Entry._child_classes = tuple()
# Entitie._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
# Entitie._child_classes = (Section, Text, Text_Person, Collection, Publication, Subsection,)
# Person._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'firstname': DataField.from_name('varchar')(**{ }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'linked_texts': DataField.from_name('list')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Text], 'back_reference': (Text, 'linked_persons') }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'lastname': DataField.from_name('varchar')(**{ }),
# 'fullname': DataField.from_name('Concat')(**{ 'field_list': ['firstname', 'lastname'], 'immutable': True }),
# 'classname': DataField.from_name('LeobjectSubclassIdentifier')(**{ 'internal': True }),
# 'alias': DataField.from_name('set')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Person] }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
# Person._child_classes = tuple()
# Text._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'subtitle': DataField.from_name('varchar')(**{ 'nullable': True, 'default': None }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'linked_persons': DataField.from_name('list')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Person], 'back_reference': (Person, 'linked_texts') }),
# 'indexes': DataField.from_name('list')(**{ 'nullable': True, 'default': None, 'allowed_classes': [Indexabs], 'back_reference': (Indexabs, 'texts') }),
# 'title': DataField.from_name('varchar')(**{ 'nullable': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
# Text._child_classes = (Section, Subsection,)
# Publication._set__fields({
# 'date_update': DataField.from_name('datetime')(**{ 'now_on_update': True, 'internal': True }),
# 'classname': DataField.from_name('LeobjectSubclassIdentifier')(**{ 'internal': True }),
# 'lodel_id': DataField.from_name('uniqid')(**{ 'internal': True }),
# 'collection': DataField.from_name('link')(**{ 'back_reference': (Collection, 'publications') }),
# 'help_text': DataField.from_name('text')(**{ 'internal': True }),
# 'date_create': DataField.from_name('datetime')(**{ 'internal': True, 'now_on_create': True })})
# Publication._child_classes = tuple()
#
# #List of dynamically generated classes
# dynclasses = [Abstract_Object, User, Object, Entry, Entitie, Person, Indexabs, Text, Publication, Collection, Indextheme, Text_Person, Section, Subsection]
# #Dict of dynamically generated classes indexed by name
# dynclasses_dict = {'Abstract_Object': Abstract_Object, 'User': User, 'Object': Object, 'Entry': Entry, 'Entitie': Entitie, 'Person': Person, 'Indexabs': Indexabs, 'Text': Text, 'Publication': Publication, 'Collection': Collection, 'Indextheme': Indextheme, 'Text_Person': Text_Person, 'Section': Section, 'Subsection': Subsection}
#
#
# ##@brief Return a dynamically generated class given it's name
# #@param name str : The dynamic class name
# #@return False or a child class of LeObject
# def name2class(name):
# if name not in dynclasses_dict:
# return False
# return dynclasses_dict[name]
#
#
# ##@brief Return a dynamically generated class given it's name
# #@note Case insensitive version of name2class
# #@param name str
# #@retrun False or a child class of LeObject
# def lowername2class(name):
# name = name.lower()
# new_dict = {k.lower():v for k,v in dynclasses_dict.items()}
# if name not in new_dict:
# return False
# return new_dict[name]
#
#
# ##@brief Trigger dynclasses datasources initialisation
# @LodelHook("lodel2_plugins_loaded")
# def lodel2_dyncode_datasources_init(self, caller, payload):
# for cls in dynclasses:
# cls._init_datasources()
# from lodel.plugin.hooks import LodelHook
# LodelHook.call_hook("lodel2_dyncode_loaded", __name__, dynclasses)
#
#
#
#</pre>
#

View file

@ -1,22 +1,2 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
dh_PYTHON=*.py
dhdir=$(pkgpythondir)/leapi/datahandlers

View file

@ -1,80 +1,62 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from lodel.leapi.datahandlers.base_classes import DataHandler
DataHandler.load_base_handlers()
## @defgroup lodel2_datahandlers Datahandlers
# @ingroup lodel2_leapi
# @ingroup lodel2_em
##@defgroup lodel2_datahandlers Datahandlers
#@ingroup lodel2_leapi
#@ingroup lodel2_em
## @defgroup lodel2_dh_checks Datahandlers datas checking
# @ingroup lodel2_datahandlers
##@defgroup lodel2_dh_checks Datahandlers datas checking
#@ingroup lodel2_datahandlers
## @package lodel.leapi.datahandlers Lodel2 datahandlers
##@package lodel.leapi.datahandlers Lodel2 datahandlers
#
# Datahandlers are object that handles datas check, construction and
# consistency check
#Datahandlers are object that handles datas check, construction and
#consistency check
#
## @page lodel2_dh_checks_page Datahandlers datas checking
# @ingroup lodel2_dh_checks
##@page lodel2_dh_checks_page Datahandlers datas checking
#@ingroup lodel2_dh_checks
#
# @section lodel2_dh_check_mech Datas checking mechanism
#@section lodel2_dh_check_mech Datas checking mechanism
#
# The data checking mechanism is divided into 3 stages :
# 1. **value checking** : a basic value check. Example : is len(value) < 52 ?
# 2. **data construction** : for data that needs to be modified. Example :
# a date that will be transformed into a Datetime object associated with a
# timezone
# 3. **data consistency checking** : performs a consistency checking on the
# object from the "point of view" of the current field. Example : is the
# given lodel_id an identifier of an Article ?
#The data checking mechanism is divided into 3 stages :
# 1. **value checking** : a basic value check. Example : is len(value) < 52
# 2. **data construction** : for datas that needs to be modified. Example :
#a date that will be transformed into a Datetime object associated with
#a timezone
# 3. **data consistency checking** : perform a consistency checking on the
#object from the "point of view" of the current field. Example : is the given
#lodel_id an identifier of a Article
#
# @subsection lodel2_dh_check_impl Implementation
#@subsection lodel2_dh_check_impl Implementation
#
# Those three stages are implemented by 3 datahandlers methods :
#Those three stages are implemented by 3 datahandlers methods :
# - @ref base_classes.DataHandler.check_data_value() "check_data_value"
# - @ref base_classes.DataHandler.construct_data() "construct_data"
# - @ref base_classes.DataHandler.check_data_value() "check_data_consitency"
#
# @note To ensure the calls of the base classes methods child classes implements
# those method with a '_' prefix :
#@note To ensure the calls of the base classes methods child classes implements
#those method with a '_' preffix :
# - @ref base_classes.DataHandler._check_data_value "_check_data_value()"
# - @ref base_classes.DataHandler._construct_data() "_construct_data"
# - @ref base_classes.DataHandler._check_data_value() "_check_data_consitency"
#
# Examples of child classes can be found @ref datahandlers.datas "here"
#Examples of child classes can be found @ref datahandlers.datas "here"
#
# @subsubsection lodel2_dh_datas_construction Datas construction
#@subsubsection lodel2_dh_datas_construction Datas construction
#
# When constructing a data handled by a datahandler we may need to have access
# to other datas in the object (see @ref base_classes.DataHandler.construct_data() "construct_data() arguments").
#Datas construction is a bit tricky. When constructing a data handled by a
#datahandler we may need to have access to other datas in the object (see
#@ref base_classes.DataHandler.construct_data() "construct_data() arguments").
#
# The problem resides in the construction order, if we need other data we have
# to be sure that they are already constructed. To achieve this goal the data
# dictionary given as arguement to @ref base_classes.DataHandler.construct_data() "construct_data()"
# is a @ref base_classes.DatasConstructor "DatasConstructor" instance. This class
# checks if a data is constructed when trying to access it, if not it runs
# the corresponding construct_data() (and have a circular dependencies detection
# mechanism)
#The problem reside in the construction order, if we need other datas we have
#to be sure that they are allready constructed. To achieve this goal the datas
#dict given as arguement to
#@ref base_classes.DataHandler.construct_data() "construct_data()" is an
#@ref base_classes.DatasConstructor "DatasConstructor" instance. This class
#check if a data is constructed when trying to access to it, if not it runs
#the corresponding construct_data() (and have a circular dependencies detection
#mechanism)
#
#@see base_classes.DatasConstructor.

View file

@ -1,26 +1,6 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
##
#  @package lodel.leapi.datahandlers.base_classes Defines all base/abstract
# classes for DataHandlers
#  @package lodel.leapi.datahandlers.base_classes Define all base/abstract class for data handlers
#
# Contains custom exceptions too
@ -50,33 +30,15 @@ LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.utils.mlstring': ['MlString']})
##
# @brief Base class for all DataHandlers
# @brief Base class for all data handlers
# @ingroup lodel2_datahandlers
#
# @remarks Some of the methods and properties in this "abstract" class are
# bounded to its children. This implies that the parent
# is aware of its children, which is an absolute anti-pattern
# (Liskov / OC violation), a source of confusion and a decrased
# maintainability. Aggregation =/= Inheritance
# Concerned methods are: is_reference; is_singlereference.
# Concerned properties are __custom_datahandlers; base_handlers.
# @remarks What is the purpose of an internal property being set to a
# string (namely 'automatic')
# @remarks Two sets of methods appears a little strange in regards to their
# visibility.
# - @ref _construct_data / @ref construct_data
# - @ref _check_data_consistency / @ref check_data_consistency
class DataHandler(MlNamedObject):
base_type = "type"
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
##
# @brief Stores the DataHandler child classes indexed by name
# @brief Stores the DataHandler childs classes indexed by name
_base_handlers = None
##
# @brief Stores custom DataHandlers classes indexed by name
# @brief Stores custom datahandlers classes indexed by name
# @todo do it ! (like plugins, register handlers... blablabla)
__custom_handlers = dict()
@ -85,22 +47,17 @@ class DataHandler(MlNamedObject):
options_spec = dict()
options_values = dict()
##
# @brief Lists fields that will be exposed to the construct_data method
# @brief List fields that will be exposed to the construct_data_method
_construct_datas_deps = []
directly_editable = True
##
# @brief constructor
#
# @param internal False | str : define whether or not a field is internal
# @param immutable bool : Indicates if the fieldtype has to be defined in child classes of
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of
# LeObject or if it is designed globally and immutable
# @throw NotImplementedError If it is instantiated directly
# @remarks Shouldn't the class be declared abstract? No need to check if it
# is instantiated directly, no exception to throw, cleaner code.
# @throw NotImplementedError if it is instanciated directly
def __init__(self, **kwargs):
if self.__class__ == DataHandler:
raise NotImplementedError("Abstract class")
@ -110,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:
@ -117,18 +75,21 @@ 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))
help_text = kwargs.get('help_text', MlString(self.help_text))
super().__init__(display_name, help_text)
##
# @brief Sets properly cast and checked options for the DataHandler
#
# @throw LodelDataHandlerNotAllowedOptionException when a passed option
# is not in the option specifications of the DataHandler
# @brief Sets properly casted and checked options for the datahandler
# @raises LodelDataHandlerNotAllowedOptionException when a passed option is not in the option
# specifications of the datahandler
def check_options(self):
for option_name, option_datas in self.options_spec.items():
if option_name in self.options_values:
@ -142,70 +103,45 @@ class DataHandler(MlNamedObject):
# This option was not configured, we get the default value from the specs
self.options_values[option_name] = option_datas[0]
##
# @return string: Field type name
# Fieldtype name
@classmethod
def name(cls):
return cls.__module__.split('.')[-1]
##
# @return bool: True if subclass is of Reference type, False otherwise.
@classmethod
def is_reference(cls):
return issubclass(cls, Reference)
##
# @return bool: True if subclass is of SingleRef type, False otherwise.
@classmethod
def is_singlereference(cls):
return issubclass(cls, SingleRef)
##
# @return bool: True if the field is a primary_key, False otherwise.
def is_primary_key(self):
return self.primary_key
def is_uniq(self):
return self.uniq
##
# @brief checks if a field type is internal
# @return bool: True if the field is internal, False otherwise.
# @brief checks if a fieldtype is internal
# @return bool
def is_internal(self):
return self.internal is not False
##
# @brief check if a value can be nullable
#
# @param value *
# @throw DataNoneValid if value is None and nullable.
# @throw LodelExceptions if not nullable
# @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
# @return value (if not None)
# @return value
#
# @remarks why are there an thrown exception if it is allowed?
# Exceptions are no message brokers
def _check_data_value(self, value):
if value is None:
if not self.nullable:
raise LodelExceptions("None value is forbidden for this data field")
raise DataNoneValid("None with a nullable. This exception is allowed")
raise DataNoneValid("None with a nullable. This exeption is allowed")
return value
##
# @brief calls the data_field (defined in derived class) _check_data_value() method
# @param value *
# @return tuple (value|None, None|error) value can be cast if NoneError
# @remarks Consider renaming this method, such as '_is_data_nullable'.
# @remarks Exceptions ARE NOT message brokers! Moreover, those two methods
# are more complicated than required. In case 'value' is None,
# the returned value is the same as the input value. This is the
# same behavior as when the value is not None!
# @return What's a "NoneError"? Value can be cast to what?
def check_data_value(self, value):
try:
value = self._check_data_value(value)
@ -215,36 +151,25 @@ class DataHandler(MlNamedObject):
return None, expt
return value, None
##
# @brief Checks if this class can override the given data handler.
# i.e. both class having the same base_type.
# @brief checks if this class can override the given data handler
# @param data_handler DataHandler
# @return bool
# @remarks Simplify by "return data_handler.__class__.base_type == self.__class__.base_type"?
def can_override(self, data_handler):
if data_handler.__class__.base_type != self.__class__.base_type:
return False
return True
##
# @brief Build field value
#
# @ingroup lodel2_dh_checks
# @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
# @ref _construct_data() and @ref lodel2_dh_check_impl )
#
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname str : The field name
# @param datas dict : dict storing fields values (from the component)
# @param cur_value : the value from the current field (identified by fieldname)
# @return the value
# @throw RunTimeError if data construction fails
#
# @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
# @todo raise something else
#
# @remarks What the todo up right here means? Raise what? When?
# @remarks Nothing is being raised in this method, should it?
def construct_data(self, emcomponent, fname, datas, cur_value):
emcomponent_fields = emcomponent.fields()
data_handler = None
@ -259,71 +184,50 @@ class DataHandler(MlNamedObject):
new_val = None
return self._construct_data(emcomponent, fname, datas, new_val)
##
# @brief Designed to be reimplemented by child classes
#
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname str : The field name
# @param datas dict : dict storing fields values (from the component)
# @param cur_value : the value from the current field (identified by fieldname)
# @return the value
# @see construct_data() lodel2_dh_check_impl
def _construct_data(self, emcomponent, fname, datas, cur_value):
def _construct_data(self, empcomponent, fname, datas, cur_value):
return cur_value
##
# @brief Check data consistency
# @brief Check datas consistency
# @ingroup lodel2_dh_checks
#
# @ref lodel2_dh_datas_construction "Data construction section"
# @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
# @ref _construct_data() and @ref lodel2_dh_check_impl )
# @warning the datas argument looks like a dict but is not a dict
# see @ref base_classes.DatasConstructor "DatasConstructor" and
# @ref lodel2_dh_datas_construction "Datas construction section"
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname : the field name
# @param datas dict : dict storing fields values
# @return an Exception instance if fails else True
#
# @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
# @ref _construct_data() and @ref lodel2_dh_check_impl )
# @warning the data argument looks like a dict but is not a dict
# see @ref base_classes.DatasConstructor "DatasConstructor" and
# @todo A implémenter
def check_data_consistency(self, emcomponent, fname, datas):
return self._check_data_consistency(emcomponent, fname, datas)
##
# @brief Designed to be reimplemented by child classes
#
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname : the field name
# @param datas dict : dict storing fields values
# @return an Exception instance if fails else True
#
# @see check_data_consistency() lodel2_dh_check_impl
def _check_data_consistency(self, emcomponent, fname, datas):
return True
##
# @brief Makes consistency after a query
#
# @brief make consistency after a query
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname : the field name
# @param datas dict : dict storing fields values
# @return an Exception instance if fails else True
#
# @todo To be implemented
# @remarks It not clear what is the intent of this method...
# @todo A implémenter
def make_consistency(self, emcomponent, fname, datas):
pass
##
# @brief Registers a new data handlers
#
# @note Used by plugins.
# @remarks This method is actually never used anywhere. May consider removing it.
# @brief This method is use by plugins to register new data handlers
@classmethod
def register_new_handler(cls, name, data_handler):
if not inspect.isclass(data_handler):
@ -332,9 +236,7 @@ class DataHandler(MlNamedObject):
raise ValueError("A data handler HAS TO be a child class of DataHandler")
cls.__custom_handlers[name] = data_handler
##
# @brief Loads all DataHandlers
# @brief Load all datahandlers
@classmethod
def load_base_handlers(cls):
if cls._base_handlers is None:
@ -347,38 +249,22 @@ class DataHandler(MlNamedObject):
cls._base_handlers[name.lower()] = obj
return copy.copy(cls._base_handlers)
##
# @brief given a field type name, returns the associated python class
#
# @param name str : A field type name (not case sensitive)
# @param fieldtype_name str : A field type name (not case sensitive)
# @return DataField child class
# @throw NameError
#
# @note Would not it be better to prefix the DataHandler name with the
# plugin's one so that it is ensured names are unique?
# @remarks "do/get what from name?" Consider renaming this method (e.g.
# 'get_datafield_from_name')
# @note To access custom data handlers it can be cool to prefix the handler name by plugin
# name for example ? (to ensure name unicity)
@classmethod
def from_name(cls, name):
cls.load_base_handlers()
all_handlers = dict(cls._base_handlers, **cls.__custom_handlers)
name = name.lower()
if name not in all_handlers:
raise NameError("No data handlers named '%s'" % (name,))
return all_handlers[name]
##
# @brief List all DataHandlers
# @brief List all datahandlers
# @return a dict with, display_name for keys, and a dict for value
# @remarks ATM, solely used by the EditorialModel.
# @remarks EditorialModel own class does nothing but calls this class.
# Moreover, nothing calls it anyway.
# @remarks It also seems like it is an EM related concern, and has
# nothing to do with this class. That list appears to be doing
# a purely presentational job. Isn't that a serialization instead?
@classmethod
def list_data_handlers(cls):
cls.load_base_handlers()
@ -393,39 +279,33 @@ class DataHandler(MlNamedObject):
return list_dh
##
# @brief Return the module name to import in order to use the DataHandler
# @param datahandler_name str : Data handler name
# @return str
# @remarks consider renaming this (e.g. "datahandler_module_name")
# @brief Return the module name to import in order to use the datahandler
# @param data_handler_name str : Data handler name
# @return a str
@classmethod
def module_name(cls, datahandler_name):
datahandler_name = datahandler_name.lower()
handler_class = cls.from_name(datahandler_name)
def module_name(cls, name):
name = name.lower()
handler_class = cls.from_name(name)
return '{module_name}.{class_name}'.format(
module_name=handler_class.__module__,
class_name=handler_class.__name__
)
##
# @brief __hash__ implementation for field types
# @brief __hash__ implementation for fieldtypes
def __hash__(self):
hash_dats = [self.__class__.__module__]
for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
hash_dats.append((kdic, getattr(self, kdic)))
return hash(tuple(hash_dats))
##
# @brief Base class for data data handler (by opposition with references)
# @brief Base class for datas data handler (by opposition with references)
# @ingroup lodel2_datahandlers
class DataField(DataHandler):
pass
##
# @brief Abstract class for all references
# @ingroup lodel2_datahandlers
#
@ -433,31 +313,21 @@ class DataField(DataHandler):
# editorial object
# @todo Construct data implementation : transform the data into a LeObject instance
class Reference(DataHandler):
base_type = "ref"
##
# @brief Instantiation
# @brief Instanciation
# @param allowed_classes list | None : list of allowed em classes if None no restriction
# @param back_reference tuple | None : tuple containing (LeObject child class, field name)
# @param internal bool | string: if False, the field is not internal
# @param back_reference tuple | None : tuple containing (LeObject child class, fieldname)
# @param internal bool : if False, the field is not internal
# @param **kwargs : other arguments
# @throw ValueError
# @remarks internal may hold the string value 'automatic'. So far, nothing
# mentions what that means, and nothing seems to be aware
# of an 'automatic' value (at least not in leapi package)
def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs):
self.__allowed_classes = set() if allowed_classes is None else set(allowed_classes)
##
# @note what is "useful to Jinja 2"?
# For now useful to jinja 2
# For now usefull to jinja 2
self.allowed_classes = list() if allowed_classes is None else allowed_classes
if back_reference is not None:
if len(back_reference) != 2:
raise ValueError(
"A tuple (classname, fieldname) expected but got '%s'" % back_reference)
##
# @note Why is there commented out code? Should it be deleted? Ractivated?
# if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0])
# or not isinstance(back_reference[1], str):
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str)
@ -465,24 +335,17 @@ class Reference(DataHandler):
self.__back_reference = back_reference
super().__init__(internal=internal, **kwargs)
##
# @brief Method designed to return an empty value for this kind of
# multipleref
# @remarks purpose!?
@classmethod
def empty(cls):
return None
##
# @brief Property that takes value of a copy of the back_reference tuple
@property
def back_reference(self):
return copy.copy(self.__back_reference)
##
# @brief Property that takes value of datahandler of the backreference or
# None
@property
@ -495,17 +358,12 @@ class Reference(DataHandler):
def linked_classes(self):
return copy.copy(self.__allowed_classes)
##
# @brief Sets a back reference.
# @brief Set the back reference for this field.
def _set_back_reference(self, back_reference):
self.__back_reference = back_reference
##
# @brief Check and cast value in the appropriate type
#
# @param value
# @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is an appropriate type
# @return value
# @todo implement the check when we have LeObject uid check value
@ -521,85 +379,62 @@ class Reference(DataHandler):
value = uiddh._check_data_value(value)
else:
raise FieldValidationError(
"Reference datahandler can not check this value %s if any allowed_class is allowed. " % value)
"Reference datahandler can not check this value %s if any allowed_class is allowed." % value)
return value
##
# @brief Check data consistency
#
# @param emcomponent EmComponent :
# @param fname string : the field name
# @brief Check datas consistency
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname : the field name
# @param datas dict : dict storing fields values
# @return bool | Exception :
#
# @todo check for performance issues and checks logic
# @warning composed uid capabilities are broken
# @remarks Is that really a legitimate case of retuning an Exception object?
# @return an Exception instance if fails else True
# @todo check for performance issue and check logics
# @warning composed uid capabilities broken here
def check_data_consistency(self, emcomponent, fname, datas):
rep = super().check_data_consistency(emcomponent, fname, datas)
if isinstance(rep, Exception):
return rep
if self.back_reference is None:
return True
##
# @todo Reimplement instance fetching in construct data
# @remarks Set the previous todo as one, looked like it was intended to be.
# !! Reimplement instance fetching in construct data !!
target_class = self.back_reference[0]
if target_class not in self.__allowed_classes:
logger.warning('Class of the back_reference given is not an allowed class')
return False
value = datas[fname]
##
# @warning multi uid broken here
# @remarks Why is that broken? Any clue? Set as a warning.
target_uidfield = target_class.uid_fieldname()[0]
target_uidfield = target_class.uid_fieldname()[0] # multi uid broken here
obj = target_class.get([(target_uidfield, '=', value)])
if len(obj) == 0:
logger.warning('Object referenced does not exist')
return False
return True
##
# @brief Utility method designed to fetch referenced objects
#
# @param value mixed : the field value
# @throw NotImplementedError
# @remarks Not implemented? Consider renaming?
def get_referenced(self, value):
raise NotImplementedError
##
# @brief DataHandler for single reference to another object
# @brief This class represent a data_handler for single reference to another object
#
# An instance of this class acts like a "foreign key" to another object
# The fields using this data handlers are like "foreign key" on another object
class SingleRef(Reference):
def __init__(self, allowed_classes=None, **kwargs):
super().__init__(allowed_classes=allowed_classes, **kwargs)
##
# @brief Checks and casts value to the appropriate type
#
# @param value: mixed
# @throw FieldValidationError if value is inappropriate or can not be cast
# @return mixed
# @brief Check and cast value in appropriate type
# @param value: *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
return value
##
# @brief Utility method to fetch referenced objects
#
# @brief Utility method designed to fetch referenced objects
# @param value mixed : the field value
# @return A LeObject child class instance
# @throw LodelDataHandlerConsistencyException if no referenced object found
# @remarks Consider renaming (e.g. get_referenced_object)?
def get_referenced(self, value):
for leo_cls in self.linked_classes:
res = leo_cls.get_from_uid(value)
@ -609,39 +444,30 @@ class SingleRef(Reference):
referenced object with uid %s" % value)
##
# @brief DataHandler for multiple references to another object
# @brief This class represent a data_handler for multiple references to another object
# @ingroup lodel2_datahandlers
#
# The fields using this data handlers are like SingleRef but can store multiple
# references in one field.
# The fields using this data handlers are like SingleRef but can store multiple references in one field
# @note for the moment split on ',' chars
class MultipleRef(Reference):
##
# @brief Constructor
#
# @param max_item int | None : indicate the maximum number of item referenced
# by this field, None mean no limit
# @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
def __init__(self, max_item=None, **kwargs):
self.max_item = max_item
super().__init__(**kwargs)
##
# @brief Method designed to return an empty value for this kind of
# multipleref
# @remarks Purpose!?
# multipleref
@classmethod
def empty(cls):
return []
##
# @brief Check and cast value in appropriate type
# @param value mixed
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
# @todo Writing test error for errors when stored multiple references in one field
# @TODO Writing test error for errors when stored multiple references in one field
def _check_data_value(self, value):
value = DataHandler._check_data_value(self, value)
if not hasattr(value, '__iter__'):
@ -663,9 +489,7 @@ class MultipleRef(Reference):
"MultipleRef have for invalid values [%s] :" % (",".join(error_list)))
return new_val
##
# @brief Utility method designed to fetch referenced objects
#
# @param values mixed : the field values
# @return A list of LeObject child class instance
# @throw LodelDataHandlerConsistencyException if some referenced objects
@ -688,51 +512,37 @@ class MultipleRef(Reference):
some referenced objects. Following uids were not found : %s" % ','.join(left))
##
# @brief Class designed to handle data access while field types are constructing data
#  @brief Class designed to handle datas access will fieldtypes are constructing datas
# @ingroup lodel2_datahandlers
#
# This class is designed to allow automatic scheduling of construct_data calls.
#
# In theory it has the ability to detect circular dependencies
# In theory it's able to detect circular dependencies
# @todo test circular deps detection
# @todo test circular deps false positive
# @remarks Would not it be better to make sure what the code actually is doing?
class DatasConstructor(object):
##
# @brief Init a DatasConstructor
#
# @param leobject LeObject
# @param leobject LeCrud : @ref LeObject child class
# @param datas dict : dict with field name as key and field values as value
# @param fields_handler dict : dict with field name as key and data handler instance as value
def __init__(self, leobject, datas, fields_handler):
# Stores concerned class
self._leobject = leobject
# Stores datas and constructed datas
self._datas = copy.copy(datas)
# Stores fieldtypes
self._fields_handler = fields_handler
# Stores list of fieldname for constructed
# Stores list of fieldname for constructed datas
self._constructed = []
# Stores construct calls list
self._construct_calls = []
##
# @brief Implements the dict.keys() method on instance
#
# @return list
def keys(self):
return self._datas.keys()
##
# @brief Allows to access the instance like a dict
#
# @param fname string: The field name
# @return field values
# @throw RuntimeError
#
# @note Determine return type
#  @brief Allows to access the instance like a dict
def __getitem__(self, fname):
if fname not in self._constructed:
if fname in self._construct_calls:
@ -743,25 +553,17 @@ class DatasConstructor(object):
self._constructed.append(fname)
return self._datas[fname]
##
# @brief Allows to set instance values like a dict
#
# @warning Should not append in theory
#
# @remarks Why is a warning issued any time we call this method?
def __setitem__(self, fname, value):
self._datas[fname] = value
warnings.warn("Setting value of an DatasConstructor instance")
##
# @brief Class designed to handle a DataHandler option
# @brief Class designed to handle an option of a DataHandler
class DatahandlerOption(MlNamedObject):
##
# @brief instantiates a new DataHandlerOption object
# @brief instanciates a new Datahandler option object
#
# @param id str
# @param display_name MlString
@ -772,18 +574,13 @@ class DatahandlerOption(MlNamedObject):
self.__validator = validator
super().__init__(display_name, help_text)
##
# @brief Accessor to the id property.
@property
def id(self):
return self.__id
##
# @brief checks a value corresponding to this option is valid
#
# @param value mixed
# @return cast value
# @throw ValueError
# @param value
# @return casted value
def check_value(self, value):
try:
return self.__validator(value)

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
## @package lodel.leapi.datahandlers.datas
# This module contains specific datahandlers extending the basic ones from the lodel.leapi.datahandlers.datas_base module.

View file

@ -1,38 +1,16 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
import warnings
import datetime
import time
import os
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.leapi.datahandlers.base_classes': ['DataField'],
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
##
## @brief Data field designed to handle boolean values
class Boolean(DataField):
@ -41,17 +19,13 @@ class Boolean(DataField):
## @brief A boolean field
def __init__(self, **kwargs):
##
# @remarks Commented out code left in the code base. Consider deletion.
#if 'check_data_value' not in kwargs:
# kwargs['check_data_value'] = self._check_data_value
super().__init__(ftype='bool', **kwargs)
##
# @brief Checks value.
#
# @param value mixed:
# @throw FieldValidationError: if value is not valid
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
@ -60,8 +34,7 @@ class Boolean(DataField):
return value
##
# @brief Data field designed to handle integer values.
## @brief Data field designed to handle integer values
class Integer(DataField):
help = 'Basic integer field'
@ -71,12 +44,10 @@ class Integer(DataField):
def __init__(self, **kwargs):
super().__init__(**kwargs)
##
# @brief Checks and casts value in appropriate type
#
# @param value *:
# @param strict bool: Whether the value type should be treated strictly or cast.
# @throw FieldValidationError: if value is inappropriate or can not be cast
## @brief Check and cast value in appropriate type
# @param value *
# @param strict bool : tells if the value must be an integer or a value that can be converted into an integer
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value, strict=False):
value = super()._check_data_value(value)
@ -88,28 +59,23 @@ class Integer(DataField):
else:
value = int(float(value))
except(ValueError, TypeError):
raise FieldValidationError("The value '%s' is not an integer nor could be cast to." % value)
raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value)
return value
##
# @brief Data field designed to handle string
## @brief Data field designed to handle string
class Varchar(DataField):
help = 'Basic string (varchar) field. Default size is 64 characters'
base_type = 'char'
##
# @brief A string field
#
## @brief A string field
# @brief max_length int: The maximum length of this field
def __init__(self, max_length=64, **kwargs):
self.max_length = int(max_length)
super().__init__(**kwargs)
##
# @brief checks if this class can override the given data handler
#
## @brief checks if this class can override the given data handler
# @param data_handler DataHandler
# @return bool
def can_override(self, data_handler):
@ -118,12 +84,10 @@ class Varchar(DataField):
if data_handler.max_length != self.max_length:
return False
return True
##
# @brief Check and cast value in appropriate type
#
# @param value *:
# @throw FieldValidationError if value is inappropriate or can not be cast
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
@ -134,33 +98,25 @@ class Varchar(DataField):
return value
##
# @brief Data field designed to handle date & time
## @brief Data field designed to handle date & time
class DateTime(DataField):
help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
base_type = 'datetime'
##
# @brief A datetime field
#
## @brief A datetime field
# @param now_on_update bool : If true, the date is set to NOW on update
# @param now_on_create bool : If true, the date is set to NEW on creation
# @param **kwargs
#
# @remarks Is it intended that DateTime objects can not be instantiated with
# a provided arbitrary string value or something else?
def __init__(self, now_on_update=False, now_on_create=False, **kwargs):
self.now_on_update = now_on_update
self.now_on_create = now_on_create
self.datetime_format = '%Y-%m-%d' if 'format' not in kwargs else kwargs['format']
super().__init__(**kwargs)
##
# @brief Check and cast value in appropriate type
#
# @param value mixed:
# @throw FieldValidationError: if value is inappropriate or can not be cast
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
@ -179,19 +135,15 @@ class DateTime(DataField):
return cur_value
##
# @brief Data field designed to handle long string
## @brief Data field designed to handle long string
class Text(DataField):
help = 'A text field (big string)'
base_type = 'text'
def __init__(self, **kwargs):
super(self.__class__, self).__init__(ftype='text', **kwargs)
##
# @brief Check and cast value in appropriate type
#
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
@ -202,22 +154,18 @@ class Text(DataField):
return value
##
# @brief Data field designed to handle Files
## @brief Data field designed to handle Files
class File(DataField):
base_type = 'file'
##
# @brief a file field
#
## @brief a file field
# @param upload_path str : None by default
# @param **kwargs
def __init__(self, upload_path=None, **kwargs):
self.upload_path = upload_path
super().__init__(**kwargs)
##
# @todo Add a check for the validity of the given value (should have a correct path syntax)
# @todo Add here a check for the validity of the given value (should have a correct path syntax)
def _check_data_value(self, value):
return super()._check_data_value(value)

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @brief Exception classes for datahandlers
class LodelDataHandlerException(Exception):

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {

View file

@ -1,24 +1,5 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
## @package lodel.leapi.lefactory
import os
import os.path
import functools
@ -31,7 +12,7 @@ LodelContext.expose_modules(globals(), {
'lodel.leapi.datahandlers.base_classes': ['DataHandler'],
'lodel.logger': 'logger'})
## @brief Generates python module code from a given model
# @brief Generates python module code from a given model
# @param model lodel.editorial_model.model.EditorialModel
@ -71,7 +52,7 @@ dynclasses_dict = {class_dict}
)
return res_code
## @brief Returns the content of lodel.leapi.lefactory_common
# @brief Returns the content of lodel.leapi.lefactory_common
#
# @return a string
@ -87,7 +68,7 @@ def common_code():
return res
## @brief return A list of EmClass sorted by dependencies
# @brief return A list of EmClass sorted by dependencies
#
# The first elts in the list depend on nothing, etc.
# @param a list of Emclass instances to be sorted
@ -98,7 +79,7 @@ def emclass_sorted_by_deps(emclass_list):
ret = sorted(emclass_list, key=functools.cmp_to_key(emclass_deps_cmp))
return ret
## @brief Returns a list of EmClass instances that will be represented as LeObject child classes
# @brief Returns a list of EmClass instances that will be represented as LeObject child classes
# @param model : an EditorialModel instance
# @return a list of EmClass instances
@ -106,11 +87,9 @@ def emclass_sorted_by_deps(emclass_list):
def get_classes(model):
return [cls for cls in emclass_sorted_by_deps(model.classes()) if not cls.pure_abstract]
## @brief Given an EmField returns the data_handler constructor suitable for dynamic code
# @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)
@ -128,29 +107,33 @@ def data_handler_constructor(emfield):
handler_instr=get_handler_class_instr,
options=', '.join(options))
## @brief Return a python repr of option values
# @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)
## @brief Generate dyncode from an EmClass
# @brief Generate dyncode from an EmClass
# @param model EditorialModel :
# @return a tuple with emclass python code, a set containing modules name to import, and a list of python instruction to bootstrap dynamic code, in this order

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-
#- THE CONTENT OF THIS FILE IS DESIGNED TO BE INCLUDED IN DYNAMICALLY
#- GENERATED CODE

View file

@ -1,25 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.leapi.leobject
# This module is centered around the basic LeObject class, which is the main class for all the objects managed by lodel.
#-*- coding: utf-8 -*-
import importlib
import warnings
@ -38,21 +17,25 @@ 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']})
## @brief Stores the name of the field present in each LeObject that indicates the name of LeObject subclass represented by this object
# @brief Stores the name of the field present in each LeObject that indicates
# the name of LeObject subclass represented by this object
CLASS_ID_FIELDNAME = "classname"
## @brief Wrapper class for LeObject getter & setter
# @brief Wrapper class for LeObject getter & setter
#
# This class intend to provide easy & friendly access to LeObject fields values without name collision problems
# This class intend to provide easy & friendly access to LeObject fields values
# without name collision problems
# @note Wrapped methods are : LeObject.data() & LeObject.set_data()
class LeObjectValues(object):
# @brief Construct a new LeObjectValues
# @param fieldnames_callback method
# @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
# @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
@ -60,49 +43,47 @@ class LeObjectValues(object):
self._setter = set_callback
self._getter = get_callback
## @brief Provides read access to datas values
# @brief Provide read access to datas values
# @note Read access should be provided for all fields
# @param fname str : Field name
# @return method
def __getattribute__(self, fname):
getter = super().__getattribute__('_getter')
return getter(fname)
## @brief Provides write access to datas values
# @brief Provide write access to datas values
# @note Write acces shouldn't be provided for internal or immutable fields
# @param fname str : Field name
# @param fval * : the field value
# @return method
def __setattribute__(self, fname, fval):
setter = super().__getattribute__('_setter')
return setter(fname, fval)
## @brief Represents a handled object in Lodel.
class LeObject(object):
## @brief boolean that tells if an object is abtract or not
# @brief boolean that tells if an object is abtract or not
_abstract = None
## @brief A dict that stores DataHandler instances indexed by field name
# @brief A dict that stores DataHandler instances indexed by field name
_fields = None
## @brief A tuple of fieldname (or a uniq fieldname) representing uid
# @brief A tuple of fieldname (or a uniq fieldname) representing uid
_uid = None
## @brief Read only datasource ( see @ref lodel2_datasources )
# @brief Read only datasource ( see @ref lodel2_datasources )
_ro_datasource = None
## @brief Read & write datasource ( see @ref lodel2_datasources )
# @brief Read & write datasource ( see @ref lodel2_datasources )
_rw_datasource = None
## @brief Store the list of child classes
# @brief Store the list of child classes
_child_classes = None
## @brief Name of the datasource plugin
# @brief Name of the datasource plugin
_datasource_name = None
def __new__(cls, **kwargs):
self = object.__new__(cls)
## @brief A dict that stores fieldvalues indexed by fieldname
# @brief A dict that stores fieldvalues indexed by fieldname
self.__datas = {fname: None for fname in self._fields}
## @brief Store a list of initianilized fields when instanciation not complete else store True
# @brief Store a list of initianilized fields when instanciation not complete else store True
self.__initialized = list()
## @brief Datas accessor. Instance of @ref LeObjectValues
# @brief Datas accessor. Instance of @ref LeObjectValues
self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
for fieldname, fieldval in kwargs.items():
self.__datas[fieldname] = fieldval
@ -111,10 +92,8 @@ class LeObject(object):
self.__set_initialized()
return self
# @brief Construct an object representing an Editorial component
# @note Can be considered as EmClass instance
# @param **kwargs
# @throw NotImplementedError when the class being instanciated is noted as abstract and then should not be instanciated.
# @throw LeApiError in case of missing or invalid data.
def __init__(self, **kwargs):
if self._abstract:
raise NotImplementedError(
@ -151,21 +130,19 @@ class LeObject(object):
# Fields datas handling methods #
#-----------------------------------#
## @brief Property method True if LeObject is initialized else False
# @return bool
# @brief Property method True if LeObject is initialized else False
@property
def initialized(self):
return self.__is_initialized
## @brief Returns the uid field name
# @return str
# @return The uid field name
@classmethod
def uid_fieldname(cls):
return cls._uid
## @brief Returns a list of fieldnames
# @param include_ro bool : if True includes the read only field names
# @return list of string
# @brief Return a list of fieldnames
# @param include_ro bool : if True include read only field names
# @return a list of str
@classmethod
def fieldnames(cls, include_ro=False):
if not include_ro:
@ -173,17 +150,13 @@ class LeObject(object):
else:
return list(cls._fields.keys())
## @brief Returns a name, capitalizing the first character of each word
# @param name str
# @return str
@classmethod
def name2objname(cls, name):
return name.title()
## @brief Returns the datahandler asssociated with a LeObject field
# @param fieldname str : The field's name
# @brief Return the datahandler asssociated with a LeObject field
# @param fieldname str : The fieldname
# @return A data handler instance
# @throw NameError when the given field name doesn't exist
#@todo update class of exception raised
@classmethod
def data_handler(cls, fieldname):
@ -191,9 +164,9 @@ class LeObject(object):
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
return cls._fields[fieldname]
## @brief Returns a dictionary containing the reference datahandlers
# @param with_backref bool : if true return only references with back_references
# @return dict : <code>{'fieldname': datahandler, ...}</code>
# @brief Getter for references datahandlers
#@param with_backref bool : if true return only references with back_references
#@return <code>{'fieldname': datahandler, ...}</code>
@classmethod
def reference_handlers(cls, with_backref=True):
return {fname: fdh
@ -201,12 +174,11 @@ class LeObject(object):
if fdh.is_reference() and
(not with_backref or fdh.back_reference is not None)}
## @brief Returns a LeObject child class from a name
# @brief Return a LeObject child class from a name
# @warning This method has to be called from dynamically generated LeObjects
# @param leobject_name str : LeObject name
# @return A LeObject child class
# @throw NotImplementedError if the method is abstract (if we use the LeObject class)
# @throw LeApiError if an unexisting name is given
# @throw NameError if invalid name given
@classmethod
def name2class(cls, leobject_name):
if cls.__module__ == 'lodel.leapi.leobject':
@ -217,16 +189,14 @@ class LeObject(object):
except (AttributeError, TypeError):
raise LeApiError("No LeObject named '%s'" % leobject_name)
## @brief Checks if the class is abstract or not
# @return bool
@classmethod
def is_abstract(cls):
return cls._abstract
## @brief Field data handler getter
# @param fieldname str : The field name
# @return A datahandler instance
# @throw NameError if the field doesn't exist
# @brief Field data handler getter
#@param fieldname str : The field name
#@return A datahandler instance
#@throw NameError if the field doesn't exist
@classmethod
def field(cls, fieldname):
try:
@ -234,9 +204,8 @@ class LeObject(object):
except KeyError:
raise NameError("No field named '%s' in %s" % (fieldname,
cls.__name__))
## @brief Returns the fields' datahandlers as a dictionary
# @param include_ro bool : if True, includes the read-only fields (default value : False)
# @return dict
# @return A dict with fieldname as key and datahandler as instance
@classmethod
def fields(cls, include_ro=False):
if include_ro:
@ -245,12 +214,14 @@ class LeObject(object):
return {fname: cls._fields[fname] for fname in cls._fields\
if not cls._fields[fname].is_internal()}
## @brief Return the list of parents classes
# @brief Return the list of parents classes
#
# @note the first item of the list is the current class, the second is its parent etc...
# @warning multiple inheritance broken by this method
# @return a list of LeObject child classes
# @todo multiple parent capabilities implementation
#@note the first item of the list is the current class, the second is it's
# parent etc...
#@param cls
#@warning multiple inheritance broken by this method
#@return a list of LeObject child classes
#@todo multiple parent capabilities implementation
@classmethod
def hierarch(cls):
res = [cls]
@ -263,15 +234,16 @@ class LeObject(object):
res.append(cur)
return res
## @brief Returns a tuple of child classes
# @return tuple
# @brief Return a tuple a child classes
#@return a tuple of child classes
@classmethod
def child_classes(cls):
return copy.copy(cls._child_classes)
## @brief Returns the parent class that defines the unique id
# @brief Return the parent class that is the "source" of uid
#
# @return a LeObject child class or false if no UID defined
# The method goal is to return the parent class that defines UID.
#@return a LeObject child class or false if no UID defined
@classmethod
def uid_source(cls):
if cls._uid is None or len(cls._uid) == 0:
@ -287,48 +259,53 @@ class LeObject(object):
prev = pcls
return prev
## @brief Initialise both datasources (ro and rw)
# @brief Initialise both datasources (ro and rw)
#
# This method is used once at dyncode load to replace the datasource string
# by a datasource instance to avoid doing this operation for each query
# @see LeObject::_init_datasource()
#@see LeObject::_init_datasource()
@classmethod
def _init_datasources(cls):
if isinstance(cls._datasource_name, str):
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 Returns the uid of the current LeObject instance
# @return str
# @warning Broke multiple uid capabilities
# @brief Return the uid of the current LeObject instance
#@return the uid value
#@warning Broke multiple uid capabilities
def uid(self):
return self.data(self._uid[0])
## @brief Returns the value of a field
# @brief Read only access to all datas
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param field_name str : field's name
# @return the value
# @param field_name str : field name
# @return the Value
# @throw RuntimeError if the field is not initialized yet
# @throw NameError if name is not an existing field name
def data(self, field_name):
@ -339,19 +316,18 @@ class LeObject(object):
"The field %s is not initialized yet (and have no value)" % field_name)
return self.__datas[field_name]
## @brief Returns a dictionary containing all the fields' values
# @return dict
# @brief Read only access to all datas
#@return a dict representing datas of current instance
def datas(self, internal=False):
return {fname: self.data(fname) for fname in self.fieldnames(internal)}
## @brief Datas setter
# @brief Datas setter
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param fname str : field's name
# @param fname str : field name
# @param fval * : field value
# @return the value that is really set
# @throw NameError if fname is not valid
# @throw AttributeError if the field is not writtable
# @throw LeApiErrors if the data check generates an error
def set_data(self, fname, fval):
if fname not in self.fieldnames(include_ro=False):
if fname not in self._fields.keys():
@ -382,18 +358,18 @@ class LeObject(object):
else:
self.__datas[fname] = val
## @brief Updates the __initialized attribute according to LeObject internal state
# @brief Update the __initialized attribute according to LeObject internal state
#
# Checks the list of initialized fields and sets __initialized at True if all fields initialized
# Check the list of initialized fields and set __initialized to True if all fields initialized
def __set_initialized(self):
if isinstance(self.__initialized, list):
expected_fields = self.fieldnames(include_ro=False) + self._uid
if set(expected_fields) == set(self.__initialized):
self.__is_initialized = True
## @brief Designed to be called when datas are modified
# @brief Designed to be called when datas are modified
#
# Makes different checks on the LeObject given it's state (fully initialized or not)
# Make different checks on the LeObject given it's state (fully initialized or not)
# @return None if checks succeded else return an exception list
def __check_modified_values(self):
err_list = dict()
@ -438,24 +414,24 @@ class LeObject(object):
# Other methods #
#--------------------#
## @brief Temporary method to set private fields attribute at dynamic code generation
# @brief Temporary method to set private fields attribute at dynamic code generation
#
# This method is used in the generated dynamic code to set the _fields attribute
# at the end of the dyncode parse
# @warning This method is deleted once the dynamic code loaded
# @param cls
# @param field_list list : list of EmField instance
# @param cls
@classmethod
def _set__fields(cls, field_list):
cls._fields = field_list
## @brief Checks if the data is valid for this type
# @brief Check that datas are valid for this type
# @param datas dict : key == field name value are field values
# @param complete bool : if True expects that values are provided for all non internal fields
# @param allow_internal bool : if True does not raise an error if a field is internal
# @param complete bool : if True expect that datas provide values for all non internal fields
# @param allow_internal bool : if True don't raise an error if a field is internal
# @param cls
# @return Checked datas
# @throw LeApiDataCheckErrors if errors are reported during check
# @throw LeApiDataCheckError if errors reported during check
@classmethod
def check_datas_value(cls, datas, complete=False, allow_internal=True):
err_l = dict() # Error storing
@ -488,13 +464,14 @@ class LeObject(object):
raise LeApiDataCheckErrors("Error while checking datas", err_l)
return checked_datas
## @brief Checks and prepares all the data
# @brief Check and prepare datas
#
# @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
#
# @param datas dict : {fieldname : fieldvalue, ...}
# @param complete bool : If True you MUST give all the datas (default value : False)
# @param allow_internal : Wether or not interal fields are expected in datas (default value : True)
# @param complete bool : If True you MUST give all the datas
# @param allow_internal : Wether or not interal fields are expected in datas
# @param cls
# @return Datas ready for use
# @todo: complete is very unsafe, find a way to get rid of it
@classmethod
@ -510,8 +487,9 @@ construction and consitency when datas are not complete\n")
cls._check_datas_consistency(ret_datas)
return ret_datas
## @brief Constructs datas values
# @brief Construct datas values
#
# @param cls
# @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
# @return A new dict of datas
# @todo IMPLEMENTATION
@ -525,12 +503,12 @@ construction and consitency when datas are not complete\n")
}
return ret
## @brief Checks datas consistency
# @brief Check datas consistency
# 
# @warning assert that datas is complete
# @param cls
# @param datas dict : Datas that have been returned by LeCrud._construct_datas() method
# @throw LeApiDataCheckError in case of failure
# @throw LeApiDataCheckError if fails
@classmethod
def _check_datas_consistency(cls, datas):
err_l = []
@ -543,28 +521,27 @@ construction and consitency when datas are not complete\n")
if len(err_l) > 0:
raise LeApiDataCheckError("Datas consistency checks fails", err_l)
## @brief Checks data consistency
# @brief Check datas consistency
# 
# @warning assert that datas is complete
# @param datas dict : Data that have been returned by prepare_datas() method
# @param cls
# @param datas dict : Datas that have been returned by LeCrud.prepare_datas() method
# @param type_query str : Type of query to be performed , default value : insert
@classmethod
def make_consistency(cls, datas, type_query='insert'):
for fname, dh in cls._fields.items():
ret = dh.make_consistency(fname, datas, type_query)
## @brief Adds a new instance of LeObject
# @param datas dict : LeObject's data
# @return a new uid in case of success, False otherwise
# @brief Add a new instance of LeObject
# @return a new uid en case of success, False otherwise
@classmethod
def insert(cls, datas):
query = LeInsertQuery(cls)
return query.execute(datas)
## @brief Update an instance of LeObject
# @brief Update an instance of LeObject
#
# @param datas : list of new datas
# @return LeObject
#@param datas : list of new datas
def update(self, datas=None):
datas = self.datas(internal=False) if datas is None else datas
uids = self._uid
@ -583,9 +560,9 @@ construction and consitency when datas are not complete\n")
return result
## @brief Delete an instance of LeObject
# @brief Delete an instance of LeObject
#
# @return 1 if the objet has been deleted
#@return 1 if the objet has been deleted
def delete(self):
uids = self._uid
query_filter = list()
@ -598,9 +575,9 @@ construction and consitency when datas are not complete\n")
return result
## @brief Deletes instances of LeObject
# @param query_filters list
# @return the number of deleted items
# @brief Delete instances of LeObject
#@param query_filters list
#@returns the number of deleted items
@classmethod
def delete_bundle(cls, query_filters):
deleted = 0
@ -617,16 +594,16 @@ construction and consitency when datas are not complete\n")
deleted += result
return deleted
## @brief Gets instances of LeObject
# @brief Get instances of LeObject
#
# @param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
# @param field_list list|None : list of string representing fields see
# @ref leobject_filters
# @param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
# @param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
# @param limit int : The maximum number of returned results
# @param offset int : offset (default value : 0)
# @return a list of items (lists of (fieldname, fieldvalue))
#@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
#@param field_list list|None : list of string representing fields see
#@ref leobject_filters
#@param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
#@param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
#@param limit int : The maximum number of returned results
#@param offset int : offset
#@return a list of items (lists of (fieldname, fieldvalue))
@classmethod
def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
if field_list is not None:
@ -656,10 +633,7 @@ construction and consitency when datas are not complete\n")
return objects
## @brief Retrieves an object given an UID
# @param uid str : Unique ID of the searched LeObject
# @return LeObject
# @throw LodelFatalError if the class does not have such a UID defined or if duplicates are found
# @brief Retrieve an object given an UID
#@todo broken multiple UID
@classmethod
def get_from_uid(cls, uid):
@ -676,7 +650,7 @@ construction and consitency when datas are not complete\n")
while len(res_cp) > 0:
cur_res = res_cp.pop()
if cur_res.uid() in [r.uid() for r in res_cp]:
logger.error("Duplicates detected in query results !!!")
logger.error("DOUBLON detected in query results !!!")
else:
res.append(cur_res)
if len(res) > 1:

View file

@ -1,24 +1,5 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
## @package lodel.leapi.lequery
import re
import copy
import inspect
@ -37,12 +18,12 @@ LodelContext.expose_modules(globals(), {
class LeQuery(object):
## @brief Hookname prefix
# @brief Hookname prefix
_hook_prefix = None
## @brief arguments for the LeObject.check_data_value()
# @brief arguments for the LeObject.check_data_value()
_data_check_args = {'complete': False, 'allow_internal': False}
## @brief Abstract constructor
# @brief Abstract constructor
# @param target_class LeObject : class of object the query is about
def __init__(self, target_class):
from .leobject import LeObject
@ -56,7 +37,7 @@ class LeQuery(object):
self._ro_datasource = target_class._ro_datasource
self._rw_datasource = target_class._rw_datasource
## @brief Executes a query and returns the result
# @brief Executes a query and returns the result
#@param **data
#@return the query result
#@see LeQuery._query()
@ -78,7 +59,7 @@ class LeQuery(object):
ret)
return ret
## @brief Child classes implement this method to execute the query
# @brief Child classes implement this method to execute the query
#@param **data
#@return query result
def _query(self, **data):
@ -94,11 +75,11 @@ class LeQuery(object):
classname=self.__class__.__name__,
target_class=self._target_class)
## @brief Abstract class handling query with filters
# @brief Abstract class handling query with filters
class LeFilteredQuery(LeQuery):
## @brief The available operators used in query definitions
# @brief The available operators used in query definitions
_query_operators = [
' = ',
' <= ',
@ -111,19 +92,19 @@ class LeFilteredQuery(LeQuery):
' like ',
' not like ']
## @brief Regular expression to process filters
# @brief Regular expression to process filters
_query_re = None
## @brief Abtract constructor for queries with filter
# @brief Abtract constructor for queries with filter
#@param target_class LeObject : class of object the query is about
#@param query_filters list : with a tuple (only one filter) or a list of
# tuple or a dict: {OP,list(filters)} with OP = 'OR' or 'AND for tuple
# (FIELD,OPERATOR,VALUE)
def __init__(self, target_class, query_filters=None):
super().__init__(target_class)
## @brief The query filter tuple(std_filter, relational_filters)
# @brief The query filter tuple(std_filter, relational_filters)
self._query_filter = None
## @brief Stores potential subqueries (used when a query implies
# @brief Stores potential subqueries (used when a query implies
# more than one datasource.
#
# Subqueries are tuple(target_class_ref_field, LeGetQuery)
@ -131,7 +112,7 @@ class LeFilteredQuery(LeQuery):
query_filters = [] if query_filters is None else query_filters
self.set_query_filter(query_filters)
## @brief Abstract FilteredQuery execution method
# @brief Abstract FilteredQuery execution method
#
# This method takes care to execute subqueries before calling super execute
def execute(self, data=None):
@ -157,7 +138,7 @@ class LeFilteredQuery(LeQuery):
self._query_filter = orig_filters
return res
## @brief Add filter(s) to the query
# @brief Add filter(s) to the query
#
# This method is also able to slice query if different datasources are
# implied in the request
@ -168,27 +149,27 @@ class LeFilteredQuery(LeQuery):
def set_query_filter(self, query_filter):
if isinstance(query_filter, str):
query_filter = [query_filter]
# Query filter preparation
# Query filter prepration
filters_orig, rel_filters = self._prepare_filters(query_filter)
# Here we know that each relational filter concerns only one datasource
# Here we now that each relational filter concern only one datasource
# thank's to _prepare_relational_fields
# Multiple datasources detection
self_ds_name = self._target_class._datasource_name
result_rel_filters = list() # The filters that will remain in the query
result_rel_filters = list() # The filters that will stay in the query
other_ds_filters = dict()
for rfilter in rel_filters:
(rfield, ref_dict), op, value = rfilter
# rfield : the field in self._target_class
tmp_rel_filter = dict() # designed to store rel_field of same DS
tmp_rel_filter = dict() # designed to stores rel_field of same DS
# First step : simplification
# Trying to delete relational filters done on referenced class uid
for tclass, tfield in copy.copy(ref_dict).items():
# tclass : referenced target class
# tclass : reference target class
# tfield : referenced field from target class
#
# !!!WARNING!!!
# The line below breaks multi UID support
# The line below brake multi UID support
#
if tfield == tclass.uid_fieldname()[0]:
# This relational filter can be simplified as
@ -198,17 +179,17 @@ class LeFilteredQuery(LeQuery):
del(ref_dict[tclass])
if len(ref_dict) == 0:
continue
# Determines what to do with the other relational filters according
# to the referenced class datasource
# Remember : all classes in a relational filter have the same
# Determine what to do with other relational filters given
# referenced class datasource
# Remember : each class in a relational filter has the same
# datasource
tclass = list(ref_dict.keys())[0]
cur_ds = tclass._datasource_name
if cur_ds == self_ds_name:
# Same datasource, the filter stays in this query
# Same datasource, the filter stay is self query
result_rel_filters.append(((rfield, ref_dict), op, value))
else:
# Different datasources, we will have to create a subquery
# Different datasource, we will have to create a subquery
if cur_ds not in other_ds_filters:
other_ds_filters[cur_ds] = list()
other_ds_filters[cur_ds].append(
@ -262,7 +243,7 @@ class LeFilteredQuery(LeQuery):
res += '>'
return res
## @brief Prepares filters for datasource
# @brief Prepare filters for datasource
#
# A filter can be a string or a tuple with len = 3.
#
@ -270,7 +251,7 @@ class LeFilteredQuery(LeQuery):
#
#@par Simple filters
#
# Those filters concern fields that represent object values (a title,
# Those filters concerns fields that represent object values (a title,
# the content, etc.) They are composed of three elements : FIELDNAME OP
# VALUE . Where :
#- FIELDNAME is the name of the field
@ -280,12 +261,12 @@ class LeFilteredQuery(LeQuery):
#
#@par Relational filters
#
# Those filters concern on reference fields (see the corresponding
# Those filters concerns on reference fields (see the corresponding
# abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
# The filter as quite the same composition than simple filters :
# FIELDNAME[.REF_FIELD] OP VALUE . Where :
#- FIELDNAME is the name of the reference field
#- REF_FIELD is an optionnal addon to the base field. It indicates on which
#- REF_FIELD is an optionnal addon to the base field. It indicate on wich
# field of the referenced object the comparison as to be done. If no
# REF_FIELD is indicated the comparison will be done on identifier.
#
@ -293,7 +274,7 @@ class LeFilteredQuery(LeQuery):
#@param filters_l list : This list of str or tuple (or both)
#@return a tuple(FILTERS, RELATIONNAL_FILTERS
#@todo move this doc in another place (a dedicated page ?)
#@warning Does not support multiple UID for an EmClass
#@warning Does not supports multiple UID for an EmClass
def _prepare_filters(self, filters_l):
filters = list()
res_filters = list()
@ -311,7 +292,7 @@ class LeFilteredQuery(LeQuery):
for field, operator, value in filters:
err_key = "%s %s %s" % (field, operator, value) # to push in err_l
# Splitting field name to be able to detect a relational field
# Spliting field name to be able to detect a relational field
field_spl = field.split('.')
if len(field_spl) == 2:
field, ref_field = field_spl
@ -355,14 +336,14 @@ a relational field, but %s.%s was present in the filter"
else:
if len(ref_uid) > 1:
msg = "The referenced classes are identified by \
fields with different names. Unable to determine which field to use for the \
fields with different name. Unable to determine wich field to use for the \
reference"
else:
msg = "Unknow error when trying to determine which \
msg = "Unknow error when trying to determine wich \
field to use for the relational filter"
err_l[err_key] = RuntimeError(msg)
continue
# Prepares relational field
# Prepare relational field
ret = self._prepare_relational_fields(field, ref_field)
if isinstance(ret, Exception):
err_l[err_key] = ret
@ -381,7 +362,7 @@ field to use for the relational filter"
err_l)
return (res_filters, rel_filters)
## @brief Checks and splits a query filter
# @brief Check and split a query filter
# @note The query_filter format is "FIELD OPERATOR VALUE"
# @param query_filter str : A query_filter string
# @param cls
@ -406,8 +387,8 @@ field to use for the relational filter"
raise ValueError(msg % query_filter)
return result
## @brief Compiles the regex for query_filter processing
# @note Sets _LeObject._query_re
# @brief Compile the regex for query_filter processing
# @note Set _LeObject._query_re
@classmethod
def __compile_query_re(cls):
op_re_piece = '(?P<operator>(%s)'
@ -431,10 +412,10 @@ field to use for the relational filter"
msg %= (fieldname, target_class.__name__)
return NameError(msg)
## @brief Prepares a relational filter
# @brief Prepare a relational filter
#
# Relational filters are composed of a tuple like the simple filters
# but the first element of this tuple is also a tuple :
# but the first element of this tuple is a tuple to :
#
#<code>((FIELDNAME, {REF_CLASS: REF_FIELD}), OP, VALUE)</code>
# Where :
@ -445,7 +426,7 @@ field to use for the relational filter"
#
# Visibly the REF_FIELD value of the dict will vary only when
# no REF_FIELD is explicitly given in the filter string notation
# and REF_CLASS classes have different uids
# and REF_CLASSES has differents uid
#
#@par String notation examples
#<pre>contributeur IN (1,2,3,5)</pre> will be transformed into :
@ -462,7 +443,7 @@ field to use for the relational filter"
#@todo move the documentation to another place
#
#@param fieldname str : The relational field name
#@param ref_field str|None : The referenced field name (if None uses
#@param ref_field str|None : The referenced field name (if None use
# uniq identifiers as referenced field
#@return a well formed relational filter tuple or an Exception instance
def _prepare_relational_fields(self, fieldname, ref_field=None):
@ -480,7 +461,7 @@ field to use for the relational filter"
if r_ds is None:
r_ds = ref_class._datasource_name
elif ref_class._datasource_name != r_ds:
return RuntimeError("All referenced classes don't have the\
return RuntimeError("All referenced class doesn't have the\
same datasource. Query not possible")
if ref_field in ref_class.fieldnames(True):
ref_dict[ref_class] = ref_field
@ -496,7 +477,7 @@ the relational filter %s"
return (fieldname, ref_dict)
## @brief A query to insert a new object
# @brief A query to insert a new object
class LeInsertQuery(LeQuery):
_hook_prefix = 'leapi_insert_'
_data_check_args = {'complete': True, 'allow_internal': False}
@ -524,20 +505,20 @@ abstract LeObject : %s" % target_class)
return nb_inserted
"""
#  @brief Executes the insert query
#  @brief Execute the insert query
def execute(self, data):
return super().execute(data=data)
## @brief A query to update data for a given object
# @brief A query to update data for a given object
#
#@todo Change behavior, Huge optimization problem when updating using filters
# and not instance. We have to run a GET and then one update by fetched object...
# and not instance. We have to run a GET and then 1 update by fecthed object...
class LeUpdateQuery(LeFilteredQuery):
_hook_prefix = 'leapi_update_'
_data_check_args = {'complete': False, 'allow_internal': False}
## @brief Instanciates an update query
# @brief Instanciate an update query
#
# If a class and not an instance is given, no query_filters are expected
# and the update will be fast and simple. Else we have to run a get query
@ -548,7 +529,7 @@ class LeUpdateQuery(LeFilteredQuery):
#@todo change strategy with instance update. We have to accept data for
# the execute method
def __init__(self, target, query_filters=None):
## @brief This attr is set only if the target argument is an
# @brief This attr is set only if the target argument is an
# instance of a LeObject subclass
self.__leobject_instance_datas = None
target_class = target
@ -566,9 +547,9 @@ target to LeUpdateQuery constructor"
super().__init__(target_class, query_filters)
## @brief Implements an update query
# @brief Implements an update query
#@param data dict : data to be updated
#@return the number of updated items
#@returns the number of updated items
#@todo change stategy for instance update. Data should be allowed
# for execute method (and query)
def _query(self, data):
@ -612,21 +593,21 @@ query on a class with filters")
return super().execute(data=data)
## @brief A query to delete an object
# @brief A query to delete an object
class LeDeleteQuery(LeFilteredQuery):
_hook_prefix = 'leapi_delete_'
def __init__(self, target_class, query_filter):
super().__init__(target_class, query_filter)
#  @brief Executes the delete query
#  @brief Execute the delete query
# @param data
def execute(self, data=None):
return super().execute()
## @brief Implements delete query operations
# @brief Implements delete query operations
# @param data
#@return the number of deleted items
#@returns the number of deleted items
def _query(self, data=None):
filters, rel_filters = self._query_filter
nb_deleted = self._rw_datasource.delete(
@ -637,7 +618,7 @@ class LeDeleteQuery(LeFilteredQuery):
class LeGetQuery(LeFilteredQuery):
_hook_prefix = 'leapi_get_'
## @brief Instanciates a new get query
# @brief Instanciate a new get query
#@param target_class LeObject : class of object the query is about
#@param query_filters dict : {OP, list of query filters}
# or tuple (FIELD, OPERATOR, VALUE) )
@ -649,15 +630,15 @@ class LeGetQuery(LeFilteredQuery):
# - offset int : offset
def __init__(self, target_class, query_filters, **kwargs):
super().__init__(target_class, query_filters)
## @brief The fields to get
# @brief The fields to get
self._field_list = None
## @brief An equivalent to the SQL ORDER BY
# @brief An equivalent to the SQL ORDER BY
self._order = None
## @brief An equivalent to the SQL GROUP BY
# @brief An equivalent to the SQL GROUP BY
self._group = None
## @brief An equivalent to the SQL LIMIT x
# @brief An equivalent to the SQL LIMIT x
self._limit = None
## @brief An equivalent to the SQL LIMIT x, OFFSET
# @brief An equivalent to the SQL LIMIT x, OFFSET
self._offset = 0
# Checking kwargs and assigning default values if there is some
@ -694,7 +675,7 @@ class LeGetQuery(LeFilteredQuery):
msg = "offset argument expected to be an integer >= 0"
raise ValueError(msg)
## @brief Set the field list
# @brief Set the field list
# @param field_list list | None : If None use all fields
# @return None
# @throw LeApiQueryError if unknown field given
@ -713,12 +694,12 @@ class LeGetQuery(LeFilteredQuery):
raise LeApiQueryErrors(msg=msg, exceptions=err_l)
self._field_list = list(set(field_list))
## @brief Executes the get query
# @brief Execute the get query
def execute(self, data=None):
return super().execute()
## @brief Implements select query operations
# @return a list containing the item(s)
# @brief Implements select query operations
# @returns a list containing the item(s)
def _query(self, data=None):
# select data corresponding to query_filter
fl = list(self._field_list) if self._field_list is not None else None
@ -733,8 +714,7 @@ class LeGetQuery(LeFilteredQuery):
offset=self._offset)
return l_data
## @brief Returns a dict with query infos
# @return a dict
# @return a dict with query infos
def dump_infos(self):
ret = super().dump_infos()
ret.update({'field_list': self._field_list,
@ -745,8 +725,6 @@ class LeGetQuery(LeFilteredQuery):
})
return ret
## @brief Returns a string representation of the query
# @return a string
def __repr__(self):
res = "<LeGetQuery target={target_class} filter={query_filter} \
field_list={field_list} order={order} group={group} limit={limit} \

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
import copy
import logging, logging.handlers

View file

@ -1,22 +1,2 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
mlnamedobject_PYTHON= *.py
mlnamedobjectdir=$(pkgpythondir)/mlnamedobject

View file

@ -1,25 +0,0 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.mlnamedobject A package dedicated to manage the objects' properties which can be
# translated in several languages.
#
# Each object in Lodel which can be translated will see his displayed name and help text being
# managed by a MlNamedObject instance, a class that is part of this package.

View file

@ -1,39 +1,18 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding:utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString']})
## @package lodel.mlnamedobject.mlnamedobject Lodel2 description of objects module
# @package lodel.mlnamedobject Lodel2 description of objects module
#
# Display name and Description of a lodel2 object
## @brief Represents a multi-language object (dealing with its translations)
# @brief Class allows display name and help text for lodel2 objects and fields
class MlNamedObject(object):
##
# @param display_name str|dict : displayed string to name the object (either a string or a dictionnary of the translated strings can be passed)
# @param help_text str|dict : description text for this object (either a string or a dictionnary of the translated strings can be passed)
def __init__(self, display_name=None, help_text=None):
## @brief The object's name which will be used in all the user interfaces
self.display_name = None if display_name is None else MlString(display_name)
## @brief Description text for this object
self.help_text = None if help_text is None else MlString(help_text)

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
plugin_PYTHON=*.py
plugindir=$(pkgpythondir)/plugin

View file

@ -1,92 +1,75 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
## @defgroup lodel2_plugins Plugins
# @ingroup lodel2_leapi
##@defgroup lodel2_plugins Plugins
#@ingroup lodel2_leapi
#
# Groups all stuff that concerns plugins
#Groups all stuff that concerns plugins
## @page plugin_doc Lodel2 plugin documentation
# @ingroup lodel2_plugins
#@ingroup lodel2_plugins
# @section plugin_doc_type Plugin types
#
# In Lodel2, plugins are organized into types. Each type helps specifying a
# behavior. As of now, we have four plugin types :
# - **datasource** : a connector exposing C.R.U.D. (Create/Read/Update/Delete)
# operations on a particular datasource (a database, a remote service, etc ...)
# - **ui** : a user interface that will provide a way to interact with Lodel2.
# As of now, we have the following "ui" plugins :
# Plugins are organized into types. A type specify a behavior. For the moment
# Lodel2 has 4 plugin types :
# - **datasource** : A datasource connector plugin expose CRUD operation on a
#particular datasource
# - **ui** : A user interface plugin provide an interface to lodel2. For the
#moment two ui are implemented
# - interactive python : the default interface, provides access to LeApi
# through an interactive python interpreter
# - webui : a web interface to lodel2
# - **session_handler** : handles user sessions
# - **extensions** : a basic plugin that will extend Lodel2 functionalities.
# It can define two kinds of objects :
# - hooks using @ref lodel.plugin.hooks.LodelHook decorator
#through interactive python interpreter
# - webui : a plugin providing a web interface to lodel2
# - **session_handler** : A session handler plugin expose functions that handles
#user sessions.
# - **extensions** : An extension plugin can define 2 kinds of objects :
# - hooks using @ref lodel.plugin.hooks.LodelHook decorator
# - custom LeApi obect methods using @ref lodel.plugin.plugins.CustomMethod
# decorator
#decorator
#
# @subsection Lodel2 scripts
#
# In every instances of Lodel, one can use a manager script to execute some
# defined administration commands that can be launched as CLI commands.
# This utility script is provided by @ref install.lodel_admin. The syntax
# to execute it is :
# <code>usage: lodel_admin.py [-h] [-L] [ACTION] [OPTIONS [OPTIONS ...]]</code>
# In instances an utility is provided : @ref install.lodel_admin . This
# utility can be runned as a CLI script
#<code>usage: lodel_admin.py [-h] [-L] [ACTION] [OPTIONS [OPTIONS ...]]</code>
#
# Each action is a "lodel2 script". All those scripts are parts of plugins.
# Each actions is a "lodel2 script". Thoses scripts are parts of plugins.
# @ref lodel2_script "More informations on lodel2 scripting utilities"
#
# @section plugin_doc_struct Common plugin structure
#
# All plugin, whatever its type, has to provide mandatory informations in
# order to be loaded :
# All plugin types has to provide mandatories information in order to be
# loaded :
#
# - A plugin name
# - A plugin version
# - A confspec indicating where to find the wanted plugin list (for example
# datasources plugins list are indicated in lodel2.datasource_connectors
# configuration key see @ref datasource_plugin.DatasourcePlugin::_plist_confspecs ).
#  In fact settings MUST begin by loading wanted plugin list in order to build a "full" confspec
# - A confspec indicating the plugins allowed settings (will be merged with lodel2 confspecs)
# - A loader module filename. This module is imported once settings are fully bootstraped and loader.
# It triggers the module "startup".
#datasources plugins list are indicated in lodel2.datasource_connectors
#configuration key see @ref datasource_plugin.DatasourcePlugin::_plist_confspecs ). In
#fact settings MUST begin by loading wanted plugin list in order to build
#a "full" confspec
# - A confspec indicating the plugins allowed settings (will be merged with
#lodel2 confspecs)
# - A loader module filename. This module is imported once settings are
#fully bootstraped and loader. It triggers the module "startup".
#
# In order to provide these informations, the developper can use the plugin's
# package <code>__init__.py</code> file. Some informations are stored in
# variables in this file. Available variables are documented in
# @ref plugin_init_specs . Here a list of basics variables :
# In order to provide this informations the develloper can use the plugin's
#package <code>__init__.py</code> file. Some informations are stored in
#variables in this file. Available variables are documented in
#@ref plugin_init_specs . Here a list of basics variables :
# - the plugin's name @ref plugins.PLUGIN_NAME_VARNAME
# - the plugin's version @ref plugins.PLUGIN_VERSION_VARNAME
# - the plugin's loader filename @ref plugins.LOADER_FILENAME_VARNAME
# - the plugin's confspec filename @ref plugins.CONFSPEC_FILENAME_VARNAME
# (set this variable only if you want your confspecs to be in a separated file,
# else you can put the confspecs directly in a CONFSPEC variable in the
# <code>__init__.py</code> file see @ref plugins.CONFSPEC_VARNAME )
# - the plugin's confspec filename @ref plugins.CONFSPEC_FILENAME_VARNAME (
#set this variable only if you want your confspecs to be in a separated file,
#else you can put the confspecs directly in a CONFSPEC variable in the
#<code>__init__.py</code> file see @ref plugins.CONFSPEC_VARNAME )
# - the plugin's type @ref plugins.PLUGIN_TYPE_VARNAME (if not set use
# @ref plugins.DEFAULT_PLUGIN_TYPE )
# - the plugin's dependencies list @ref plugins.PLUGIN_DEPS_VARNAME
#
# This was the variable specification of the <code>__init__.py</code> file.
# plugins can provide (in the same file) an _activate function (
# <code>def _activate(): returns bool</code>) that return True if the plugin
# is activable else False
#plugins can provide (in the same file) an _activate function (
#<code>def _activate(): returns bool</code>) that return True if the plugin
#is activable else False
#
#An example dummy plugin exists in @ref plugins.dummy
#
@ -104,10 +87,11 @@
#
#More informations :
# - @ref lodel.plugin.datasource_plugin.DatasourcePlugin "DatasourcePlugin"
# - @ref lodel2_datasources "datasources"
# - @ref lodel2_datasources "datasources"
# - @ref lodel.plugin.extensions.Extension "Extensions"
# - @ref lodel.plugin.interface.InterfacePlugin "InterfacePlugin"
# - @ref lodel.plugin.sessionhandler.SessionHandlerPlugin "SessionHandlerPlugin"
#
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
@ -24,15 +6,11 @@ LodelContext.expose_modules(globals(), {
'lodel.settings': ['Settings'],
'lodel.logger': 'logger'})
## @package lodel.plugin.core_hooks
# @brief Lodel2 internal hooks declaration
# @ingroup lodel2_plugins
##@package lodel.plugin.core_hooks
#@brief Lodel2 internal hooks declaration
#@ingroup lodel2_plugins
## @brief Bootstrap hook that checks datasources configuration
# @param hook_name str
# @param caller * : the hook's caller
# @param payload * : data to be given to the hook
# @throw NameError when : a set datasource family name can not be found or a datasource identifier does not match with a configured datasource.
##@brief Bootstrap hook to check datasources configuration
@LodelHook('lodel2_bootstraped')
def datasources_bootstrap_hook(hook_name, caller, payload):
for ds_name in Settings.datasources._fields:
@ -54,14 +32,12 @@ def datasources_bootstrap_hook(hook_name, caller, payload):
msg %= identifier
raise NameError(msg)
log_msg = "Found a datasource named '%s' identified by '%s'"
log_msg %= (ds_name, identifier)
logger.debug(log_msg)
## @brief Bootstrap hook that prints debug infos about registered hooks
# @param name str
# @param caller * : the hook's caller
# @param payload * : data to be given to the hook
##@brief Bootstrap hook that print debug infos about registered hooks
@LodelHook('lodel2_bootstraped')
def list_hook_debug_hook(name, caller, payload):
LodelContext.expose_modules(globals(), {
@ -79,9 +55,7 @@ def list_hook_debug_hook(name, caller, payload):
## @brief Hook that triggers custom methods injection in dynamic classes
# @param caller * : the hook's caller
# @param dynclasses list : a list of classes in which the injection will occur
##@brief Hooks that trigger custom methods injection in dynmic classes
@LodelHook("lodel2_dyncode_loaded")
def lodel2_plugins_custom_methods(self, caller, dynclasses):
LodelContext.expose_modules(globals(), {

View file

@ -1,78 +1,52 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import operator
import shutil
import tempfile
import os
import os.path
import argparse
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
##@package lodel.plugin.core_scripts
#@brief Lodel2 internal scripts declaration
#@ingroup lodel2_plugins
#@ingroup lodel2_script
## @brief Implement lodel_admin.py list-plugins action
##@brief Implements lodel_admin.py list-plugins action
#@ingroup lodel2_plugins
#@ingroup lodel2_script
#
class ListPlugins(lodel_script.LodelScript):
class ListPlugins(LodelScript):
_action = 'plugins-list'
_description = "List all installed plugins"
## @brief Set available arguments for the script lodel_admin with action plugin-list
# @param parser : a parser (see argparse python module)
@classmethod
def argparser_config(cls, parser):
parser.add_argument('-v', '--verbose',
help="Display more informations on installed plugins",
action='store_true')
help="Display more informations on installed plugins",
action='store_true')
parser.add_argument('-c', '--csv',
help="Format output in CSV format",
action='store_true')
help="Format output in CSV format",
action='store_true')
## @brief Display the list of plugins according to given arguments
# @param args : grabbed from argv of command line
@classmethod
def run(cls, args):
import lodel.plugin.plugins
from lodel.plugin.plugins import Plugin
if args.verbose:
#_discover does not return duplicated names
#_discover do not returns duplicated names
tmp_plist = Plugin._discover(lodel.plugin.plugins.PLUGINS_PATH)
plist = []
# ordering the list by plugin's name
#ordering the list by plugin's name
for pname in sorted(set([d['name'] for d in tmp_plist])):
for pinfos in tmp_plist:
if pinfos['name'] == pname:
plist.append(pinfos)
else:
# Retrieve the dict with the list of plugins
pdict = Plugin.discover()
# casting to a list ordered by names
#casting to a list ordered by names
plist = []
for pname in sorted(pdict.keys()):
plist.append(pdict[pname])
@ -98,39 +72,35 @@ class ListPlugins(lodel_script.LodelScript):
print(res)
## @brief Handle install & uninstall of lodel plugins
class PluginManager(lodel_script.LodelScript):
##@brief Handle install & uninstall of lodel plugins
class PluginManager(LodelScript):
_action = 'plugins'
_description = "Install/Uninstall plugins"
## @brief Set parser's available arguments for lodel_admin.py with action plugins
# @param parser : a parser (see argparse python module)
@classmethod
def argparser_config(cls, parser):
parser.add_argument('-u', '--uninstall',
help="Uninstall specified plugin",
action='store_true')
help="Uninstall specified plugin",
action='store_true')
parser.add_argument('-c', '--clean',
help="Uninstall duplicated plugins with smallest version number",
action="store_true")
help="Uninstall duplicated plugins with smallest version number",
action="store_true")
parser.add_argument('-n', '--plugin-name', nargs='*',
default=list(),
help="Indicate a plugin name to uninstall",
type=str)
default = list(),
help="Indicate a plugin name to uninstall",
type=str)
parser.add_argument('-a', '--archive', nargs='*',
default=list(),
help="(NOT IMPLEMENTED) Specify a tarball containing a plugin \
default = list(),
help="(NOT IMPLEMENTED) Specify a tarball containing a plugin \
to install",
type=str)
type=str)
parser.add_argument('-d', '--directory', nargs='*',
default=list(),
help="Specify a plugin by its directory",
type=str)
default = list(),
help="Specify a plugin by its directory",
type=str)
parser.add_argument('-f', '--force', action="store_true",
help="Force plugin directory deletion in case of check errors")
help="Force plugin directory deletion in case of check errors")
## @brief Install, uninstall or clean a plugin according to the option given
# @param args : grabbed from argv of command line
@classmethod
def run(cls, args):
if args.clean:
@ -142,55 +112,46 @@ only use option -n --name to clean plugins with specified names")
if args.uninstall:
return cls.uninstall(args)
return cls.install(args)
## @brief Install plugins
# @param args : grabbed from argv of command line
##@brief Handles plugins install
@classmethod
def install(cls, args):
import lodel.plugin.plugins
from lodel.plugin.plugins import Plugin
from lodel.plugin.exceptions import PluginError
# We can't install a plugin with just its name, we have to know where
# it is
if len(args.plugin_name) > 0:
raise RuntimeError("Unable to install a plugin from its name !\
We do not know where to find it...")
plist = Plugin.discover()
errors = dict()
# For now we do not handle archive for plugins
if len(args.archive) > 0:
raise NotImplementedError("Not supported yet")
plugins_infos = {}
for cur_dir in args.directory:
# Check that the directories obtained correspond to plugins
try:
res = Plugin.dir_is_plugin(cur_dir, assert_in_package=False)
res = Plugin.dir_is_plugin(cur_dir, assert_in_package = False)
if res is False:
errors[cur_dir] = PluginError("Not a plugin")
else:
plugins_infos[res['name']] = res
except Exception as e:
errors[cur_dir] = e
# Abording because of previous errors
#Abording because of previous errors
if len(errors) > 0:
msg = "Abording installation because of following errors :\n"
for path, expt in errors.items():
msg += ("\t- For path '%s' : %s\n" % (path, expt))
raise RuntimeError(msg)
# No errors continuing to install
#No errors continuing to install
for pname, pinfos in plugins_infos.items():
if pname in plist:
# Found an installed plugin with the same name
# Checking both versions
#Found an installed plugin with the same name
#Cehcking both versions
if plist[pname]['version'] == pinfos['version']:
errors[pinfos['path']] = 'Abording installation of %s \
found in %s because it seems to be allready installed in %s' % (
pname, pinfos['path'], plist[pname]['path'])
pname, pinfos['path'], plist[pname]['path'])
continue
if plist[pname]['version'] > pinfos['version']:
errors[pinfos['path']] = 'Abording installation of %s \
@ -199,49 +160,46 @@ installed in %s' % (pname, pinfos['path'], plist[pname]['path'])
continue
logger.info("Found a plugin with the same name but with an \
inferior version. Continuing to install")
# Checking that we can safely copy our plugin
#Checking that we can safely copy our plugin
dst_path = os.path.join(lodel.plugin.plugins.PLUGINS_PATH,
os.path.basename(os.path.dirname(pinfos['path'])))
os.path.basename(os.path.dirname(pinfos['path'])))
orig_path = dst_path
if os.path.isdir(dst_path):
dst_path = tempfile.mkdtemp(
prefix=os.path.basename(dst_path) + '_',
dir=lodel.plugin.plugins.PLUGINS_PATH)
logger.warning("A plugin already exists in %s. Installing \
prefix = os.path.basename(dst_path)+'_',
dir = lodel.plugin.plugins.PLUGINS_PATH)
logger.warning("A plugin allready exists in %s. Installing \
in %s" % (orig_path, dst_path))
shutil.rmtree(dst_path)
# Install the plugin
shutil.copytree(pinfos['path'], dst_path, symlinks=False)
#Installing the plugin
shutil.copytree(pinfos['path'], dst_path, symlinks = False)
print("%s(%s) installed in %s" % (
pname, pinfos['version'], dst_path))
if len(errors) > 0:
msg = "Following errors occurs during installation process :\n"
msg = "Following errors occurs during instalation process :\n"
for path, error_msg in errors.items():
msg += "\t- For '%s' : %s" % (path, error_msg)
print(msg)
## @brief Uninstall plugins
# @param args : grabbed from argv of command line
# @todo Does nothing for now : delete is commented
##@brief Handles plugins uninstall
#@todo uninstall by path is broken
@classmethod
def uninstall(cls, args):
import lodel.plugin.plugins
from lodel.plugin.plugins import Plugin
if len(args.archive) > 0:
raise RuntimeError("Cannot uninstall plugin using -f --file \
options. Use -d --directory instead")
to_delete = dict() # will contain all pathes of plugins to delete
oprtions. Use -d --directory instead")
to_delete = dict() #Path to delete accumulator
errors = dict()
# Uninstall by pathes
if len(args.directory) > 0:
# processing & checking -d --directory arguments
#processing & checking -d --directory arguments
for path in args.directory:
apath = os.path.abspath(path)
# We assume plugins are in lodel/plugins
if not apath.startswith(lodel.plugins.PLUGINS_PATH):
if not apath.startswith(lodel.plugin.plugins.PLUGINS_PATH):
errors[path] = "Not a subdir of %s"
errors[path] %= lodel.plugins.PLUGINS_PATH
errors[path] %= lodel.plugin.plugins.PLUGINS_PATH
continue
try:
pinfos = Plugin.dir_is_plugin(apath)
@ -251,16 +209,13 @@ options. Use -d --directory instead")
continue
to_delete[path] = pinfos
# Uninstall by plugin's names
# We retrieve the path of the plugin from its name
if len(args.plugin_name) > 0:
# Processing -n --plugin-name arguments
plist = Plugin._discover(lodel.plugins.PLUGINS_PATH)
#Processing -n --plugin-name arguments
plist = Plugin._discover(lodel.plugin.plugins.PLUGINS_PATH)
for pinfos in plist:
if pinfos['name'] in args.plugin_name:
to_delete[pinfos['path']] = pinfos
# Manage errors and exit if there is no force option
if len(errors) > 0:
msg = "Following errors detected before begining deletions :\n"
for path, errmsg in errors.items():
@ -268,17 +223,15 @@ options. Use -d --directory instead")
print(msg)
if not args.force:
exit(1)
print("Begining deletion :")
for path, pinfos in to_delete.items():
# shutil.rmtree(path)
#shutil.rmtree(path)
print("rm -R %s" % path)
print("\t%s(%s) in %s deleted" % (
pinfos['name'], pinfos['version'], pinfos['path']))
## @brief Clean plugins by removing plugins with same names \
# The last version is kept
# @param args : grabbed from argv of command line
##@brief Handles plugins clean
@classmethod
def clean(cls, args):
import lodel.plugin.plugins
@ -290,11 +243,9 @@ options. Use -d --directory instead")
names = args.plugin_name
else:
names = list(Plugin.discover().keys())
#_discover do not remove duplicated names
full_list = Plugin._discover(lodel.plugins.PLUGINS_PATH)
# Casting into a dict with list of plugins infos
#_dicover do not remove duplicated names
full_list = Plugin._discover(lodel.plugin.plugins.PLUGINS_PATH)
#Casting into a dict with list of plugins infos
pdict = dict()
for pinfos in full_list:
if pinfos['name'] in names:
@ -306,7 +257,7 @@ options. Use -d --directory instead")
clean_count = 0
for pname, pinfos_l in pdict.items():
if len(pinfos_l) > 1:
# There are some plugins to clean
#There are some plugins to clean
tmp_l = sorted(pinfos_l, key=lambda item: item['version'])
to_clean += tmp_l[:-1]
msg = "Found %s(%s). Cleaning " % (
@ -320,13 +271,13 @@ options. Use -d --directory instead")
if clean_count > 0:
print("%d plugins were uninstalled" % clean_count)
else:
print("Already clean")
print("Allready clean")
## @brief Implements lodel_admin.py **hooks-list** action
##@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'
@ -334,7 +285,6 @@ class ListHooks(lodel_script.LodelScript):
def argparser_config(cls, parser):
pass
## @brief Display the list of hooks registered
@classmethod
def run(cls, args):
import loader
@ -347,7 +297,109 @@ class ListHooks(lodel_script.LodelScript):
for hfun, priority in hlist[name]:
msg = "\t\t- {modname}.{funname} with priority : {priority}"
print(msg.format(
modname=hfun.__module__,
funname=hfun.__name__,
priority=priority))
modname = hfun.__module__,
funname = hfun.__name__,
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,92 +1,66 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugin.datasource_plugin Datasource plugins management module
#
# It contains the base classes for all the datasource plugins that could be added to Lodel
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'],
})
## @brief The plugin type that is used in the global settings of Lodel
_glob_typename = 'datasource'
## @brief Main abstract class from which the plugins' datasource classes must inherit.
##@brief Datasource class in plugins HAVE TO inherit from this abstract class
class AbstractDatasource(object):
## @brief Trigger LodelFatalError when abtract method called
# @throw LodelFatalError if there is an attempt to instanciate an object from this class
##@brief Trigger LodelFatalError when abtract method called
@staticmethod
def _abs_err():
raise LodelFatalError("This method is abstract and HAVE TO be \
reimplemented by plugin datasource child class")
##
# @param *conn_args
# @param **conn_kwargs
##@brief The constructor
def __init__(self, *conn_args, **conn_kwargs):
self._abs_err()
## @brief Provides a new uniq numeric ID
# @param emcomp LeObject subclass (not instance) : defines against which objects type the id should be unique
# @return int
##@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._abs_err()
## @brief Returns a selection of documents from the datasource
# @param target Emclass : class of the documents
# @param field_list list : fields to get from the datasource
# @param filters list : List of filters
# @param rel_filters list : List of relational filters (default value : None)
# @param order list : List of column to order. ex: order = [('title', 'ASC'),] (default value : None)
# @param group list : List of tupple representing the column to group together. ex: group = [('title', 'ASC'),] (default value : None)
# @param limit int : Number of records to be returned (default value None)
# @param offset int: used with limit to choose the start record (default value : 0)
# @param instanciate bool : If true, the records are returned as instances, else they are returned as dict (default value : True)
# @return list
def select(self, target, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0,
instanciate=True):
##@brief returns a selection of documents from the 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):
self._abs_err()
## @brief Deletes records according to given filters
# @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
##@brief Deletes records according to given filters
#@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):
self._abs_err()
## @brief updates records according to given filters
# @param target Emclass : class of the object to insert
# @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
#@param target Emclass : class of the object to insert
#@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):
self._abs_err()
@ -105,21 +79,22 @@ reimplemented by plugin datasource child class")
self._abs_err()
## @brief Represents a Datasource plugin
##@brief Designed to handles datasources plugins
#
# It will provide an access to a data collection to LeAPI (i.e. database connector, API ...).
#A datasource provide data access to LeAPI typically a connector on a DB
#or an API
#
# It provides the methods needed to initialize the datasource attribute in LeAPI LeObject child
# classes (see @ref leapi.leobject.LeObject._init_datasources() )
#Provide methods to initialize datasource attribute in LeAPI LeObject child
#classes (see @ref leapi.leobject.LeObject._init_datasources() )
#
# @note For the moment implementation is done with a retro-compatibilities priority and not with a convenience priority.
# @todo Refactor and rewrite lodel2 datasource handling
# @todo Write abstract classes for Datasource and MigrationHandler !!!
#@note For the moment implementation is done with a retro-compatibilities
#priority and not with a convenience priority.
#@todo Refactor and rewrite lodel2 datasource handling
#@todo Write abstract classes for Datasource and MigrationHandler !!!
class DatasourcePlugin(Plugin):
_type_conf_name = _glob_typename
## @brief Stores confspecs indicating where DatasourcePlugin list is stored
##@brief Stores confspecs indicating where DatasourcePlugin list is stored
_plist_confspecs = {
'section': 'lodel2',
'key': 'datasource_connectors',
@ -131,17 +106,15 @@ class DatasourcePlugin(Plugin):
'none_is_valid': False})
}
##
# @param name str : plugin's name
# @see plugins.Plugin
##@brief Construct a DatasourcePlugin
#@param name str : plugin name
#@see plugins.Plugin
def __init__(self, name):
super().__init__(name)
self.__datasource_cls = None
## @brief Returns an accessor to the datasource class
# @return A python datasource class
# @throw DatasourcePluginError if the plugin's datasource class is not a child of
# @ref lodel.plugin.datasource_plugin.AbstractDatasource
##@brief Accessor to the datasource class
#@return A python datasource class
def datasource_cls(self):
if self.__datasource_cls is None:
self.__datasource_cls = self.loader_module().Datasource
@ -151,26 +124,31 @@ class DatasourcePlugin(Plugin):
lodel.plugin.datasource_plugin.AbstractDatasource" % (self.name))
return self.__datasource_cls
## @brief Returns an accessor to migration handler class
# @return A python migration handler class
##@brief Accessor to migration handler class
#@return A python migration handler class
def migration_handler_cls(self):
return self.loader_module().migration_handler_class()
## @brief Returns an initialized Datasource instance
# @param ds_name str : The name of the datasource to instanciate
# @param ro bool : indicates if it will be in read only mode, else it will be in write only mode
# @return A properly initialized Datasource instance
##@brief Return an initialized Datasource instance
#@param ds_name str : The name of the datasource to instanciate
#@param ro bool
#@return A properly initialized Datasource instance
#@throw SettingsError if an error occurs in settings
#@throw DatasourcePluginError for various errors
@classmethod
def init_datasource(cls, ds_name, ro):
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 Returns an initialized MigrationHandler instance
# @param ds_name str : The datasource name
# @return A properly initialized MigrationHandler instance
# @throw PluginError if a read only datasource instance was given to the migration handler.
##@brief Return an initialized MigrationHandler instance
#@param ds_name str : The datasource name
#@return A properly initialized MigrationHandler instance
@classmethod
def init_migration_handler(cls, ds_name):
plugin_name, ds_identifier = cls.plugin_name(ds_name, False)
@ -184,12 +162,13 @@ migration handler !!!")
return mh_cls(**ds_conf)
## @brief Given a datasource name returns a DatasourcePlugin name
# @param ds_name str : datasource name
# @param ro bool : if true consider the datasource as readonly
# @return a DatasourcePlugin name
# @throw DatasourcePluginError if the given datasource is unknown or not configured, or if there is a conflict in its "read-only" property (between the instance and the settings).
# @throw SettingsError if there are misconfigured datasource settings.
##@brief Given a datasource name returns a DatasourcePlugin name
#@param ds_name str : datasource name
#@param ro bool : if true consider the datasource as readonly
#@return a DatasourcePlugin name
#@throw PluginError if datasource name not found
#@throw DatasourcePermError if datasource is read_only but ro flag arg is
#false
@staticmethod
def plugin_name(ds_name, ro):
LodelContext.expose_modules(globals(), {
@ -222,11 +201,11 @@ True found in settings for datasource '%s'" % ds_name)
DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier)
return res
## @brief Returns a datasource's configuration
# @param ds_identifier str : datasource name
# @param ds_plugin_name : datasource plugin name
# @return a dict containing datasource initialisation options
# @throw DatasourcePluginError if a datasource plugin or instance cannot be found
##@brief Try to fetch a datasource configuration
#@param ds_identifier str : datasource name
#@param ds_plugin_name : datasource plugin name
#@return a dict containing datasource initialisation options
#@throw NameError if a datasource plugin or instance cannot be found
@staticmethod
def _get_ds_connection_conf(ds_identifier,ds_plugin_name):
LodelContext.expose_modules(globals(), {
@ -243,55 +222,56 @@ DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier)
ds_conf = getattr(ds_conf, ds_identifier)
return {k: getattr(ds_conf,k) for k in ds_conf._fields }
## @brief Returns a DatasourcePlugin instance from a plugin's name
# @param ds_name str : plugin name
# @return DatasourcePlugin
# @throw PluginError if no plugin named ds_name found (@see lodel.plugin.plugins.Plugin)
# @throw PluginTypeError if ds_name ref to a plugin that is not a DatasourcePlugin
##@brief DatasourcePlugin instance accessor
#@param ds_name str : plugin name
#@return a DatasourcePlugin instance
#@throw PluginError if no plugin named ds_name found
#@throw PluginTypeError if ds_name ref to a plugin that is not a
#DatasourcePlugin
@classmethod
def get(cls, ds_name):
pinstance = super().get(ds_name) # Will raise PluginError if bad name
pinstance = super().get(ds_name) #Will raise PluginError if bad name
if not isinstance(pinstance, DatasourcePlugin):
raise PluginTypeErrror("A name of a DatasourcePlugin was excepted \
but %s is a %s" % (ds_name, pinstance.__class__.__name__))
return pinstance
## @brief Returns a datasource class given a datasource name
# @param ds_plugin_name str : datasource plugin name
# @return Datasource class
##@brief Return a datasource class given a datasource name
#@param ds_plugin_name str : datasource plugin name
#@throw PluginError if ds_name is not an existing plugin name
#@throw PluginTypeError if ds_name is not the name of a DatasourcePlugin
@classmethod
def get_datasource(cls, ds_plugin_name):
return cls.get(ds_plugin_name).datasource_cls()
## @brief Returns a migration handler class, given a plugin name
# @param ds_plugin_name str : a datasource plugin name
# @return MigrationHandler class
##@brief Given a plugin name returns a migration handler class
#@param ds_plugin_name str : a datasource plugin name
@classmethod
def get_migration_handler(cls, ds_plugin_name):
return cls.get(ds_plugin_name).migration_handler_cls()
## @page lodel2_datasources Lodel2 datasources
##@page lodel2_datasources Lodel2 datasources
#
# @par lodel2_datasources_intro Introduction
#@par lodel2_datasources_intro Intro
# A single lodel2 website can interact with multiple datasources. This page
# aims to describe configuration and organisation of datasources in lodel2.
# aims to describe configuration & organisation of datasources in lodel2.
# Each object is attached to a datasource. This association is done in the
# editorial model, in which the datasource is identified by its name.
# editorial model, the datasource is identified by a name.
#
# @par Datasources declaration
# To define a datasource you have to write something like this in configuration file :
# <pre>
# [lodel2.datasources.DATASOURCE_NAME]
# identifier = DATASOURCE_FAMILY.SOURCE_NAME
# </pre>
# See below for DATASOURCE_FAMILY & SOURCE_NAME
#@par Datasources declaration
# To define a datasource you have to write something like this in confs file :
#<pre>
#[lodel2.datasources.DATASOURCE_NAME]
#identifier = DATASOURCE_FAMILY.SOURCE_NAME
#</pre>
# See below for DATASOURCE_FAMILY & SOURCE_NAME
#
# @par Datasources plugins
# Each datasource family is a plugin ( @ref plugin_doc "More informations on plugins" ).
# For example mysql or a mongodb plugins. \n
#
# Here is the CONFSPEC variable templates for datasources plugin
#@par Datasources plugins
# Each datasource family is a plugin (
#@ref plugin_doc "More informations on plugins" ). For example mysql or a
#mongodb plugins. Here is the CONFSPEC variable templates for datasources
#plugin
#<pre>
#CONFSPEC = {
# 'lodel2.datasource.example.*' : {
@ -301,8 +281,7 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
# }
#}
#</pre>
#
#MySQL example \n
#MySQL example
#<pre>
#CONFSPEC = {
# 'lodel2.datasource.mysql.*' : {
@ -318,8 +297,8 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
#}
#</pre>
#
# @par Configuration example
# <pre>
#@par Configuration example
#<pre>
# [lodel2.datasources.main]
# identifier = mysql.Core
# [lodel2.datasources.revues_write]

View file

@ -1,26 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugin.exceptions Plugin management specific exceptions
class PluginError(Exception):
pass

View file

@ -1,26 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugin.extensions A package to manage the Extension plugins
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
@ -30,12 +7,9 @@ LodelContext.expose_modules(globals(), {
_glob_typename = 'extension'
## @brief A class representing a basic Extension plugin
#
# This class will be extended for each plugin of this type.
class Extension(Plugin):
## @brief Specifies the settings linked to this plugin
_plist_confspecs = {
'section': 'lodel2',
'key': 'extensions',
@ -46,8 +20,6 @@ class Extension(Plugin):
'ptype': _glob_typename,
'none_is_valid': False})
}
## @brief A property defining the type's name of this plugin.
# By default, it's the global type name ("extension" here).
_type_conf_name = _glob_typename

View file

@ -1,40 +1,20 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugin.hooks This module deals with the Hook management in Lodel
#-*- coding: utf-8 -*-
import os
import copy
from lodel.context import LodelContext
## @brief Class designed to handle a hook's callback with a priority
##@brief Class designed to handle a hook's callback with a priority
class DecoratedWrapper(object):
##
##@brief Constructor
# @param hook function : the function to wrap
# @param priority int : the callbacl priority
def __init__(self, hook, priority):
self._priority = priority
self._hook = hook
## @brief Calls the callback
##@brief Call the callback
# @param hook_name str : The name of the called hook
# @param caller * : The caller (depends on the hook)
# @param payload * : Datas that depends on the hook
@ -42,14 +22,12 @@ class DecoratedWrapper(object):
def __call__(self, hook_name, caller, payload):
return self._hook(hook_name, caller, payload)
## @brief Returns the string representation of the class
# It shows the name and the priority of the hook
def __str__(self):
return "<LodelHook '%s' priority = %s>" % (
self._hook.__name__, self._priority)
## @brief Decorator designed to register hook's callbacks
# @ingroup lodel2_plugins
##@brief Decorator designed to register hook's callbacks
#@ingroup lodel2_plugins
#
# @note Decorated functions are expected to take 3 arguments :
#  - hook_name : the called hook name
@ -57,17 +35,17 @@ class DecoratedWrapper(object):
# - payload : datas depending on the hook
class LodelHook(object):
## @brief Stores all hooks (DecoratedWrapper instances)
##@brief Stores all hooks (DecoratedWrapper instances)
_hooks = dict()
##
##@brief Decorator constructor
# @param hook_name str : the name of the hook to register to
# @param priority int : the hook priority (default value : None)
# @param priority int : the hook priority
def __init__(self, hook_name, priority = None):
self._hook_name = hook_name
self._priority = 0xFFFF if priority is None else priority
## @brief called just after __init__
##@brief called just after __init__
# @param hook function : the decorated function
# @return the hook argument
def __call__(self, hook):
@ -78,10 +56,11 @@ class LodelHook(object):
self._hooks[self._hook_name] = sorted(self._hooks[self._hook_name], key = lambda h: h._priority)
return hook
## @brief Calls a hook
##@brief Call hooks
# @param hook_name str : the hook's name
# @param caller * : the hook caller (depends on the hook)
# @param payload * : datas for the hook
# @param cls
# @return modified payload
@classmethod
def call_hook(cls, hook_name, caller, payload):
@ -94,9 +73,10 @@ class LodelHook(object):
payload = hook(hook_name, caller, payload)
return payload
## @brief Fetches registered hooks
# @param names list | None : optionnal filter on name (default value : None)
# @return dict containing for each name a list of the hooks and their priorities
##@brief Fetch registered hooks
# @param names list | None : optionnal filter on name
# @param cls
# @return a list of functions
@classmethod
def hook_list(cls, names = None):
res = None
@ -106,7 +86,8 @@ class LodelHook(object):
res = copy.copy(cls._hooks)
return { name: [(hook._hook, hook._priority) for hook in hooks] for name, hooks in res.items() }
## @brief Unregister all hooks
##@brief Unregister all hooks
# @param cls
# @warning REALLY NOT a good idea !
# @note implemented for testing purpose
@classmethod

View file

@ -1,25 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugin.interface Handles the Interface type plugins
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
@ -27,18 +5,16 @@ LodelContext.expose_modules(globals(), {
'LodelScriptError', 'DatasourcePluginError'],
'lodel.validator.validator': ['Validator']})
## @brief Global type name used in the settings of Lodel for this type of plugins
_glob_typename = 'ui'
##@brief A plugin Interface
##@brief Handles interfaces plugin
#@note It's a singleton class. Only 1 interface allowed by instance.
class InterfacePlugin(Plugin):
## @brief Singleton instance storage
##@brief Singleton instance storage
_instance = None
## @brief Settings description
_plist_confspecs = {
'section': 'lodel2',
'key': 'interface',
@ -46,20 +22,16 @@ class InterfacePlugin(Plugin):
'validator': Validator(
'plugin', none_is_valid = True, ptype = _glob_typename)}
## @brief plugin type name
_type_conf_name = _glob_typename
##
# @param name str : Name of the interface plugin
# @throw PluginError if there is already an interface plugin instanciated
def __init__(self, name):
if InterfacePlugin._instance is not None:
raise PluginError("Maximum one interface allowed")
super().__init__(name)
self._instance = self
## @brief Clears the singleton from its active instance
# @see plugins.Plugin::clear()
##@brief Clear class
#@see plugins.Plugin::clear()
@classmethod
def clear_cls(cls):
if cls._instance is not None:

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
import sys
@ -27,31 +7,29 @@ LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
## @defgroup lodel2_script Administration scripts
# @ingroup lodel2_plugins
##@defgroup lodel2_script Administration scripts
#@ingroup lodel2_plugins
## @package lodel.plugin.scripts Lodel2 utility for writting administration scripts
# @ingroup lodel2_plugins
# @ingroup lodel2_script
##@package lodel.plugin.script
#@brief Lodel2 utility for writting administration scripts
#@ingroup lodel2_plugins
#@ingroup lodel2_script
## @brief Stores registered scripts
# @todo store it in MetaLodelScript
##@brief Stores registered scripts
#@todo store it in MetaLodelScript
__registered_scripts = dict()
## @brief LodelScript metaclass that allows to "catch" child class declaration
# @ingroup lodel2_script
# @ingroup lodel2_plugins
##@brief LodelScript metaclass that allows to "catch" child class
#declaration
#@ingroup lodel2_script
#@ingroup lodel2_plugins
#
# Automatic action registration on child class declaration
#Automatic action registration on child class declaration
class MetaLodelScript(type):
##
# @param name str : action's name
# @param bases list
# @param attrs list
def __init__(self, name, bases, attrs):
# Here we can store all child classes of LodelScript
#Here we can store all child classes of LodelScript
super().__init__(name, bases, attrs)
if len(bases) == 1 and bases[0] == object:
return
@ -71,9 +49,9 @@ action identifier" % name)
self.argparser_config(self._parser)
## @brief Handles script registration
# @note Script list is maitained in lodel.plugin.admin_script.__registered_scripts
# @param name str
##@brief Handles script registration
#@note Script list is maitained in
#lodel.plugin.admin_script.__registered_scripts
def __register_script(self, name):
if self._action is None:
logger.warning("%s._action is None. Trying to use class name as \
@ -86,67 +64,57 @@ action identifier" % name)
return '%s : %s' % (self._action, self._description)
## @brief Class designed to facilitate custom script writting
# @ingroup lodel2_plugins
# @ingroup lodel2_script
##@brief Class designed to facilitate custom script writting
#@ingroup lodel2_plugins
#@ingroup lodel2_script
class LodelScript(object, metaclass=MetaLodelScript):
## @brief A string to identify the action
##@brief A string to identify the action
_action = None
## @brief Script descripiton (argparse argument)
##@brief Script descripiton (argparse argument)
_description = None
## @brief argparse.ArgumentParser instance
##@brief argparse.ArgumentParser instance
_parser = None
## @brief No instanciation
##@brief No instanciation
def __init__(self):
raise NotImplementedError("Static class")
## @brief Virtual method. Designed to initialize arguement parser.
# @param parser ArgumentParser : Child class argument parser instance
# @return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it works by reference)
# @throw LodelScriptError in case it is not implemented in the child class
##@brief Virtual method. Designed to initialize arguement parser.
#@param parser ArgumentParser : Child class argument parser instance
#@return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it \
#works by reference)
@classmethod
def argparser_config(cls, parser):
raise LodelScriptError("LodelScript.argparser_config() is a pure \
virtual method! MUST be implemented by ALL child classes")
## @brief Virtual method. Runs the script
# @param args list
# @return None or an integer that will be the script return code
# @throw LodelScriptError in case it is not implemented in the child class
##@brief Virtual method. Run the script
#@return None or an integer that will be the script return code
@classmethod
def run(cls, args):
raise LodelScriptError("LodelScript.run() is a pure virtual method. \
MUST be implemented by ALL child classes")
## @brief Executes a script
#
# Called by main_run()
##@brief Called by main_run() to execute a script.
#
# Handles argument parsing and then call LodelScript.run()
#Handles argument parsing and then call LodelScript.run()
@classmethod
def _run(cls):
args = cls._parser.parse_args()
return cls.run(args)
## @brief Append action name to the prog name
# @note See argparse.ArgumentParser() prog argument
# @return str
##@brief Append action name to the prog name
#@note See argparse.ArgumentParser() prog argument
@classmethod
def _prog_name(cls):
return '%s %s' % (sys.argv[0], cls._action)
## @brief Return the default description for an action
# @return str
##@brief Return the default description for an action
@classmethod
def _default_description(cls):
return "Lodel2 script : %s" % cls._action
## @brief handles the help message of an action
# @param msg str
# @param return_code int : the default return code is 1
# @param exit_after bool : default value is True, so that there is an exit after the message printing
@classmethod
def help_exit(cls,msg = None, return_code = 1, exit_after = True):
if not (msg is None):
@ -155,23 +123,15 @@ MUST be implemented by ALL child classes")
if exit_after:
exit(1)
## @brief Registers the script class for an action
# @param action_name str
# @param cls LodelScript
def script_registration(action_name, cls):
__registered_scripts[action_name] = cls
logger.info("New script registered : %s" % action_name)
## @brief Returns a list containing all available actions
# @return list
##@brief Return a list containing all available actions
def _available_actions():
return [ act for act in __registered_scripts ]
## @brief Returns default runner's argument parser
# @param ArgumentParser
##@brief Returns default runner argument parser
def _default_parser():
action_list = _available_actions()
@ -190,12 +150,12 @@ def _default_parser():
action" % sys.argv[0])
return parser
## @brief Main function of lodel_admin.py script
##@brief Main function of lodel_admin.py script
#
# This function take care to run the good plugins and clean sys.argv from
# action name before running script
#This function take care to run the good plugins and clean sys.argv from
#action name before running script
#
# @return DO NOT RETURN BUT exit() ONCE SCRIPT EXECUTED !!
#@return DO NOT RETURN BUT exit() ONCE SCRIPT EXECUTED !!
def main_run():
default_parser = _default_parser()
if len(sys.argv) == 1:
@ -222,44 +182,43 @@ def main_run():
ret = 0 if ret is None else ret
exit(ret)
## @page lodel2_script_doc Lodel2 scripting
# @ingroup lodel2_script
##@page lodel2_script_doc Lodel2 scripting
#@ingroup lodel2_script
#
# @section lodel2_script_adm Lodel2 instance administration scripts
#@section lodel2_script_adm Lodel2 instance administration scripts
#
# In Lodel2, it is possible to administrate instances using either Makefiles
# or lodel_admin.py script ( see @ref lodel2_instance_admin ).
#Lodel2 provides instance administration operation using Makefiles or
#lodel_admin.py script ( see @ref lodel2_instance_admin ).
#
# The lodel_admin.py script takes an action as first argument. Each action
# corresponds to a sub-script with its own options etc. To get a list
# of all available action run <code>python3 lodel_admin.py -L</code>.
#The lodel_admin.py script take as first option an action. Each action
#correspond to a sub-script with it's own options etc. To get a list
#of all available action run <code>python3 lodel_admin.py -L</code>.
#
# @section lodel2_script_action lodel_admin.py actions
#@section lodel2_script_action lodel_admin.py actions
#
# Action implementation is done by class inheritance. To create a new action,
# one has to write a @ref lodel.plugin.scripts.LodelScript "LodelScript"
# derived class ( see @ref lodel.plugin.core_scripts "core_scripts.py" file
# as example )
#Action implementation is done by class inheritance. To create a new action
#write a @ref lodel.plugin.scripts.LodelScript "LodelScript" derived class (
#see @ref lodel.plugin.core_scripts "core_scripts.py" file as example )
#
# @subsection lodel2_script_inheritance LodelScript inheritance
#@subsection lodel2_script_inheritance LodelScript inheritance
#
# In order to implement properly a new action you have to write a new
# @ref lodel.plugin.scripts.LodelScript "LodelScript" derivated class.
# Some methods and attributes are mandatory to write a fully functionnal
# derivated class. Here is a list :
#In order to implement properly a new action you have to write a new
#@ref lodel.plugin.scripts.LodelScript "LodelScript" derivated class.
#Some methods and attributes are mandatory to write a fully functionnal
#derivated class. Here is a list :
#
# - mandatory methods
# - @ref plugin.scripts.LodelScript.argparser_config() "argparser_config()" :
#- mandatory methods
# - @ref plugin.scripts.LodelScript.argparser_config() "argparser_config()" :
#(classmethod) initialize argparse.Parser
# - @ref plugin.scripts.LodelScript.run() "run()" : (classmethod) contains the
# - @ref plugin.scripts.LodelScript.run() "run()" : (classmethod) contains the
#code that runs to perform the action
# - mandatory attributes
# - @ref plugin.scripts.LodelScript::_action "_action" : (class attribute)
#- mandatory attributes
# - @ref plugin.scripts.LodelScript::_action "_action" : (class attribute)
#stores action name
# - @ref plugin.scripts.LodelScript::_description "_description" : (class
# - @ref plugin.scripts.LodelScript::_description "_description" : (class
#attribute) sotres a short action description
#
# @note On script's action registration : once child class is written you only
# need to import it to trigger script's action registration (see
# @ref plugin.scripts.MetaLodelScript )
#@note On script's action registration : once child class is written you only
#need to import it to trigger script's action registration (see
#@ref plugin.scripts.MetaLodelScript )
#

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin', 'MetaPlugType'],

View file

@ -1,37 +1,17 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
plugins_PYTHON=__init__.py
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

@ -1,22 +1,2 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##@defgroup lodel2_plugins_list Plugins lodel
#@brief Regroup all implemented plugin documentation

View file

@ -1,49 +1,20 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.dummy Basic plugin used as a template for developping new plugins
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
## @brief plugin's name (matching the package's name)
__plugin_name__ = "dummy"
## @brief plugin's version
__version__ = '0.0.1' #or __version__ = [0,0,1]
## @brief plugin's loader module
__loader__ = "main.py"
## @brief plugin's options' definition module
__confspec__ = "confspec.py"
## @brief plugin's author(s)
__author__ = "Lodel2 dev team"
## @brief plugin's full name
__fullname__ = "Dummy plugin"
__name__ = 'dummy'
## @brief plugin's category
__plugin_type__ = 'extension'
## @brief This methods allow plugin writter to write some checks
##@brief This methods allow plugin writter to write some checks
#
# @return bool : True if checks are OK else return a string with a reason
#@return True if checks are OK else return a string with a reason
def _activate():
import leapi_dyncode
print("Testing dynamic objects : ")

View file

@ -1,30 +1,9 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.dummy.confspec The module that defines the plugin's configuration options
#-*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
## @brief Dictionary defining the plugin's configuration options and their validators
CONFSPEC = {
'lodel2.section1': {
'key1': ( None,

View file

@ -1,36 +1,10 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.dummy.main Plugin's loader module
#-*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin': ['LodelHook', 'CustomMethod'],
'lodel.settings' : 'settings'})
## @brief callback method using lodel's hook system
# @param hook_name str
# @param caller function : action to perform
# @param payload : data to pass to the caller
# @return payload
@LodelHook('leapi_get_post')
@LodelHook('leapi_update_pre')
@LodelHook('leapi_update_post')
@ -43,17 +17,15 @@ def dummy_callback(hook_name, caller, payload):
print("\tHook %s\tcaller %s with %s" % (hook_name, caller, payload))
return payload
## @brief instance method
# This is an example of a basic plugin's custom method
@CustomMethod('Object', 'dummy_method')
def dummy_instance_method(self):
print("Hello world !\
I'm a custom method on an instance of class %s" % self.__class__)
## @brief instance method
# This is an example of a basic plugin's custom method
@CustomMethod('Object', 'dummy_class_method', CustomMethod.CLASS_METHOD)
def dummy_instance_method(self):
print("Hello world !\
I'm a custom method on class %s" % self.__class__)

View file

@ -1,43 +1,14 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.dummy_datasource Example of a datasource type plugin
# Here we use the Lodel Context Manager to expose the modules which are specific to the application
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
from .datasource import DummyDatasource as Datasource
## @brief plugin's category
__plugin_type__ = 'datasource'
## @brief plugin's name (matching the package's name)
__plugin_name__ = "dummy_datasource"
## @brief plugin's version
__version__ = '0.0.1'
## @brief plugin's main entry module
__loader__ = 'main.py'
## @brief plugin's dependances
__plugin_deps__ = []
## @brief Plugin's configuration options and their corresponding validators
CONFSPEC = {
'lodel2.datasource.dummy_datasource.*' : {
'dummy': ( None,

View file

@ -1,74 +1,51 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.dummy_datasource.datasource This module contains the main class of the datasource, implementing the basic operations one can perform.
#-*- coding:utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin.datasource_plugin': ['AbstractDatasource']})
## @brief Datasource class, inherited from @ref lodel.plugin.datasource.AbstractDatasource
class DummyDatasource(AbstractDatasource):
##
# @param conn_args list
# @param conn_kwargs dict
def __init__(self, *conn_args, **conn_kwargs):
self.conn_args = conn_args
self.conn_kwargs = conn_kwargs
## @brief Provides a new unique numeric ID
# @param emcomp LeObject subclass (not instance) : The class against whom we have to be unique
# @return an integer
##@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):
pass
## @brief returns a selection of documents from the datasource
# @param target Emclass
# @param field_list list
# @param filters list : List of filters
# @param relational_filters list : List of relational filters (default value : None)
# @param order list : List of column to order. ex: order = [('title', 'ASC'),] (default value : None)
# @param group list : List of tupple representing the column to group together. ex: group = [('title', 'ASC'),] (default value : None)
# @param limit int : Number of records to be returned (default value : None)
# @param offset int: used with limit to choose the start record (default value : 0)
# @param instanciate bool : If true, the records are returned as instances, else they are returned as dict (default value : True)
# @return list
##@brief returns a selection of documents from the datasource
#@param target Emclass
#@param field_list list
#@param filters list : List of filters
#@param relational_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):
pass
## @brief Deletes records according to given filters
# @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
##@brief Deletes records according to given filters
#@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):
return 0
## @brief updates records according to given filters
# @param target Emclass : class of the object to insert
# @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
#@param target Emclass : class of the object to insert
#@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):
return 0

View file

@ -1,32 +1,10 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.dummy_datasource.main The main module of the plugin.
#-*- coding:utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin': ['LodelHook']})
from .datasource import DummyDatasource as Datasource
## @brief returns the migration handler of this plugin.
# @retunr DummyMigrationHandler
def migration_handler_class():
from .migration_handler import DummyMigrationHandler as migration_handler
return migration_handler

View file

@ -1,57 +1,22 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
## @package lodel.plugins.dummy_datasource.migration_handler Migration handler of the datasource plugin.
#
# The migration handler is here to report the changes made in the editorial model to the data source.
# 
# For a database, it could be the creation/deletion of tables, the addition/deletion/change of certain columns, etc...
#
# We also have here the datasource initialization process, for the first use of it. The migration handler will then
# use the editorial model to generate the corresponding data storage.
## @brief Abtract class for migration handlers
##@brief Abtract class for migration handlers
class DummyMigrationHandler(object):
## @brief Create a new migration handler given DB connection options
##@brief Create a new migration handler given DB connection options
def __init__(self, *args, **kwargs):
pass
## @brief DB initialisation
# @param emclass_list list : list of EmClasses concerned by this MH
##@brief DB initialisation
#@param emclass_list list : list of EmClasses concerned by this MH
def init_db(self, emclass_list):
pass
## @brief register a change in the editorial model
# @param model EditorialModel
# @param uid
# @param initial_state
# @param new_state
# @todo redefine
##@todo redefine
def register_change(self, model, uid, initial_state, new_state):
pass
##
# @param em EditorialModel
# @param state_hash str
# @todo redefine
##@todo redefine
def register_model_state(self, em, state_hash):
pass

View file

@ -1,25 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package plugins.filesystem_session This package is a plugin for filesystem based session management
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})

View file

@ -1,30 +1,9 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.filesystem_session.confspec A module that defines the configuration options available for that plugin
# -*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
## @brief Dictionary of the options and their corresponding validators
CONFSPEC = {
'lodel2.sessions':{
'directory': ('/tmp/', Validator('path')),

View file

@ -1,29 +1,10 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
## @package lodel.plugins.filesystem_session.filesystem_session Session objects management module
## @brief An extended dictionary representing a session in the file system
class FileSystemSession(dict):
##
## @brief Constructor
# @param token str
def __init__(self, token):
self.__token = token

View file

@ -1,70 +1,26 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package lodel.plugins.filesystem_session.main Main entry point of the plugin
# -*- coding: utf-8 -*-
import binascii
import datetime
import os
import pickle
import re
import time
from .filesystem_session import FileSystemSession
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.auth.exceptions': ['ClientAuthenticationFailure'],
'lodel.settings': ['Settings']})
from .filesystem_session import FileSystemSession
__sessions = dict()
SESSION_TOKENSIZE = 150
##
# @brief generates a new session token
#
## @brief generates a new session token
# @return str
#
# @warning The tokensize should absolutely be used as set! os.urandom function
# takes a number of bytes as a parameter, dividing it by 2 is an
# extremely dangerous idea as it drastically decrease the token expected
# entropy expected from the value set in configs.
# @remarks There is no valid reason for checking the generated token uniqueness:
# - checking for uniqueness is slow ;
# - keeping a dict with a few thousand keys of hundred bytes also is
# memory expensive ;
# - should the system get distributed while sharing session storage, there
# would be no reasonable way to efficiently check for uniqueness ;
# - sessions do have a really short life span, drastically reducing
# even more an already close to inexistent risk of collision. A 64 bits
# id would perfectly do the job, or to be really cautious, a 128 bits
# one (actual size of UUIDs) ;
# - if we are still willing to ensure uniqueness, then simply salt it
# with a counter, or a timestamp, and hash the whole thing with a
# cryptographically secured method such as sha-2 if we are paranoids
# and trying to avoid what will never happen, ever ;
# - sure, two hexadecimal characters is one byte long. Simply go for
# bit length, not chars length.
def generate_token():
token = binascii.hexlify(os.urandom(SESSION_TOKENSIZE//2))
if token in __sessions.keys():
@ -72,40 +28,20 @@ def generate_token():
return token.decode('utf-8')
##
# @brief checks the validity of a given session token
#
## @brief checks the validity of a given session token
# @param token str
# @raise ClientAuthenticationFailure for invalid or not found session token
#
# @remarks It is useless to check the token size, unless urandom you don't
# trust in PRNG such as urandom.
# @remarks Linear key search...
# @remarks Consider renaming. The "validity of a session token" usually means
# that it is a active session token and/or that it was actually
# produced by the application (signed for exemple).
def check_token(token):
if len(token) != SESSION_TOKENSIZE:
raise ClientAuthenticationFailure("Invalid token string")
if token not in __sessions.keys():
raise ClientAuthenticationFailure("No session found for this token")
## @brief returns a session file path for a specific token
# @param token str
# @return str
def generate_file_path(token):
return os.path.abspath(os.path.join(Settings.sessions.directory, Settings.sessions.file_template) % token)
##
# @brief Retrieve the token from the file system
#
# @param filepath str
# @return str|None : returns the token or None if no token was found
#
# @remarks What is the purpose of the regex right here? There should be a way
# to avoid slow operations.
def get_token_from_filepath(filepath):
token_regex = re.compile(os.path.abspath(os.path.join(Settings.sessions.directory, Settings.sessions.file_template % '(?P<token>.*)')))
token_search_result = token_regex.match(filepath)
@ -114,15 +50,10 @@ def get_token_from_filepath(filepath):
return None
##
# @brief Returns the session's last modification timestamp
#
## @brief returns the session's last modification timestamp
# @param token str
# @return float
# @raise ValueError if the given token doesn't match with an existing session
#
# @remarks Consider renaming
# @warning Linear search in array, again. See @ref generate_token().
def get_session_last_modified(token):
if token in __sessions[token]:
return os.stat(__sessions[token]).st_mtime
@ -130,26 +61,19 @@ def get_session_last_modified(token):
raise ValueError("The given token %s doesn't match with an existing session")
##
# @brief Starts a new session and returns a new token
#
# @return str : the new token
## @brief returns the token of a new session
# @return str
def start_session():
session = FileSystemSession(generate_token())
session.path = generate_file_path(session.token)
with open(session.path, 'wb') as session_file:
pickle.dump(session, session_file)
__sessions[session.token] = session.path
logger.debug("New session created")
return session.token
##
# @brief destroys a session given its token
#
## @brief destroys a session given its token
# @param token str
def destroy_session(token):
check_token(token)
@ -160,9 +84,7 @@ def destroy_session(token):
logger.debug("Session %s unregistered" % token)
##
# @brief Restores a session's content
#
## @brief restores a session's content
# @param token str
# @return FileSystemSession|None
def restore_session(token):
@ -197,8 +119,6 @@ def save_session(token, datas):
## @brief session store's garbage collector
#
# @remarks
def gc():
# Unregistered files in the session directory
session_files_directory = os.path.abspath(Settings.sessions.directory)
@ -218,29 +138,17 @@ def gc():
logger.debug("Expired session %s has been destroyed" % token)
##
# @param token str
# @param key str
# @param value
def set_session_value(token, key, value):
session = restore_session(token)
session[key] = value
save_session(token, session)
##
# @param token str
# @param key str
def get_session_value(token, key):
session = restore_session(token)
return session[key]
##
# @brief deletes a session value
#
# @param token str
# @param key str
#
# @todo Should we add a save_session at the end of this method?
def del_session_value(token, key):
session = restore_session(token)
if key in session:

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,23 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#-*- coding: utf-8 -*-
__plugin_type__ = 'datasource'
__plugin_name__ = 'mongodb_datasource'
__version__ = '0.0.1'
@ -32,54 +13,52 @@ __fullname__ = "MongoDB plugin"
## @brief Activates the plugin
#
# @note This function can contain specific actions (like checks, etc ...) in
# order to activate the plugin.
# @note It is possible there to add some specific actions (like checks, etc ...) for the plugin
#
# @return bool|str : True if all the checks are OK, an error message if not
def _activate():
from lodel import buildconf # NOTE : this one do not have to pass through the context
from lodel import buildconf #NOTE : this one do not have to pass through the context
return buildconf.PYMONGO
#
# Doxygen comments
#
## @defgroup plugin_mongodb_datasource MongoDB datasource plugin
# @brief Doc about mongodb datasource
##@defgroup plugin_mongodb_datasource MongoDB datasource plugin
#@brief Doc about mongodb datasource
## @page plugin_mongodb_backref_complexity Reflexion on back reference complexity
# @ingroup plugin_mongodb_bref_op
##@page plugin_mongodb_backref_complexity Reflexion on back reference complexity
#@ingroup plugin_mongodb_bref_op
#
# There is a huge performance issue in the way we implemented references and
# back references for mongodb datasource :
#Their is a huge performance issue in the way we implemented references
#and back references for mongodb datasource :
#
# For each write action (update, delete or insert) we HAVE TO run a select
# on all concerned LeObject instances. Those methods' headers look like
# <pre>def write_action(target_cls, filters, [datas])</pre>.
#For each write action (update, delete or insert) we HAVE TO run a select
#on all concerned LeObject. In fact those methods headers looks like
#<pre>def write_action(target_cls, filters, [datas])</pre>
#
# We have no idea if all the modified objects are of the target class (they
# can be of any target's child classes). So that means we have no idea of the
# @ref base_classes.Reference "References" that will be modified by the action.
#We have no idea if all the modified objects are of the target class (they
#can be of any target's child classes). So that means we have no idea of the
#@ref base_classes.Reference "References" that will be modified by the action.
#
# Another problem is that when we run an update or a delete we have no idea
# of the values that will be updated or deleted (we do not have the concerned
# instances datas). As a result we cannot replace or delete the concerned
# back references.
#Another problem is that when we run an update or a delete we have no idea
#of the values that will be updated or deleted (we do not have the concerned
#instances datas). As a result we cannot replace or delete the concerned
#back references.
#
# In term of complexity the number of DB query looks like :
# <pre>
# With n the number of instances to modify :
# queryO(n) ~= 2n ( n * select + n * update )
# </pre>
#In term of complexity the number of DB query looks like :
#<pre>
#With n the number of instances to modify :
#queryO(n) ~= 2n ( n * select + n * update )
#</pre>
#But it can go really bad, really fast if we take in consideration that
#query's can be done on mixed classes or abstract classes. With :
#- n : the number of LeObect child classes represented by the abstract class
#- m : the number of LeObject child classes for each n
#- o : the number of concerned back_reference classes for each m
#
# But it can go really bad, really fast if we take in consideration that
# query's can be done on mixed classes or abstract classes. With :
# - n : the number of LeObect child classes represented by the abstract class
# - m : the number of LeObject child classes for each n
# - o : the number of concerned back_reference classes for each m
#<pre>queryO(n,m,o) ~= n + (n*m) + (n*m*o) => n + n*m select and n*m*o updates</pre>
#
# <pre>queryO(n,m,o) ~= n + (n*m) + (n*m*o) => n + n*m select and n*m*o updates</pre>
#
# All of this is really sad especially as the update and the delete will be
# run on LeObject instances....
#All of this is really sad especially as the update and the delete will be
#run on LeObject instances....
#

View file

@ -1,31 +1,13 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.validator.validator': ['Validator']})
## @brief Mongodb datasource plugin confspec
# @ingroup plugin_mongodb_datasource
##@brief Mongodb datasource plugin confspec
#@ingroup plugin_mongodb_datasource
#
# Describes mongodb plugin configuration and the corresponding validators
#Describe mongodb plugin configuration. Keys are :
CONFSPEC = {
'lodel2.datasource.mongodb_datasource.*':{
'read_only': (False, Validator('bool')),

View file

@ -1,27 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
## @package plugins.mongodb_datasource.datasource Main datasource module
#
# In this class, there is the MongoDbDatasource class, that handles the basic
# operations that can be done (CRUD ones).
object# -*- coding: utf-8 -*-
import re
import warnings
@ -46,54 +23,51 @@ from .utils import object_collection_name, collection_name, \
MONGODB_SORT_OPERATORS_MAP, connection_string, mongo_fieldname
## @brief Datasource class
# @ingroup plugin_mongodb_datasource
##@brief Datasource class
#@ingroup plugin_mongodb_datasource
class MongoDbDatasource(AbstractDatasource):
## @brief Stores existing connections
##@brief Stores existing connections
#
# The key of this dict is a hash built upon the connection string and the
# ro (read-only) parameter.
#
# The value is a dict with 2 keys :
#The key of this dict is a hash of the connection string + ro parameter.
#The value is a dict with 2 keys :
# - conn_count : the number of instanciated datasource that use this
# connection
#connection
# - db : the pymongo database object instance
_connections = dict()
## @brief Mapping from lodel2 operators to mongodb operators
##@brief Mapping from lodel2 operators to mongodb operator
lodel2mongo_op_map = {
'=':'$eq', '<=':'$lte', '>=':'$gte', '!=':'$ne', '<':'$lt',
'>':'$gt', 'in':'$in', 'not in':'$nin' }
## @brief List of mongodb operators that expect re as value
##@brief List of mongodb operators that expect re as value
mongo_op_re = ['$in', '$nin']
wildcard_re = re.compile('[^\\\\]\*')
## @brief instanciates a database object given a connection name
# @param host str : hostname or IP
# @param port int : mongodb listening port
# @param db_name str
# @param username str
# @param password str
# @param read_only bool : If True the Datasource is for read only, else the
# Datasource is write only !
##@brief instanciates a database object given a connection name
#@param host str : hostname or IP
#@param port int : mongodb listening port
#@param db_name str
#@param username str
#@param password str
#@param read_only bool : If True the Datasource is for read only, else the
#Datasource is write only !
def __init__(self, host, port, db_name, username, password, read_only = False):
## @brief Connections infos that can be kept securly
##@brief Connections infos that can be kept securly
self.__db_infos = {'host': host, 'port': port, 'db_name': db_name}
## @brief Is the instance read only ? (if not it's write only)
##@brief Is the instance read only ? (if not it's write only)
self.__read_only = bool(read_only)
## @brief Uniq ID for mongodb connection
##@brief Uniq ID for mongodb connection
self.__conn_hash= None
## @brief Stores the database cursor
##@brief Stores the database cursor
self.database = self.__connect(
username, password, db_name, self.__read_only)
## @brief Destructor that attempt to close connection to DB
##@brief Destructor that attempt to close connection to DB
#
# Decrease the conn_count of associated MongoDbDatasource::_connections
# item. If it reach 0 close the connection to the db
# @see MongoDbDatasource::__connect()
#Decrease the conn_count of associated MongoDbDatasource::_connections
#item. If it reach 0 close the connection to the db
#@see MongoDbDatasource::__connect()
def __del__(self):
self._connections[self.__conn_hash]['conn_count'] -= 1
if self._connections[self.__conn_hash]['conn_count'] <= 0:
@ -101,11 +75,11 @@ class MongoDbDatasource(AbstractDatasource):
del(self._connections[self.__conn_hash])
logger.info("Closing connection to database")
## @brief Provides a new uniq numeric ID
# @param emcomp LeObject subclass (not instance) : To know on wich things we
# have to be uniq
# @warning multiple UID broken by this method
# @return an integer
##@brief Provide a new uniq numeric ID
#@param emcomp LeObject subclass (not instance) : To know on wich things we
#have to be uniq
#@warning multiple UID broken by this method
#@return an integer
def new_numeric_id(self, emcomp):
target = emcomp.uid_source()
tuid = target._uid[0] # Multiple UID broken here
@ -116,34 +90,35 @@ class MongoDbDatasource(AbstractDatasource):
return 1
return results[0][tuid]+1
## @brief returns a selection of documents from the datasource
# @param target Emclass
# @param field_list list
# @param filters list : List of filters
# @param relational_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 used as
# "group by" fields. ex: group = [('title', 'ASC'),]
# @param limit int : Number of records to be returned
# @param offset int: used with limit to choose the start record
# @return list
# @todo Implement group for abstract LeObject childs
##@brief returns a selection of documents from the datasource
#@param target Emclass
#@param field_list list
#@param filters list : List of filters
#@param relational_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 used as
#"group by" fields. ex: group = [('title', 'ASC'),]
#@param limit int : Number of records to be returned
#@param offset int: used with limit to choose the start record
#@return list
#@todo Implement group for abstract LeObject childs
def select(self, target, field_list, filters = None,
relational_filters=None, order=None, group=None, limit=None,
offset=0):
if target.is_abstract():
# Reccursive calls for abstract LeObject child
#Reccursiv calls for abstract LeObject child
results = self.__act_on_abstract(target, filters,
relational_filters, self.select, field_list = field_list,
order = order, group = group, limit = limit)
# Here we may implement the group
# If sorted query we have to sort again
#Here we may implement the group
#If sorted query we have to sort again
if order is not None:
key_fun = functools.cmp_to_key(
self.__generate_lambda_cmp_order(order))
results = sorted(results, key=key_fun)
# If limit given apply limit again
#If limit given apply limit again
if offset > len(results):
results = list()
else:
@ -217,35 +192,35 @@ class MongoDbDatasource(AbstractDatasource):
return results
## @brief Deletes records according to given filters
# @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
##@brief Deletes records according to given filters
#@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):
if target.is_abstract():
logger.debug("Delete called on %s filtered by (%s,%s). Target is \
abstract, preparing reccursiv calls" % (target, filters, relational_filters))
# Deletion with abstract LeObject as target (reccursiv calls)
#Deletion with abstract LeObject as target (reccursiv calls)
return self.__act_on_abstract(target, filters,
relational_filters, self.delete)
logger.debug("Delete called on %s filtered by (%s,%s)." % (
target, filters, relational_filters))
# Non abstract beahavior
#Non abstract beahavior
mongo_filters = self.__process_filters(
target, filters, relational_filters)
# Updating backref before deletion
#Updating backref before deletion
self.__update_backref_filtered(target, filters, relational_filters,
None)
res = self.__collection(target).remove(mongo_filters)
return res['n']
## @brief updates records according to given filters
# @param target Emclass : class of the object to insert
# @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
##@brief updates records according to given filters
#@param target Emclass : class of the object to insert
#@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):
self._data_cast(upd_datas)
#fetching current datas state
@ -262,9 +237,9 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
upd_datas, old_datas_l)
return res
## @brief Designed to be called by backref update in order to avoid
# infinite updates between back references
# @see update()
##@brief Designed to be called by backref update in order to avoid
#infinite updates between back references
#@see update()
def __update_no_backref(self, target, filters, relational_filters,
upd_datas):
logger.debug("Update called on %s filtered by (%s,%s) with datas \
@ -310,18 +285,18 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
target.make_consistency(datas=new_datas)
return list(res.inserted_ids)
## @brief Update backref giving an action
# @param target leObject child class
# @param filters
# @param relational_filters,
# @param new_datas None | dict : optional new datas if None mean we are deleting
# @param old_datas_l None | list : if None fetch old datas from db (usefull
# when modifications are made on instance before updating backrefs)
# @return nothing (for the moment
##@brief Update backref giving an action
#@param target leObject child class
#@param filters
#@param relational_filters,
#@param new_datas None | dict : optional new datas if None mean we are deleting
#@param old_datas_l None | list : if None fetch old datas from db (usefull
#when modifications are made on instance before updating backrefs)
#@return nothing (for the moment
def __update_backref_filtered(self, target,
filters, relational_filters, new_datas = None, old_datas_l = None):
# Getting all the UID of the object that will be deleted in order
# to update back_references
#Getting all the UID of the object that will be deleted in order
#to update back_references
if old_datas_l is None:
mongo_filters = self.__process_filters(
target, filters, relational_filters)
@ -329,45 +304,46 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
mongo_filters)
old_datas_l = list(old_datas_l)
uidname = target.uid_fieldname()[0] # MULTIPLE UID BROKEN HERE
uidname = target.uid_fieldname()[0] #MULTIPLE UID BROKEN HERE
for old_datas in old_datas_l:
self.__update_backref(
target, old_datas[uidname], old_datas, new_datas)
## @brief Update back references of an object
# @ingroup plugin_mongodb_bref_op
##@brief Update back references of an object
#@ingroup plugin_mongodb_bref_op
#
# old_datas and new_datas arguments are set to None to indicate
# insertion or deletion. Calls examples :
# @par LeObject insert __update backref call
# <pre>
# Insert(datas):
# self.make_insert(datas)
# self.__update_backref(self.__class__, None, datas)
# </pre>
# @par LeObject delete __update backref call
# Delete()
# old_datas = self.datas()
# self.make_delete()
#  self.__update_backref(self.__class__, old_datas, None)
# @par LeObject update __update_backref call
# <pre>
# Update(new_datas):
# old_datas = self.datas()
# self.make_udpdate(new_datas)
#  self.__update_backref(self.__class__, old_datas, new_datas)
# </pre>
#old_datas and new_datas arguments are set to None to indicate
#insertion or deletion. Calls examples :
#@par LeObject insert __update backref call
#<pre>
#Insert(datas):
# self.make_insert(datas)
# self.__update_backref(self.__class__, None, datas)
#</pre>
#@par LeObject delete __update backref call
#Delete()
# old_datas = self.datas()
# self.make_delete()
#  self.__update_backref(self.__class__, old_datas, None)
#@par LeObject update __update_backref call
#<pre>
#Update(new_datas):
# old_datas = self.datas()
# self.make_udpdate(new_datas)
#  self.__update_backref(self.__class__, old_datas, new_datas)
#</pre>
#
# @param target LeObject child classa
# @param tuid mixed : The target UID (the value that will be inserted in
# back references)
# @param old_datas dict : datas state before update
# @param new_datas dict : datas state after the update process
#@param target LeObject child classa
#@param tuid mixed : The target UID (the value that will be inserted in
#back references)
#@param old_datas dict : datas state before update
#@param new_datas dict : datas state after the update process
#retun None
def __update_backref(self, target, tuid, old_datas, new_datas):
#upd_dict is the dict that will allow to run updates in an optimized
#way (or try to help doing it)
#
#Its structure looks like :
#It's struct looks like :
# { LeoCLASS : {
# UID1: (
# LeoINSTANCE,
@ -386,17 +362,17 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
and not new_datas[fname] is None
if (oldd and newd and old_datas[fname] == new_datas[fname])\
or not(oldd or newd):
# No changes or not concerned
#No changes or not concerned
continue
bref_cls = fdh.back_reference[0]
bref_fname = fdh.back_reference[1]
if not fdh.is_singlereference():
# fdh is a multiple reference. So the update preparation will be
# divided into two loops :
# - one loop for deleting old datas
# - one loop for inserting updated datas
#fdh is a multiple ref. So the update preparation will be
#divided into two loops :
#- one loop for deleting old datas
#- one loop for inserting updated datas
#
# Preparing the list of values to delete or to add
#Preparing the list of values to delete or to add
if newd and oldd:
old_values = old_datas[fname]
new_values = new_datas[fname]
@ -412,29 +388,29 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
elif not oldd and newd:
to_del = []
to_add = new_datas[fname]
# Calling __back_ref_upd_one_value() with good arguments
#Calling __back_ref_upd_one_value() with good arguments
for vtype, vlist in [('old',to_del), ('new', to_add)]:
for value in vlist:
# fetching backref infos
#fetching backref infos
bref_infos = self.__bref_get_check(
bref_cls, value, bref_fname)
# preparing the upd_dict
#preparing the upd_dict
upd_dict = self.__update_backref_upd_dict_prepare(
upd_dict, bref_infos, bref_fname, value)
# preparing updated bref_infos
#preparing updated bref_infos
bref_cls, bref_leo, bref_dh, bref_value = bref_infos
bref_infos = (bref_cls, bref_leo, bref_dh,
upd_dict[bref_cls][value][1][bref_fname])
vdict = {vtype: value}
# fetch and store updated value
#fetch and store updated value
new_bref_val = self.__back_ref_upd_one_value(
fname, fdh, tuid, bref_infos, **vdict)
upd_dict[bref_cls][value][1][bref_fname] = new_bref_val
else:
# fdh is a single ref so the process is simpler, we do not have
# to loop and we may do an update in only one
# __back_ref_upd_one_value() call by giving both old and new
# value
#fdh is a single ref so the process is simpler, we do not have
#to loop and we may do an update in only one
#__back_ref_upd_one_value() call by giving both old and new
#value
vdict = {}
if oldd:
vdict['old'] = old_datas[fname]
@ -443,38 +419,39 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
vdict['new'] = new_datas[fname]
if not oldd:
uid_val = vdict['new']
# Fetching back ref infos
#Fetching back ref infos
bref_infos = self.__bref_get_check(
bref_cls, uid_val, bref_fname)
# prepare the upd_dict
#prepare the upd_dict
upd_dict = self.__update_backref_upd_dict_prepare(
upd_dict, bref_infos, bref_fname, uid_val)
# forging update bref_infos
#forging update bref_infos
bref_cls, bref_leo, bref_dh, bref_value = bref_infos
bref_infos = (bref_cls, bref_leo, bref_dh,
upd_dict[bref_cls][uid_val][1][bref_fname])
# fetch and store updated value
#fetche and store updated value
new_bref_val = self.__back_ref_upd_one_value(
fname, fdh, tuid, bref_infos, **vdict)
upd_dict[bref_cls][uid_val][1][bref_fname] = new_bref_val
# Now we've got our upd_dict ready.
# running the updates
#Now we've got our upd_dict ready.
#running the updates
for bref_cls, uid_dict in upd_dict.items():
for uidval, (leo, datas) in uid_dict.items():
# MULTIPLE UID BROKEN 2 LINES BELOW
#MULTIPLE UID BROKEN 2 LINES BELOW
self.__update_no_backref(
leo.__class__, [(leo.uid_fieldname()[0], '=', uidval)],
[], datas)
## @brief Utility function designed to handle the upd_dict of __update_backref()
##@brief Utility function designed to handle the upd_dict of
#__update_backref()
#
# Basically checks if a key exists at some level, if not create it with
# the good default value (in most case dict())
# @param upd_dict dict : in & out args modified by reference
# @param bref_infos tuple : as returned by __bref_get_check()
# @param bref_fname str : name of the field in referenced class
# @param uid_val mixed : the UID of the referenced object
# @return the updated version of upd_dict
#Basically checks if a key exists at some level, if not create it with
#the good default value (in most case dict())
#@param upd_dict dict : in & out args modified by reference
#@param bref_infos tuple : as returned by __bref_get_check()
#@param bref_fname str : name of the field in referenced class
#@param uid_val mixed : the UID of the referenced object
#@return the updated version of upd_dict
@staticmethod
def __update_backref_upd_dict_prepare(upd_dict,bref_infos, bref_fname,
uid_val):
@ -488,14 +465,14 @@ abstract, preparing reccursiv calls" % (target, filters, relational_filters))
return upd_dict
## @brief Prepare a one value back reference update
# @param fname str : the source Reference field name
# @param fdh DataHandler : the source Reference DataHandler
# @param tuid mixed : the uid of the Leo that make reference to the backref
# @param bref_infos tuple : as returned by __bref_get_check() method
# @param old mixed : (optional **values) the old value
# @param new mixed : (optional **values) the new value
# @return the new back reference field value
##@brief Prepare a one value back reference update
#@param fname str : the source Reference field name
#@param fdh DataHandler : the source Reference DataHandler
#@param tuid mixed : the uid of the Leo that make reference to the backref
#@param bref_infos tuple : as returned by __bref_get_check() method
#@param old mixed : (optional **values) the old value
#@param new mixed : (optional **values) the new value
#@return the new back reference field value
def __back_ref_upd_one_value(self, fname, fdh, tuid, bref_infos, **values):
bref_cls, bref_leo, bref_dh, bref_val = bref_infos
oldd = 'old' in values
@ -510,7 +487,7 @@ delete in this back reference update was not found in the back referenced \
object : %s. Value was : '%s'" % (bref_leo, tuid))
return bref_val
elif oldd and not newdd:
# deletion
#deletion
old_value = values['old']
if tuid not in bref_val:
raise MongoDbConsistencyError("The value we want to \
@ -534,7 +511,7 @@ object : %s. Value was : '%s'" % (bref_leo, tuid))
else:
bref_val.append(tuid)
else:
# Single value backref
#Single value backref
if oldd and newdd:
if bref_val != tuid:
raise MongoDbConsistencyError("The backreference doesn't \
@ -542,7 +519,7 @@ have expected value. Expected was %s but found %s in %s" % (
tuid, bref_val, bref_leo))
return bref_val
elif oldd and not newdd:
# deletion
#deletion
if not hasattr(bref_dh, "default"):
raise MongoDbConsistencyError("Unable to delete a \
value for a back reference update. The concerned field don't have a default \
@ -552,14 +529,17 @@ value : in %s field %s" % (bref_leo,fname))
bref_val = tuid
return bref_val
## @brief Fetch back reference informations
# @warning thank's to __update_backref_act() this method is useless
# @param bref_cls LeObject child class : __back_reference[0]
# @param uidv mixed : UID value (the content of the reference field)
# @param bref_fname str : the name of the back_reference field
# @return tuple(bref_class, bref_LeObect_instance, bref_datahandler, bref_value)
# @throw MongoDbConsistencyError when LeObject instance not found given uidv
# @throw LodelFatalError if the back reference field is not a Reference subclass (major failure)
##@brief Fetch back reference informations
#@warning thank's to __update_backref_act() this method is useless
#@param bref_cls LeObject child class : __back_reference[0]
#@param uidv mixed : UID value (the content of the reference field)
#@param bref_fname str : the name of the back_reference field
#@return tuple(bref_class, bref_LeObect_instance, bref_datahandler,
#bref_value)
#@throw MongoDbConsistencyError when LeObject instance not found given
#uidv
#@throw LodelFatalError if the back reference field is not a Reference
#subclass (major failure)
def __bref_get_check(self, bref_cls, uidv, bref_fname):
bref_leo = bref_cls.get_from_uid(uidv)
if bref_leo is None:
@ -572,17 +552,17 @@ is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
bref_val = bref_leo.data(bref_fname)
return (bref_leo.__class__, bref_leo, bref_dh, bref_val)
## @brief Act on abstract LeObject child
##@brief Act on abstract LeObject child
#
# This method is designed to be called by insert, select and delete method
# when they encounter an abtract class
# @param target LeObject child class
# @param filters
# @param relational_filters
# @param act function : the caller method
# @param **kwargs other arguments
# @return sum of results (if it's an array it will result in a concat)
# @todo optimization implementing a cache for __bref_get_check()
#This method is designed to be called by insert, select and delete method
#when they encounter an abtract class
#@param target LeObject child class
#@param filters
#@param relational_filters
#@param act function : the caller method
#@param **kwargs other arguments
#@return sum of results (if it's an array it will result in a concat)
#@todo optimization implementing a cache for __bref_get_check()
def __act_on_abstract(self,
target, filters, relational_filters, act, **kwargs):
@ -597,7 +577,7 @@ on non abstract childs" % act.__name__)
for target_child in target_childs:
logger.debug(
"Abstract %s on %s" % (act.__name__, target_child.__name__))
# Add target_child to filter
#Add target_child to filter
new_filters = copy.copy(filters)
for i in range(len(filters)):
fname, op, val = filters[i]
@ -615,11 +595,12 @@ on non abstract childs" % act.__name__)
**kwargs)
return result
## @brief Connect to database
# @note this method avoid opening two times the same connection using MongoDbDatasource::_connections static attribute
# @param username str
# @param password str
# @param ro bool : If True the Datasource is for read only, else it will be write only
##@brief Connect to database
#@note this method avoid opening two times the same connection using
#MongoDbDatasource::_connections static attribute
#@param username str
#@param password str
#@param ro bool : If True the Datasource is for read only, else the
def __connect(self, username, password, db_name, ro):
conn_string = connection_string(
username = username, password = password,
@ -640,26 +621,31 @@ on non abstract childs" % act.__name__)
return self._connections[conn_h]['db'][self.__db_infos['db_name']]
## @brief Return a pymongo collection given a LeObject child class
# @param leobject LeObject child class (no instance)
# @return a pymongo.collection instance
##@brief Return a pymongo collection given a LeObject child class
#@param leobject LeObject child class (no instance)
#return a pymongo.collection instance
def __collection(self, leobject):
return self.database[object_collection_name(leobject)]
## @brief Perform subqueries implies by relational filters and append the
##@brief Perform subqueries implies by relational filters and append the
# result to existing filters
#
# The processing is divided in multiple steps :
# - determine (for each relational field of the target) every collection that are involved
# - generate subqueries for relational_filters that concerns a different collection than target collection filters
# - execute subqueries
# - transform subqueries results in filters
# - merge subqueries generated filters with existing filters
#The processing is divided in multiple steps :
# - determine (for each relational field of the target) every collection
#that are involved
# - generate subqueries for relational_filters that concerns a different
#collection than target collection
#filters
# - execute subqueries
# - transform subqueries results in filters
# - merge subqueries generated filters with existing filters
#
# @param target LeObject subclass (no instance) : Target class
# @param filters list : List of tuple(FIELDNAME, OP, VALUE)
# @param relational_filters : same composition thant filters except that FIELD is represented by a tuple(FIELDNAME, {CLASS1:RFIELD1, CLASS2:RFIELD2})
# @return a list of pymongo filters ( dict {FIELD:{OPERATOR:VALUE}} )
#@param target LeObject subclass (no instance) : Target class
#@param filters list : List of tuple(FIELDNAME, OP, VALUE)
#@param relational_filters : same composition thant filters except that
# FIELD is represented by a tuple(FIELDNAME, {CLASS1:RFIELD1,
# CLASS2:RFIELD2})
#@return a list of pymongo filters ( dict {FIELD:{OPERATOR:VALUE}} )
def __process_filters(self,target, filters, relational_filters):
# Simple filters lodel2 -> pymongo converting
res = self.__filters2mongo(filters, target)
@ -702,19 +688,21 @@ on non abstract childs" % act.__name__)
logger.debug("End of subquery execution")
return res
## @brief Generate subqueries from rfilters tree
##@brief Generate subqueries from rfilters tree
#
# Returned struct organization :
# - 1st level keys : relational field name of target
# - 2nd level keys : referenced leobject
# - 3th level values : pymongo filters (dict)
#Returned struct organization :
# - 1st level keys : relational field name of target
# - 2nd level keys : referenced leobject
# - 3th level values : pymongo filters (dict)
#
# @note The only caller of this method is __process_filters
# @warning No return value, the rfilters arguement is modified by reference
#@note The only caller of this method is __process_filters
#@warning No return value, the rfilters arguement is modified by
#reference
#
# @param target LeObject subclass (no instance) : Target class
# @param rfilters dict : A struct as returned by MongoDbDatasource.__prepare_relational_filters()
# @return None, the rfilters argument is modified by reference
#@param target LeObject subclass (no instance) : Target class
#@param rfilters dict : A struct as returned by
#MongoDbDatasource.__prepare_relational_filters()
#@return None, the rfilters argument is modified by reference
@classmethod
def __subqueries_from_relational_filters(cls, target, rfilters):
for fname in rfilters:
@ -727,20 +715,21 @@ on non abstract childs" % act.__name__)
rfilters[fname][leobject][rfield], target.field(fname))
rfilters[fname][leobject][rfield] = mongofilters
## @brief Generate a tree from relational_filters
##@brief Generate a tree from relational_filters
#
# The generated struct is a dict with :
# - 1st level keys : relational field name of target
# - 2nd level keys : referenced leobject
# - 3th level keys : referenced field in referenced class
# - 4th level values : list of tuple(op, value)
#The generated struct is a dict with :
# - 1st level keys : relational field name of target
# - 2nd level keys : referenced leobject
# - 3th level keys : referenced field in referenced class
# - 4th level values : list of tuple(op, value)
#
# @note The only caller of this method is __process_filters
# @warning An assertion is done : if two leobject are stored in the same collection they share the same uid
#@note The only caller of this method is __process_filters
#@warning An assertion is done : if two leobject are stored in the same
#collection they share the same uid
#
# @param target LeObject subclass (no instance) : Target class
# @param relational_filters : same composition thant filters except that
# @return a struct as described above
#@param target LeObject subclass (no instance) : Target class
#@param relational_filters : same composition thant filters except that
#@return a struct as described above
@classmethod
def __prepare_relational_filters(cls, target, relational_filters):
# We are going to regroup relationnal filters by reference field
@ -772,9 +761,9 @@ on non abstract childs" % act.__name__)
rfilters[fname][repr_leo][rfield].append((op, value))
return rfilters
## @brief Convert lodel2 filters to pymongo conditions
# @param filters list : list of lodel filters
# @return dict representing pymongo conditions
##@brief Convert lodel2 filters to pymongo conditions
#@param filters list : list of lodel filters
#@return dict representing pymongo conditions
@classmethod
def __filters2mongo(cls, filters, target):
res = dict()
@ -808,13 +797,12 @@ by an equality filter")
return res
## @brief Convert lodel2 operator and value to pymongo struct
##@brief Convert lodel2 operator and value to pymongo struct
#
# Convertion is done using MongoDbDatasource::lodel2mongo_op_map
# @param op str : take value in LeFilteredQuery::_query_operators
# @param value mixed : the value
# @param dhdl
# @return a tuple(mongo_op, mongo_value)
#Convertion is done using MongoDbDatasource::lodel2mongo_op_map
#@param op str : take value in LeFilteredQuery::_query_operators
#@param value mixed : the value
#@return a tuple(mongo_op, mongo_value)
@classmethod
def __op_value_conv(cls, op, value, dhdl):
if op not in cls.lodel2mongo_op_map:
@ -822,8 +810,8 @@ by an equality filter")
raise MongoDbDataSourceError(msg)
mongop = cls.lodel2mongo_op_map[op]
mongoval = value
# Converting lodel2 wildcarded string into a case insensitive
# mongodb re
#Converting lodel2 wildcarded string into a case insensitive
#mongodb re
if mongop in cls.mongo_op_re:
if value.startswith('(') and value.endswith(')'):
if (dhdl.cast_type is not None):
@ -843,10 +831,8 @@ by an equality filter")
mongoval = {'$regex': mongoval, '$options': 'i'}
return (op, mongoval)
## @brief Convert a list of tuple(OP, VALUE) into a pymongo filter dict
# @param op_value_list list
# @param dhdl
# @return a dict with mongo op as key and value as value...
##@brief Convert a list of tuple(OP, VALUE) into a pymongo filter dict
#@return a dict with mongo op as key and value as value...
@classmethod
def __op_value_listconv(cls, op_value_list, dhdl):
result = dict()

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelException', 'LodelExceptions',

View file

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.plugin': ['LodelHook']})

View file

@ -1,23 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
import datetime
from lodel.context import LodelContext

View file

@ -1,22 +1,4 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
import pymongo
from pymongo import MongoClient

View file

@ -1,22 +1,21 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
##@brief
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#@par Notes on FS organisation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#The plan is as follow :
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#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)
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from lodel.context import LodelContext, ContextError
try:

View file

@ -1,54 +1,20 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

@ -1,23 +1,3 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
[lodel2.logging.templog]
level = INFO
filename = /tmp/log

View file

@ -1,33 +1,31 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- coding: utf-8 -*-
import os
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:
@ -35,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,37 +1,6 @@
#
# This file is part of Lodel 2 (https://github.com/OpenEdition)
#
# Copyright (C) 2015-2017 Cléo UMS-3287
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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:
@ -39,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())

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