mirror of
https://github.com/yweber/lodel2.git
synced 2026-09-14 13:50:28 +02:00
Compare commits
114 commits
master
...
lodelsites
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a03f6f58ec | ||
|
|
745446f6e3 | ||
|
|
366f56059f | ||
|
|
a9c457d38f | ||
|
|
71213ef58e | ||
|
|
6d8d3fd021 | ||
|
|
3c41d40072 | ||
|
|
e17c4f4468 | ||
|
|
ac75d1cd71 | ||
|
|
56cd806ceb | ||
|
|
bdb7007525 | ||
|
|
ff1baa4467 | ||
|
|
10332f3f87 | ||
|
|
f1eb6ac41a | ||
|
|
f620b4e31b | ||
|
|
82a6ab95c3 | ||
|
|
773f6cdd06 | ||
|
|
ceee53c5bb | ||
|
|
ffd4ef21d5 | ||
|
|
3e3b4740da | ||
|
|
286e3ffe10 | ||
|
|
97b59379f4 | ||
|
|
689d6556c3 | ||
|
|
a567abc187 | ||
|
|
b5f9f97031 | ||
|
|
4cd5be0147 | ||
|
|
2f6cdd279d | ||
|
|
47fa14e566 | ||
|
|
c3aa0aa85c | ||
|
|
cad6962fea | ||
|
|
236861083f | ||
|
|
5467df8184 | ||
|
|
95305a88ba | ||
|
|
d3b88c06b2 | ||
|
|
8006bfb080 | ||
|
|
63f6143003 | ||
|
|
f2a8578981 | ||
|
|
ee6db4f895 | ||
|
|
6e4942c6a9 | ||
|
|
f9ade94a06 | ||
|
|
e877adf9c9 | ||
|
|
c4b37e151a | ||
|
|
52c8be3398 | ||
|
|
373772cf01 | ||
|
|
5eef806aa4 | ||
|
|
9a011fc615 | ||
|
|
4135760ee4 | ||
|
|
ccbb05f5d0 | ||
|
|
3e8b6bc30d | ||
|
|
3bff03424c | ||
|
|
6cb55301cc | ||
|
|
e10d8eb3bc | ||
|
|
83b54cd523 | ||
|
|
5aa1ac55cf | ||
|
|
afea7e74e2 | ||
|
|
4052440dab | ||
|
|
46626b8830 | ||
|
|
0fc3503a12 | ||
|
|
e2c39e7b96 | ||
|
|
87de9d653e | ||
|
|
e640ff96e7 | ||
|
|
cf8a1f488f | ||
|
|
98c72f5d9d | ||
|
|
1e15deaec3 | ||
|
|
26f586ad84 | ||
|
|
15e97bf2bd | ||
|
|
57c21f7f16 | ||
|
|
97ab8b8ea3 | ||
|
|
7d221b499e | ||
|
|
6c721a702c | ||
|
|
e68312098c | ||
|
|
2bd1d3cf48 | ||
|
|
93b713edf0 | ||
|
|
a7874c34c8 | ||
|
|
cb236c6450 | ||
|
|
f4386d9b83 | ||
|
|
e621f4adc5 | ||
|
|
1ea521c971 | ||
|
|
816d99adf6 | ||
|
|
8acd34dfdd | ||
|
|
4c27a6bf87 | ||
|
|
ffd4e1d2b3 | ||
|
|
c117f97c6e | ||
|
|
aadfdac24b | ||
|
|
6586fbebfc | ||
|
|
c6ec0aba2a | ||
|
|
68cd6a0c7d | ||
|
|
ac1d704791 | ||
|
|
0b337fd4b5 | ||
|
|
6953362a6b | ||
|
|
6c642e3a41 | ||
|
|
d3bf43f3ff | ||
|
|
9df0443a8b | ||
|
|
d8b95da6f8 | ||
|
|
738fff7494 | ||
|
|
f2394ba7e1 | ||
|
|
3ede546be4 | ||
|
|
d5cf4cf668 | ||
|
|
f6001dbd20 | ||
|
|
d0ff939544 | ||
|
|
470dbfbca0 | ||
|
|
1c3edfba98 | ||
|
|
3619f66a48 | ||
|
|
bc9a0a0e6b | ||
|
|
d202a55773 | ||
|
|
a33f1daaeb | ||
|
|
695ce80fc5 | ||
|
|
8a8a624cb7 | ||
|
|
a3e048dcd3 | ||
|
|
a3eb448093 | ||
|
|
df2c6810a6 | ||
|
|
819e9f62cf | ||
|
|
5f304cd64e | ||
|
|
94e7cf412a |
342 changed files with 3860 additions and 12786 deletions
141
COPYING
141
COPYING
|
|
@ -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>.
|
||||
|
|
|
|||
17
ChangeLog
17
ChangeLog
|
|
@ -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/>.
|
||||
#
|
||||
661
GNU-AGPL-3.0.txt
661
GNU-AGPL-3.0.txt
|
|
@ -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/>.
|
||||
651
LICENSE.md
651
LICENSE.md
|
|
@ -1,651 +0,0 @@
|
|||
GNU Affero General Public License
|
||||
=================================
|
||||
|
||||
_Version 3, 19 November 2007_
|
||||
_Copyright © 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/>>.
|
||||
2
Makefile-common.am.inc
Normal file
2
Makefile-common.am.inc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
lodel2_multisite_data_dirname=sites_data
|
||||
lodel2_multisite_ctx_dirname=.sites_contexts/lodelsites
|
||||
31
Makefile.am
31
Makefile.am
|
|
@ -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
|
||||
|
|
|
|||
19
bootstrap.sh
19
bootstrap.sh
|
|
@ -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
|
||||
|
|
|
|||
26
configure.ac
26
configure.ac
|
|
@ -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])
|
||||
|
|
|
|||
BIN
doc/img/dia/auth_client_user_session.dia
Normal file
BIN
doc/img/dia/auth_client_user_session.dia
Normal file
Binary file not shown.
BIN
doc/img/dia/authorizations_handling.dia
Normal file
BIN
doc/img/dia/authorizations_handling.dia
Normal file
Binary file not shown.
BIN
doc/img/dia/session_creation.dia
Normal file
BIN
doc/img/dia/session_creation.dia
Normal file
Binary file not shown.
14
doc/img/graphviz/Makefile
Normal file
14
doc/img/graphviz/Makefile
Normal 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
|
||||
BIN
doc/img/openedition_logo.png
Normal file
BIN
doc/img/openedition_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 923 B |
|
|
@ -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
|
||||
|
||||
|
|
|
|||
20
em_test.py
20
em_test.py
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 :
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
281
lodel/bootstrap.py
Normal 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'])
|
||||
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
212
lodel/context.py
212
lodel/context.py
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
lodel/editorial_model/translator/externaly_handled.py
Normal file
13
lodel/editorial_model/translator/externaly_handled.py
Normal 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')
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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(), {
|
||||
|
|
|
|||
|
|
@ -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(), {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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} \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(), {
|
||||
|
|
|
|||
|
|
@ -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(), {
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 )
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 : ")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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']})
|
||||
|
|
|
|||
|
|
@ -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')),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
49
lodel/plugins/lodelsite_datasource/__init__.py
Normal file
49
lodel/plugins/lodelsite_datasource/__init__.py
Normal 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
|
||||
|
||||
|
||||
5
lodel/plugins/lodelsite_datasource/exceptions.py
Normal file
5
lodel/plugins/lodelsite_datasource/exceptions.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class LodelSiteDatasourceError(Exception):
|
||||
pass
|
||||
|
||||
class LodelSiteDatasourceInconsistency(LodelSiteDatasourceError):
|
||||
pass
|
||||
218
lodel/plugins/lodelsite_datasource/fs_utils.py
Normal file
218
lodel/plugins/lodelsite_datasource/fs_utils.py
Normal 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)
|
||||
164
lodel/plugins/lodelsite_datasource/main.py
Normal file
164
lodel/plugins/lodelsite_datasource/main.py
Normal 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
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[lodel2]
|
||||
debug=False
|
||||
|
||||
[lodel2.logging.stderr]
|
||||
level = INFO
|
||||
filename = -
|
||||
context = True
|
||||
|
||||
|
||||
42
lodel/plugins/lodelsite_datasource/sites_utils.py
Normal file
42
lodel/plugins/lodelsite_datasource/sites_utils.py
Normal 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))
|
||||
|
||||
|
||||
22
lodel/plugins/lodelsite_datasource/specs.py
Normal file
22
lodel/plugins/lodelsite_datasource/specs.py
Normal 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')
|
||||
|
|
@ -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....
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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')),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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']})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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')),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
122
lodel/plugins/multisite/loader_utils.py
Normal file
122
lodel/plugins/multisite/loader_utils.py
Normal 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)
|
||||
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue