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

Compare commits

..

No commits in common. "master" and "datasource_fieldtypes" have entirely different histories.

474 changed files with 18886 additions and 33072 deletions

2
.gitignore vendored
View file

@ -6,5 +6,3 @@ settings_local.py
doc/html
doc/*.sqlite3.db
.*.swp
Makefile
Makefile.in

View file

@ -1,5 +0,0 @@
Arnaud Corider
Roland Haroutiounian
Mathieu Orban
Hélène Prieto
Yann Weber

661
COPYING
View file

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

View file

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

View file

@ -0,0 +1,86 @@
#-*- coding: utf-8 -*-
## @package DataSource.MySQL.fieldtypes
#
# Defines usefull function to handle fieldtype in MySQL datasource
import EditorialModel
from EditorialModel import fieldtypes as ed_fieldtypes
import EditorialModel.fieldtypes.generic
from EditorialModel.fieldtypes.generic import SingleValueFieldType, MultiValueFieldType, ReferenceFieldType
import EditorialModel.fieldtypes.integer
import EditorialModel.fieldtypes.char
import EditorialModel.fieldtypes.bool
import EditorialModel.fieldtypes.text
import EditorialModel.fieldtypes.rel2type
import EditorialModel.fieldtypes.leo
## @brief Returns column specs from fieldtype
# @param emfieldtype EmFieldType : An EmFieldType insance
# @todo escape default value
def singlevaluefieldtype_db_init_specs(emfieldtype, noauto_inc = False):
colspec = ''
if not emfieldtype.nullable:
colspec = 'NOT NULL'
if hasattr(emfieldtype, 'default'):
colspec += ' DEFAULT '
if emfieldtype.default is None:
colspec += 'NULL '
else:
colspec += emfieldtype.default # ESCAPE VALUE HERE !!!!
if emfieldtype.name == 'pk' and not noauto_inc:
colspec += ' AUTO_INCREMENT'
return colspec
## @brief Given a fieldtype return instructions to be executed by the migration handler
#
# The returned value in a tuple of len = 3
#
# The first items gives instruction type. Possible values are :
# - 'column' : add a column
# - the second tuple item is the SQL type of the new column
# - the third tuple item is the SQL specs (constraints like default value, nullable, unique , auto_increment etc.)
# - 'table' : add a column in another table and make a fk to the current table
# - the second tuple item is a tuple(key_name, key_value)
# - the third tuple item is a tuple(column_type, column_spec)
# @param fieldtype GenericFieldType : A FieldType instance
# @return a tuple (instruction_type, infos)
def fieldtype_db_init(fieldtype, noauto_inc = False):
if isinstance(fieldtype, EditorialModel.fieldtypes.rel2type.EmFieldType):
return (None, None, None)
elif isinstance(fieldtype, SingleValueFieldType):
res = [ 'column', None, singlevaluefieldtype_db_init_specs(fieldtype, noauto_inc) ]
# We will create a column
if isinstance(fieldtype, EditorialModel.fieldtypes.integer.EmFieldType):
res[1] = 'INT'
elif isinstance(fieldtype, EditorialModel.fieldtypes.char.EmFieldType):
res[1] = 'VARCHAR(%d)' % fieldtype.max_length
elif isinstance(fieldtype, EditorialModel.fieldtypes.text.EmFieldType):
res[1] = 'TEXT'
elif isinstance(fieldtype, EditorialModel.fieldtypes.bool.EmFieldType):
res[1] = 'BOOL'
elif isinstance(fieldtype, EditorialModel.fieldtypes.datetime.EmFieldType):
res[1] = 'DATETIME'
elif isinstance(fieldtype, EditorialModel.fieldtypes.generic.ReferenceFieldType):
res[1] = 'INT'
else:
raise RuntimeError("Unsupported fieldtype : ", fieldtype)
elif isinstance(fieldtype, MultiValueFieldType):
res = [ 'table', None, None ]
res[1] = (fieldtype.keyname, fieldtype.key_fieldtype)
res[2] = fieldtype.value_fieldtype
else:
raise NotImplementedError("Not yet implemented")
return tuple(res)
## @brief Cast a value given a fieldtype
# @param fieldtype EmFieldType : a fieldtype instance
# @param value : mixed value
# @return The value in a way usable by the datasource
def fieldtype_cast(fieldtype, value):
if isinstance(fieldtype, EditorialModel.fieldtypes.leo.EmFieldType):
return value.uidget()
return value

View file

@ -0,0 +1,417 @@
#-*- coding: utf-8 -*-
import pymysql
import copy
import leapi
from leapi.leobject import REL_SUB, REL_SUP
from leapi.lecrud import _LeCrud, REL_SUP, REL_SUB
from mosql.db import Database, all_to_dicts, one_to_dict
from mosql.query import select, insert, update, delete, join, left_join
from mosql.util import raw, or_
import mosql.mysql
from DataSource.dummy.leapidatasource import DummyDatasource
from DataSource.MySQL import utils
from EditorialModel.classtypes import EmNature
from EditorialModel.fieldtypes.generic import MultiValueFieldType
from Lodel.settings import Settings
from .fieldtypes import fieldtype_cast
## MySQL DataSource for LeObject
class LeDataSourceSQL(DummyDatasource):
RELATIONS_POSITIONS_FIELDS = {REL_SUP: 'superior_id', REL_SUB: 'subordinate_id'}
def __init__(self, module=pymysql, conn_args=None):
super(LeDataSourceSQL, self).__init__()
self.module = module
if conn_args is None:
conn_args = copy.copy(Settings.get('datasource')['default'])
self.module = conn_args['module']
del conn_args['module']
self.connection = Database(self.module, **conn_args)
## @brief select lodel editorial components using given filters
# @param target_cls LeCrud(class): The component class concerned by the select (a LeCrud child class (not instance !) )
# @param field_list list: List of field to fetch
# @param filters list: List of filters (see @ref lecrud_filters)
# @param rel_filters list: List of relational filters (see @ref lecrud_filters)
# @return a list of LeCrud child classes
# @todo this only works with LeObject.get(), LeClass.get() and LeType.get()
# @todo for speed get rid of all_to_dicts
# @todo filters: all use cases are not implemented
# @todo group: mosql does not permit direction in group_by clause, it should, so for now we don't use direction in group clause
def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=None, instanciate=True):
joins = []
mandatory_fields = []
class_table = False
# it is a LeObject, query only on main table
if target_cls.__name__ == 'LeObject':
main_table = utils.common_tables['object']
fields = [(main_table, target_cls.fieldlist())]
# it is a LeType or a LeClass, query on main table left join class table on lodel_id
elif target_cls.is_letype() or target_cls.is_leclass():
# find main table
main_table = utils.common_tables['object']
main_class = target_cls.leo_class()
# find class table
class_table = utils.object_table_name(main_class.__name__)
class_fk = main_class.uidname()
main_lodel_id = utils.column_prefix(main_table, main_class.uidname())
class_lodel_id = utils.column_prefix(class_table, main_class.uidname())
# do the join
joins = [left_join(class_table, {main_lodel_id:class_lodel_id})]
mandatory_fields = [class_fk, 'type_id']
fields = [(main_table, target_cls.name2class('LeObject').fieldlist()), (class_table, main_class.fieldlist())]
elif target_cls.is_lehierarch():
main_table = utils.common_tables['relation']
fields = [(main_table, target_cls.name2class('LeRelation').fieldlist())]
elif target_cls.is_lerel2type():
# find main table
main_table = utils.common_tables['relation']
# find relational table
class_table = utils.r2t_table_name(target_cls._superior_cls.__name__, target_cls._subordinate_cls.__name__)
class_fk = target_cls.name2class('LeRelation').uidname()
main_relation_id = utils.column_prefix(main_table, class_fk)
class_relation_id = utils.column_prefix(class_table, class_fk)
# do the joins
lodel_id = target_cls.name2class('LeObject').uidname()
joins = [
left_join(class_table, {main_relation_id:class_relation_id}),
# join with the object table to retrieve class and type of superior and subordinate
left_join(utils.common_tables['object'] + ' as sup_obj', {'sup_obj.'+lodel_id:target_cls._superior_field_name}),
left_join(utils.common_tables['object'] + ' as sub_obj', {'sub_obj.'+lodel_id:target_cls._subordinate_field_name})
]
mandatory_fields = [class_fk, 'class_id', 'type_id']
fields = [
(main_table, target_cls.name2class('LeRelation').fieldlist()),
(class_table, target_cls.fieldlist()),
('sup_obj', ['class_id']),
('sub_obj', ['type_id'])
]
else:
raise AttributeError("Target class '%s' in get() is not a Lodel Editorial Object !" % target_cls)
# extract mutltivalued field from field_list
if class_table:
multivalue_fields = self.get_multivalue_fields(target_cls)
for field_names in multivalue_fields.values():
for field_name in field_names:
try:
field_list.remove(field_name)
except ValueError:
pass # field_name is not in field_list
else:
multivalue_fields = False
# add mandatory fields to field_list
for mandatory_field in mandatory_fields:
if mandatory_field not in field_list:
field_list.append(mandatory_field)
# prefix column name in fields list
prefixed_field_list = [utils.find_prefix(name, fields) for name in field_list]
kwargs = {}
if group:
kwargs['group_by'] = (utils.find_prefix(column, fields) for column, direction in group)
if order:
kwargs['order_by'] = (utils.find_prefix(column, fields) + ' ' + direction for column, direction in order)
if limit:
kwargs['limit'] = limit
if offset:
kwargs['offset'] = offset
# relational filters
# @todo this only works with hierarchy relations
if rel_filters:
le_relation = target_cls.name2class('LeRelation')
rel_cpt = 0
for rel_filter in rel_filters:
rel_cpt += 1
rel_name = 'rel' + str(rel_cpt)
name, op, value = rel_filter
direction, nature = name
if direction == REL_SUP:
join_column, filter_column = (le_relation._subordinate_field_name, le_relation._superior_field_name)
else:
join_column, filter_column = (le_relation._superior_field_name, le_relation._subordinate_field_name)
rel_join = left_join(utils.common_tables['relation'] + ' as ' + rel_name, {utils.column_prefix(main_table, main_class.uidname()):utils.column_prefix(rel_name, join_column)})
filters.append((utils.column_prefix(rel_name, 'nature'), '=', nature))
filters.append((utils.column_prefix(rel_name, filter_column), op, value))
joins.append(rel_join)
# prefix filters'' column names, and prepare dict for mosql where {(fieldname, op): value}
# TODO: this will not work with special filters
wheres = {(utils.find_prefix(name, fields), op):fieldtype_cast(target_cls.fieldtypes()[name], value) for name,op,value in filters}
query = select(main_table, select=prefixed_field_list, where=wheres, joins=joins, **kwargs)
# Executing the query
cur = utils.query(self.connection, query)
results = all_to_dicts(cur)
#print(results)
# query multivalued tables, inject result in main result
if multivalue_fields:
for result in results:
for key_name, fields in multivalue_fields.items():
query_fields = [key_name]
query_fields.extend(fields)
table_name = utils.multivalue_table_name(class_table, key_name)
sql = select(table_name, select=query_fields, where={(class_fk, '='):result[class_fk]})
multi = {name:{} for name in fields}
cur = utils.query(self.connection, sql)
multi_results = all_to_dicts(cur)
for multi_result in multi_results:
for field in fields:
multi[field][multi_result[key_name]] = multi_result[field]
result.update(multi)
# instanciate each row to editorial components
if instanciate:
results = [target_cls.object_from_data(datas) for datas in results]
#print('results', results)
return results
## @brief delete lodel editorial components given filters
# @param target_cls LeCrud(class): The component class concerned by the delete (a LeCrud child class (not instance !) )
# @param uid int : A uniq ID to identify the object we want to delete
# @return the number of deleted components
def delete(self, target_cls, uid):
if target_cls.implements_leobject():
tablename = utils.common_tables['object']
elif target_cls.implements_lerelation():
tablename = utils.common_tables['relation']
else:
raise AttributeError("'%s' is not a LeObject nor a LeRelation, it's not possible to delete it")
uidname = target_cls.uidname()
sql = delete(tablename, ((uidname, uid),))
utils.query(self.connection, sql) #No way to check the result ?
return True
## @brief update ONE existing lodel editorial component
# @param target_cls LeCrud(class) : Instance of the object concerned by the update
# @param lodel_id : id of the component
# @param rel_filters list : List of relationnal filters (see @ref leobject_filters)
# @param **datas : Datas in kwargs
# @return the number of updated components
# @todo implement other filters than lodel_id
def update(self, target_cls, lodel_id, **datas):
# it is a LeType
if target_cls.is_letype():
# find main table and main table datas
main_table = utils.common_tables['object']
fk_name = target_cls.uidname()
main_datas = {fk_name: raw(fk_name)} # be sure to have one SET clause
main_fields = target_cls.name2class('LeObject').fieldlist()
class_table = utils.object_table_name(target_cls.leo_class().__name__)
elif target_cls.is_lerel2type():
main_table = utils.common_tables['relation']
le_relation = target_cls.name2class('LeRelation')
fk_name = le_relation.uidname()
main_datas = {fk_name: raw(fk_name)} # be sure to have one SET clause
main_fields = le_relation.fieldlist()
class_table = utils.r2t_table_name(target_cls._superior_cls.__name__, target_cls._subordinate_cls.__name__)
else:
raise AttributeError("'%s' is not a LeType nor a LeRelation, it's impossible to update it" % target_cls)
datas = { fname: fieldtype_cast(target_cls.fieldtypes()[fname], datas[fname]) for fname in datas }
for main_column_name in main_fields:
if main_column_name in datas:
main_datas[main_column_name] = datas[main_column_name]
del(datas[main_column_name])
# extract multivalued field from class_table datas
multivalued_datas = self.create_multivalued_datas(target_cls, datas)
where = {fk_name: lodel_id}
sql = update(main_table, where, main_datas)
utils.query(self.connection, sql)
# update on class table
if datas:
sql = update(class_table, where, datas)
utils.query(self.connection, sql)
# do multivalued insert
# first delete old values, then insert new ones
for key_name, lines in multivalued_datas.items():
table_name = utils.multivalue_table_name(class_table, key_name)
sql = delete(table_name, where)
utils.query(self.connection, sql)
for key_value, line_datas in lines.items():
line_datas[key_name] = key_value
line_datas[fk_name] = lodel_id
sql = insert(table_name, line_datas)
utils.query(self.connection, sql)
return True
## @brief inserts a new lodel editorial component
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param **datas : The datas to insert
# @return The inserted component's id
# @todo should work with LeType, LeClass, and Relations
def insert(self, target_cls, **datas):
class_table = False
# it is a LeType
if target_cls.is_letype():
main_table = utils.common_tables['object']
main_datas = {'class_id':target_cls.leo_class()._class_id, 'type_id':target_cls._type_id}
main_fields = target_cls.name2class('LeObject').fieldlist()
class_table = utils.object_table_name(target_cls.leo_class().__name__)
fk_name = target_cls.uidname()
# it is a hierarchy
elif target_cls.is_lehierarch():
main_table = utils.common_tables['relation']
main_datas = {
utils.column_name(target_cls._superior_field_name): datas[target_cls._superior_field_name].lodel_id,
utils.column_name(target_cls._subordinate_field_name): datas[target_cls._subordinate_field_name].lodel_id
}
main_fields = target_cls.name2class('LeRelation').fieldlist()
# it is a relation
elif target_cls.is_lerel2type():
main_table = utils.common_tables['relation']
main_datas = {
utils.column_name(target_cls._superior_field_name): datas[target_cls._superior_field_name].lodel_id,
utils.column_name(target_cls._subordinate_field_name): datas[target_cls._subordinate_field_name].lodel_id
}
main_fields = target_cls.name2class('LeRelation').fieldlist()
superior_class = datas['superior'].leo_class()
class_table = utils.r2t_table_name(superior_class.__name__, datas['subordinate'].__class__.__name__)
fk_name = superior_class.name2class('LeRelation').uidname()
else:
raise AttributeError("'%s' is not a LeType nor a LeRelation, it's impossible to insert it" % target_cls)
# cast datas
datas = { fname: fieldtype_cast(target_cls.fieldtypes()[fname], datas[fname]) for fname in datas }
# extract main table datas from datas
for main_column_name in main_fields:
if main_column_name in datas:
if main_column_name not in main_datas:
main_datas[main_column_name] = datas[main_column_name]
del(datas[main_column_name])
# extract multivalued field from class_table datas
if class_table:
multivalued_datas = self.create_multivalued_datas(target_cls, datas)
sql = insert(main_table, main_datas)
cur = utils.query(self.connection, sql)
lodel_id = cur.lastrowid
if class_table:
# insert in class_table
datas[fk_name] = lodel_id
sql = insert(class_table, datas)
utils.query(self.connection, sql)
# do multivalued inserts
for key_name, lines in multivalued_datas.items():
table_name = utils.multivalue_table_name(class_table, key_name)
for key_value, line_datas in lines.items():
line_datas[key_name] = key_value
line_datas[fk_name] = lodel_id
sql = insert(table_name, line_datas)
utils.query(self.connection, sql)
return lodel_id
# extract multivalued field from datas, prepare multivalued data list
def create_multivalued_datas(self, target_cls, datas):
multivalue_fields = self.get_multivalue_fields(target_cls)
if multivalue_fields:
# construct multivalued datas
multivalued_datas = {key:{} for key in multivalue_fields}
for key_name, names in multivalue_fields.items():
for field_name in names:
try:
for key, value in datas[field_name].items():
if key not in multivalued_datas[key_name]:
multivalued_datas[key_name][key] = {}
multivalued_datas[key_name][key][field_name] = value
del(datas[field_name])
except KeyError:
pass # field_name is not in datas
return multivalued_datas
else:
return {}
# return multivalue fields of a class
def get_multivalue_fields(self, target_cls):
multivalue_fields = {}
# scan fieldtypes to get mutltivalued field
for field_name, fieldtype in target_cls.fieldtypes(complete=False).items():
if isinstance(fieldtype, MultiValueFieldType):
if fieldtype.keyname in multivalue_fields:
multivalue_fields[fieldtype.keyname].append(field_name)
else:
multivalue_fields[fieldtype.keyname] = [field_name]
return multivalue_fields
## @brief insert multiple editorial component
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param datas_list list : A list of dict representing the datas to insert
# @return int the number of inserted component
def insert_multi(self, target_cls, datas_list):
res = list()
for data in datas_list:
res.append(self.insert(target_cls, data))
return len(res)
## @brief Sets a new rank on a relation
# @param le_relation LeRelation
# @param new_rank int: integer representing the absolute new rank
# @return True if success, False if failure
# TODO Conserver cette méthode dans le datasource du fait des requêtes SQL. Elle est appelée par le set_rank de LeRelation
def update_rank(self, le_relation, rank):
lesup = le_relation.id_sup
lesub = le_relation.id_sub
current_rank = le_relation.rank
relations = le_relation.__class__.get(query_filters=[('id_sup', '=', lesup)], order=[('rank', 'ASC')])
# relations = self.get_related(lesup, lesub.__class__, get_sub=True)
# insert the relation at the right position considering its new rank
our_relation = relations.pop(current_rank)
relations.insert(our_relation, rank)
# rebuild now the list of relations from the resorted list and recalculating the ranks
rdatas = [(attrs['relation_id'], new_rank+1) for new_rank, (sup, sub, attrs) in enumerate(relations)]
sql = insert(MySQL.relations_table_name, columns=(MySQL.relations_pkname, 'rank'), values=rdatas, on_duplicate_key_update={'rank', mosql.util.raw('VALUES(`rank`)')})
with self.connection as cur:
if cur.execute(sql) != 1:
return False
else:
return True

View file

@ -0,0 +1,649 @@
# -*- coding: utf-8 -*-
import copy
import pymysql
from Lodel.settings import Settings
import EditorialModel
import EditorialModel.classtypes
import EditorialModel.fieldtypes
import EditorialModel.fieldtypes.generic
from EditorialModel.fieldtypes.generic import MultiValueFieldType
from DataSource.MySQL import fieldtypes as fieldtypes_utils
from DataSource.MySQL import utils
from DataSource.dummy.migrationhandler import DummyMigrationHandler
# The global MH algorithm is as follow :
# A create_table(table_name, pk_name, pk_opt) method that create a table
# with one pk field
# An add_column(table_name, field_name, field_opt) method that add a column to a table
#
# The create_default_table method will call both methods to create the object and relation tables
#
# Supported operations :
# - EmClass creation
# - EmClass deletion
# - EmField creation
# - EmField deletion
# - rel2type attribute creation
# - rel2type attribute deletion
#
# Unsupported operations :
# - EmClass rename
# - EmField rename
# - rel2type field rename
# - rel2type attribute rename
# - EmFieldType changes
#
# @todo Unified datasources and migration handlers via utils functions
## @brief Modify a MySQL database given editorial model changes
class MysqlMigrationHandler(DummyMigrationHandler):
## @brief Construct a MysqlMigrationHandler
# @param module : sql module
# @param conn_args dict : A dict containing connection options
def __init__(self, module=pymysql, conn_args=None, db_engine='InnoDB', **kwargs):
# Database connection
self._dbmodule = module
if conn_args is None:
conn_args = copy.copy(Settings.get('datasource')['default'])
self._dbmodule = conn_args['module']
del conn_args['module']
self.db_conn = self._dbmodule.connect(**conn_args)
# Fetch options
mh_settings = Settings.migration_options
self.debug = kwargs['debug'] if 'debug' in kwargs else Settings.debug_sql
self.dryrun = kwargs['dryrun'] if 'dryrun' in kwargs else mh_settings['dryrun']
self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else mh_settings['foreign_keys']
self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_if_exists' in kwargs else mh_settings['drop_if_exists']
self.db_engine = db_engine
#Create default tables
self._create_default_tables(self.drop_if_exists)
## @brief Modify the db given an EM change
#
# @note Here we don't care about the relation parameter of _add_column() method because the
# only case in wich we want to add a field that is linked with the relation table is for rel2type
# attr creation. The relation parameter is set to True in the add_relationnal_field() method
# 
# @param em model : The EditorialModel.model object to provide the global context
# @param uid int : The uid of the change EmComponent
# @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
# @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
# @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
def register_change(self, em, uid, initial_state, new_state, engine=None):
if engine is None:
engine = self.db_engine
if isinstance(em.component(uid), EditorialModel.classes.EmClass):
if initial_state is None:
#EmClass creation
self.create_emclass_table(em, uid, engine)
elif new_state is None:
#EmClass deletion
self.delete_emclass_table(em, uid)
elif isinstance(em.component(uid), EditorialModel.fields.EmField):
emfield = em.component(uid)
if emfield.rel_field_id is None:
#non relationnal field
if initial_state is None:
#non relationnal EmField creation
if emfield.name not in EditorialModel.classtypes.common_fields.keys():
self.add_col_from_emfield(em, uid)
elif new_state is None:
#non relationnal EmField deletion
if emfield.name not in EditorialModel.classtypes.common_fields.keys():
self.delete_col_from_emfield(em, uid)
else:
#relationnal field
if initial_state is None:
#Rel2type attr creation
self.add_relationnal_field(em, uid)
elif new_state is None:
#Rel2type attr deletion
self.del_relationnal_field(em, uid)
## @brief dumdumdummy
# @note implemented to avoid the log message of EditorialModel.migrationhandler.dummy.DummyMigrationHandler
def register_model_state(self, em, state_hash):
pass
## @brief Add a relationnal field
# Add a rel2type attribute
# @note this function handles the table creation
# @param em Model : EditorialModel.model.Model instance
# @param rfuid int : Relationnal field uid
def add_relationnal_field(self, edmod, rfuid):
emfield = edmod.component(rfuid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
r2tf = edmod.component(emfield.rel_field_id)
tname = self._r2t2table_name(edmod, r2tf)
pkname, pkftype = self._relation_pk
#If not exists create a relational table
self._create_table(
tname,
pkname,
pkftype,
self.db_engine,
if_exists='nothing',
noauto_inc = True,
)
#Add a foreign key if wanted
if self.foreign_keys:
self._add_fk(tname, utils.common_tables['relation'], pkname, pkname)
#Add the column
self._add_column(tname, emfield.name, emfield.fieldtype_instance(), relation=True)
#Update table triggers
self._generate_triggers(tname, self._r2type2cols(edmod, r2tf))
## @brief Delete a rel2type attribute
#
# Delete a rel2type attribute
# @note this method handles the table deletion
# @param em Model : EditorialModel.model.Model instance
# @param rfuid int : Relationnal field uid
def del_relationnal_field(self, edmod, rfuid):
emfield = edmod.component(rfuid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
r2tf = edmod.component(emfield.rel_field_id)
tname = self._r2t2table_name(edmod, r2tf)
if len(self._r2type2cols(edmod, r2tf)) == 1:
#The table can be deleted (no more attribute for this rel2type)
self._query("""DROP TABLE {table_name}""".format(table_name=tname))
else:
self._del_column(tname, emfield.name)
#Update table triggers
self._generate_triggers(tname, self._r2type2cols(edmod, r2tf))
## @brief Given an EmField uid add a column to the corresponding table
# @param em Model : A Model instance
# @param uid int : An EmField uid
def add_col_from_emfield(self, edmod, uid):
emfield = edmod.component(uid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
emclass = emfield.em_class
tname = utils.object_table_name(emclass.name)
self._add_column(tname, emfield.name, emfield.fieldtype_instance())
# Refresh the table triggers
cols_l = self._class2cols(emclass)
self._generate_triggers(tname, cols_l)
## @brief Given a class uid create the coressponding table
# @param em Model : A Model instance
# @param uid int : An EmField uid
def create_emclass_table(self, edmod, uid, engine):
emclass = edmod.component(uid)
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The given uid is not an EmClass uid")
pkname, pktype = self._object_pk
table_name = utils.object_table_name(emclass.name)
self._create_table(
table_name,
pkname,
pktype,
engine=engine,
noauto_inc = True
)
if self.foreign_keys:
self._add_fk(table_name, utils.common_tables['object'], pkname, pkname)
## @brief Given an EmClass uid delete the corresponding table
# @param em Model : A Model instance
# @param uid int : An EmField uid
def delete_emclass_table(self, edmod, uid):
emclass = edmod.component(uid)
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The give uid is not an EmClass uid")
tname = utils.object_table_name(emclass.name)
# Delete the table triggers to prevent errors
self._generate_triggers(tname, dict())
tname = utils.escape_idname(tname)
self._query("""DROP TABLE {table_name};""".format(table_name=tname))
## @brief Given an EmField delete the corresponding column
# @param em Model : an @ref EditorialModel.model.Model instance
# @param uid int : an EmField uid
def delete_col_from_emfield(self, edmod, uid):
emfield = edmod.component(uid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
if isinstance(emfield.fieldtype_instance(), MultiValueFieldType):
return self._del_column_multivalue(emfield)
emclass = emfield.em_class
tname = utils.object_table_name(emclass.name)
# Delete the table triggers to prevent errors
self._generate_triggers(tname, dict())
self._del_column(tname, emfield.name)
# Refresh the table triggers
cols_l = self._class2cols(emclass)
self._generate_triggers(tname, cols_l)
## @brief Delete a column from a table
# @param tname str : The table name
# @param fname str : The column name
def _del_column(self, tname, fname):
tname = utils.escape_idname(tname)
fname = utils.escape_idname(fname)
self._query("""ALTER TABLE {table_name} DROP COLUMN {col_name};""".format(table_name=tname, col_name=fname))
## @brief Construct a table name given a rela2type EmField instance
# @param em Model : A Model instance
# @param emfield EmField : An EmField instance
# @return a table name
def _r2t2table_name(self, edmod, emfield):
emclass = emfield.em_class
emtype = edmod.component(emfield.rel_to_type_id)
return utils.r2t_table_name(emclass.name, emtype.name)
## @brief Generate a columns_fieldtype dict given a rel2type EmField
# @param em Model : an @ref EditorialModel.model.Model instance
# @param emfield EmField : and @ref EditorialModel.fields.EmField instance
def _r2type2cols(self, edmod, emfield):
return {f.name: f.fieldtype_instance() for f in edmod.components('EmField') if f.rel_field_id == emfield.uid}
## @brief Generate a columns_fieldtype dict given an EmClass
# @param emclass EmClass : An EmClass instance
# @return A dict with column name as key and EmFieldType instance as value
def _class2cols(self, emclass):
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The given uid is not an EmClass uid")
return {f.name: f.fieldtype_instance() for f in emclass.fields() if f.name not in EditorialModel.classtypes.common_fields.keys()}
## @brief Create object and relations tables
# @param drop_if_exist bool : If true drop tables if exists
def _create_default_tables(self, drop_if_exist=False):
if_exists = 'drop' if drop_if_exist else 'nothing'
#Object table
tname = utils.common_tables['object']
pk_name, pk_ftype = self._object_pk
self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
#Adding columns
cols = {fname: self._common_field_to_ftype(fname) for fname in EditorialModel.classtypes.common_fields}
for fname, ftype in cols.items():
if fname != pk_name:
self._add_column(tname, fname, ftype, relation=False)
#Creating triggers
self._generate_triggers(tname, cols)
object_tname = tname
#Relation table
tname = utils.common_tables['relation']
pk_name, pk_ftype = self._relation_pk
self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
#Adding columns
for fname, ftype in self._relation_cols.items():
self._add_column(tname, fname, ftype, relation=True)
#Creating triggers
self._generate_triggers(tname, self._relation_cols)
# Creating foreign keys between relation and object table
sup_cname, sub_cname = self.get_sup_and_sub_cols()
## @brief Returns the fieldname for superior and subordinate in relation table
# @return a tuple (superior_name, subordinate_name)
@classmethod
def get_sup_and_sub_cols(cls):
sup = None
sub = None
for fname, finfo in EditorialModel.classtypes.relations_common_fields.items():
if finfo['fieldtype'] == 'leo':
if finfo['superior']:
sup = fname
else:
sub = fname
return utils.column_name(sup), utils.column_name(sub)
## @brief Create a table with primary key
# @param table_name str : table name
# @param pk_name str | tuple : pk column name (give tuple for multi pk)
# @param pk_ftype fieldtype | tuple : pk fieldtype (give a tuple for multi pk)
# @param engine str : The engine to use with this table
# @param charset str : The charset of this table
# @param if_exist str : takes values in ['nothing', 'drop']
# @param noauto_inc bool : if True forbids autoincrement on PK
def _create_table(self, table_name, pk_name, pk_ftype, engine, charset='utf8', if_exists='nothing', noauto_inc = False):
#Escaped table name
etname = utils.escape_idname(table_name)
if not isinstance(pk_name, tuple):
pk_name = tuple([pk_name])
pk_ftype = tuple([pk_ftype])
if len(pk_name) != len(pk_ftype):
raise ValueError("You have to give as many pk_name as pk_ftype")
pk_instr_cols = ''
pk_format = "{pk_name} {pk_type} {pk_specs},\n"
for i in range(len(pk_name)):
instr_type, pk_type, pk_specs = fieldtypes_utils.fieldtype_db_init(pk_ftype[i], noauto_inc)
if instr_type != 'column':
raise ValueError("Migration handler doesn't support MultiValueFieldType as primary keys")
pk_instr_cols += pk_format.format(
pk_name = utils.escape_idname(pk_name[i]),
pk_type = pk_type,
pk_specs = pk_specs
)
pk_instr_cols += "PRIMARY KEY("+(','.join([utils.escape_idname(pkn) for pkn in pk_name]))+')'
if if_exists == 'drop':
self._query("""DROP TABLE IF EXISTS {table_name};""".format(table_name=etname))
qres = """CREATE TABLE IF NOT EXISTS {table_name} (
{pk_cols}
) ENGINE={engine} DEFAULT CHARSET={charset};""".format(
table_name = table_name,
pk_cols = pk_instr_cols,
engine = engine,
charset = charset
)
self._query(qres)
## @brief Add a column to a table
# @param table_name str : The table name
# @param col_name str : The columns name
# @param col_fieldtype EmFieldype the fieldtype
# @param relation bool | None : a flag to indicate if we add a column in a table linked with an bject or with a relation (used only when the column is MultiValueFieldType )
# @return True if the column was added else return False
def _add_column(self, table_name, col_name, col_fieldtype, drop_if_exists=False, relation=False):
instr, col_type, col_specs = fieldtypes_utils.fieldtype_db_init(col_fieldtype)
if instr == 'table':
# multivalue field. We are not going to add a column in this table
# but in corresponding multivalue table
self._add_column_multivalue(
ref_table_name = table_name,
key_infos = col_type,
column_infos = (col_name, col_specs),
relation = relation
)
return True
col_name = utils.column_name(col_name)
add_col = """ALTER TABLE {table_name}
ADD COLUMN {col_name} {col_type} {col_specs};"""
etname = utils.escape_idname(table_name)
ecname = utils.escape_idname(col_name)
if instr is None:
return True
if instr != "column":
raise RuntimeError("Bad implementation")
add_col = add_col.format(
table_name=etname,
col_name=ecname,
col_type=col_type,
col_specs=col_specs,
)
try:
self._query(add_col)
except self._dbmodule.err.InternalError:
if drop_if_exists:
self._del_column(table_name, col_name)
self._add_column(table_name, col_name, col_fieldtype, drop_if_exists)
else:
#LOG
print("Aborded, column `%s` exists" % col_name)
return False
if isinstance(col_fieldtype, EditorialModel.fieldtypes.generic.ReferenceFieldType):
# We have to create a FK !
if col_fieldtype.reference == 'object':
dst_table_name = utils.common_tables['object']
dst_col_name, _ = self._object_pk
elif col_fieldtypes.reference == 'relation':
dst_table_name = utils.common_tables['relation']
dst_col_name, _ = self._relation_pk
fk_name = 'fk_%s-%s_%s-%s' % (
table_name,
col_name,
dst_table_name,
dst_col_name,
)
self._add_fk(
src_table_name = table_name,
dst_table_name = dst_table_name,
src_col_name = col_name,
dst_col_name = dst_col_name,
fk_name = fk_name
)
return True
## @brief Add a column to a multivalue table
#
# Add a column (and create a table if not existing) for storing multivalue
# datas. (typically i18n)
# @param ref_table_name str : Referenced table name
# @param key_infos tuple : tuple(key_name, key_fieldtype)
# @param column_infos tuple : tuple(col_name, col_fieldtype)
def _add_column_multivalue(self, ref_table_name, key_infos, column_infos, relation):
key_name, key_ftype = key_infos
col_name, col_ftype = column_infos
table_name = utils.multivalue_table_name(ref_table_name, key_name)
if relation:
pk_infos = self._relation_pk
else:
pk_infos = self._object_pk
# table creation
self._create_table(
table_name = table_name,
pk_name = (key_name, pk_infos[0]),
pk_ftype = (key_ftype, pk_infos[1]),
engine = self.db_engine,
if_exists = 'nothing',
noauto_inc = True
)
# with FK
self._add_fk(table_name, ref_table_name, pk_infos[0], pk_infos[0])
# adding the column
self._add_column(table_name, col_name, col_ftype)
## @brief Delete a multivalue column
# @param emfield EmField : EmField instance
# @note untested
def _del_column_multivalue(self, emfield):
ftype = emfield.fieldtype_instance()
if not isinstance(ftype, MultiValueFieldType):
raise ValueError("Except an emfield with multivalue fieldtype")
tname = utils.object_table_name(emfield.em_class.name)
tname = utils.multivalue_table_name(tname, ftype.keyname)
self._del_column(tname, emfield.name)
if len([ f for f in emfield.em_class.fields() if isinstance(f.fieldtype_instance(), MultiValueFieldType)]) == 0:
try:
self._query("DROP TABLE %s;" % utils.escape_idname(tname))
except self._dbmodule.err.InternalError as expt:
print(expt)
## @brief Add a foreign key
# @param src_table_name str : The name of the table where we will add the FK
# @param dst_table_name str : The name of the table the FK will point on
# @param src_col_name str : The name of the concerned column in the src_table
# @param dst_col_name str : The name of the concerned column in the dst_table
def _add_fk(self, src_table_name, dst_table_name, src_col_name, dst_col_name, fk_name=None):
stname = utils.escape_idname(src_table_name)
dtname = utils.escape_idname(dst_table_name)
scname = utils.escape_idname(src_col_name)
dcname = utils.escape_idname(dst_col_name)
if fk_name is None:
fk_name = utils.get_fk_name(src_table_name, dst_table_name)
self._del_fk(src_table_name, dst_table_name, fk_name)
self._query("""ALTER TABLE {src_table}
ADD CONSTRAINT {fk_name}
FOREIGN KEY ({src_col}) references {dst_table}({dst_col})
ON DELETE CASCADE
ON UPDATE CASCADE;""".format(
fk_name=utils.escape_idname(fk_name),
src_table=stname,
src_col=scname,
dst_table=dtname,
dst_col=dcname
))
## @brief Given a source and a destination table, delete the corresponding FK
# @param src_table_name str : The name of the table where the FK is
# @param dst_table_name str : The name of the table the FK point on
# @warning fails silently
def _del_fk(self, src_table_name, dst_table_name, fk_name=None):
if fk_name is None:
fk_name = utils.get_fk_name(src_table_name, dst_table_name)
fk_name = utils.escape_idname(fk_name)
try:
self._query("""ALTER TABLE {src_table}
DROP FOREIGN KEY {fk_name}""".format(
src_table=utils.escape_idname(src_table_name),
fk_name=fk_name
))
except self._dbmodule.err.InternalError:
# If the FK don't exists we do not care
pass
## @brief Generate triggers given a table_name and its columns fieldtypes
# @param table_name str : Table name
# @param cols_ftype dict : with col name as key and column fieldtype as value
def _generate_triggers(self, table_name, cols_ftype):
colval_l_upd = dict() # param for update trigger
colval_l_ins = dict() # param for insert trigger
for cname, cftype in cols_ftype.items():
if isinstance(cftype, EditorialModel.fieldtypes.datetime.EmFieldType):
if cftype.now_on_update:
colval_l_upd[cname] = 'NOW()'
if cftype.now_on_create:
colval_l_ins[cname] = 'NOW()'
self._table_trigger(table_name, 'UPDATE', colval_l_upd)
self._table_trigger(table_name, 'INSERT', colval_l_ins)
## @brief Create trigger for a table
#
# Primarly designed to create trigger for DATETIME types
# The method generates triggers of the form
#
# CREATE TRIGGER BEFORE <moment> ON <table_name>
# FOR EACH ROW SET <for colname, colval in cols_val>
# NEW.<colname> = <colval>,
# <endfor>;
# @param table_name str : The table name
# @param moment str : can be 'update' or 'insert'
# @param cols_val dict : Dict with column name as key and column value as value
def _table_trigger(self, table_name, moment, cols_val):
trigger_name = utils.escape_idname("%s_%s_trig" % (table_name, moment))
#Try to delete the trigger
drop_trig = """DROP TRIGGER IF EXISTS {trigger_name};""".format(trigger_name=trigger_name)
self._query(drop_trig)
col_val_l = ', '.join(["NEW.%s = %s" % (utils.escape_idname(utils.column_name(cname)), cval)for cname, cval in cols_val.items()])
#Create a trigger if needed
if len(col_val_l) > 0:
trig_q = """CREATE TRIGGER {trigger_name} BEFORE {moment} ON {table_name}
FOR EACH ROW SET {col_val_list};""".format(
trigger_name=trigger_name,
table_name=utils.escape_idname(table_name),
moment=moment, col_val_list=col_val_l
)
self._query(trig_q)
## @brief Delete all table created by the MH
# @param model Model : the Editorial model
def __purge_db(self, model):
for uid in [
field
for field in model.components('EmField')
if isinstance(field.fieldtype_instance(), MultiValueFieldType)
]:
self._del_column_multivalue(field)
for uid in [c.uid for c in model.components('EmClass')]:
try:
self.delete_emclass_table(model, uid)
except self._dbmodule.err.InternalError as expt:
print(expt)
for tname in [utils.r2t_table_name(f.em_class.name, model.component(f.rel_to_type_id).name) for f in model.components('EmField') if f.fieldtype == 'rel2type']:
try:
self._query("DROP TABLE %s;" % tname)
except self._dbmodule.err.InternalError as expt:
print(expt)
for tname in [utils.common_tables['relation'], utils.common_tables['relation']]:
try:
self._query("DROP TABLE %s;" % tname)
except self._dbmodule.err.InternalError as expt:
print(expt)
## @brief Return primary key name & fieldtype for relation or object
@classmethod
def extract_pk(cls, common_fields):
for fname, finfo in common_fields.items():
if finfo['fieldtype'] == 'pk':
fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name('pk')
finfo_cp = copy.copy(finfo)
del(finfo_cp['fieldtype'])
return (utils.column_name(fname), fto(**finfo_cp))
raise RuntimeError("No primary key found in common fields : %s" % common_fields)
## @brief Exec a query
# @param query str : SQL query
def _query(self, query):
if self.debug:
print(query + "\n")
if not self.dryrun:
with self.db_conn.cursor() as cur:
cur.execute(query)
self.db_conn.commit() # autocommit
## @brief Given a common field name return an EmFieldType instance
# @param cname str : Common field name
# @return An EmFieldType instance
@classmethod
def _common_field_to_ftype(cls, cname):
fta = copy.copy(EditorialModel.classtypes.common_fields[cname])
fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fta['fieldtype'])
del fta['fieldtype']
return fto(**fta)
## @brief Returns a tuple (pkname, pk_ftype)
@property
def _object_pk(self):
return self.extract_pk(EditorialModel.classtypes.common_fields)
## @brief Returns a tuple (rel_pkname, rel_ftype)
@property
def _relation_pk(self):
return self.extract_pk(EditorialModel.classtypes.relations_common_fields)
## @brief Returns a dict { colname:fieldtype } of relation table columns
@property
def _relation_cols(self):
res = dict()
for fieldname, fieldinfo in EditorialModel.classtypes.relations_common_fields.items():
finfo = copy.copy(fieldinfo)
fieldtype_name = finfo['fieldtype']
del(finfo['fieldtype'])
res[fieldname] = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fieldtype_name)(**finfo)
return res

View file

@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.migrationhandler.sql
# @brief A dummy migration handler
#
# According to it every modifications are possible
#
import EditorialModel
from DataSource.dummy.migrationhandler import DummyMigrationHandler
from EditorialModel.fieldtypes.generic import GenericFieldType
from EditorialModel.model import Model
from mosql.db import Database
from Lodel.utils.mosql import create, alter_add
## Manage Model changes
class SQLMigrationHandler(DummyMigrationHandler):
fieldtype_to_sql = {
'char': "CHAR(255)",
'integer': 'INT'
}
def __init__(self, module=None, *conn_args, **conn_kargs):
super(SQLMigrationHandler, self).__init__(False)
self.db = Database(module, *conn_args, **conn_kargs)
self._pk_column = (EditorialModel.classtypes.pk_name(), 'INTEGER PRIMARY KEY AUTO_INCREMENT')
self._main_table_name = 'object'
self._relation_table_name = 'relation'
self._install_tables()
## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
# @note The states ( initial_state and new_state ) contains only fields that changes
# @param model model : The EditorialModel.model object to provide the global context
# @param uid int : The uid of the change EmComponent
# @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
# @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
# @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
def register_change(self, model, uid, initial_state, new_state):
# find type of component change
if initial_state is None:
state_change = 'new'
elif new_state is None:
state_change = 'del'
else:
state_change = 'upgrade'
# call method to handle the database change
component_name = Model.name_from_emclass(type(model.component(uid)))
handler_func = component_name.lower() + '_' + state_change
if hasattr(self, handler_func):
getattr(self, handler_func)(model, uid, initial_state, new_state)
# New Class, a table must be created
def emclass_new(self, model, uid, initial_state, new_state):
class_table_name = self._class_table_name(new_state['name'])
self._query_bd(
create(table=class_table_name, column=[self._pk_column])
)
# New Field, must create a column in Class table or in Class_Type relational attribute table
# @todo common fields creation does not allow to add new common fields. It should
def emfield_new(self, model, uid, initial_state, new_state):
# field is of type rel2type, create the relational class_type table and return
if new_state['fieldtype'] == 'rel2type':
# find relational_type name, and class name of the field
class_name = self._class_table_name_from_field(model, new_state)
type_name = model.component(new_state['rel_to_type_id']).name
table_name = self._relational_table_name(class_name, type_name)
self._query_bd(
create(table=table_name, column=[self._pk_column]),
)
return
# Column creation
#
# field is internal, create a column in the objects table
if new_state['internal']:
if new_state['fieldtype'] == 'pk': # this column has already beeen created by self._install_tables()
return
if new_state['name'] in EditorialModel.classtypes.common_fields: # this column has already beeen created by self._install_tables()
return
# field is relational (rel_field_id), create a column in the class_type table
elif new_state['rel_field_id']:
class_name = self._class_table_name_from_field(model, new_state)
rel_type_id = model.component(new_state['rel_field_id']).rel_to_type_id
type_name = model.component(rel_type_id).name
table_name = self._relational_table_name(class_name, type_name)
# else create a column in the class table
else:
table_name = self._class_table_name_from_field(model, new_state)
field_definition = self._fieldtype_definition(new_state['fieldtype'], new_state)
self._query_bd(
alter_add(table=table_name, column=[(new_state['name'],field_definition)])
)
## convert fieldtype name to SQL definition
def _fieldtype_definition(self, fieldtype, options):
basic_type = GenericFieldType.from_name(fieldtype).ftype
if basic_type == 'int':
return 'INT'
elif basic_type == 'char':
max_length = options['max_length'] if 'max_length' in options else 255
return 'CHAR(%s)' % max_length
elif basic_type == 'text':
return 'TEXT'
elif basic_type == 'bool':
return 'BOOLEAN'
elif basic_type == 'datetime':
definition = 'DATETIME'
if 'now_on_create' in options and options['now_on_create']:
definition += ' DEFAULT CURRENT_TIMESTAMP'
#if 'now_on_update' in options and options['now_on_update']:
#definition += ' ON UPDATE CURRENT_TIMESTAMP'
return definition
raise EditorialModel.exceptions.MigrationHandlerChangeError("Basic type '%s' of fieldtype '%s' is not compatible with SQL migration Handler" % basic_type, fieldtype)
## Test if internal tables must be created, create it if it must
def _install_tables(self):
# create common fields definition
common_fields = [self._pk_column]
for name, options in EditorialModel.classtypes.common_fields.items():
if options['fieldtype'] != 'pk':
common_fields.append((name, self._fieldtype_definition(options['fieldtype'], options)))
# create common tables
self._query_bd(
create(table=self._main_table_name, column=common_fields),
create(table=self._relation_table_name, column=[('relation_id','INTEGER PRIMARY KEY AUTOINCREMENT'), ('superior_id','INT'), ('subdordinate_id','INT'), ('nature','CHAR(255)'), ('depth','INT'), ('rank','INT')])
)
def _query_bd(self, *queries):
with self.db as cur:
for query in queries:
print(query)
cur.execute(query)
def _class_table_name(self, class_name):
return 'class_' + class_name
def _relational_table_name(self, class_name, type_name):
return 'r2t_' + class_name + '_' + type_name
def _class_table_name_from_field(self, model, field):
class_id = field['class_id']
class_name = model.component(class_id).name
class_table_name = self._class_table_name(class_name)
return class_table_name

View file

View file

@ -0,0 +1,251 @@
"""
Tests for MySQL Datasource
"""
import random
import copy
import unittest
import unittest.mock as mock
from unittest import TestCase
from unittest.mock import patch, Mock, call
import mosql
import mosql.db
from mosql.util import Query as mosql_Query
import pymysql
import leapi.test.utils #Code generation functions
from Lodel.settings import Settings
import DataSource.MySQL
from DataSource.MySQL.leapidatasource import LeDataSourceSQL as DataSource
import DataSource.MySQL.utils as db_utils
from EditorialModel.classtypes import common_fields, relations_common_fields
from EditorialModel.fieldtypes.generic import MultiValueFieldType
class DataSourceTestCase(TestCase):
#Dynamic code generation & import
@classmethod
def setUpClass(cls):
""" Write the generated code in a temporary directory and import it """
cls.tmpdir = leapi.test.utils.tmp_load_factory_code()
@classmethod
def tearDownClass(cls):
""" Remove the temporary directory created at class setup """
leapi.test.utils.cleanup(cls.tmpdir)
def test_init(self):
""" Test __init__ for datasource """
with patch.object(mosql.db.Database, '__init__', return_value=None) as mock_db:
#Test __init__ without arguments
DataSource()
conn_args = Settings.get('datasource')['default']
db_module = conn_args['module']
del(conn_args['module'])
mock_db.assert_called_once_with(db_module, **conn_args)
mock_db.reset_mock()
#test with arguments
conn_args = { 'hello': 'world', 'answer': 42 }
DataSource(mosql, conn_args)
mock_db.assert_called_once_with(mosql, **conn_args)
mock_db.reset_mock()
DataSource(conn_args = conn_args)
mock_db.assert_called_once_with(pymysql, **conn_args)
@unittest.skip("Broken because of multivalue fields")
def test_insert_leobject(self):
""" Test the insert method on LeObjects """
from dyncode import Article, Personne, Rubrique
for letype in [Article, Personne, Rubrique]:
lodel_id = random.randint(0,4096) # Choose a random lodel_id
# Mock the cursor to superseed the lastrowid property
cursor_mock = Mock()
cursor_mock.lastrowid = lodel_id
# Mock the cursor() call on connection
cursor_call_mock = Mock(return_value=cursor_mock)
# Mock the connection to set the cursor() call mock
connection_mock = Mock()
connection_mock.cursor = cursor_call_mock
# Mock the connect() call on dbmodule to bla bla
connect_call_mock = Mock(return_value=connection_mock)
# Mock the db module to set the connection mock (on connect() call)
dbmodule_mock = Mock()
dbmodule_mock.connect = connect_call_mock
datasource = DataSource(module=dbmodule_mock, conn_args = {})
sql_query = 'SELECT wow FROM splendid_table'
class_table_datas = { fname: random.randint(42,1337) for fname in letype.fieldlist(complete = False) }
for fname in class_table_datas:
if isinstance(letype.fieldtypes()[fname], MultiValueFieldType):
class_table_datas[fname] = {'fre': 'bonjour', 'eng': 'hello'}
#class_table_datas = { 'title': 'foo', 'number': 42 }
object_table_datas = { 'string': random.randint(-42,42) }
# build the insert datas argument
insert_datas = copy.copy(object_table_datas)
insert_datas.update(class_table_datas)
with patch.object(mosql_Query, '__call__', return_value=sql_query) as mock_insert:
with patch.object(db_utils, 'query', return_value=cursor_mock) as mock_utils_query:
# call the insert() method
datasource.insert(letype, **insert_datas)
# construct expected datas for object table insert
object_table_datas['class_id'] = letype._class_id
object_table_datas['type_id'] = letype._type_id
# construct expected datas used in class table insert
class_table_datas['lodel_id'] = lodel_id
expected_calls = [
# insert in object table call
call(
db_utils.common_tables['object'],
object_table_datas
),
# insert in class specific table call
call(
db_utils.object_table_name(letype._leclass.__name__),
class_table_datas
),
]
expected_utils_query_calls = [
call(datasource.connection, sql_query),
call(datasource.connection, sql_query),
]
mock_insert.assert_has_calls(expected_calls, any_order = False)
mock_utils_query.assert_has_calls(expected_utils_query_calls)
def test_select_leobject(self):
""" Test select method on leobject without relational filters """
from dyncode import LeObject, Article, Textes
# Utils var and stuff to make tests queries write easier
# lodel_id field name
lodel_id = LeObject.uidname()
# pre-fetch tables name to make the tests_queries
table_names = {
LeObject: db_utils.common_tables['object'],
Article: db_utils.object_table_name(Article._leclass.__name__),
Textes: db_utils.object_table_name(Textes.__name__),
}
# lodel_id name to be use in joins (leobject side)
join_lodel_id = db_utils.column_prefix(table_names[LeObject], lodel_id)
# lodel_id name to be use in joins (leclass side)
def cls_lodel_id(cls): return db_utils.column_prefix(table_names[cls], lodel_id)
tests_queries = [
# call leobject.select(fields = ['lodel_id', 'string'], filters = [])
(
#field_list details
{
'leobject': [lodel_id, 'string'],
'leclass': [],
},
#Select args
{
'target_cls': LeObject,
'filters': [],
'rel_filters': [],
},
# expt args
{
'where':{},
'joins':None, #Expected call on Query.__call__ (called when we call left_join)
}
),
# call leobject.select(fields = ['lodel_id', 'string'], filters = ['lodel_id = 42', 'string = "hello"', 'rank > 1'])
(
{
'leobject': [lodel_id, 'string'],
'leclass': [],
},
{
'target_cls': LeObject,
'filters': [
(lodel_id,'=', 42),
('string', '=', 'Hello'),
('modification_date', '>', 1),
],
'rel_filters': [],
},
{
'where':{
(
db_utils.column_prefix(table_names[LeObject], lodel_id),
'='
): 42,
(
db_utils.column_prefix(table_names[LeObject], 'string'),
'='
): 'Hello',
(
db_utils.column_prefix(table_names[LeObject], 'modification_date'),
'>'
): 1
},
'joins':None, #Expected call on Query.__call__ (called when we call left_join)
}
),
]
# mock the database module to avoid connection tries
dbmodule_mock = Mock()
datasource = DataSource(module=dbmodule_mock, conn_args = {})
sql_query = 'SELECT foo FROM LeObject' # Fake return of mosql.select()
for fields_details, select_args, expt_args in tests_queries:
mosql_query_return_value = sql_query
with patch.object(mosql_Query, '__call__', return_value=mosql_query_return_value) as mock_select:
with patch.object(db_utils, 'query') as mock_utils_query:
# building the field_list argument
select_args['field_list'] = copy.copy(fields_details['leobject'])
select_args['field_list'] += fields_details['leclass']
# call
datasource.select(**select_args)
# building the select expected argument
select_arg = [ db_utils.column_prefix(db_utils.common_tables['object'], fname) for fname in fields_details['leobject'] ]
if len(fields_details['leclass']) > 0:
leclass = select_args['target_cls']
leclass = leclass._leclass if leclass.is_letype() else leclass
table_name = db_utils.object_table_name(leclass.__name__)
select_arg += [ db_utils.column_prefix(table_name, field_name) for field_name in fields_details['leclass'] ]
if expt_args['joins'] is None:
# If the call is made with LeObject as target class no joins call is made
mock_select.assert_called_once_with(
db_utils.common_tables['object'],
select= select_arg,
where=expt_args['where'],
joins=[]
)
else:
# Else there has to be 2 calls, 1 for the join another for the select
expected_calls = [
expt_args['joins'],
call(
db_utils.common_tables['object'],
select= select_arg,
where=expt_args['where'],
joins=[mosql_query_return_value]
)
]
mock_select.assert_has_calls(expected_calls, any_order=False)
# Tests that the query is really sent to the query method
mock_utils_query.assert_called_once_with(
datasource.connection,
sql_query
)

88
DataSource/MySQL/utils.py Normal file
View file

@ -0,0 +1,88 @@
# -*- coding: utf8 -*-
from Lodel.settings import Settings
common_tables = {
'relation': 'relation',
'object': 'object'
}
table_preffix = {
'relation': 'rel_',
'object': 'class_',
}
## @brief indicates if we want ON DELETE CASCADE on foreign keys
# @todo implementation in migration handler
fk_on_delete_cascade = False
## @brief Lodel_id for the hierachy root
leroot_lodel_id = 0
## @brief Return a table name given a EmClass or LeClass name
# @param class_name str : The class name
# @return a table name
def object_table_name(class_name):
return ("%s%s" % (table_preffix['object'], class_name)).lower()
## @brief Return a table name given a class name and a type name
# @param class_name str : The (Em|Le)Class name
# @param type_name str : The (Em|Le)Type name
# @return a table name
def r2t_table_name(class_name, type_name):
return ("%s%s_%s" % (table_preffix['relation'], class_name, type_name)).lower()
def multivalue_table_name(referenced_table_name, key_name):
return ("%s%s" % (key_name, referenced_table_name))
## @brief Return a column name given a field name
# @param field_name : The EmField or LeObject field name
# @return A column name
def column_name(field_name):
return field_name.lower()
## @brief gets the fk name between two tables
# @param src_table_name str
# @param dst_table_name str
# @return str
def get_fk_name(src_table_name, dst_table_name):
return ("fk_%s_%s" % (src_table_name, dst_table_name)).lower()
## @brief Exec a query
# @param query str : SQL query
def query(connection, query_string):
if Settings.debug_sql:
print("SQL : ", query_string)
with connection as cur:
try:
cur.execute(query_string)
except Exception as err:
raise err
return cur
## @brief Identifier escaping
# @param idname str : An SQL identifier
def escape_idname(idname):
if '`' in idname:
raise ValueError("Invalid name : '%s'" % idname)
return '`%s`' % idname
## Brief add table prefix to a column name
# @param name string: column name to prefix
# @param prefixes dict(prefix:list(name,))
# @return prefixed_name string: the name prefixed
# find the same name in some list of names, prepend the key of the dict to the name
def find_prefix(name, prefixes):
for prefix, names in prefixes:
if name in names:
return column_prefix(prefix, name)
return name
## prefix a column name with the table name
def column_prefix(table, column):
return '%s.%s' % (table, column)

0
DataSource/__init__.py Normal file
View file

View file

View file

@ -0,0 +1,60 @@
#-*- coding: utf-8 -*-
## @brief Dummy datasource for LeObject
#
# This class has to be extended to apply to a real datasource
# But it can be used as an empty and debug datasource
class DummyDatasource(object):
def __init__(self, module=None, *conn_args, **conn_kargs):
self.module = module
self.conn_args = conn_args
self.conn_kargs = conn_kargs
## @brief select lodel editorial components given filters
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param filters list : List of filters (see @ref leobject_filters )
# @param rel_filters list : List of relationnal filters (see @ref leobject_filters )
# @param group list of tupple: List of column to group together. group = [('titre', 'ASC'), ]
# @param order list of tupple : List of column to order. order = [('titre', 'ASC'), ]
# @param limit int : Number of row to be returned
# @param offset int : Used with limit to choose the start row
# @return a list of LeCrud child classes
def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0, instanciate=True):
pass
## @brief delete lodel editorial components given filters
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param leo_id int : The component ID (lodel_id or relation_id)
# @return the number of deleted components
def delete(self, target_cls, leo_id):
pass
## @brief update an existing lodel editorial component
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param leo_id int : The uniq ID of the object we want to update
# @param **datas : Datas in kwargs
# @return The number of updated components
def update(self, target_cls, leo_id, **datas):
pass
## @brief insert a new lodel editorial component
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param **datas : The datas to insert
# @return The inserted component's id
def insert(self, target_cls, **datas):
pass
## @brief insert multiple editorial component
# @param target_cls LeCrud(class) : The component class concerned by the insert (a LeCrud child class (not instance !) )
# @param datas_list list : A list of dict representing the datas to insert
# @return int the number of inserted component
def insert_multi(self, target_cls, datas_list):
pass
## @brief Update a rank for a relation (and shift properly every concerned relations)
# @param le_relation LeRelation : A LeRelation instance
# @param new_rank int : An integer representing the absolute new rank
# @return ???
def update_rank(self, le_relation, new_rank):
pass

View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.migrationhandler.dummy
# @brief A dummy migration handler
#
# According to it every modifications are possible
#
## Manage Model changes
class DummyMigrationHandler(object):
def __init__(self, debug=False):
self.debug = debug
## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
# @note The states ( initial_state and new_state ) contains only fields that changes
# @param em model : The EditorialModel.model object to provide the global context
# @param uid int : The uid of the change EmComponent
# @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
# @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
# @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
def register_change(self, em, uid, initial_state, new_state):
if self.debug:
print("\n##############")
print("DummyMigrationHandler debug. Changes for component with uid %d :" % uid)
if initial_state is None:
print("Component creation (uid = %d): \n\t" % uid, new_state)
elif new_state is None:
print("Component deletion (uid = %d): \n\t" % uid, initial_state)
else:
field_list = set(initial_state.keys()).union(set(new_state.keys()))
for field_name in field_list:
str_chg = "\t%s " % field_name
if field_name in initial_state:
str_chg += "'" + str(initial_state[field_name]) + "'"
else:
str_chg += " creating "
str_chg += " => "
if field_name in new_state:
str_chg += "'" + str(new_state[field_name]) + "'"
else:
str_chg += " deletion "
print(str_chg)
print("##############\n")
## @brief Not usefull for the moment
def register_model_state(self, em, state_hash):
if self.debug:
print("New EditorialModel state registered : '%s'" % state_hash)

View file

@ -51,7 +51,7 @@ PROJECT_BRIEF = "Logiciel d'edition electronique v2"
# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
# to the output directory.
PROJECT_LOGO = doc/img/openedition_logo.png
PROJECT_LOGO = "doc/img/openedition_logo.png"
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is
@ -824,12 +824,7 @@ FILE_PATTERNS = *.c \
*.ucf \
*.qsf \
*.as \
*.js \
*.m4 \
*.patch \
*.ac \
*.am \
*.in
*.js
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@ -914,7 +909,7 @@ IMAGE_PATH = doc/img
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
INPUT_FILTER = doxypy
INPUT_FILTER = "doxypy"
# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
# basis. Doxygen will compare the file name with each pattern and apply the

View file

@ -0,0 +1,2 @@
## @package EditorialModel
# @brief Group all objects usefull for editorial model management

View file

@ -0,0 +1,2 @@
## @package EditorialModel.backend
# @brief Backend that allows the EM to handle different data sources

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.backend.dummy_backend
# @brief Gives an empty backend
#
# Allows an editorial model to use an empty backend
# Mostly for migration and test purpose
## Manages a Json file based backend structure
class EmBackendDummy(object):
def load(self):
data = {}
return data
def save(self):
return True

View file

@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
import datetime
import EditorialModel
from EditorialModel.classtypes import EmClassType
from EditorialModel.types import EmType
from EditorialModel.fields import EmField
from Lodel.utils.mlstring import MlString
from EditorialModel.backend.dummy_backend import EmBackendDummy
class EmBackendGraphviz(EmBackendDummy):
## @brief Constructor
# @param dot_fname str : The filename where we want to save the dot repr of the EM
def __init__(self, dot_fname):
self.edges = ""
self.dot_fname = dot_fname
#with open(dot_file, 'w+') as dot_fp:
## @brief Not implementend
# @warning Not implemented
def load(self):
raise NotImplementedError(self.__class__.__name__+' cannot load an EM')
## @brief Save an EM in a dot file
# @param em model : The EM to save
# @warning hardcoded classtype
def save(self, em):
self.edges = ""
with open(self.dot_fname, 'w') as dotfp:
dotfp.write("digraph G {\n\trankdir = BT\n")
dotfp.write('subgraph cluster_classtype {\nstyle="invis"\n')
for ct in [ 'entity', 'entry', 'person' ]:
dotfp.write('\n\nct%s [ label="classtype %s" shape="tripleoctagon" ]\n'%(ct, ct))
dotfp.write("}\n")
dotfp.write('subgraph cluster_class {\nstyle="invis"\n')
for c in em.classes():
dotfp.write(self._component_node(c, em))
cn = c.__class__.__name__
cid = self._component_id(c)
self.edges += cid+' -> ct%s [ style="dashed" ]\n'%c.classtype
dotfp.write("}\n")
#dotfp.write('subgraph cluster_type {\nstyle="invis"\n')
for c in em.components(EmType):
dotfp.write(self._component_node(c, em))
cn = c.__class__.__name__
cid = self._component_id(c)
self.edges += cid+' -> '+self._component_id(c.em_class)+' [ style="dotted" ]\n'
for nat, sups in c.superiors().items():
for sup in sups:
self.edges += cid+' -> '+self._component_id(sup)+' [ label="%s" color="green" ]\n'%nat
#dotfp.write("}\n")
"""
for rf in [ f for f in em.components(EmField) if f.fieldtype == 'rel2type']:
dotfp.write(self._component_node(rf, em))
cid = self._component_id(rf)
self.edges += cid+' -> '+self._component_id(rf.em_class)+'\n'
self.edges += cid+' -> '+self._component_id(em.component(rf.rel_to_type_id))+'\n'
"""
dotfp.write(self.edges)
dotfp.write("\n}")
pass
@staticmethod
def _component_id(c):
return 'emcomp%d'%c.uid
def _component_node(self, c, em):
ret = "\t"+EmBackendGraphviz._component_id(c)
cn = c.__class__.__name__
rel_field = ""
if cn == 'EmClass':
ret += '[ label="EmClass %s", shape="%s" ]'%(c.name.replace('"','\\"'), 'doubleoctagon')
elif cn == 'EmField':
str_def = '[ label="Rel2Type {fname}|{{{records}}}", shape="record"]'
records = ' | '.join([f.name for f in em.components('EmField') if f.rel_field_id == c.uid])
ret += str_def.format(fname=c.name.replace('"','\\"'), records=records)
elif cn == 'EmType':
ret += '[ label="%s %s '%(cn, c.name.replace('"','\\"'))
cntref = 0
first = True
for f in [ f for f in c.fields() if f.name not in c.em_class.default_fields_list().keys()]:
if f.rel_field_id is None:
if f.fieldtype == 'rel2type':
rel_node_id = '%s%s'%(EmBackendGraphviz._component_id(c), EmBackendGraphviz._component_id(em.component(f.rel_to_type_id)))
rel_node = '\t%s [ label="rel_to_type'%rel_node_id
if len(f.rel_to_type_fields()) > 0:
#rel_node += '| {'
first = True
for rf in f.rel_to_type_fields():
rel_node += ' | '
if first:
rel_node += '{ '
first = False
rel_node += rf.name
rel_node += '}" shape="record"]\n'
rel_field += rel_node
ref_node = EmBackendGraphviz._component_id(em.component(f.rel_to_type_id))
self.edges += '%s:f%d -> %s [ color="purple" dir="both" ]\n'%(EmBackendGraphviz._component_id(c), cntref, rel_node_id)
self.edges += '%s -> %s [color="purple" dir="both" ]\n'%(rel_node_id, ref_node)
ret += '|'
if first:
ret += ' { '
first = False
if f.fieldtype == 'rel2type':
ret += '<f%d> '%cntref
cntref += 1
ret += f.name
ret += '}" shape="record" color="%s" ]'%('blue' if cn == 'EmType' else 'red')
else:
return ""
ret +="\n"+rel_field
return ret

View file

@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.backend.json_backend
# @brief Handle json files
#
# Allows an editorial model to be loaded/saved in
# json format
import json
import datetime
from Lodel.utils.mlstring import MlString
from EditorialModel.backend.dummy_backend import EmBackendDummy
def date_cast(date):
if len(date):
return datetime.datetime.strptime(date, '%c')
else:
return None
## @brief dirty obsolote cast function
def int_or_none(i):
if isinstance(i, int):
return i
elif i is None:
return None
elif len(i):
return int(i)
else:
return None
## Manages a Json file based backend structure
class EmBackendJson(EmBackendDummy):
cast_methods = {
'uid': int,
'rank': int,
'class_id': int,
'fieldgroup_id': int,
'rel_to_type_id': int_or_none,
'rel_field_id': int_or_none,
'optional': bool,
'string': MlString.load,
'help_text': MlString.load,
'date_create': date_cast,
'date_update': date_cast,
}
## Constructor
#
# @param json_file str: path to the json_file used as data source
# @param json_string str: json_data used as data source
def __init__(self, json_file=None, json_string=None):
if (not json_file and not json_string) or (json_file and json_string):
raise AttributeError
self._json_file = json_file
self._json_string = json_string
@staticmethod
## @brief Used by json.dumps to serialize date and datetime
def date_handler(obj):
return obj.strftime('%c') if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date) else None
## Loads the data from given file or string
#
# @return list
def load(self):
data = {}
json_string = self._load_from_file() if self._json_file else self._json_string
json_data = json.loads(json_string)
for index, component in json_data.items():
attributes = {}
for attr_name, attr_value in component.items():
if attr_name in EmBackendJson.cast_methods:
attributes[attr_name] = EmBackendJson.cast_methods[attr_name](attr_value)
else:
attributes[attr_name] = attr_value
data[int(index)] = attributes
return data
def _load_from_file(self):
with open(self._json_file) as content:
data = content.read()
return data
## Saves the data in the data source json file
# @param model Model : The editorial model
# @param filename str|None : The filename to save the EM in (if None use self.json_file provided at init )
# @return json string
def save(self, model, filename=None):
json_dump = json.dumps({component.uid: component.attr_flat() for component in model.components()}, default=self.date_handler, indent=True)
if self._json_file:
with open(self._json_file if filename is None else filename, 'w') as json_file:
json_file.write(json_dump)
else:
return json_dump

View file

@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.backend.lodel1_backend
# @brief Handle convertion of lodel 1.0 model.xml
#
import xml.etree.ElementTree as ET
import datetime
import re
from Lodel.utils.mlstring import MlString
from EditorialModel.backend.dummy_backend import EmBackendDummy
## Manages a Json file based backend structure
class EmBackendLodel1(EmBackendDummy):
def __init__(self, xml_file=None, xml_string=None):
if (not xml_file and not xml_string) or (xml_file and xml_string):
raise AttributeError
self._xml_file = xml_file
self._xml_string = xml_string
self.lodel2_components = self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': [], 'EmFieldGroup': []}
self._uids = []
self._fieldgroups_map = {}
## Loads the data from given file or string
#
# @return list
def load(self):
xml_string = self._load_from_file() if self._xml_file else self._xml_string
root_element = ET.fromstring(xml_string)
self._import_components(root_element)
# change uid of EmField and EmFieldGroup DONE
# take care of fieldgroup_id 0 !!
# add relational fields and rel_to_type_id
# add superiors_list in types
# create dict with all components DONE
#print (self.lodel2_components)
return self.lodel2_components['uids']
## Import the four basic components
def _import_components(self, root_element):
# component name and xpath to find them in the xml model
# the order is very important
# class and type first, they put their uid in self._uids
# fieldgroups must change their uid, it will be used by fields later
to_import = [
('EmClass', "./table[@name='#_TP_classes']/datas/row"),
('EmType', "./table[@name='##_TP_types']/datas/row"),
('EmFieldGroup', "./table[@name='#_TP_tablefieldgroups']/datas/row"),
('EmField', "./table[@name='#_TP_tablefields']/datas/row")
]
for comp in to_import:
component_name, xpath = comp
#print (component_name, xpath)
components = root_element.findall(xpath)
for lodel1_component in components:
cols = lodel1_component.findall('*')
fields = self._dom_elements_to_dict(cols)
#print(fields)
lodel2_component = self._map_component(component_name, fields)
lodel2_component['component'] = component_name
#print(lodel2_component)
uid = lodel2_component['uid']
self.lodel2_components[component_name].append(lodel2_component)
#if component_name in ['EmClass', 'EmType']:
self._uids.append(uid)
#del lodel2_component['uid']
self.lodel2_components['uids'][uid] = lodel2_component
#print ('————')
def _get_uid(self):
uid = 1
while True:
if uid not in self._uids:
return uid
uid += 1
## map lodel1 values to lodel2 values
def _map_component(self, component, fields):
new_dic = {}
for mapping in ONE_TO_TWO[component]:
lodel1_fieldname, lodel2_fieldname = mapping[0:2]
if len(mapping) == 3:
cast_function = mapping[2]
if callable(cast_function):
value = cast_function(fields[lodel1_fieldname])
values = {lodel2_fieldname: value}
else:
values = getattr(self, cast_function)(lodel2_fieldname, fields[lodel1_fieldname], fields)
else:
values = {lodel2_fieldname: fields[lodel1_fieldname]}
if values:
for name, value in values.items():
new_dic[name] = value
#print (lodel1_fieldname, lodel2_fieldname, value)
return new_dic
## convert collection of dom element to a dict
# \<col name="id"\>252\</col\> => {'id':'252'}
def _dom_elements_to_dict(self, elements):
fields = {}
for element in elements:
if 'name' in element.attrib:
fields[element.attrib['name']] = element.text if element.text is not None else ''
return fields
def _load_from_file(self):
with open(self._xml_file) as content:
data = content.read()
return data
def save(self, model, filename=None):
pass
# Map methods lodel1 to lodel2
## combine title and altertitle into one MlString
def title_to_mlstring(self, name, value, fields):
title = MlString({'fre': value})
if 'altertitle' in fields:
langs = re.findall('lang="(..)">([^<]*)', fields['altertitle'])
for string in langs:
title.set(string[0], string[1])
return {name: title}
## set a new unused uid for EmFieldGroup
# save the oldid to apply to fields
def new_fieldgroup_id(self, name, value, fields):
uid = self._get_uid()
print(value)
self._fieldgroups_map[int(value)] = uid
return {name: uid}
# give a new unused uid
def new_uid(self, name, value, fields):
uid = self._get_uid()
return {name: uid}
# return the new fieldgroup_id given the old one
def fieldgroup_id(self, name, value, fields):
old_id = int(value)
try:
new_id = self._fieldgroups_map[old_id]
except KeyError:
print(old_id, fields)
return False
return {name: new_id}
def mlstring_cast(self, name, value, fields):
return {name: MlString({'fre': value})}
def to_classid(self, name, value, fields):
for em_class in self.lodel2_components['EmClass']:
if em_class['name'] == value:
return {name: em_class['uid']}
return False
def date_cast(self, name, value, fields):
date = None
if len(value):
try: # 2015-09-14 14:20:28
date = datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
except ValueError:
pass
return {name: date}
def classtype_cast(self, name, value, fields):
classtype_map = {'entries': 'entry', 'entities': 'entity', 'persons': 'person'}
if value in classtype_map:
return {name: classtype_map[value]}
return False
def map_fieldtypes(self, name, value, fields):
fieldtypes = {
'longtext': 'text',
'date': 'datetime',
'tinytext': 'text',
'lang': 'char',
'boolean': 'bool',
'email': 'char',
'url': 'char',
'mltext': 'text',
'image': 'text',
'number': 'int',
#'persons': 'rel2type',
#'entries': 'rel2type',
#'entities': 'rel2type',
'persons': 'text',
'entries': 'text',
'entities': 'text'
}
if value in fieldtypes:
return {name: fieldtypes[value]}
return {name: value}
ONE_TO_TWO = {
'EmClass': [
("id", "uid", int),
("icon", "icon"),
("class", "name"),
("title", "string", 'title_to_mlstring'),
("classtype", "classtype", 'classtype_cast'),
("comment", "help_text", 'mlstring_cast'),
#("status",""),
("rank", "rank", int),
("upd", "date_update", 'date_cast')
],
'EmFieldGroup': [
("id", "uid", 'new_fieldgroup_id'),
("name", "name"),
("class", "class_id", 'to_classid'),
("title", "string", 'title_to_mlstring'),
("comment", "help_text", 'mlstring_cast'),
#("status",""),
("rank", "rank", int),
("upd", "date_update", 'date_cast')
],
'EmType': [
("id", "uid", int),
("icon", "icon"),
("type", "name"),
("title", "string", 'title_to_mlstring'),
("class", "class_id", 'to_classid'),
("comment", "help_text", 'mlstring_cast'),
#("status",""),
("rank", "rank", int),
("upd", "date_update", 'date_cast')
],
'EmField': [
("id", "uid", 'new_uid'),
("name", "name"),
("idgroup", "fieldgroup_id", 'fieldgroup_id'),
("type", "fieldtype", 'map_fieldtypes'),
("title", "string", 'title_to_mlstring'),
("comment", "help_text", 'mlstring_cast'),
#("status",""),
("rank", "rank", int),
("upd", "date_update", 'date_cast')
]
}

142
EditorialModel/classes.py Normal file
View file

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
## @file classes.py
# @see EditorialModel::classes::EmClass
import copy
# imports used in classtypes <-> actual model checks
import difflib
import warnings
import EditorialModel
from EditorialModel.components import EmComponent
from EditorialModel.classtypes import EmClassType
## @brief Manipulate Classes of the Editorial Model
# Create classes of object.
# @see EmClass, EditorialModel.types.EmType, EditorialModel.fieldgroups.EmFieldGroup, EmField
# @todo sortcolumn handling
class EmClass(EmComponent):
ranked_in = 'classtype'
## EmClass instanciation
# @todo Classtype initialisation and test is not good EmClassType should give an answer or something like that
# @todo defines types check for icon and sortcolumn
def __init__(self, model, uid, name, classtype, icon='0', sortcolumn='rank', string=None, help_text=None, date_update=None, date_create=None, rank=None):
if EmClassType.get(classtype) is None:
raise AttributeError("Unknown classtype '%s'" % classtype)
self.classtype = classtype
self.icon = icon
self.sortcolumn = sortcolumn # 'rank'
super(EmClass, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
## @brief Check if the EmComponent is valid
#
# This function add default and common fields to the EmClass if they are not yet created (and raises warning if existing common fields are outdated given the one describe in EditorialModel.classtypes
# @throw EmComponentCheckError if fails
#
# @warning If default parameters of EmField constructor changes this method can be broken
def check(self):
# Checks that this class is up to date given the common_fields described in EditorialModel.classtypes
# if not just print a warning, don't raise an Exception
for field in self.fields():
if field.name in EditorialModel.classtypes.common_fields:
# Building fieltypes options to match the ones stored in EditorialModel.classtypes
ftype_opts = field.fieldtype_options()
ftype_opts['fieldtype'] = field.fieldtype
ftype_opts['string'] = field.string
ctype_opts = EditorialModel.classtypes.common_fields[field.name]
#Adding default value for options nullable, uniq and internal to fieldtypes options stored in classtypes
defaults = { 'nullable': False, 'uniq': False, 'internal': False}
for opt_name, opt_val in defaults.items():
if opt_name not in ctype_opts:
ctype_opts[opt_name] = opt_val
if ftype_opts != ctype_opts:
field.set_fieldtype_options(**ctype_opts)
# If options mismatch produce a diff and display a warning
ctype_opts = [ "%s: %s\n"%(repr(k), repr(ctype_opts[k])) for k in sorted(ctype_opts.keys())]
ftype_opts = [ "%s: %s\n"%(repr(k), repr(ftype_opts[k])) for k in sorted(ftype_opts.keys())]
diff_list = difflib.unified_diff(
ctype_opts,
ftype_opts,
fromfile="Classtypes.%s" % field.name,
tofile="CurrentModel_%s.%s" % (self.name, field.name)
)
diff = ''.join(diff_list)
warnings.warn("LOADED MODEL IS OUTDATED !!! The common_fields defined in classtypes differs from the fields in this model.\nHere is a diff : \n%s" % diff)
for fname in self.default_fields_list().keys():
if fname not in [f.name for f in self.fields()]:
self.model.add_default_class_fields(self.uid)
super(EmClass, self).check()
## @brief Return the default fields list for this EmClass
# @return a dict with key = fieldname and value is a dict to pass to the EditorialModel::model::Model::creat_component() method
def default_fields_list(self):
ctype = EditorialModel.classtypes.EmClassType.get(self.classtype)
res = ctype['default_fields']
for k, v in EditorialModel.classtypes.common_fields.items():
res[k] = copy.copy(v)
return res
## @brief Delete a class if it's ''empty''
# If a class has no fieldgroups delete it
# @return bool : True if deleted False if deletion aborded
def delete_check(self):
for emtype in self.model.components(EditorialModel.types.EmType):
if emtype.class_id == self.uid:
return False
#If the class contains EmField that are not added by default, you cannot delete the EmClass
if len([f for f in self.fields() if f.name not in self.default_fields_list().keys()]) > 0:
return False
return True
## Retrieve list of the field_groups of this class
# @return A list of fieldgroups instance
def _fieldgroups(self):
ret = []
for fieldgroup in self.model.components(EditorialModel.fieldgroups.EmFieldGroup):
if fieldgroup.class_id == self.uid:
ret.append(fieldgroup)
return ret
## Retrieve list of fields
# @return fields [EmField]:
def fields(self, relational=True):
if relational:
return [f for f in self.model.components('EmField') if f.class_id == self.uid]
else:
return [f for f in self.model.components('EmField') if f.class_id == self.uid and f.fieldtype != 'rel2type' and f.rel_field_id is None]
## Retrieve list of type of this class
# @return types [EditorialModel.types.EmType]:
def types(self):
ret = []
for emtype in self.model.components(EditorialModel.types.EmType):
if emtype.class_id == self.uid:
ret.append(emtype)
return ret
## Add a new EditorialModel.types.EmType that can ben linked to this class
# @param em_type EditorialModel.types.EmType: type to link
# @return success bool: done or not
# @deprecated To do this add a rel2type field to any fieldtype of this EmClass
def link_type(self, em_type):
pass
## Retrieve list of EditorialModel.types.EmType that are linked to this class
# @return types [EditorialModel.types.EmType]:
def linked_types(self):
res = list()
for field in self.fields():
if field.fieldtype_instance().name == 'rel2type':
res.append(self.model.component(field.rel_to_type_id))
return res

View file

@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
object_uid = 'lodel_id'
object_em_class_id = 'class_id'
object_em_type_id = 'type_id'
relation_uid = 'id_relation'
relation_superior = 'superior'
relation_subordinate = 'subordinate'
relation_name = 'relation_name'
common_fields = {
object_uid: {
'fieldtype': 'pk',
'internal': 'autosql',
'immutable' : True,
'string': '{"___": "", "fre": "identifiant lodel", "eng": "lodel identifier"}'
},
object_em_class_id : {
'fieldtype': 'emuid',
'is_id_class': True,
'internal': 'automatic',
'immutable' : True,
'string': '{"___": "", "fre": "identifiant de la classe", "eng": "class identifier"}'
},
object_em_type_id : {
'fieldtype': 'emuid',
'is_id_class': False,
'internal': 'automatic',
'immutable' : True,
'string': '{"___": "", "fre": "identifiant de la type", "eng": "type identifier"}'
},
'string': {
'fieldtype': 'char',
'max_length': 128,
'internal': 'automatic',
'nullable': True,
'immutable' : False,
'string': '{"___": "", "fre": "Représentation textuel", "eng": "String representation"}',
},
'creation_date': {
'fieldtype': 'datetime',
'now_on_create': True,
'internal': 'autosql',
'immutable' : True,
'string': '{"___": "", "fre": "Date de création", "eng": "Creation date"}',
},
'modification_date': {
'fieldtype': 'datetime',
'now_on_create': True,
'now_on_update': True,
'internal': 'autosql',
'immutable' : True,
'string': '{"___": "", "fre": "Date de modification", "eng": "Modification date"}',
}
}
relations_common_fields = {
relation_uid: {
'fieldtype': 'pk',
'internal': 'autosql',
'immutable' : True,
},
'nature': {
'fieldtype': 'naturerelation',
'immutable' : True,
},
'depth': {
'fieldtype': 'integer',
'internal': 'automatic',
'immutable' : True,
},
'rank': {
'fieldtype': 'rank',
'internal': 'automatic',
'immutable' : True,
},
relation_superior : {
'fieldtype': 'leo',
'superior': True,
'immutable' : True,
},
relation_subordinate: {
'fieldtype': 'leo',
'superior': False,
'immutable' : True,
},
relation_name: {
'fieldtype': 'namerelation',
'max_length': 128,
'immutable' : True,
}
}
def pk_name():
for name, option in common_fields.items():
if option['fieldtype'] == 'pk':
return name
## EmNature (Class)
#
# constant name for the nature of type hierarchy
class EmNature(object):
PARENT = 'parent'
TRANSLATION = 'translation'
IDENTITY = 'identity'
@classmethod
def getall(cls):
return [cls.PARENT, cls.TRANSLATION, cls.IDENTITY]
## EmClassType (Class)
#
# Representation of the classTypes
#
# Defines 3 generic classtype : entity, entry and person
# - entity : to define editorial content
# - entry : to define keywords
# - person : to define people (in the real world, this classtype will only have one class and one type)
#
# The hierarchy dict fixes the possible hierarchical links the types of each classtype can have :
# - 'attach' : what type of superior a type can have
# - 'classtype' a type can have superiors of the same classtype
# - 'type' a type can only have superiors of the same type
# - automatic : possible superiors
# - False : possible superiors must be defined
# - True : possible superiors can not be defined, they will be enforced by the ME automatically
# - maxdepth : maximum depth
# - maxchildren : maximum children
#
# Classtypes contains default internal fields too
#
class EmClassType(object):
entity = {
'name': 'entity',
'hierarchy': {
EmNature.PARENT: {
'attach': 'classtype',
'automatic': False,
'maxdepth': -1,
'maxchildren': -1
},
EmNature.TRANSLATION: {
'attach': 'type',
'automatic': False,
'maxdepth': 1,
'maxchildren': -1
},
},
'default_fields': {}
}
entry = {
'name': 'entry',
'hierarchy': {
EmNature.PARENT: {
'attach': 'type',
'automatic': False,
},
EmNature.TRANSLATION: {
'attach': 'type',
'automatic': False,
'maxdepth': 1,
'maxchildren': -1
},
},
'default_fields': {}
}
person = {
'name': 'person',
'hierarchy': {
EmNature.IDENTITY: {
'attach': 'classtype',
'automatic': True,
'maxdepth': -1,
'maxchildren': 1
},
},
'default_fields': {}
}
## @brief return a classtype from its name
# @param cls
# @param classtype str : A classtype name
# @return None if no classtype with this name, else return a dict containing classtype informations
@classmethod
def get(cls, classtype):
try:
return getattr(cls, classtype.lower())
except AttributeError:
return None
## @brief Get all the classtype
# @return A list of dict representing classtypes
@classmethod
def getall(cls):
return [cls.entity, cls.entry, cls.person]
## @brief Return possible nature of relations for a classtype name
#
# @param classtype_name str: The classtype name
# @return A list of EmNature names (list of str)
@staticmethod
def natures(classtype_name):
if not isinstance(classtype_name, str):
raise TypeError("Excepted <class str> but got %s" % str(type(classtype_name)))
try:
classtype = getattr(EmClassType, classtype_name)
except AttributeError:
raise AttributeError("Unknown classtype : '%s'" % classtype_name)
return classtype['hierarchy'].keys()

View file

@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.components
# @brief Base objects for all EditorialModel components
#
# Defines the EditorialModel::components::EmComponent class
import datetime
import hashlib
import EditorialModel
from Lodel.utils.mlstring import MlString
## @brief This class is the mother class of all editorial model objects
#
# It gather all the properties and mechanism that are common to every editorial model objects
# @see EditorialModel::classes::EmClass, EditorialModel::types::EmType, EditorialModel::fieldgroups::EmFieldGroup, EditorialModel::fields::EmField
class EmComponent(object):
## Used by EmComponent::modify_rank
ranked_in = None
def __init__(self, model, uid, name, string=None, help_text=None, date_update=None, date_create=None, rank=None):
if type(self) == EmComponent:
raise NotImplementedError('Abstract class')
if model.__class__.__name__ != 'Model':
raise TypeError("Excepted type for 'model' arg is <class 'Model'> but got {} instead".format(type(model)))
self.model = model
self.uid = uid
self.check_type('uid', int)
self.name = name
self.check_type('name', str)
self.string = string
self.help_text = MlString() if help_text is None else help_text
self.check_type('help_text', MlString)
self.date_update = datetime.datetime.now() if date_update is None else date_update # WARNING timezone !
self.check_type('date_update', datetime.datetime)
self.date_create = datetime.datetime.now() if date_create is None else date_create # WARNING timezone !
self.check_type('date_create', datetime.datetime)
self._inited = False
#Handling specials ranks for component creation
self.rank = rank
## @brief Return a dict with attributes name as key and attributes value as value
# @note Used at creation and deletion to call the migration handler
def attr_dump(self):
return {fname: fval for fname, fval in self.__dict__.items() if not (fname.startswith('_') or (fname == 'uid') or (fname == 'model'))}
## @brief Return a dict with attributes name as key and attributes value flattened
def attr_flat(self):
attributes_dump = self.attr_dump()
for attr_name in list(attributes_dump.keys()):
if isinstance(attributes_dump[attr_name], EmComponent):
attributes_dump[attr_name] = attributes_dump[attr_name].uid
elif isinstance(attributes_dump[attr_name], MlString):
attributes_dump[attr_name] = attributes_dump[attr_name].json_dumps()
attributes_dump['component'] = self.__class__.__name__
return attributes_dump
@property
## @brief Provide a uniq name
#
# Identify a component with his type and name
def uniq_name(self):
uname = self.__class__.__name__
if not isinstance(self, EditorialModel.fields.EmField): # WARNING this could crash with fieldtypes
try:
uname += '_' + self.em_class.name # TODO Ajouter la propriété
except AttributeError:
pass
uname += '_' + self.name
return uname
## @brief This function has to be called after the instanciation, checks, and init manipulations are done
# @note Create a new attribute _inited that allow __setattr__ to know if it has or not to call the migration handler
def init_ended(self):
self._inited = True
## @brief Reimplementation for calling the migration handler to register the change
def __setattr__(self, attr_name, value):
if attr_name == 'string':
if not(isinstance(value, MlString)):
value = MlString(value)
super().__setattr__('string', value)
inited = '_inited' in self.__dict__ and self.__dict__['_inited']
if inited:
# if fails raise MigrationHandlerChangeError
self.model.migration_handler.register_change(self.model, self.uid, {attr_name: getattr(self, attr_name)}, {attr_name: value})
super(EmComponent, self).__setattr__(attr_name, value)
if inited:
self.model.migration_handler.register_model_state(self.model, hash(self.model))
## Check the type of attribute named var_name
# @param var_name str : the attribute name
# @param excepted_type tuple|type : Tuple of type or a type
# @throw AttributeError if wrong type detected
def check_type(self, var_name, excepted_type):
var = getattr(self, var_name)
if not isinstance(var, excepted_type):
raise AttributeError("Excepted %s to be an %s but got %s instead" % (var_name, str(excepted_type), str(type(var))))
## @brief Hash function that allows to compare two EmComponent
# @return EmComponent+ClassName+uid
def __hash__(self):
# flatten list of attributes of the component to an ordered list
# so every time we have the same string representation
attributes_flat = self.attr_flat()
ordered_attributes = sorted(list(attributes_flat.keys()))
component_dump = []
for attr_name in ordered_attributes:
if isinstance(attributes_flat[attr_name], datetime.datetime): # drop date values
continue
component_dump.append((attr_name, attributes_flat[attr_name]))
return int(hashlib.md5(str(component_dump).encode('utf-8')).hexdigest(), 16)
## @brief Test if two EmComponent are "equals"
# @return True or False
def __eq__(self, other):
return hash(self) == hash(other)
## Check if the EmComponent is valid
# This function has to check that rank are correct and continuous other checks are made in childs classes
# @warning Hardcoded minimum rank
# @warning Rank modified by _fields['rank'].value
# @throw EmComponentCheckError if fails
def check(self):
self.model.sort_components(self.__class__)
if self.get_max_rank() != len(self.same_rank_group()) or self.rank <= 0:
#Non continuous ranks
for i, component in enumerate(self.same_rank_group()):
component.rank = i + 1
# No need to sort again here
## @brief Delete predicate. Indicates if a component can be deleted
# @return True if deletion OK else return False
def delete_check(self):
raise NotImplementedError("Virtual method")
## @brief Get the maximum rank given an EmComponent child class and a ranked_in filter
# @return The max rank is the rank group or 0 if no components in that group
def get_max_rank(self):
same_rgroup = self.same_rank_group()
return max([comp.rank for comp in same_rgroup]) if len(same_rgroup) > 0 else 0
## Return an array of instances that are concerned by the same rank
# @return An array of instances that are concerned by the same rank
def same_rank_group(self):
components = self.model.components(self.__class__)
ranked_in = self.__class__.ranked_in
return [c for c in components if getattr(c, ranked_in) == getattr(self, ranked_in)]
## Set a new rank for this component
# @note This function assume that ranks are properly set from 1 to x with no gap
#
# @warning Hardcoded minimum rank
# @warning Rank modified by _fields['rank'].value
#
# @param new_rank int: The new rank
#
# @throw TypeError If bad argument type
# @throw ValueError if out of bound value
def set_rank(self, new_rank):
if not isinstance(new_rank, int):
raise TypeError("Excepted <class int> but got " + str(type(new_rank)))
if new_rank <= 0 or (new_rank > 1 and new_rank > self.get_max_rank()):
raise ValueError("Invalid new rank : " + str(new_rank))
mod = new_rank - self.rank # Indicates the "direction" of the "move"
if mod == 0:
return True
limits = [self.rank + (1 if mod > 0 else -1), new_rank] # The range of modified ranks
limits.sort()
for component in [c for c in self.same_rank_group() if c.rank >= limits[0] and c.rank <= limits[1]]:
component.rank = component.rank + (-1 if mod > 0 else 1)
self.rank = new_rank
self.model.sort_components(self.__class__)
## Modify a rank given an integer modifier
# @note this method always tries to make the modification : if modifier is too big put
# the component in last position, if modifier is to small put the component
# in first position
# @param rank_mod int : can be a negative positive or zero integer
# @return True if the modification was made as wanted else return false
# @throw TypeError if rank_mod is not an integer
def modify_rank(self, rank_mod):
if not isinstance(rank_mod, int):
raise TypeError("Excepted <class int>. But got %s" % str(type(rank_mod)))
ret = True
new_rank = self.rank + rank_mod
if new_rank < 1:
ret = False
new_rank = 1
elif new_rank > self.get_max_rank():
ret = False
new_rank = self.get_max_rank()
self.set_rank(new_rank)
return ret
## @brief Return a string representation of the component
# @return A string representation of the component
def __repr__(self):
if self.name is None:
return "<%s #%s, 'non populated'>" % (type(self).__name__, self.uid)
else:
return "<%s #%s, '%s'>" % (type(self).__name__, self.uid, self.name)

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
## @brief An exception class to tell that a component don't exist
class EmComponentNotExistError(Exception):
pass
## @brief Raised on uniq constraint error at creation
# This exception class is dedicated to be raised when create() method is called
# if an EmComponent with this name but different parameters allready exist
class EmComponentExistError(Exception):
pass
## @brief An exception class to tell that no ranking exist yet for the group of the object
class EmComponentRankingNotExistError(Exception):
pass
## @brief An exception class to return a failure reason for EmComponent.check() method
class EmComponentCheckError(Exception):
pass
class MigrationHandlerChangeError(Exception):
pass

151
EditorialModel/fields.py Normal file
View file

@ -0,0 +1,151 @@
#-*- coding: utf-8 -*-
import copy
import importlib
import warnings
from EditorialModel.components import EmComponent
from EditorialModel.exceptions import EmComponentCheckError
from EditorialModel.fieldtypes.generic import GenericFieldType
import EditorialModel
import EditorialModel.fieldtypes
#from django.db import models
## EmField (Class)
#
# Represents one data for a lodel2 document
class EmField(EmComponent):
ranked_in = 'class_id'
## Instanciate a new EmField
# @todo define and test type for icon
# @warning nullable == True by default
# @param model Model : Editorial model
# @param uid int : Uniq id
# @param fieldtype str : Fieldtype name ( see Editorialmodel::fieldtypes )
# @param optional bool : Indicate if a field is optional or not
# @param internal str|bool : If False the field is not internal, else it can takes value in "object" or "automatic"
# @param rel_field_id int|None : If not None indicates that the field is a relation attribute (and the value is the UID of the rel2type field)
# @param nullable bool : If True None values are allowed
# @param uniq bool : if True the value should be uniq in the db table
# @param **kwargs : more keywords arguments for the fieldtype
def __init__(self, model, uid, name, class_id, fieldtype, optional=False, internal=False, rel_field_id=None, icon='0', string=None, help_text=None, date_update=None, date_create=None, rank=None, nullable=False, uniq=False, **kwargs):
self.class_id = class_id
self.check_type('class_id', int)
self.optional = bool(optional)
if not internal:
self.internal = False
else:
if internal.lower() not in ['automatic', 'autosql']:
raise ValueError("The internal arguments possible values are : [False, 'autosql', 'automatic']")
self.internal = internal.lower()
if self.internal == 'object' and name not in EditorialModel.classtypes.common_fields.keys():
raise ValueError("Only common_fields are allowed to have the argument internal='object'")
self.rel_field_id = rel_field_id
self.check_type('rel_field_id', (int, type(None)))
self.icon = icon
#Field type elements
self._fieldtype_cls = GenericFieldType.from_name(fieldtype)
self.fieldtype = fieldtype
self._fieldtype_args = kwargs
self._fieldtype_args.update({'nullable': nullable, 'uniq': uniq, 'internal': self.internal})
self.set_fieldtype_options(**self._fieldtype_args)
if 'default' in kwargs:
if not fieldtype_instance.check(default):
raise TypeError("Default value ('%s') is not valid given the fieldtype '%s'" % (default, fieldtype))
super(EmField, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
@staticmethod
## @brief Return the list of allowed field type
def fieldtypes_list():
return [f for f in EditorialModel.fieldtypes.__all__ if f != '__init__' and f != 'generic']
##@brief Return the EmFieldgroup this EmField belongs to
@property
def _fieldgroup(self):
return self.model.component(self.fieldgroup_id)
## @brief Returns the EmClass this EmField belongs to
@property
def em_class(self):
return self.model.component(self.class_id)
## @brief Update the fieldtype of a field
def set_fieldtype(self, fieldtype_name):
self.fieldtype = fieldtype_name
self._fieldtype_cls = GenericFieldType.from_name(self.fieldtype)
self.set_fieldtype_options(**self._fieldtype_args)
## @brief Set fieldtype options
def set_fieldtype_options(self, nullable = False, uniq = False, internal=False, **kwargs):
# Cleaning old options
if hasattr(self, 'name'): # If not called by __init__
for opt_name in self._fieldtype_args:
if hasattr(self, opt_name):
delattr(self,opt_name)
self._fieldtype_args = kwargs
self._fieldtype_args.update({'nullable': nullable, 'uniq': uniq, 'internal': internal})
try:
fieldtype_instance = self._fieldtype_cls(**self._fieldtype_args)
except AttributeError as e:
raise AttributeError("Error will instanciating fieldtype : %s" % e)
for opt_name, opt_val in self._fieldtype_args.items():
setattr(self, opt_name, opt_val)
## @brief Getter for private property EmField._fieldtype_args
# @return A copy of private dict _fieldtype_args
def fieldtype_options(self):
return copy.copy(self._fieldtype_args)
## @brief Get the fieldtype instance
# @return a fieldtype instance
def fieldtype_instance(self):
return self._fieldtype_cls(**self._fieldtype_args)
## @brief Return the list of relation fields for a rel_to_type
# @return None if the field is not a rel_to_type else return a list of EmField
def rel_to_type_fields(self):
if not self.rel_to_type_id: # TODO Ajouter cette propriété
return None
return [f for f in self.model.components(EmField) if f.rel_field_id == self.uid]
## Check if the EmField is valid
#
# Check multiple things :
# - the fieldgroup_id is valid
# - the name is uniq in the EmClass
# - if its a rel2type check that the linked_type is uniq in the EmClass
# @return True if valid False if not
def check(self):
super(EmField, self).check()
"""
#Fieldgroup check
em_fieldgroup = self.model.component(self.fieldgroup_id)
if not em_fieldgroup:
raise EmComponentCheckError("fieldgroup_id contains a non existing uid : '%d'" % self.fieldgroup_id)
if not isinstance(em_fieldgroup, EditorialModel.fieldgroups.EmFieldGroup):
raise EmComponentCheckError("fieldgroup_id contains an uid from a component that is not an EmFieldGroup but a %s" % str(type(em_fieldgroup)))
"""
#Uniq Name check
if len([f for f in self.em_class.fields() if f.name == self.name]) > 1:
raise EmComponentCheckError("The field %d has a name '%s' that is not uniq in the EmClass %d" % (self.uid, self.name, self.em_class.uid))
#rel2type uniq check
if self.fieldtype == 'rel2type':
if len([f for f in self.em_class.fields() if f.fieldtype == 'rel2type' and f.rel_to_type_id == self.rel_to_type_id]) > 1:
raise EmComponentCheckError("The rel2type %d is not uniq, another field is linked to the same type '%s' in the same class '%s'" % (self.uid, self.model.component(self.rel_to_type_id).name, self.em_class.name))
## @brief Delete a field if it's not linked
# @return bool : True if deleted False if deletion aborded
# @todo Check if unconditionnal deletion is correct
def delete_check(self):
return True

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,25 @@
#-*- coding: utf-8 -*-
from .generic import GenericFieldType
class EmFieldType(GenericFieldType):
help = 'A basic boolean field'
ftype = 'bool'
## @brief A boolean field
# @brief max_length int : The maximum length of this field
def __init__(self, **kwargs):
if 'check_data_value' not in kwargs:
kwargs['check_data_value'] = self.check_value
super(EmFieldType, self).__init__(ftype='bool', **kwargs)
def check_value(self, value):
error = None
try:
value = bool(value)
except (ValueError, TypeError):
error = TypeError("the value '%s' is not, and will never be a boolean" % value)
return (value, error)

View file

@ -0,0 +1,15 @@
#-*- coding: utf-8 -*-
from .generic import SingleValueFieldType
class EmFieldType(SingleValueFieldType):
help = 'Basic string (varchar) field. Take max_length=64 as option'
## @brief A char field
# @brief max_length int : The maximum length of this field
def __init__(self, max_length=64, **kwargs):
self.max_length = max_length
super().__init__(**kwargs)

View file

@ -0,0 +1,17 @@
#-*- coding: utf-8 -*-
from .generic import SingleValueFieldType
class EmFieldType(SingleValueFieldType):
help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
## @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
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
super(EmFieldType, self).__init__(**kwargs)

View file

@ -0,0 +1,39 @@
#-*- coding: utf-8 -*-
import leapi.lecrud as lecrud
import leapi.letype as letype
from .generic import FieldTypeError
from . import integer
class EmFieldType(integer.EmFieldType):
help = 'Fieldtypes designed to handle editorial model UID for LeObjects'
_construct_datas_deps = []
def __init__(self, is_id_class, **kwargs):
self._is_id_class = is_id_class
kwargs['internal'] = 'automatic'
super().__init__(is_id_class = is_id_class, **kwargs)
def _check_data_value(self, value):
return (value, None)
def construct_data(self, lec, fname, datas, cur_value):
ret = None
if self.is_id_class:
if lec.implements_leclass():
ret = lec._class_id
else:
if lec.implements_letype():
ret = lec._type_id
return ret
def check_data_consistency(self, lec, fname, datas):
if datas[fname] != (lec._class_id if self.is_id_class else lec._type_id):
return FieldTypeError("Given Editorial model uid doesn't fit with given LeObject")

View file

@ -0,0 +1,16 @@
#-*- coding: utf-8 -*-
from .generic import GenericFieldType
class EmFieldType(GenericFieldType):
help = 'A file field. With one options upload_path'
ftype = 'char'
## @brief A char field
# @brief max_length int : The maximum length of this field
def __init__(self, upload_path=None, **kwargs):
self.upload_path = upload_path
super(EmFieldType, self).__init__(ftype='char', max_length=512, **kwargs)

View file

@ -0,0 +1,23 @@
#-*- coding: utf-8 -*-
import warnings
from . import char
class EmFieldType(char.EmFieldType):
help = 'Automatic string field, designed to use the str % operator to build its content'
## @brief Build its content with a field list and a format string
# @param format_string str :
# @param field_list list : List of field to use
def __init__(self, format_string, field_list, max_length, **kwargs):
self._field_list = field_list
self._format_string = format_string
super().__init__(internal='automatic', max_length = max_length)
def construct_data(self, lec, fname, datas, cur_value):
ret = self._format_string % tuple([ datas[fname] for fname in self._field_list ])
if len(ret) > self.max_length:
warnings.warn("Format field overflow. Truncating value")
ret = ret[:self.max_length-1]
return ret

View file

@ -0,0 +1,288 @@
#-*- coding: utf-8 -*-
## @package EditorialModel.fieldtypes.generic Class definition for fieldtypes
import copy
import types
import importlib
## @brief Abstract class for all fieldtypes
class GenericFieldType(object):
help_text = 'Generic field type : abstract class for every fieldtype'
## @brief List fields that will be exposed to the construct_data_method
_construct_datas_deps = []
## @brief Generic constructor for fieldtypes 
# @param internal False | str : define wheter or not a field is internal
# @param immutable bool : indicate if the fieldtype has to be defined in child classes of LeObject or if it is defined globally and immutable
# @throw NotImplementedError if called from bad class
def __init__(self, internal = False, immutable = False, **args):
if self.__class__ == GenericFieldType:
raise NotImplementedError("Abstract class")
self.internal = internal #Check this value ?
self.immutable = bool(immutable)
for argname, argval in args.items():
setattr(self, argname, argval)
## Fieldtype name
# @todo should be a staticmethod
@property
def name(self):
return self.__module__.split('.')[-1]
## @return True if a fieldtype is internal
def is_internal(self):
return self.internal != False
## @brief Take care to call the fieldtype defined _check_data_value() method
# @return a tuple (value, error|None)
def check_data_value(self, value):
return self._check_data_value(value)
def _check_data_value(self, value):
return (value, None)
## @brief Build field value
# @param lec LeCrud : A LeCrud child class
# @param fname str : The field name
# @param datas dict : dict storing fields values (from the lec)
# @param cur_value : the value for the current field (identified by fieldname)
# @return constructed datas (for the fname field)
# @throw RuntimeError if unable to construct data
def construct_data(self, lec, fname, datas, cur_value):
if fname in datas.keys():
return cur_value
elif hasattr(lec.fieldtypes()[fname], 'default'):
return lec.fieldtypes()[fname].default
elif lec.fieldtypes()[fname].nullable:
return None
raise RuntimeError("Unable to construct data for field %s", fname)
## @brief Check datas consistency
# @param leo LeCrud : A LeCrud child class instance
# @param fname str : The field name
# @param datas dict : dict storing fields values
# @return an Exception instance if fails else True
def check_data_consistency(self, lec, fname, datas):
return True
## @brief Given a fieldtype name return the associated python class
# @param fieldtype_name str : A fieldtype name
# @return An GenericFieldType derivated class
@staticmethod
def from_name(fieldtype_name):
mod = importlib.import_module(GenericFieldType.module_name(fieldtype_name))
return mod.EmFieldType
## @brief Get a module name given a fieldtype name
# @param fieldtype_name str : A fieldtype name
# @return a string representing a python module name
@staticmethod
def module_name(fieldtype_name):
return 'EditorialModel.fieldtypes.%s' % (fieldtype_name)
## @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 Represent fieldtypes handling a single value
class SingleValueFieldType(GenericFieldType):
## @brief Instanciate a new fieldtype
# @param nullable bool : is None allowed as value ?
# @param uniqu bool : Indicate if a field should handle uniq value
# @param primary bool : If True the field is a primary key
# @param **args : Other arguments
# @throw NotImplementedError if called from bad class
def __init__(self, internal = False, nullable = True, uniq = False, primary = False, **args):
if self.__class__ == SingleValueFieldType:
raise NotImplementedError("Asbtract class")
super().__init__(internal, **args)
self.nullable = nullable
self.uniq = uniq
self.primary = primary
if 'default' in args:
self.default, error = self.check_data_value(args['default'])
if error:
raise error
del(args['default'])
def check_data_value(self, value):
if value is None:
if not self.nullable:
return (None, TypeError("'None' value but field is not nullable"))
return (None, None)
return super().check_data_value(value)
## @brief Abstract class for fieldtype representing references
#
# In MySQL its foreign keys (for example leo fieldtypes)
class ReferenceFieldType(SingleValueFieldType):
## @brief Instanciate a new fieldtype
#
#
# @param reference str : A string that defines the reference (can be 'object' or 'relation')
# @param nullable bool : is None allowed as value ?
# @param unique bool : Indicate if a field should handle uniq value
# @param primary bool : If True the field is a primary key
# @param **args : Other arguments
# @throw NotImplementedError if called from bad class
def __init__(self, reference, internal=False, nullable = True, uniq = False, primary = False, **args):
if reference.lower() not in ['relation', 'object']:
raise ValueError("Bad value for reference : %s. Excepted on of 'relation', 'object'" % reference)
self.reference = reference.lower()
super().__init__(
internal = internal,
nullable = nullable,
uniq = uniq,
primary = primary,
**args
);
pass
## @brief Abstract class for fieldtypes handling multiple values identified by a key
#
# For example i18n fieldtype
class MultiValueFieldType(GenericFieldType):
## @brief Instanciate a new multivalue fieldtype
#
# This fieldtype describe a field that handles multiple values referenced by a key (like a dict).
# A typicall example is the i18n fieldtype
# @param keyname str : The identifier key name
# @param key_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
# @param value_fieldtype SingleValueFieldType : A SingleValueFieldType child class instance
def __init__(self, keyname, key_fieldtype, value_fieldtype, internal = False, **args):
super().__init__(internal)
## stores the keyname
self.keyname = keyname
## stores the fieldtype that handles the key
self.key_fieldtype = key_fieldtype
## stores the fieldtype that handles the values
self.value_fieldtype = value_fieldtype
## @brief Class designed to handle datas access will fieldtypes are constructing datas
#
# This class is designed to allow automatic scheduling of construct_data calls.
#
# In theory it's able to detect circular dependencies
# @todo test circular deps detection
# @todo test circulat deps false positiv
class DatasConstructor(object):
## @brief Init a DatasConstructor
# @param lec LeCrud : LeCrud child class
# @param datas dict : dict with field name as key and field values as value
# @param fieldtypes dict : dict with field name as key and fieldtype as value
def __init__(self, lec, datas, fieldtypes):
## Stores concerned class
self._lec = lec
## Stores datas and constructed datas
self._datas = copy.copy(datas)
## Stores fieldtypes
self._fieldtypes = fieldtypes
## Stores list of fieldname for constructed datas
self._constructed = []
## Stores construct calls list
self._construct_calls = []
## @brief Implements the dict.keys() method on instance
def keys(self):
return self._datas.keys()
## @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:
raise RuntimeError('Probably circular dependencies in fieldtypes')
cur_value = self._datas[fname] if fname in self._datas else None
self._datas[fname] = self._fieldtypes[fname].construct_data(self._lec, fname, self, cur_value)
self._constructed.append(fname)
return self._datas[fname]
## @brief Allows to set instance values like a dict
# @warning Should not append in theory
def __setitem__(self, fname, value):
self._datas[fname] = value
warnings.warn("Setting value of an DatasConstructor instance")
#
#
# Exceptions
#
#
class FieldTypeError(Exception):
pass
class FieldTypeDataCheckError(FieldTypeError):
## @brief Instanciate a new data check error
# @param expt_l list : A list of data check Exception
def __init__(self, expt_l):
self._expt_l = expt_l
def __str__(self):
msg = "Data check errors : "
for expt in self._expt_l:
msg += "{expt_name}:{expt_msg}; ".format(expt_name=expt.__class__.__name__, expt_msg=str(expt))
return msg
## @page lodel2_fieldtypes Lodel2 fieldtypes
#
# @section fieldtypes_features Main features
#
# Lodel2 defines Class and Types containing fields that handle values.
# Fields are defined by FieldTypes. It's objects that are able to check datas value, to construct values (~cast) and to check datas consistency given the list of datas of an Lodel2 object (Class or Type)
#
# @subsection fieldtypes_hierarchy Fieldtypes family
#
# Fieldtypes are python objects. We use inheritance to defines FieldTypes. Here is a list of main FieldTypes inheritance :
# - GenericFieldType
# - SingleValueFieldType <- handles a single value
# - ReferenceFieldType <- handles a reference to another field
# - leo.EmFieldType <- handles references to a LeObject (designed for LeRelation)
# - char.EmFieldType <- handles string
# - integer.EmFieldType <- handles integer
# - pk.EmFieldType <- handles primary keys (identifier)
# - MultiValueFieldType <- handles multiple values identified by a key
# - i18n.EmFieldType <- handles a string and its translations
#
# @subsection fieldtypes_options Fieldtypes main options
#
# There is 2 options that are common for every fieldtypes :
# - internal : that indicate who construct the data. Possible values are
# - False : the field is not internal, its user that provides datas
# - 'automatic' : The field is internal, its leapi that provides datas (see construct in @ref fieldtypes_validator )
# - 'autosql' : BAD NAME. The field is internal but it is the datasource that provide the data
# - immutable : Its a boolean that indicate if a fieldtype defined in EditorialModel.classtypes is immutable or HAVE TO be defined in EmClass
#
# @subsubsection fieldtypes_options_single_value SingleValueFieldType options
#
# SingleValueFieldType have more standart options :
# - nullable (boolean) : is None allowed as value
# - uniq (boolean) : if True the value has to be uniq in all instances of concerned Lodel2 API object
# - primary (boolean) : if True the field is an identifier (primary key)
# - default : if given as argument defines a default value for the FieldType
#
# @subsection fieldtypes_validator Data validation
#
# For each Lodel2 API objects (LeObject, LeRelation, ...) there is a sequence that is run to check datas and transform them. Each step is run for each fieldtypes of a Lodel2 API object
#
# -# Check data : this is a basic data check (for example if an integer is expected and the string 'foo' is given the check will fails)
# -# Construct data : this is a data 'casting' step, this method is called with all other datas from the Lodel2 API object as argument (to be able to construct datas with other parts of the object) @ref fieldtypes_construct_order
# -# Datas consistency checks : this step, as the construct step, has all datas from the Lodel2 API object as argument, and check for the whole datas consistency
#
# @subsubsection fieldtypes_construct_order Data construction dependencies
#
# To handle data construction dependencies there is an object DatasConstructor able to call the data construction when needed and to detect (not tested yet) circular dependencies
# 

View file

@ -0,0 +1,45 @@
#-*- coding: utf-8 -*-
import json
from Lodel.utils.mlstring import MlString
from .generic import MultiValueFieldType
from . import char
class EmFieldType(MultiValueFieldType):
help = 'Fieldtype designed to handle translations'
def __init__(self, value_max_length = 64, **args):
args['keyname'] = 'lang'
args['key_fieldtype'] = char.EmFieldType(max_length = 4)
args['value_fieldtype'] = char.EmFieldType(value_max_length)
super().__init__(**args)
def _check_data_value(self, value):
if value is None:
return (None, None)
if isinstance(value, MlString):
return (value, None)
if isinstance(value, dict):
for val in value.values():
if not(isinstance(val, str) or val is None) :
return (None, ValueError("Expected str as dict values. Bad dict : '%s'" % value))
return (value, None)
if isinstance(value, str):
try:
MlString(value)
return (value, None)
except ValueError:
return (None, ValueError("Unable to load an MlString from value '%s'" % value))
return (None, ValueError("Bad value : '%s'" % value))
def construct_data(self, lec, fname, datas, cur_value):
if not isinstance(cur_value, MlString):
ret = MlString(cur_value)
else:
ret = cur_value
if len(ret.get_default()) == 0 and len(ret.values()) > 0:
ret.set_default(ret[list(ret.keys())[0]])
return ret

View file

@ -0,0 +1,21 @@
#-*- coding: utf-8 -*-
from .generic import SingleValueFieldType
class EmFieldType(SingleValueFieldType):
help = 'Basic integer field'
ftype = 'int'
def __init__(self, **kwargs):
super(EmFieldType, self).__init__(ftype='int', **kwargs)
def _check_data_value(self, value):
error = None
try:
value = int(value)
except (ValueError, TypeError):
error = TypeError("the value '%s' is not, and will never be an integer" % value)
return (value, error)

View file

@ -0,0 +1,25 @@
#-*- coding: utf-8 -*-
import warnings
from . import char
class EmFieldType(char.EmFieldType):
help = 'Automatic string field, defined as concatenation of other fields'
## @brief Instanciate a fieldtype designed to concatenate fields
# @param field_list list : fieldname list
# @param glue str : separator
# @param max_length int : Field max length
def __init__(self, field_list, max_length, glue = ' ', **kwargs):
self._field_list = field_list
super().__init__(internal='automatic', max_length = max_length)
def construct_data(self, lec, fname, datas, cur_value):
ret = ''
for fname in self._field_list:
ret += datas[fname]
if len(ret) > self.max_length:
warnings.warn("Join field overflow. Truncating value")
ret = ret[:self.max_length-1]
return ret

View file

@ -0,0 +1,68 @@
#-*- coding: utf-8 -*-
import leapi.letype as letype
import leapi.leclass as leclass
from .generic import ReferenceFieldType, FieldTypeError
class EmFieldType(ReferenceFieldType):
help = 'Fieldtypes designed to handle pk of LeObject in LeRelations'
_construct_data_deps = []
## @todo Replace hardcoded string for reference initialisation
def __init__(self, superior=True, **kwargs):
super().__init__(superior = superior, reference='object', **kwargs)
def _check_data_value(self, value):
if not isinstance(value, int):
if not letype._LeType.implements_leobject():
return (None, ValueError("An instance of a child class of LeType was expected"))
if not hasattr(value, 'lodel_id'):
return (None, ValueError("The LeType instance given has no lodel_id !"))
return (value, None)
## @brief If field value is an integer, returns a partially instanciated LeObject (only with an ID)
# @todo what should we do if the get fails ? Raise ?
def construct_data(self, lec, fname, datas, cur_value):
if isinstance(cur_value, str):
# Cast to int
try:
cur_value = int(cur_value)
except (ValueError, TypeError) as e:
raise e # Raise Here !?
if hasattr(cur_value, 'is_leobject') and cur_value.is_leobject():
# Its an object not populated (we dont now its type)
cur_value = cur_value.lodel_id #Optimize here giving only class_id and type_id to populate ?
if isinstance(cur_value, int):
# Get instance with id
resget = lec.name2class('LeObject').get(['lodel_id = %d' % cur_value])
if resget is None or len(resget) != 1:
# Bad filter or bad id... raise ?
raise Exception("BAAAAAD")
return cur_value
## @brief checks datas consistency
# @param lec LeCrud : A LeCrud child instance
# @param fname str : concerned field name
# @param datas dict : Datas of lec
# @return True if ok else an Exception instance
def check_data_consistency(self, lec, fname, datas):
if self.superior:
return self.check_sup_consistency(lec, fname, datas)
else:
return self.check_sub_consistency(lec, fname, datas)
def check_sup_consistency(self, lec, fname, datas):
if lec.implements_lerel2type():
# Checking consistency for a rel2type relation
lesup = datas[lec._superior_field_name]
lesub = datas[lec._subordinate_field_name]
if lesub.__class__ not in lesup._linked_types.values():
return FieldTypeError("Rel2type not authorized between %s and %s"%(lesup, lesub))
pass
def check_sub_consistency(self, lec, fname, datas):
pass

View file

@ -0,0 +1,24 @@
#-*- coding: utf-8 -*-
from EditorialModel import classtypes as lodelconst
from . import char
from .generic import FieldTypeError
class EmFieldType(char.EmFieldType):
help = 'Only designed to handle relation_name field value'
def __init__(self, **kwargs):
super().__init__(**kwargs)
def check_data_consistency(self, lec, fname, datas):
# We are in a context where lec is a LeRelation child class
if lec.implements_lerel2type():
superior = datas[lodelconst.relation_superior]
if datas[fname] not in superior._linked_types.keys():
return FieldTypeError("Bad relation_name for rel2type %s : '%s'" % (lec.__name__, datas[fname]))
elif (datas[fname] is not None) and len(datas[fname] > 0):
return FieldTypeError("No relation_name allowed for hierarchical relations")
return True

View file

@ -0,0 +1,48 @@
#-*- coding: utf-8 -*-
from .char import EmFieldType
import EditorialModel.classtypes as classtypes
import leapi.lerelation as lerelation
class EmFieldType(EmFieldType):
help = 'Stores a relation\'s nature'
def __init__(self, **kwargs):
kwargs.update({'nullable': True})
super().__init__(**kwargs)
def _check_data_value(self, value):
if value is None or ( value in classtypes.EmNature.getall()):
return value, None
else:
return None, ValueError("Unknown relation nature '%s'"%value)
## @todo implements types checks
def check_data_consistency(self, lec, fname, datas):
#Checking given component
#Replace with a test from _LeCrud
#if not isinstance(lec, lerelation._LeRelation):
# return ValueError("A field naturerelation has to be in a LeRelation object, but this one is in a '%s'"%lec.__name__)
nature = datas[fname]
lesup = datas[lec._superior_field_name]
lesub = datas[lec._subordinate_field_name]
if nature is None:
#if not isinstance(lec, lerelation.LeRel2Type):
#Replace with a test from _LeCrud
# return ValueError("Only LeRel2Type are allowed to have NULL nature")
pass
else:
#if not isinstance(lec, lerelation.LeHierarch):
#Replace with a test from _LeCrud
# return ValueError("Only LeHierarch has not NULL nature")
#Checking if nature <=> lesup & lesub classtypes
if not lesub.is_partial():
if nature not in classtypes.EmClassType.natures(lesub._classtype):
return ValueError("Invalid nature '%s' for %s"%(nature, lesub.__class__.__name__))
if not lesup.is_partial() and not lesup.is_root():
if nature not in classtypes.EmClassType.natures(lesup._classtype):
return ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
return True

View file

@ -0,0 +1,26 @@
#-*- coding: utf-8 -*-
from . import integer
## @todo This EmFieldType is a bit dirty....
class EmFieldType(integer.EmFieldType):
help = 'Integer primary key fieldtype'
def __init__(self, **kwargs):
# Allowed argument => value for this EmFieldType
allowed = {
'nullable': False,
'uniq': False,
'internal': 'autosql',
'primary': True,
}
# Checking args
for name, value in kwargs.items():
if name in allowed and value != allowed[name]:
raise ValueError("The value '%s' for argument '%s' for pk EmFieldType is not allowed" % (value, name))
kwargs.update(allowed)
super(EmFieldType, self).__init__(**kwargs)

View file

@ -0,0 +1,27 @@
#-*- coding utf-8 -*-
import EditorialModel.classtypes
import leapi.lerelation as lerelation
from .generic import FieldTypeError
from . import integer
class EmFieldType(integer.EmFieldType):
help = 'Fieldtype designed to handle relations rank'
_construct_datas_deps = [EditorialModel.classtypes.relation_superior]
def __init__(self, **kwargs):
super().__init__(**kwargs)
def construct_data(self, lec, fname, datas, cur_value):
superior = datas[EditorialModel.classtypes.relation_superior]
if lec.is_lerel2type():
relation_name = datas[EditorialModel.classtypes.relation_name]
cur_value = lec.get_max_rank(superior, relation_name)
elif lec.is_lehierarch():
cur_value = lec.get_max_rank(superior, datas['nature'])
else:
raise ValueError("Called with bad class : ", lec.__name__)
return cur_value

View file

@ -0,0 +1,27 @@
#-*- coding: utf-8 -*-
import re
from . import char
class EmFieldType(char.EmFieldType):
help = 'String field validated with a regex. Take two options : max_length and regex'
## @brief A char field validated with a regex
# @param regex str : a regex string (passed as argument to re.compile() )
# @param max_length int : the maximum length for this field
# @param **kwargs
def __init__(self, regex='', max_length=10, **kwargs):
self.regex = regex
self.compiled_re = re.compile(regex) # trigger an error if invalid regex
super().__init__(check_data_value=check_value, max_length=max_length, **kwargs)
def _check_data_value(self,value):
error = None
if not self.compiled_re.match(value):
value = ''
error = TypeError('"%s" don\'t match the regex "%s"' % (value, self.regex))
return (value, error)

View file

@ -0,0 +1,21 @@
#-*- coding: utf-8 -*-
from .generic import GenericFieldType
from EditorialModel.fields import EmField
class EmFieldType(GenericFieldType):
help = 'Relationnal field (relation2type). Take rel_to_type_id as option (an EmType uid)'
ftype = 'rel2type'
def __init__(self, rel_to_type_id, **kwargs):
self.rel_to_type_id = rel_to_type_id
super(EmFieldType, self).__init__(**kwargs)
def get_related_type(self):
return self.model.component(self.rel_to_type_id)
def get_related_fields(self):
return [f for f in self.model.components(EmField) if f.rel_field_id == self.uid]

View file

@ -0,0 +1,14 @@
#-*- coding: utf-8 -*-
from .generic import GenericFieldType
class EmFieldType(GenericFieldType):
help = 'A text field (big string)'
ftype = 'text'
def __init__(self, **kwargs):
super(EmFieldType, self).__init__(ftype='text', **kwargs)

330
EditorialModel/model.py Normal file
View file

@ -0,0 +1,330 @@
#-*- coding: utf-8 -*-
## @package EditorialModel.model
# Contains the class managing and editorial model
import EditorialModel
from DataSource.dummy.migrationhandler import DummyMigrationHandler
from EditorialModel.backend.dummy_backend import EmBackendDummy
from EditorialModel.classes import EmClass
from EditorialModel.fields import EmField
from EditorialModel.types import EmType
from EditorialModel.exceptions import EmComponentCheckError, EmComponentNotExistError, MigrationHandlerChangeError
import hashlib
## @brief Manages the Editorial Model
class Model(object):
components_class = [EmClass, EmType, EmField]
## Constructor
#
# @param backend unknown: A backend object instanciated from one of the classes in the backend module
# @param migration_handler : A migration handler
def __init__(self, backend, migration_handler=None):
if migration_handler is None:
self.migration_handler = DummyMigrationHandler()
elif issubclass(migration_handler.__class__, DummyMigrationHandler):
self.migration_handler = migration_handler
else:
raise TypeError("migration_handler should be an instance from a subclass of DummyMigrationhandler")
self.backend = None
self.set_backend(backend)
self._components = {'uids': {}, 'EmClass': [], 'EmType': [], 'EmField': []}
self.load()
def __hash__(self):
components_dump = ""
for _, comp in self._components['uids'].items():
components_dump += str(hash(comp))
hashstring = hashlib.new('sha512')
hashstring.update(components_dump.encode('utf-8'))
return int(hashstring.hexdigest(), 16)
def __eq__(self, other):
return self.__hash__() == other.__hash__()
@staticmethod
## Given a name return an EmComponent child class
# @param class_name str : The name to identify an EmComponent class
# @return A python class or False if the class_name is not a name of an EmComponent child class
def emclass_from_name(class_name):
for cls in Model.components_class:
if cls.__name__ == class_name:
return cls
return False
@staticmethod
## Given a python class return a name
# @param em_class : The python class we want the name
# @return A class name as string or False if cls is not an EmComponent child class
def name_from_emclass(em_class):
if em_class not in Model.components_class:
return False
return em_class.__name__
## Loads the structure of the Editorial Model
#
# Gets all the objects contained in that structure and creates a dict indexed by their uids
# @todo Change the thrown exception when a components check fails
# @throw ValueError When a component class don't exists
def load(self):
datas = self.backend.load()
for uid, kwargs in datas.items():
#Store and delete the EmComponent class name from datas
cls_name = kwargs['component']
del kwargs['component']
cls = self.emclass_from_name(cls_name)
if cls:
kwargs['uid'] = uid
# create a dict for the component and one indexed by uids, store instanciated component in it
self._components['uids'][uid] = cls(model=self, **kwargs)
self._components[cls_name].append(self._components['uids'][uid])
else:
raise ValueError("Unknow EmComponent class : '" + cls_name + "'")
#Sorting by rank
for component_class in Model.components_class:
self.sort_components(component_class)
#Check integrity
loaded_comps = [(uid, component) for uid, component in self._components['uids'].items()]
for uid, component in loaded_comps:
try:
component.check()
except EmComponentCheckError as exception_object:
raise EmComponentCheckError("The component with uid %d is not valid. Check returns the following error : \"%s\"" % (uid, str(exception_object)))
#Everything is done. Indicating that the component initialisation is over
component.init_ended()
## Saves data using the current backend
# @param filename str | None : if None use the current backend file (provided at backend instanciation)
def save(self, filename=None):
return self.backend.save(self, filename)
## Given a EmComponent child class return a list of instances
# @param cls EmComponent|str : A python class
# @return a list of instances or False if the class is not an EmComponent child
# @todo better implementation
def components(self, cls=None):
if isinstance(cls, str):
cls = self.emclass_from_name(cls)
if not cls:
return False
if cls is None:
return [self.component(uid) for uid in self._components['uids']]
key_name = self.name_from_emclass(cls)
return False if key_name is False else self._components[key_name]
## Return an EmComponent given an uid
# @param uid int : An EmComponent uid
# @return The corresponding instance or False if uid don't exists
def component(self, uid):
return False if uid not in self._components['uids'] else self._components['uids'][uid]
## @brief Search in all the editorial model for a component with a specific name
# @param name str : the searched name
# @param comp_cls str|EmComponent : filter on component type (see components() method)
# @return a list of component with a specific name
def component_from_name(self, name, comp_cls=None):
if comp_cls == EmField or comp_cls == 'EmField':
res = list()
for field, fieldname in [(f, f.name) for f in self.components('EmField')]:
if fieldname == name:
res.append(field)
return res
for comp, compname in [(c, c.name) for c in self.components(comp_cls)]:
if compname == name:
return comp
return False
## Sort components by rank in Model::_components
# @param component_class pythonClass : The type of components to sort
# @throw AttributeError if emclass is not valid
# @warning disabled the test on component_class because of EmField new way of working
def sort_components(self, component_class):
#if component_class not in self.components_class:
# raise AttributeError("Bad argument emclass : '" + str(component_class) + "', excpeting one of " + str(self.components_class))
self._components[self.name_from_emclass(component_class)] = sorted(self.components(component_class), key=lambda comp: comp.rank)
## Return a new uid
# @return a new uid
def new_uid(self):
used_uid = [int(uid) for uid in self._components['uids'].keys()]
return sorted(used_uid)[-1] + 1 if len(used_uid) > 0 else 1
## Create a component from a component type and datas
#
# @note if datas does not contains a rank the new component will be added last
# @note datas['rank'] can be an integer or two specials strings 'last' or 'first'
#
# @warning The uid parameter is designed to be used only by Model.load()
# @param uid int|None : If given, don't generate a new uid
# @param component_type str : a component type ( component_class, component_fieldgroup, component_field or component_type )
# @param datas dict : the options needed by the component creation
# @return The created EmComponent
# @throw ValueError if datas['rank'] is not valid (too big or too small, not an integer nor 'last' or 'first' )
# @todo Handle a raise from the migration handler
# @todo Transform the datas arg in **datas ?
def create_component(self, component_type, datas, uid=None):
if not (uid is None) and (not isinstance(uid, int) or uid <= 0 or uid in self._components['uids']):
raise ValueError("Invalid uid provided : %s" % repr(uid))
if component_type not in [n for n in self._components.keys() if n != 'uids']:
raise ValueError("Invalid component_type rpovided")
else:
em_obj = self.emclass_from_name(component_type)
rank = 'last'
if 'rank' in datas:
rank = datas['rank']
del datas['rank']
datas['uid'] = uid if uid else self.new_uid()
em_component = em_obj(model=self, **datas)
em_component.rank = em_component.get_max_rank() + 1 # Inserting last by default
self._components['uids'][em_component.uid] = em_component
self._components[component_type].append(em_component)
if rank != 'last':
em_component.set_rank(1 if rank == 'first' else rank)
#everything done, indicating that initialisation is over
em_component.init_ended()
#register the creation in migration handler
try:
self.migration_handler.register_change(self, em_component.uid, None, em_component.attr_dump())
except MigrationHandlerChangeError as exception_object:
#Revert the creation
self.components(em_component.__class__).remove(em_component)
del self._components['uids'][em_component.uid]
raise exception_object
self.migration_handler.register_model_state(self, hash(self))
if uid is None:
#Checking the component
em_component.check()
if component_type == 'EmClass':
# !!! If uid is not None it means that we shouldn't create components automatically !!!
self.add_default_class_fields(em_component.uid)
return em_component
## @brief Add to a class (if not exists) the default fields
#
# @param class_uid int : An EmClass uid
# @throw ValueError if class_uid in not an EmClass uid
def add_default_class_fields(self, class_uid):
if class_uid not in self._components['uids']:
raise ValueError("The uid '%d' don't exists" % class_uid)
emclass = self._components['uids'][class_uid]
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The uid '%d' is not an EmClass uid" % class_uid)
"""
fgroup_name = EmClass.default_fieldgroup
if fgroup_name not in [fg.name for fg in emclass.fieldgroups() ]:
#Creating the default fieldgroup if not existing
fg_datas = { 'name' : fgroup_name, 'class_id': emclass.uid }
fgroup = self.create_component('EmFieldGroup', fg_datas)
fgid = fgroup.uid
else:
for fg in emclass.fieldgroups():
if fg.name == fgroup_name:
fgid = fg.uid
break
"""
default_fields = emclass.default_fields_list()
for fname, fdatas in default_fields.items():
if not (fname in [f.name for f in emclass.fields()]):
#Adding the field
fdatas['name'] = fname
fdatas['class_id'] = class_uid
self.create_component('EmField', fdatas)
## Delete a component
# @param uid int : Component identifier
# @throw EmComponentNotExistError
# @todo unable uid check
# @todo Handle a raise from the migration handler
def delete_component(self, uid):
em_component = self.component(uid)
if not em_component:
raise EmComponentNotExistError()
if em_component.delete_check():
#register the deletion in migration handler
self.migration_handler.register_change(self, uid, self.component(uid).attr_dump(), None)
# delete internal lists
self._components[self.name_from_emclass(em_component.__class__)].remove(em_component)
del self._components['uids'][uid]
#Register the new EM state
self.migration_handler.register_model_state(self, hash(self))
return True
return False
## Changes the current backend
#
# @param backend unknown: A backend object
def set_backend(self, backend):
if issubclass(backend.__class__, EmBackendDummy):
self.backend = backend
else:
raise TypeError('Backend should be an instance of a EmBackednDummy subclass')
## Returns a list of all the EmClass objects of the model
def classes(self):
return list(self._components[self.name_from_emclass(EmClass)])
## Use a new migration handler, re-apply all the ME to this handler
#
# @param new_mh MigrationHandler: A migration_handler object
# @warning : if a relational-attribute field (with 'rel_field_id') comes before it's relational field (with 'rel_to_type_id'), this will blow up
def migrate_handler(self, new_mh):
new_me = Model(EmBackendDummy(), new_mh)
relations = {'fields_list': [], 'superiors_list': []}
# re-create component one by one, in components_class[] order
for cls in self.components_class:
for component in self.components(cls):
component_type = self.name_from_emclass(cls)
component_dump = component.attr_dump()
# Save relations between component to apply them later
for relation in relations.keys():
if relation in component_dump and component_dump[relation]:
relations[relation].append((component.uid, component_dump[relation]))
del component_dump[relation]
new_me.create_component(component_type, component_dump, component.uid)
# apply selected field to types
for fields_list in relations['fields_list']:
uid, fields = fields_list
for field_id in fields:
new_me.component(uid).select_field(new_me.component(field_id))
# add superiors to types
for superiors_list in relations['superiors_list']:
uid, sup_list = superiors_list
for nature, superiors_uid in sup_list.items():
for superior_uid in superiors_uid:
new_me.component(uid).add_superior(new_me.component(superior_uid), nature)
del new_me
self.migration_handler = new_mh

192
EditorialModel/randomem.py Normal file
View file

@ -0,0 +1,192 @@
#-*- coding: utf-8 -*-
## @package EditorialModel.randomem
#
# @warning BROKEN because there is no more fieldgroups
# Provide methods for random EM generation
import random
from EditorialModel.backend.dummy_backend import EmBackendDummy
from EditorialModel.model import Model
from EditorialModel.fields import EmField
from EditorialModel.types import EmType
from EditorialModel.classtypes import EmClassType
from Lodel.utils.mlstring import MlString
class RandomEm(object):
## @brief Instanciate a class allowing to generate random EM
# @see RandomEm::random_em()
def __init__(self, backend=None, **kwargs):
self.backend = backend
self.kwargs = kwargs
## @brief Return a random EM
# @return A random EM
def gen(self):
return self.random_em(self.backend, **self.kwargs)
@classmethod
## @brief Generate a random editorial model
#
# The random generator can be tuned with integer parameters
# that represent probability or maximum numbers of items.
# The probability (chances) works like 1/x chances to append
# with x the tunable parameter
# Tunable generator parameters :
# - classtype : Chances for a classtype to be empty (default 0)
# - nclass : Maximum number of classes per classtypes (default 5)
# - nofg : Chances for a classe to have no fieldgroup associated to it (default 10)
# - notype : Chances for a classe to have no type associated to it (default 5)
# - seltype : Chances for a type to select an optionnal field (default 2)
# - ntypesuperiors : Chances for a type to link with a superiors (default 3)
# - nofields : Chances for a fieldgroup to be empty (default 10)
# - nfields : Maximum number of field per fieldgroups (default 8)
# - rfields : Maximum number of relation_to_type attributes fields (default 5)
# - optfield : Chances for a field to be optionnal (default 2)
# @param backend : A backend to use with the new EM
# @param **kwargs dict : Provide tunable generation parameter
# @param cls
# @return A randomly generate EM
def random_em(cls, backend=None, **kwargs):
ed_mod = Model(EmBackendDummy if backend is None else backend)
chances = {
'classtype': 0, # a class in classtype
'nclass': 5, # max number of classes per classtype
'nofg': 10, # no fieldgroup in a class
'nfg': 5, # max number of fieldgroups per classes
'notype': 10, # no types in a class
'ntype': 8, # max number of types in a class
'seltype': 2, # chances to select an optional field
'ntypesuperiors': 2, # chances to link with a superior
'nofields': 10, # no fields in a fieldgroup
'nfields': 8, # max number of fields per fieldgroups
'nr2tfields': 1, # max number of rel2type fields per fieldgroups
'rfields': 5, # max number of attributes relation fields
'optfield': 2, # chances to be optionnal
}
for name, value in kwargs.items():
if name not in chances:
#warning
pass
else:
chances[name] = value
#classes creation
for classtype in EmClassType.getall():
if random.randint(0, chances['classtype']) == 0:
for _ in range(random.randint(1, chances['nclass'])):
cdats = cls._rnd_component_datas()
cdats['classtype'] = classtype['name']
ed_mod.create_component('EmClass', cdats)
for emclass in ed_mod.classes():
#fieldgroups creation
"""
if random.randint(0, chances['nofg']) != 0:
for _ in range(random.randint(1, chances['nfg'])):
fgdats = cls._rnd_component_datas()
fgdats['class_id'] = emclass.uid
ed_mod.create_component('EmFieldGroup', fgdats)
"""
#types creation
if random.randint(0, chances['notype']) != 0:
for _ in range(random.randint(1, chances['ntype'])):
tdats = cls._rnd_component_datas()
tdats['class_id'] = emclass.uid
ed_mod.create_component('EmType', tdats)
#random type hierarchy
for emtype in ed_mod.components(EmType):
possible = emtype.possible_superiors()
for nat in possible:
if len(possible[nat]) > 0 and random.randint(0, chances['ntypesuperiors']) == 0:
random.shuffle(possible[nat])
for i in range(random.randint(1, len(possible[nat]))):
emtype.add_superior(possible[nat][i], nat)
#fields creation
ft_l = [ftname for ftname in EmField.fieldtypes_list() if ftname != 'pk' and ftname != 'rel2type']
for emc in ed_mod.components('EmClass'):
if random.randint(0, chances['nofields']) != 0:
for _ in range(random.randint(1, chances['nfields'])):
field_type = ft_l[random.randint(0, len(ft_l) - 1)]
fdats = cls._rnd_component_datas()
fdats['fieldtype'] = field_type
fdats['class_id'] = emc.uid
if random.randint(0, chances['optfield']) == 0:
fdats['optional'] = True
ed_mod.create_component('EmField', fdats)
#rel2type creation (in case none where created before
for emc in ed_mod.components('EmClass'):
for _ in range(random.randint(0, chances['nr2tfields'])):
field_type = 'rel2type'
fdats = cls._rnd_component_datas()
fdats['fieldtype'] = field_type
fdats['class_id'] = emc.uid
emtypes = ed_mod.components(EmType)
fdats['rel_to_type_id'] = emtypes[random.randint(0, len(emtypes) - 1)].uid
if random.randint(0, chances['optfield']) == 0:
fdats['optional'] = True
ed_mod.create_component('EmField', fdats)
# relationnal fields creation
ft_l = [field_type for field_type in EmField.fieldtypes_list() if field_type != 'rel2type' and field_type != 'pk']
for emrelf in [f for f in ed_mod.components(EmField) if f.fieldtype == 'rel2type']:
for _ in range(0, chances['rfields']):
field_type = ft_l[random.randint(0, len(ft_l) - 1)]
fdats = cls._rnd_component_datas()
fdats['rel_field_id'] = emrelf.uid
fdats['fieldtype'] = field_type
fdats['class_id'] = emrelf.class_id
if random.randint(0, chances['optfield']) == 0:
fdats['optional'] = True
ed_mod.create_component('EmField', fdats)
#selection optionnal fields
for emtype in ed_mod.components(EmType):
selectable = [field for field in emtype.em_class.fields() if field.optional]
for field in selectable:
if random.randint(0, chances['seltype']) == 0:
emtype.select_field(field)
return ed_mod
@staticmethod
## @brief Generate a random string
# @warning dirty cache trick with globals()
# @return a randomly selected string
def _rnd_str(words_src='/usr/share/dict/words'):
if '_words' not in globals() or globals()['_words_fname'] != words_src:
globals()['_words_fname'] = words_src
with open(words_src, 'r') as fpw:
globals()['_words'] = [l.strip() for l in fpw]
words = globals()['_words']
return words[random.randint(0, len(words) - 1)]
@classmethod
## @brief Generate a random MlString
# @param nlng : Number of langs in the MlString
# @return a random MlString with nlng translations
# @todo use a dict to generated langages
def _rnd_mlstr(cls, nlng):
ret = MlString()
for _ in range(nlng):
ret.set(cls._rnd_str(), cls._rnd_str())
return ret
@classmethod
## @brief returns randomly generated datas for an EmComponent
# @return a dict with name, string and help_text
def _rnd_component_datas(cls):
mlstr_nlang = 2
ret = dict()
ret['name'] = cls._rnd_str()
ret['string'] = cls._rnd_mlstr(mlstr_nlang)
ret['help_text'] = cls._rnd_mlstr(mlstr_nlang)
return ret

View file

603
EditorialModel/test/me.json Normal file
View file

@ -0,0 +1,603 @@
{
"1": {
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmClass",
"name": "textes",
"rank": 1,
"sortcolumn": "rank",
"date_update": "Fri Oct 16 11:05:04 2015",
"classtype": "entity",
"icon": "0",
"string": "{\"___\": \"\", \"fre\": \"Texte\"}",
"help_text": "{\"___\": \"\"}"
},
"2": {
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmClass",
"name": "personnes",
"rank": 1,
"sortcolumn": "rank",
"date_update": "Fri Oct 16 11:05:04 2015",
"classtype": "person",
"icon": "0",
"string": "{\"___\": \"\", \"fre\": \"Personnes\"}",
"help_text": "{\"___\": \"\"}"
},
"4": {
"nullable": false,
"name": "titre",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Titre\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": false,
"rank": 1,
"icon": "0",
"fieldtype": "i18n",
"rel_field_id": null,
"class_id": 1,
"uniq": false,
"internal": false
},
"5": {
"component": "EmType",
"name": "article",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Article\"}",
"sortcolumn": "rank",
"date_create": "Fri Oct 16 11:05:04 2015",
"help_text": "{\"___\": \"\"}",
"rank": 1,
"icon": "0",
"class_id": 1,
"fields_list": [
7
],
"superiors_list": {
"parent": [
14
]
}
},
"6": {
"component": "EmType",
"name": "personne",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Personne\"}",
"sortcolumn": "rank",
"date_create": "Fri Oct 16 11:05:04 2015",
"help_text": "{\"___\": \"\"}",
"rank": 1,
"icon": "0",
"class_id": 2,
"fields_list": [
10
],
"superiors_list": {}
},
"7": {
"nullable": false,
"name": "soustitre",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Sous-titre\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": true,
"rank": 5,
"icon": "0",
"fieldtype": "i18n",
"rel_field_id": null,
"class_id": 1,
"uniq": false,
"internal": false
},
"9": {
"nullable": true,
"name": "nom",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Nom\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": false,
"rank": 1,
"icon": "0",
"fieldtype": "char",
"rel_field_id": null,
"class_id": 2,
"uniq": false,
"internal": false
},
"10": {
"nullable": true,
"name": "prenom",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Pr\\u00e9nom\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": true,
"rank": 3,
"icon": "0",
"fieldtype": "char",
"rel_field_id": null,
"class_id": 2,
"uniq": false,
"internal": false
},
"11": {
"nullable": true,
"name": "auteur",
"uniq": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Auteur\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": false,
"rank": 2,
"icon": "0",
"fieldtype": "rel2type",
"rel_field_id": null,
"class_id": 1,
"rel_to_type_id": 6,
"internal": false
},
"12": {
"nullable": true,
"name": "adresse",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Adresse\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": false,
"rank": 6,
"icon": "0",
"fieldtype": "char",
"rel_field_id": 11,
"class_id": 1,
"uniq": false,
"internal": false
},
"13": {
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmClass",
"name": "publication",
"rank": 2,
"sortcolumn": "rank",
"date_update": "Fri Oct 16 11:05:04 2015",
"classtype": "entity",
"icon": "0",
"string": "{\"___\": \"\", \"fre\": \"Publication\"}",
"help_text": "{\"___\": \"\"}"
},
"14": {
"component": "EmType",
"name": "rubrique",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Rubrique\"}",
"sortcolumn": "rank",
"date_create": "Fri Oct 16 11:05:04 2015",
"help_text": "{\"___\": \"\"}",
"rank": 1,
"icon": "0",
"class_id": 13,
"fields_list": [],
"superiors_list": {
"parent": [
14,
19
]
}
},
"16": {
"nullable": true,
"name": "titre",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Titre\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": false,
"rank": 1,
"icon": "0",
"fieldtype": "char",
"rel_field_id": null,
"class_id": 13,
"uniq": false,
"internal": false
},
"18": {
"nullable": true,
"name": "age",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Age\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": true,
"rank": 5,
"icon": "0",
"fieldtype": "char",
"rel_field_id": null,
"class_id": 2,
"uniq": false,
"internal": false
},
"19": {
"component": "EmType",
"name": "numero",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Num\\u00e9ro\"}",
"sortcolumn": "rank",
"date_create": "Fri Oct 16 11:05:04 2015",
"help_text": "{\"___\": \"\"}",
"rank": 2,
"icon": "0",
"class_id": 13,
"fields_list": [],
"superiors_list": {}
},
"21": {
"nullable": true,
"name": "bleu",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"fre\": \"Bleu\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"optional": true,
"rank": 3,
"icon": "0",
"fieldtype": "char",
"rel_field_id": null,
"class_id": 1,
"uniq": false,
"internal": false
},
"23": {
"is_id_class": true,
"name": "class_id",
"component": "EmField",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
"help_text": "{\"___\": \"\"}",
"optional": false,
"nullable": false,
"rel_field_id": null,
"rank": 4,
"internal": "automatic",
"fieldtype": "emuid",
"immutable": true,
"date_create": "Fri Oct 16 11:05:04 2015",
"class_id": 1,
"uniq": false,
"icon": "0"
},
"24": {
"nullable": true,
"name": "string",
"component": "EmField",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
"help_text": "{\"___\": \"\"}",
"internal": "automatic",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 7,
"immutable": false,
"fieldtype": "char",
"class_id": 1,
"optional": false,
"max_length": 128,
"uniq": false,
"icon": "0"
},
"25": {
"is_id_class": false,
"name": "type_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 8,
"internal": "automatic",
"fieldtype": "emuid",
"class_id": 1,
"optional": false,
"immutable": true,
"nullable": false,
"uniq": false,
"icon": "0"
},
"26": {
"nullable": false,
"name": "lodel_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 9,
"internal": "autosql",
"fieldtype": "pk",
"immutable": true,
"optional": false,
"class_id": 1,
"uniq": false,
"icon": "0"
},
"28": {
"is_id_class": true,
"name": "class_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 2,
"internal": "automatic",
"fieldtype": "emuid",
"class_id": 2,
"optional": false,
"immutable": true,
"nullable": false,
"uniq": false,
"icon": "0"
},
"29": {
"nullable": true,
"name": "string",
"component": "EmField",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
"help_text": "{\"___\": \"\"}",
"internal": "automatic",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 4,
"immutable": false,
"fieldtype": "char",
"class_id": 2,
"optional": false,
"max_length": 128,
"uniq": false,
"icon": "0"
},
"30": {
"is_id_class": false,
"name": "type_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 6,
"internal": "automatic",
"fieldtype": "emuid",
"class_id": 2,
"optional": false,
"immutable": true,
"nullable": false,
"uniq": false,
"icon": "0"
},
"31": {
"nullable": false,
"name": "lodel_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 7,
"internal": "autosql",
"fieldtype": "pk",
"immutable": true,
"optional": false,
"class_id": 2,
"uniq": false,
"icon": "0"
},
"33": {
"is_id_class": true,
"name": "class_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"class identifier\", \"fre\": \"identifiant de la classe\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 2,
"internal": "automatic",
"fieldtype": "emuid",
"class_id": 13,
"optional": false,
"immutable": true,
"nullable": false,
"uniq": false,
"icon": "0"
},
"34": {
"nullable": true,
"name": "string",
"component": "EmField",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"String representation\", \"fre\": \"Repr\\u00e9sentation textuel\"}",
"help_text": "{\"___\": \"\"}",
"internal": "automatic",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 3,
"immutable": false,
"fieldtype": "char",
"class_id": 13,
"optional": false,
"max_length": 128,
"uniq": false,
"icon": "0"
},
"35": {
"is_id_class": false,
"name": "type_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"type identifier\", \"fre\": \"identifiant de la type\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 4,
"internal": "automatic",
"fieldtype": "emuid",
"class_id": 13,
"optional": false,
"immutable": true,
"nullable": false,
"uniq": false,
"icon": "0"
},
"36": {
"nullable": false,
"name": "lodel_id",
"date_update": "Fri Oct 16 11:05:04 2015",
"string": "{\"___\": \"\", \"eng\": \"lodel identifier\", \"fre\": \"identifiant lodel\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_field_id": null,
"rank": 5,
"internal": "autosql",
"fieldtype": "pk",
"immutable": true,
"optional": false,
"class_id": 13,
"uniq": false,
"icon": "0"
},
"37": {
"nullable": false,
"name": "modification_date",
"date_update": "Wed Nov 4 10:52:13 2015",
"string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Wed Nov 4 10:52:13 2015",
"optional": false,
"class_id": 1,
"rank": 10,
"internal": "autosql",
"fieldtype": "datetime",
"now_on_create": true,
"rel_field_id": null,
"immutable": true,
"uniq": false,
"now_on_update": true,
"icon": "0"
},
"38": {
"nullable": false,
"name": "creation_date",
"component": "EmField",
"date_update": "Wed Nov 4 10:52:13 2015",
"string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
"help_text": "{\"___\": \"\"}",
"internal": "autosql",
"date_create": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"rank": 11,
"immutable": true,
"fieldtype": "datetime",
"now_on_create": true,
"optional": false,
"class_id": 1,
"uniq": false,
"icon": "0"
},
"39": {
"nullable": false,
"name": "modification_date",
"date_update": "Wed Nov 4 10:52:13 2015",
"string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Wed Nov 4 10:52:13 2015",
"optional": false,
"class_id": 2,
"rank": 8,
"internal": "autosql",
"fieldtype": "datetime",
"now_on_create": true,
"rel_field_id": null,
"immutable": true,
"uniq": false,
"now_on_update": true,
"icon": "0"
},
"40": {
"nullable": false,
"name": "creation_date",
"component": "EmField",
"date_update": "Wed Nov 4 10:52:13 2015",
"string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
"help_text": "{\"___\": \"\"}",
"internal": "autosql",
"date_create": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"rank": 9,
"immutable": true,
"fieldtype": "datetime",
"now_on_create": true,
"optional": false,
"class_id": 2,
"uniq": false,
"icon": "0"
},
"41": {
"nullable": false,
"name": "modification_date",
"date_update": "Wed Nov 4 10:52:13 2015",
"string": "{\"___\": \"\", \"eng\": \"Modification date\", \"fre\": \"Date de modification\"}",
"help_text": "{\"___\": \"\"}",
"component": "EmField",
"date_create": "Wed Nov 4 10:52:13 2015",
"optional": false,
"class_id": 13,
"rank": 6,
"internal": "autosql",
"fieldtype": "datetime",
"now_on_create": true,
"rel_field_id": null,
"immutable": true,
"uniq": false,
"now_on_update": true,
"icon": "0"
},
"42": {
"nullable": false,
"name": "creation_date",
"component": "EmField",
"date_update": "Wed Nov 4 10:52:13 2015",
"string": "{\"___\": \"\", \"eng\": \"Creation date\", \"fre\": \"Date de cr\\u00e9ation\"}",
"help_text": "{\"___\": \"\"}",
"internal": "autosql",
"date_create": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"rank": 7,
"immutable": true,
"fieldtype": "datetime",
"now_on_create": true,
"optional": false,
"class_id": 13,
"uniq": false,
"icon": "0"
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,194 @@
"""
Tests for the EmClass class
"""
import os
import logging
from unittest import TestCase
import unittest
import EditorialModel
from EditorialModel.classes import EmClass
from EditorialModel.classtypes import EmClassType
from EditorialModel.types import EmType
from EditorialModel.fields import EmField
from EditorialModel.model import Model
from EditorialModel.backend.json_backend import EmBackendJson
from DataSource.dummy.migrationhandler import DummyMigrationHandler
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
EM_TEST = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'me.json')
EM_TEST_OBJECT = None
## run once for this module
def setUpModule():
global EM_TEST_OBJECT
#EM_TEST_OBJECT = Model(EmBackendJson(EM_TEST), migration_handler=DjandoMigrationHandler('LodelTestInstance'))
EM_TEST_OBJECT = Model(EmBackendJson(EM_TEST), migration_handler=DummyMigrationHandler())
logging.basicConfig(level=logging.CRITICAL)
class ClassesTestCase(TestCase):
# run before every instanciation of the class
@classmethod
def setUpClass(cls):
pass
#sqlsetup.init_db()
# run before every function of the class
def setUp(self):
pass
# creating an new EmClass should
# - create a table named like the created EmClass
# - insert a new line in em_classes
class TestEmClassCreation(ClassesTestCase):
# create a new EmClass, then test on it
def test_create(self):
ClassesTestCase.setUpClass()
test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testclass1', 'classtype': EmClassType.entity['name']})
# We check that the class has been added in the right list in the model object
class_components_records = EM_TEST_OBJECT.components(EmClass)
self.assertIn(test_class, class_components_records)
# the name should be the one given
test_class = EM_TEST_OBJECT.component(test_class.uid)
self.assertEqual(test_class.name, 'testclass1')
# the classtype should have the name of the EmClassType
test_class = EM_TEST_OBJECT.component(test_class.uid)
self.assertEqual(test_class.classtype, EmClassType.entity['name'])
def test_default_fields(self):
""" Test if the default + common_fields are created when an EmClass is created """
classtype = EmClassType.entity['name']
test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testdefaultfieldclass', 'classtype': classtype})
ctype = EditorialModel.classtypes.EmClassType.get(classtype)
default_fields = ctype['default_fields']
default_fields.update(EditorialModel.classtypes.common_fields)
fnames = [ f.name for f in test_class.fields() ]
self.assertEqual(sorted(fnames), sorted(list(default_fields.keys())))
# Testing class deletion (and associated table drop)
class TestEmClassDeletion(ClassesTestCase):
def setUp(self):
self.names = ['testClasse1', 'testClasse2', 'testClasse3']
self.emclasses = []
self.emclasses.append(EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': self.names[0], 'classtype': EmClassType.entity['name']}))
self.emclasses.append(EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': self.names[1], 'classtype': EmClassType.entry['name']}))
self.emclasses.append(EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': self.names[2], 'classtype': EmClassType.person['name']}))
# tests if the table is deleted after a call to delete
def test_table_delete(self):
""" Test associated table deletion on EmClass deletion """
for _, class_object in enumerate(self.emclasses):
self.assertTrue(EM_TEST_OBJECT.delete_component(class_object.uid), "delete method didn't return True but the class has no fieldgroups")
# TODO check : "table still exists but the class was deleted"
# TODO check : "table doesn't exist but the class was not deleted"
# tests if delete refuse to delete if a class had fieldgroups
def test_table_refuse_delete(self):
""" Test delete on an EmClass that has fieldgroup """
test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testfgclass1', 'classtype': EmClassType.entity['name']})
test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassFalseEmpty', 'classtype': EmClassType.entity['name']})
foo_field = EM_TEST_OBJECT.create_component('EmField', {'name': 'ahah', 'fieldtype':'char', 'class_id':test_class.uid})
self.assertFalse(EM_TEST_OBJECT.delete_component(test_class.uid), "delete method returns True but the class has a non-default field")
# TODO check : "table has been deleted but the class has fieldgroup"
try:
EM_TEST_OBJECT.component(test_class.uid)
except Exception:
self.fail("The class has been deleted but it has non-default field in the default fieldgroup")
# Interface to types
class TestEmClassTypes(ClassesTestCase):
@classmethod
def setUpClass(cls):
pass
def setUp(self):
ClassesTestCase.setUpClass()
self.test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassType', 'classtype': EmClassType.entity['name']})
# tests if types() returns a list of EmType
def test_types(self):
""" Tests if types method returns the right list of EmType """
test_class = EM_TEST_OBJECT.component(self.test_class.uid)
EM_TEST_OBJECT.create_component(EmType.__name__, {'name': 'testClassType1', 'class_id': test_class.uid})
EM_TEST_OBJECT.create_component(EmType.__name__, {'name': 'testClassType2', 'class_id': test_class.uid})
types = test_class.types()
self.assertIsInstance(types, list)
for t in types:
self.assertIsInstance(t, EmType)
# with no type, types() should return an empty list
def test_no_types(self):
""" Test types method on an EmClass with no associated types """
test_class = EM_TEST_OBJECT.component(self.test_class.uid)
types = test_class.types()
self.assertEqual(types, [])
# interface to fields
class TestEmClassFields(ClassesTestCase):
@classmethod
def setUpClass(cls):
pass
def setUp(self):
ClassesTestCase.setUpClass()
self.test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassFields', 'classtype': EmClassType.entity['name']})
# tests if fields() returns a list of EmField
def test_fields(self):
""" testing fields method """
test_class = EM_TEST_OBJECT.component(self.test_class.uid)
EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'f1', 'class_id': test_class.uid, 'fieldtype': 'char'})
EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'f2', 'class_id': test_class.uid, 'fieldtype': 'char'})
fields = test_class.fields()
self.assertIsInstance(fields, list)
for field in fields:
self.assertIsInstance(field, EmField)
# with no field fields() should return an empty list
def test_default_fields(self):
""" Testing fields method on an EmClass with only defaults fields """
test_class = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testClassNoFields', 'classtype': EmClassType.entity['name']})
fields = test_class.fields()
for fname in [f.name for f in fields]:
self.assertIn(fname, EmClassType.get(test_class.classtype)['default_fields'].keys())
# Creating a new EmClass should :
# - create a table named like the created EmClass
# - insert a new line in em_classes
class TestEmClassLinkType(ClassesTestCase):
# create a new EmClass, then test on it
@classmethod
def setUpClass(cls):
ClassesTestCase.setUpClass()
test_entity = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testEntity', 'classtype': EmClassType.entity['name']})
test_entry = EM_TEST_OBJECT.create_component(EmClass.__name__, {'name': 'testEntry', 'classtype': EmClassType.entry['name']})
def testLinkedTypes(self):
""" Test the EmClass.linked_types() method """
for field, linked_type in [ (f, EM_TEST_OBJECT.component(f.rel_to_type_id)) for f in EM_TEST_OBJECT.components(EmField) if 'rel_to_type_id' in f.__dict__]:
self.assertIn(linked_type, field.em_class.linked_types())

View file

@ -0,0 +1,113 @@
import unittest
from EditorialModel.model import Model
from EditorialModel.components import EmComponent
from EditorialModel.classes import EmClass
from EditorialModel.types import EmType
from EditorialModel.fields import EmField
from Lodel.utils.mlstring import MlString
from EditorialModel.backend.json_backend import EmBackendJson
from DataSource.dummy.migrationhandler import DummyMigrationHandler
class TestEmComponent(unittest.TestCase):
def setUp(self):
self.model = Model(EmBackendJson('EditorialModel/test/me.json'))
def test_hashes(self):
""" Testing __hash__ and __eq__ methods """
me1 = Model(EmBackendJson('EditorialModel/test/me.json'))
me2 = Model(EmBackendJson('EditorialModel/test/me.json'), migration_handler=DummyMigrationHandler())
for comp_class in [EmClass, EmType, EmField]:
comp_l1 = me1.components(comp_class)
comp_l2 = me2.components(comp_class)
for i, comp1 in enumerate(comp_l1):
comp2 = comp_l2[i]
self.assertEqual(hash(comp1), hash(comp2), "hashes differs for two EmComponent({}) instanciated from the same backend and files".format(comp_class.__name__))
self.assertTrue(comp1 == comp2)
if not comp1.modify_rank(1):
continue # modification not made, drop this test
self.assertNotEqual(hash(comp1), hash(comp2), "hashes are the same after a modification of rank on one of the two components")
self.assertFalse(comp1 == comp2)
comp2.modify_rank(1)
self.assertEqual(hash(comp1), hash(comp2), "hashes differs for two EmComponent({}) after applying the same modifications on both".format(comp_class.__name__))
self.assertTrue(comp1 == comp2)
def test_virtual_methods(self):
""" Testing virtual methods """
with self.assertRaises(NotImplementedError):
_ = EmComponent(self.model, self.model.new_uid(), 'Invalide')
def test_modify_rank(self):
""" Testing modify_rank and set_rank method """
cls = self.model.classes()[0]
orig_rank = cls.rank
cls.modify_rank(1)
self.assertEqual(orig_rank, cls.rank - 1)
cls.modify_rank(-1)
self.assertEqual(orig_rank, cls.rank)
cls.set_rank(1)
self.assertEqual(cls.rank, 1)
cls.set_rank(2)
self.assertEqual(cls.rank, 2)
max_rank = cls.get_max_rank()
cls.set_rank(max_rank)
self.assertEqual(cls.rank, max_rank)
self.assertFalse(cls.modify_rank(1))
self.assertFalse(cls.modify_rank(-10))
with self.assertRaises(ValueError):
cls.set_rank(0)
with self.assertRaises(ValueError):
cls.set_rank(10)
with self.assertRaises(ValueError):
cls.set_rank(-10)
def test_check(self):
""" Testing check method """
cls = self.model.classes()[0]
cls.rank = 10000
cls.check()
self.assertEqual(cls.rank, cls.get_max_rank())
cls.rank = -1000
cls.check()
self.assertEqual(cls.rank, 1)
def test_dump(self):
""" Testing dump methods """
for comp in self.model.components():
dmp = comp.attr_dump()
self.assertNotIn('uid', dmp)
self.assertNotIn('model', dmp)
self.assertTrue(dmp['help_text'] is None or isinstance(dmp['help_text'], MlString))
self.assertTrue(dmp['string'] is None or isinstance(dmp['string'], MlString))
for dmp_f in dmp:
self.assertFalse(dmp_f.startswith('_'))
def test_uniq_name(self):
""" Testing uniq_name method """
names_l = []
for comp in self.model.components():
#Should be uniq only for types and classes
if isinstance(comp, EmType) or isinstance(comp, EmClass):
self.assertNotIn(comp.uniq_name, names_l)
names_l.append(comp.uniq_name)

View file

@ -0,0 +1,115 @@
import os
from unittest import TestCase
from EditorialModel.fields import EmField
from EditorialModel.model import Model
from EditorialModel.backend.json_backend import EmBackendJson
from EditorialModel.exceptions import EmComponentCheckError
EM_TEST = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'me.json')
EM_TEST_OBJECT = None
## SetUpModule
#
# This function is called once for this module.
def setUpModule():
global EM_TEST_OBJECT
EM_TEST_OBJECT = Model(EmBackendJson(EM_TEST))
def tearDownModule():
#cleanDb(TEST_FIELD_DBNAME)
pass
## FieldTestCase (Class)
#
# The parent class of all other test cases for the fields module.
# It defines a SetUp function and some utility functions for EmField tests.
class FieldTestCase(TestCase):
@classmethod
def setUpClass(cls):
pass
def setUp(self):
self.test_fieldtype = 'integer'
self.test_class = EM_TEST_OBJECT.components('EmClass')[0]
## TestField (Class)
#
# The test class for the fields module
class TestField(FieldTestCase):
## Test_create (Function)
#
# tests the creation process of a field
def test_create(self):
field = EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'testfield1', 'class_id': self.test_class.uid, 'fieldtype': self.test_fieldtype})
# We check that the field has been added
field_records = EM_TEST_OBJECT.component(field.uid)
self.assertIsNot(field_records, False)
# We check that the field has been added in the right list in the model object
field_components_records = EM_TEST_OBJECT.components(EmField)
self.assertIn(field, field_components_records)
def test_invalid_internal(self):
""" Test that internal='object' is reserved for common_fields """
with self.assertRaises(ValueError, msg="Only common_fields should be internal='object'"):
field = EM_TEST_OBJECT.create_component(EmField.__name__, {'name': 'testbadinternal','internal': 'object', 'class_id': self.test_class.uid, 'fieldtype': self.test_fieldtype})
def test_double_rel2type(self):
""" Test the rel2type unicity """
em = EM_TEST_OBJECT
emtype = em.components('EmType')[0]
emclass = [c for c in em.components('EmClass') if c != emtype.em_class][0]
f1 = em.create_component('EmField', {'name': 'testr2t', 'class_id': emclass.uid, 'fieldtype': 'rel2type', 'rel_to_type_id': emtype.uid})
with self.assertRaises(EmComponentCheckError):
f2 = em.create_component('EmField', {'name': 'testr2t2', 'class_id': emclass.uid, 'fieldtype': 'rel2type', 'rel_to_type_id': emtype.uid})
def test_same_name(self):
""" Test the name unicity is the same EmClass"""
em = EM_TEST_OBJECT
emtype = em.components('EmType')[0]
emclass = [c for c in em.components('EmClass') if c != emtype.em_class][0]
f1 = em.create_component('EmField', {'name': 'samename', 'class_id': emclass.uid, 'fieldtype': 'char'})
with self.assertRaises(EmComponentCheckError):
f2 = em.create_component('EmField', {'name': 'samename', 'class_id': emclass.uid, 'fieldtype': 'integer'} )
## Test_Deletion
#
# tests the deletion process of a field
def test_deletion(self):
fields = []
field_names = ['field1', 'field2']
# We create the two fields
for name in field_names:
fields.append(EM_TEST_OBJECT.create_component(EmField.__name__, {'name': name, 'class_id': self.test_class.uid, 'fieldtype': self.test_fieldtype}))
for field in fields:
# We check if the delete process was performed to the end
self.assertTrue(EM_TEST_OBJECT.delete_component(field.uid))
# We check that the field object is not in the editorial model anymore
self.assertFalse(EM_TEST_OBJECT.component(field.uid))
# We check that the field object is not in the EmField components list
field_components_records = EM_TEST_OBJECT.components(EmField)
self.assertNotIn(field, field_components_records)
def test_emclass(self):
""" Test if the EmField.em_class @property method is correct """
for field in EM_TEST_OBJECT.components(EmField):
self.assertIn(field, field.em_class.fields())

View file

@ -0,0 +1,314 @@
import unittest
from unittest.mock import patch
from EditorialModel.model import Model
from EditorialModel.classes import EmClass
from EditorialModel.types import EmType
from EditorialModel.fields import EmField
from EditorialModel.components import EmComponent
from Lodel.utils.mlstring import MlString
from EditorialModel.backend.json_backend import EmBackendJson
from EditorialModel.backend.dummy_backend import EmBackendDummy
from DataSource.dummy.migrationhandler import DummyMigrationHandler
class TestModel(unittest.TestCase):
def setUp(self):
self.ed_mod = Model(EmBackendJson('EditorialModel/test/me.json'))
def test_init(self):
""" Instanciation test """
model = Model(EmBackendJson('EditorialModel/test/me.json'))
self.assertTrue(isinstance(model, Model))
model = Model(EmBackendJson('EditorialModel/test/me.json'), migration_handler=DummyMigrationHandler())
self.assertTrue(isinstance(model, Model))
def test_bad_init(self):
""" Test initialisation with bad arguments """
for bad in [None, int, EmBackendDummy, DummyMigrationHandler, 'foobar']:
with self.assertRaises(TypeError, msg="Tried to instanciate a Model with a bad backend"):
Model(bad)
for bad in [int, EmBackendDummy, DummyMigrationHandler, 'foobar']:
with self.assertRaises(TypeError, msg="Tried to instanciate a Model with a migration_handler"):
Model(EmBackendDummy(), bad)
def test_components(self):
""" Test components fetching """
uid_l = list()
for comp_class in [EmClass, EmType, EmField]:
comp_l = self.ed_mod.components(comp_class)
#Testing types of returned components
for component in comp_l:
self.assertTrue(isinstance(component, comp_class), "Model.components method doesn't return EmComponent of the right type. Asked for {} but got {}".format(type(comp_class), type(component)))
uid_l.append(component.uid)
#Testing that we have fetched all the components
for uid in self.ed_mod._components['uids']:
self.assertIn(uid, uid_l, "Component with uid %d was not fetched" % uid)
#Testing components method without parameters
uid_l = [comp.uid for comp in self.ed_mod.components()]
for uid in self.ed_mod._components['uids']:
self.assertIn(uid, uid_l, "Component with uid %d was not fetched using me.components()" % uid)
self.assertFalse(self.ed_mod.components(EmComponent))
self.assertFalse(self.ed_mod.components(int))
def test_component(self):
""" Test component fetching by uid """
#assert that __hash__, __eq__ and components() are corrects
for comp in self.ed_mod.components():
self.assertEqual(comp, self.ed_mod.component(comp.uid))
for baduid in [-1, 0xffffff, "hello"]:
self.assertFalse(self.ed_mod.component(baduid))
def test_sort_components(self):
""" Test that Model.sort_components method actually sort components """
# disordering an EmClass
cl_l = self.ed_mod.components(EmClass)
last_class = cl_l[0]
last_class.rank = 10000
self.ed_mod.sort_components(EmClass)
self.assertEqual(self.ed_mod._components['EmClass'][-1].uid, last_class.uid, "The sort_components method doesn't really sort by rank")
def test_new_uid(self):
""" Test that model.new_uid return a new uniq uid """
new_uid = self.ed_mod.new_uid()
self.assertNotIn(new_uid, self.ed_mod._components['uids'].keys())
def test_create_component_fails(self):
""" Test the create_component method with invalid arguments """
test_datas = {
'name': 'FooBar',
'classtype': 'entity',
'help_text': None,
'string': None,
}
#Invalid uid
used_uid = self.ed_mod.components()[0].uid
for bad_uid in [used_uid, -1, -1000, 'HelloWorld']:
with self.assertRaises(ValueError, msg="The component was created but the given uid (%s) whas invalid (allready used, negative or WTF)" % bad_uid):
self.ed_mod.create_component('EmClass', test_datas, uid=bad_uid)
#Invalid component_type
for bad_comp_name in ['EmComponent', 'EmFoobar', 'int', int]:
with self.assertRaises(ValueError, msg="The create_component don't raise when an invalid classname (%s) is given as parameter" % bad_comp_name):
self.ed_mod.create_component(bad_comp_name, test_datas)
#Invalid rank
for invalid_rank in [-1, 10000]:
with self.assertRaises(ValueError, msg="A invalid rank (%s) was given" % invalid_rank):
foodat = test_datas.copy()
foodat['rank'] = invalid_rank
self.ed_mod.create_component('EmClass', foodat)
with self.assertRaises(TypeError, msg="A non integer rank was given"):
foodat = test_datas.copy()
foodat['rank'] = 'hello world'
self.ed_mod.create_component('EmClass', foodat)
#Invalid datas
for invalid_datas in [dict(), [1, 2, 3, 4], ('hello', 'world')]:
with self.assertRaises(TypeError, msg="Invalid datas was given in parameters"):
self.ed_mod.create_component('EmClass', invalid_datas)
def test_create_components(self):
""" Test the create_component method mocking the EmComponent constructors """
params = {
'EmClass': {
'cls': EmClass,
'cdats': {
'name': 'FooClass',
'classtype': 'entity',
}
},
'EmType': {
'cls': EmType,
'cdats': {
'name': 'FooType',
'class_id': self.ed_mod.components(EmClass)[0].uid,
'fields_list': [],
}
},
'EmField': {
'cls': EmField,
'cdats': {
'name': 'FooField',
'class_id': self.ed_mod.components(EmClass)[0].uid,
'fieldtype': 'char',
'max_length': 64,
'optional': True,
'internal': False,
'rel_field_id': None,
'icon': '0',
'nullable': False,
'uniq': True,
}
}
}
for cnt in params:
tmpuid = self.ed_mod.new_uid()
cdats = params[cnt]['cdats']
cdats['string'] = MlString()
cdats['help_text'] = MlString()
with patch.object(params[cnt]['cls'], '__init__', return_value=None) as initmock:
try:
self.ed_mod.create_component(cnt, params[cnt]['cdats'])
except AttributeError: # Raises because the component is a MagicMock
pass
cdats['uid'] = tmpuid
cdats['model'] = self.ed_mod
#Check that the component __init__ method was called with the good arguments
initmock.assert_called_once_with(**cdats)
def test_delete_component(self):
""" Test the delete_component method """
#Test that the delete_check() method is called
for comp in self.ed_mod.components():
with patch.object(comp.__class__, 'delete_check', return_value=False) as del_check_mock:
ret = self.ed_mod.delete_component(comp.uid)
del_check_mock.assert_called_once_with()
#Check that when the delete_check() returns False de delete_component() too
self.assertFalse(ret)
#Using a new me for deletion test
new_em = Model(EmBackendJson('EditorialModel/test/me.json'))
for comp in new_em.components():
cuid = comp.uid
cname = new_em.name_from_emclass(comp.__class__)
#Simulate that the delete_check() method returns True
with patch.object(comp.__class__, 'delete_check', return_value=True) as del_check_mock:
ret = new_em.delete_component(cuid)
self.assertTrue(ret)
self.assertNotIn(cuid, new_em._components['uids'])
self.assertNotIn(comp, new_em._components[cname])
def test_set_backend(self):
""" Test the set_backend method """
for backend in [EmBackendJson('EditorialModel/test/me.json'), EmBackendDummy()]:
self.ed_mod.set_backend(backend)
self.assertEqual(self.ed_mod.backend, backend)
for bad_backend in [None, 'wow', int, EmBackendJson]:
with self.assertRaises(TypeError, msg="But bad argument (%s %s) was given" % (type(bad_backend), bad_backend)):
self.ed_mod.set_backend(bad_backend)
def test_migrate_handler_order(self):
""" Test that the migrate_handler() method create component in a good order """
with patch.object(Model, 'create_component', return_value=None) as create_mock:
try:
self.ed_mod.migrate_handler(None)
except AttributeError: # Raises because of the mock
pass
order_comp = ['EmClass', 'EmType', 'EmFieldGroup', 'EmField'] # Excpected creation order
cur_comp = 0
for mcall in create_mock.mock_calls:
#Testing EmComponent order of creation
while order_comp[cur_comp] != mcall[1][0]:
cur_comp += 1
if cur_comp >= len(order_comp):
self.fail('The order of create_component() calls was not respected by migrate_handler() methods')
#Testing uid
comp = self.ed_mod.component(mcall[1][2])
ctype = self.ed_mod.name_from_emclass(comp.__class__)
self.assertEqual(mcall[1][0], ctype, "A component was created using a uid belonging to another component type")
#Testing arguments of create_component
comp_dump = comp.attr_dump()
if 'fields_list' in comp_dump and comp_dump['fields_list']:
del(comp_dump['fields_list'])
if 'superiors_list' in comp_dump and comp_dump['superiors_list']:
del(comp_dump['superiors_list'])
self.assertEqual(mcall[1][1], comp_dump)
def test_migrate_handler_relations(self):
""" Test that the migrate_handler() method create Type relations correctly (selected fields and type hierarchy) """
field_list_orig = []
superiors_list_orig = dict()
field_list_new = []
superiors_list_new = dict()
for emtype in self.ed_mod.components(EmType):
for fluid in emtype.fields_list:
field_list_orig.append(fluid)
for nat, sup_l in emtype.superiors().items():
superiors_list_orig[nat] = [sup.uid for sup in sup_l]
with patch.object(DummyMigrationHandler, 'register_change', return_value=None) as mh_mock:
self.ed_mod.migrate_handler(DummyMigrationHandler())
#Getting cloned EM instance
new_me = mh_mock.mock_calls[-1][1][0]
for emtype in new_me.components(EmType):
for fluid in emtype.fields_list:
field_list_new.append(fluid)
for nat, sup_l in emtype.superiors().items():
superiors_list_new[nat] = [sup.uid for sup in sup_l]
for fluid in field_list_orig:
self.assertIn(fluid, field_list_orig, "Missing selected field (%d) when migrate_handler() is run" % fluid)
for fluid in field_list_new:
self.assertIn(fluid, field_list_new, "A field (%d) is selected when migrate_handler() is run but shouldn't be" % fluid)
for nat, sup_l in superiors_list_orig.items():
for supuid in sup_l:
self.assertIn(supuid, superiors_list_new[nat], "Missing superiors (%d) when migrate_handler() is run" % supuid)
for nat, sup_l in superiors_list_new.items():
for supuid in sup_l:
self.assertIn(supuid, superiors_list_orig[nat], "A superior (%d) is present when migrate_handler is called but shouldn't be" % supuid)
def test_migrate_handler_hashes(self):
""" Testing that the migrate_handler() method create an EM with the same hash as the original EM """
with patch.object(DummyMigrationHandler, 'register_change', return_value=None) as mh_mock:
self.ed_mod.migrate_handler(DummyMigrationHandler())
#Getting the cloned EM instance
last_call = mh_mock.mock_calls[-1]
self.assertEqual(hash(last_call[1][0]), hash(self.ed_mod))
def test_hash(self):
""" Test that __hash__ and __eq__ work properly on models """
me1 = Model(EmBackendJson('EditorialModel/test/me.json'))
me2 = Model(EmBackendJson('EditorialModel/test/me.json'), migration_handler=DummyMigrationHandler())
self.assertEqual(hash(me1), hash(me2), "When instanciate from the same backend & file but with another migration handler the hashes differs")
self.assertTrue(me1.__eq__(me2))
cl_l = me1.classes()
cl_l[0].modify_rank(1)
self.assertNotEqual(hash(me1), hash(me2), "After a class rank modification the hashes are the same")
self.assertFalse(me1.__eq__(me2))
cl_l = me2.classes()
cl_l[0].modify_rank(1)
self.assertEqual(hash(me1), hash(me2), "After doing sames modifications in the two models the hashes differs")
self.assertTrue(me1.__eq__(me2))
def test_compclass_getter(self):
""" Test the Model methods that handles name <-> EmComponent conversion """
for classname in ['EmField', 'EmClass', 'EmType']:
cls = Model.emclass_from_name(classname)
self.assertNotEqual(cls, False, "emclass_from_name return False when '%s' given as parameter" % classname)
self.assertEqual(cls.__name__, classname)
for classname in ['EmComponent', 'EmFoobar', int, EmClass]:
self.assertFalse(Model.emclass_from_name(classname))
for comp_cls in [EmClass, EmField, EmType]:
self.assertEqual(Model.name_from_emclass(comp_cls), comp_cls.__name__)
for comp in self.ed_mod.components(EmField):
self.assertEqual(Model.name_from_emclass(comp.__class__), 'EmField')
for cls in [EmComponent, int, str]:
self.assertFalse(Model.name_from_emclass(cls))

View file

@ -0,0 +1,115 @@
import unittest
from EditorialModel.model import Model
from EditorialModel.classtypes import EmNature
from EditorialModel.backend.json_backend import EmBackendJson
class TypeTestCase(unittest.TestCase):
def setUp(self):
self.model = Model(EmBackendJson('EditorialModel/test/me.json'))
self.rubrique = self.model.component(14)
self.article = self.model.component(5)
self.personne = self.model.component(6)
self.soustitre_field = self.model.component(7)
self.age_field = self.model.component(18)
self.nom_field = self.model.component(9)
self.numero = self.model.component(19)
self.gens_group = self.model.component(17)
self.info_group = self.model.component(3)
self.couleur_group = self.model.component(20)
self.couleur_field = self.model.component(21)
class TestSelectField(TypeTestCase):
def test_select_field(self):
""" Testing optionnal field selection """
self.personne.select_field(self.age_field)
self.assertIn(self.age_field, self.personne.selected_fields())
self.assertIn(self.age_field.uid, self.personne.fields_list)
def test_unselect_field(self):
""" Testing optionnal field unselection """
self.article.unselect_field(self.soustitre_field)
self.assertNotIn(self.soustitre_field, self.article.selected_fields())
self.assertNotIn(self.soustitre_field.uid, self.article.fields_list)
def test_select_field_invalid(self):
""" Testing optionnal field selection with invalid fields """
with self.assertRaises(ValueError):
self.personne.select_field(self.nom_field)
with self.assertRaises(ValueError):
self.personne.select_field(self.soustitre_field)
class TestTypeHierarchy(TypeTestCase):
def test_add_superior(self):
""" Testing add_superior() """
self.numero.add_superior(self.rubrique, EmNature.PARENT)
self.assertIn(self.rubrique, self.numero.superiors()[EmNature.PARENT])
self.assertIn(self.rubrique.uid, self.numero.superiors_list[EmNature.PARENT])
self.assertIn(self.numero, self.rubrique.subordinates()[EmNature.PARENT])
# add it twice, it should not be listed twice
self.numero.add_superior(self.rubrique, EmNature.PARENT)
self.assertEqual(1, len(self.numero.superiors()[EmNature.PARENT]))
def test_del_superior(self):
""" Testing del_superior() """
# rubrique should be a superior of article
self.assertIn(self.rubrique, self.article.superiors()[EmNature.PARENT])
# article should be in rubrique subordinates
self.assertIn(self.article, self.rubrique.subordinates()[EmNature.PARENT])
self.article.del_superior(self.rubrique, EmNature.PARENT)
# article should not have EmNature.PARENT superior anymore
with self.assertRaises(KeyError):
_ = self.article.superiors()[EmNature.PARENT]
# article should not be in rubrique subordinates
self.assertNotIn(self.article, self.rubrique.subordinates()[EmNature.PARENT])
# but rubrique should still be a subordinate of itself
self.assertIn(self.rubrique, self.rubrique.subordinates()[EmNature.PARENT])
# test preservation of superiors of other nature
def test_bad_hierarchy(self):
""" testing bad use of hierarchy """
# add a superior of different classtype
with self.assertRaises(ValueError):
self.numero.add_superior(self.personne, EmNature.PARENT)
# add a superior with bad nature
with self.assertRaises(ValueError):
self.numero.add_superior(self.rubrique, EmNature.IDENTITY)
# delete an invalid superior
self.article.del_superior(self.numero, EmNature.PARENT)
class TestDeleteTypes(TypeTestCase):
def test_delete_types(self):
""" Testing EmType deletion """
# should be okay to delete article
article_id = self.article.uid
self.assertTrue(self.model.delete_component(self.article.uid))
# and it should not exist anymore
self.assertFalse(self.model.component(article_id))
# relations with other type should be deleted
self.assertNotIn(self.article, self.rubrique.subordinates()[EmNature.PARENT])
# rubrique has subordinates, should not be okay to delete
self.assertFalse(self.model.delete_component(self.rubrique.uid))

357
EditorialModel/types.py Normal file
View file

@ -0,0 +1,357 @@
#-*- coding: utf-8 -*-
import EditorialModel
from EditorialModel.components import EmComponent
from EditorialModel.fields import EmField
from EditorialModel.classtypes import EmClassType, EmNature
from EditorialModel.exceptions import MigrationHandlerChangeError, EmComponentCheckError
import EditorialModel.classes
## Represents type of documents
# A type is a specialisation of a class, it can select optional field,
# they have hooks, are organized in hierarchy and linked to other
# EmType with special fields called relation_to_type fields
#
# @see EditorialModel::components::EmComponent
# @todo sortcolumn handling
class EmType(EmComponent):
ranked_in = 'class_id'
## Instanciate a new EmType
# @todo define and check types for icon and sortcolumn
# @todo better check self.subordinates
def __init__(self, model, uid, name, class_id, fields_list=None, superiors_list=None, icon='0', sortcolumn='rank', string=None, help_text=None, date_update=None, date_create=None, rank=None):
self.class_id = class_id
self.check_type('class_id', int)
self.fields_list = fields_list if fields_list is not None else []
self.check_type('fields_list', list)
for field_uid in self.fields_list:
if not isinstance(field_uid, int):
raise AttributeError("Excepted fields_list to be a list of integers, but found a " + str(type(field_uid)) + " in it")
self.superiors_list = superiors_list if superiors_list is not None else {}
self.check_type('superiors_list', dict)
for nature, superiors_uid in self.superiors_list.items():
if nature not in [EmNature.PARENT, EmNature.TRANSLATION, EmNature.IDENTITY]:
raise AttributeError("Nature '%s' of superior is not allowed !" % nature)
if not isinstance(superiors_uid, list):
raise AttributeError("Excepted superiors of nature '%s' to be an list !" % nature)
for superior_uid in superiors_uid:
if not isinstance(superior_uid, int):
raise AttributeError("Excepted superiors_list of nature '%s' to be a list of integers, but found a '%s' in it" % str(type(superior_uid)))
self.icon = icon
self.sortcolumn = sortcolumn
super(EmType, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
@classmethod
## Create a new EmType and instanciate it
# @param name str: The name of the new type
# @param em_class EmClass: The class that the new type will specialize
# @param sortcolumn str : The name of the field that will be used to sort
# @param **em_component_args : @ref EditorialModel::components::create()
# @param cls
# @return An EmType instance
# @throw EmComponentExistError if an EmType with this name but different attributes exists
# @see EmComponent::__init__()
#
# @todo check that em_class is an EmClass object (fieldtypes can handle it)
def create(cls, name, em_class, sortcolumn='rank', **em_component_args):
return super(EmType, cls).create(name=name, class_id=em_class.uid, sortcolumn=sortcolumn, **em_component_args)
@property
## Return the EmClassType of the type
# @return EditorialModel.classtypes.*
def classtype(self):
return getattr(EmClassType, self.em_class.classtype)
@property
## Return an instance of the class this type belongs to
# @return EditorialModel.EmClass
def em_class(self):
return self.model.component(self.class_id)
## @brief Delete an EmType
# The deletion is only possible if a type is not linked by any EmClass
# and if it has no subordinates
# @return True if delete False if not deleted
# @todo Check if the type is not linked by any EmClass
# @todo Check if there is no other ''non-deletion'' conditions
def delete_check(self):
if len(self.subordinates()) > 0:
return False
#Delete all relation with superiors
for nature, sups in self.superiors().items():
for sup in sups:
self.del_superior(sup, nature)
return True
## Return selected optional field
# @return A list of EmField instance
def selected_fields(self):
selected = [self.model.component(field_id) for field_id in self.fields_list]
return selected
## Return the list of associated fields
# @return A list of EmField instance
def fields(self, relational=True):
return [field for field in self.em_class.fields(relational) if not field.optional or (field.optional and field.uid in self.fields_list)]
## Select_field (Function)
#
# Indicates that an optional field is used
#
# @param field EmField: The optional field to select
#
# @throw TypeError if field is not an EmField instance
# @throw ValueError if field is not optional or is not associated with this type
# @throw MigrationHandlerChangeError if migration handler is not happy with the change
# @see EmType::_change_field_list()
def select_field(self, field):
if field.uid in self.fields_list:
return True
self._change_field_list(field, True)
## Unselect_field (Function)
#
# Indicates that an optional field will not be used
#
# @param field EmField: The optional field to unselect
#
# @throw TypeError if field is not an EmField instance
# @throw ValueError if field is not optional or is not associated with this type
# @throw MigrationHandlerChangeError if migration handler is not happy with the change
# @see EmType::_change_field_list()
def unselect_field(self, field):
if field.uid not in self.fields_list:
return True
self._change_field_list(field, False)
## @brief Select or unselect an optional field
# @param field EmField: The EmField to select or unselect
# @param select bool: If True select field, else unselect it
#
# @throw TypeError if field is not an EmField instance
# @throw ValueError if field is not optional or is not associated with this type
# @throw MigrationHandlerChangeError if migration handler is not happy with the change
def _change_field_list(self, field, select=True):
if not isinstance(field, EmField):
raise TypeError("Excepted <class EmField> as field argument. But got " + str(type(field)))
if field not in self.em_class.fields():
raise ValueError("This field " + str(field) + "is not part of the type " + str(self))
if not field.optional:
raise ValueError("This field is not optional")
try:
if select:
self.fields_list.append(field.uid)
self.model.migration_handler.register_change(self.model, self.uid, None, {'fields_list': field.uid})
else:
self.fields_list.remove(field.uid)
self.model.migration_handler.register_change(self.model, self.uid, {'fields_list': field.uid}, None)
except MigrationHandlerChangeError as exception_object:
if select:
self.fields_list.remove(field.uid)
else:
self.fields_list.append(field.uid)
raise exception_object
self.model.migration_handler.register_model_state(self.model, hash(self.model))
## Get the list of associated hooks
# @note Not conceptualized yet
# @todo Conception
def hooks(self):
raise NotImplementedError()
## Add a new hook
# @param hook EmHook: An EmHook instance
# @throw TypeError
# @note Not conceptualized yet
# @todo Conception
def add_hook(self, hook):
raise NotImplementedError()
## Delete a hook
# @param hook EmHook: An EmHook instance
# @throw TypeError
# @note Not conceptualized yet
# @todo Conception
# @todo Maybe we don't need a EmHook instance but just a hook identifier
def del_hook(self, hook):
raise NotImplementedError()
## @brief Get the list of subordinates EmType
# Get a list of EmType instance that have this EmType for superior
# @return Return a dict with relation nature as keys and values as a list of subordinates
# EmType instance
# @throw RuntimeError if a nature fetched from db is not valid
def subordinates(self):
subordinates = {}
for em_type in self.model.components(EmType):
for nature, superiors_uid in em_type.superiors_list.items():
if self.uid in superiors_uid:
if nature in subordinates:
subordinates[nature].append(em_type)
else:
subordinates[nature] = [em_type]
return subordinates
## @brief Get the list of superiors by relation's nature
# Get a list of EmType that are superiors of this type
# @return Return a dict with relation nature as keys and an EmType as value
# @throw RuntimeError if a nature has multiple superiors
def superiors(self):
return {nature: [self.model.component(superior_uid) for superior_uid in superiors_uid] for nature, superiors_uid in self.superiors_list.items()}
## @brief Given a relation's nature return all the possible type to add as superiors
# @param relation_nature str | None : if None check for all natures
# @return a list or a dict with nature as key
def possible_superiors(self, relation_nature=None):
if relation_nature is None:
ret = {}
for nat in EmNature.getall():
ret[nat] = self.possible_superiors(nat)
return ret
#One nature
if relation_nature not in self.classtype['hierarchy']:
return []
att = self.classtype['hierarchy'][relation_nature]['attach']
if att == 'type':
return [self]
else:
return [t for t in self.model.components(EmType) if t.classtype == self.classtype]
## Add a superior in the type hierarchy
# @param em_type EmType: An EmType instance
# @param relation_nature str: The name of the relation's nature
# @return False if no modification made, True if modifications success
#
# @throw TypeError when em_type not an EmType instance
# @throw ValueError when relation_nature isn't reconized or not allowed for this type
# @throw ValueError when relation_nature don't allow to link this types together
def add_superior(self, em_type, relation_nature):
# check if relation_nature is valid for this type
if relation_nature not in EmClassType.natures(self.classtype['name']):
raise ValueError("Invalid nature for add_superior : '" + relation_nature + "'. Allowed relations for this type are " + str(EmClassType.natures(self.classtype['name'])))
if relation_nature in self.superiors_list and em_type.uid in self.superiors_list[relation_nature]:
return True
att = self.classtype['hierarchy'][relation_nature]['attach']
if att == 'classtype':
if self.classtype['name'] != em_type.classtype['name']:
raise ValueError("Not allowed to put an em_type with a different classtype as superior")
elif self.name != em_type.name:
raise ValueError("Not allowed to put a different em_type as superior in a relation of nature '" + relation_nature + "'")
self._change_superiors_list(em_type, relation_nature, True)
## Delete a superior in the type hierarchy
# @param em_type EmType: An EmType instance
# @param relation_nature str: The name of the relation's nature
# @throw TypeError when em_type isn't an EmType instance
# @throw ValueError when relation_nature isn't reconized or not allowed for this type
def del_superior(self, em_type, relation_nature):
if relation_nature not in self.superiors_list or em_type.uid not in self.superiors_list[relation_nature]:
return True
self._change_superiors_list(em_type, relation_nature, False)
## Apply changes to the superiors_list
# @param em_type EmType: An EmType instance
# @param relation_nature str: The name of the relation's nature
# @param add bool: Add or delete relation
def _change_superiors_list(self, em_type, relation_nature, add=True):
# check instance of parameters
if not isinstance(em_type, EmType) or not isinstance(relation_nature, str):
raise TypeError("Excepted <class EmType> and <class str> as em_type argument. But got : " + str(type(em_type)) + " " + str(type(relation_nature)))
try:
if add:
if relation_nature in self.superiors_list:
self.superiors_list[relation_nature].append(em_type.uid)
else:
self.superiors_list[relation_nature] = [em_type.uid]
self.model.migration_handler.register_change(self.model, self.uid, None, {'superiors_list': {relation_nature: em_type.uid}})
else:
self.superiors_list[relation_nature].remove(em_type.uid)
if len(self.superiors_list[relation_nature]) == 0:
del self.superiors_list[relation_nature]
self.model.migration_handler.register_change(self.model, self.uid, {'superiors_list': {relation_nature: em_type.uid}}, None)
# roll-back
except MigrationHandlerChangeError as exception_object:
if add:
self.superiors_list[relation_nature].remove(em_type.uid)
if len(self.superiors_list[relation_nature]) == 0:
del self.superiors_list[relation_nature]
else:
if relation_nature in self.superiors_list:
self.superiors_list[relation_nature].append(em_type.uid)
else:
self.superiors_list[relation_nature] = [em_type.uid]
raise exception_object
self.model.migration_handler.register_model_state(self.model, hash(self.model))
## Checks if the EmType is valid
# @throw EmComponentCheckError if check fails
def check(self):
super(EmType, self).check()
em_class = self.model.component(self.class_id)
if not em_class:
raise EmComponentCheckError("class_id contains an uid that does not exists '%d'" % self.class_id)
if not isinstance(em_class, EditorialModel.classes.EmClass):
raise EmComponentCheckError("class_id contains an uid from a component that is not an EmClass but a %s" % str(type(em_class)))
for i, f_uid in enumerate(self.fields_list):
field = self.model.component(f_uid)
if not field:
raise EmComponentCheckError("The element %d of selected_field is a non existing uid '%d'" % (i, f_uid))
if not isinstance(field, EmField):
raise EmComponentCheckError("The element %d of selected_field is not an EmField but a %s" % (i, str(type(field))))
if not field.optional:
raise EmComponentCheckError("The element %d of selected_field is an EmField not optional" % i)
"""
if field.fieldgroup_id not in [fg.uid for fg in self.fieldgroups()]:
raise EmComponentCheckError("The element %d of selected_field is an EmField that is part of an EmFieldGroup that is not associated with this EmType" % i)
"""
for nature, superiors_uid in self.superiors_list.items():
for superior_uid in superiors_uid:
em_type = self.model.component(superior_uid)
if not em_type:
raise EmComponentCheckError("The superior is a non existing uid '%d'" % (superior_uid))
if not isinstance(em_type, EmType):
raise EmComponentCheckError("The superior is a component that is not an EmType but a %s" % (str(type(em_type))))
if nature not in EmClassType.natures(self.em_class.classtype):
raise EmComponentCheckError("The relation nature '%s' of the superior is not valid for this EmType classtype '%s'", (nature, self.classtype))
nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
if nat_spec['attach'] == 'classtype':
if self.classtype != em_type.classtype:
raise EmComponentCheckError("The superior is of '%s' classtype. But the current type is of '%s' classtype, and relation nature '%s' require two EmType of same classtype" % (em_type.classtype, self.classtype, nature))
elif nat_spec['attach'] == 'type':
if self.uid != em_type.uid:
raise EmComponentCheckError("The superior is a different EmType. But the relation nature '%s' require the same EmType" % (nature))
else:
raise NotImplementedError("The nature['attach'] '%s' is not implemented in this check !" % nat_spec['attach'])
if 'max_depth' in nat_spec and nat_spec['max_depth'] > 0:
depth = 1
cur_type = em_type
while depth >= nat_spec['max_depth']:
depth += 1
if len(cur_type.subordinates()[nature]) == 0:
break
else:
raise EmComponentCheckError("The relation with superior %d has a depth superior than the maximum depth (%d) allowed by the relation's nature '%s'" % (superior_uid, nat_spec['max_depth'], nature))
for nature in self.subordinates():
nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
if 'max_child' in nat_spec and nat_spec['max_child'] > 0:
if len(self.subordinates()[nature]) > nat_spec['max_child']:
raise EmComponentCheckError("The EmType has more child than allowed in the relation's nature : %d > %d" % (len(self.subordinates()[nature], nat_spec['max_child'])))

View file

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

370
INSTALL
View file

@ -1,370 +0,0 @@
Installation Instructions
*************************
Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation,
Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
Basic Installation
==================
Briefly, the shell command `./configure && make && make install'
should configure, build, and install this package. The following
more-detailed instructions are generic; see the `README' file for
instructions specific to this package. Some packages provide this
`INSTALL' file but do not implement all of the features documented
below. The lack of an optional feature in a given package is not
necessarily a bug. More recommendations for GNU packages can be found
in *note Makefile Conventions: (standards)Makefile Conventions.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, and a
file `config.log' containing compiler output (useful mainly for
debugging `configure').
It can also use an optional file (typically called `config.cache'
and enabled with `--cache-file=config.cache' or simply `-C') that saves
the results of its tests to speed up reconfiguring. Caching is
disabled by default to prevent problems with accidental use of stale
cache files.
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If you are using the cache, and at
some point `config.cache' contains results you don't want to keep, you
may remove or edit it.
The file `configure.ac' (or `configure.in') is used to create
`configure' by a program called `autoconf'. You need `configure.ac' if
you want to change it or regenerate `configure' using a newer version
of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system.
Running `configure' might take a while. While running, it prints
some messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Optionally, type `make check' to run any self-tests that come with
the package, generally using the just-built uninstalled binaries.
4. Type `make install' to install the programs and any data files and
documentation. When installing into a prefix owned by root, it is
recommended that the package be configured and built as a regular
user, and only the `make install' phase executed with root
privileges.
5. Optionally, type `make installcheck' to repeat any self-tests, but
this time using the binaries in their final installed location.
This target does not install anything. Running this target as a
regular user, particularly if the prior `make install' required
root privileges, verifies that the installation completed
correctly.
6. You can remove the program binaries and object files from the
source code directory by typing `make clean'. To also remove the
files that `configure' created (so you can compile the package for
a different kind of computer), type `make distclean'. There is
also a `make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
7. Often, you can also type `make uninstall' to remove the installed
files again. In practice, not all packages have tested that
uninstallation works correctly, even though it is required by the
GNU Coding Standards.
8. Some packages, particularly those that use Automake, provide `make
distcheck', which can by used by developers to test that all other
targets like `make install' and `make uninstall' work correctly.
This target is generally not run by end users.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the `configure' script does not know about. Run `./configure --help'
for details on some of the pertinent environment variables.
You can give `configure' initial values for configuration parameters
by setting variables in the command line or in the environment. Here
is an example:
./configure CC=c99 CFLAGS=-g LIBS=-lposix
*Note Defining Variables::, for more details.
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you can use GNU `make'. `cd' to the
directory where you want the object files and executables to go and run
the `configure' script. `configure' automatically checks for the
source code in the directory that `configure' is in and in `..'. This
is known as a "VPATH" build.
With a non-GNU `make', it is safer to compile the package for one
architecture at a time in the source code directory. After you have
installed the package for one architecture, use `make distclean' before
reconfiguring for another architecture.
On MacOS X 10.5 and later systems, you can create libraries and
executables that work on multiple system types--known as "fat" or
"universal" binaries--by specifying multiple `-arch' options to the
compiler but only a single `-arch' option to the preprocessor. Like
this:
./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CPP="gcc -E" CXXCPP="g++ -E"
This is not guaranteed to produce working output in all cases, you
may have to build one architecture at a time and combine the results
using the `lipo' tool if you have problems.
Installation Names
==================
By default, `make install' installs the package's commands under
`/usr/local/bin', include files under `/usr/local/include', etc. You
can specify an installation prefix other than `/usr/local' by giving
`configure' the option `--prefix=PREFIX', where PREFIX must be an
absolute file name.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
pass the option `--exec-prefix=PREFIX' to `configure', the package uses
PREFIX as the prefix for installing programs and libraries.
Documentation and other data files still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like `--bindir=DIR' to specify different values for particular
kinds of files. Run `configure --help' for a list of the directories
you can set and what kinds of files go in them. In general, the
default for these options is expressed in terms of `${prefix}', so that
specifying just `--prefix' will affect all of the other directory
specifications that were not explicitly provided.
The most portable way to affect installation locations is to pass the
correct locations to `configure'; however, many packages provide one or
both of the following shortcuts of passing variable assignments to the
`make install' command line to change installation locations without
having to reconfigure or recompile.
The first method involves providing an override variable for each
affected directory. For example, `make install
prefix=/alternate/directory' will choose an alternate location for all
directory configuration variables that were expressed in terms of
`${prefix}'. Any directories that were specified during `configure',
but not in terms of `${prefix}', must each be overridden at install
time for the entire installation to be relocated. The approach of
makefile variable overrides for each directory variable is required by
the GNU Coding Standards, and ideally causes no recompilation.
However, some platforms have known limitations with the semantics of
shared libraries that end up requiring recompilation when using this
method, particularly noticeable in packages that use GNU Libtool.
The second method involves providing the `DESTDIR' variable. For
example, `make install DESTDIR=/alternate/directory' will prepend
`/alternate/directory' before all installation names. The approach of
`DESTDIR' overrides is not required by the GNU Coding Standards, and
does not work on platforms that have drive letters. On the other hand,
it does better at avoiding recompilation issues, and works well even
when some directory options were not specified in terms of `${prefix}'
at `configure' time.
Optional Features
=================
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Some packages offer the ability to configure how verbose the
execution of `make' will be. For these packages, running `./configure
--enable-silent-rules' sets the default to minimal output, which can be
overridden with `make V=1'; while running `./configure
--disable-silent-rules' sets the default to verbose, which can be
overridden with `make V=0'.
Particular systems
==================
On HP-UX, the default C compiler is not ANSI C compatible. If GNU
CC is not installed, it is recommended to use the following options in
order to use an ANSI C compiler:
./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
and if that doesn't work, install pre-built binaries of GCC for HP-UX.
HP-UX `make' updates targets which have the same time stamps as
their prerequisites, which makes it generally unusable when shipped
generated files such as `configure' are involved. Use GNU `make'
instead.
On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
parse its `<wchar.h>' header file. The option `-nodtk' can be used as
a workaround. If GNU CC is not installed, it is therefore recommended
to try
./configure CC="cc"
and if that doesn't work, try
./configure CC="cc -nodtk"
On Solaris, don't put `/usr/ucb' early in your `PATH'. This
directory contains several dysfunctional programs; working variants of
these programs are available in `/usr/bin'. So, if you need `/usr/ucb'
in your `PATH', put it _after_ `/usr/bin'.
On Haiku, software installed for all users goes in `/boot/common',
not `/usr/local'. It is recommended to use the following options:
./configure --prefix=/boot/common
Specifying the System Type
==========================
There may be some features `configure' cannot figure out
automatically, but needs to determine by the type of machine the package
will run on. Usually, assuming the package is built to be run on the
_same_ architectures, `configure' can figure that out, but if it prints
a message saying it cannot guess the machine type, give it the
`--build=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name which has the form:
CPU-COMPANY-SYSTEM
where SYSTEM can have one of these forms:
OS
KERNEL-OS
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the machine type.
If you are _building_ compiler tools for cross-compiling, you should
use the option `--target=TYPE' to select the type of system they will
produce code for.
If you want to _use_ a cross compiler, that generates code for a
platform different from the build platform, you should specify the
"host" platform (i.e., that on which the generated programs will
eventually be run) with `--host=TYPE'.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share,
you can create a site shell script called `config.site' that gives
default values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Defining Variables
==================
Variables not defined in a site shell script can be set in the
environment passed to `configure'. However, some packages may run
configure again during the build, and the customized values of these
variables may be lost. In order to avoid this problem, you should set
them in the `configure' command line, using `VAR=value'. For example:
./configure CC=/usr/local2/bin/gcc
causes the specified `gcc' to be used as the C compiler (unless it is
overridden in the site shell script).
Unfortunately, this technique does not work for `CONFIG_SHELL' due to
an Autoconf limitation. Until the limitation is lifted, you can use
this workaround:
CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash
`configure' Invocation
======================
`configure' recognizes the following options to control how it
operates.
`--help'
`-h'
Print a summary of all of the options to `configure', and exit.
`--help=short'
`--help=recursive'
Print a summary of the options unique to this package's
`configure', and exit. The `short' variant lists options used
only in the top level, while the `recursive' variant lists options
also present in any nested packages.
`--version'
`-V'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`--cache-file=FILE'
Enable the cache: use and save the results of the tests in FILE,
traditionally `config.cache'. FILE defaults to `/dev/null' to
disable caching.
`--config-cache'
`-C'
Alias for `--cache-file=config.cache'.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to `/dev/null' (any error
messages will still be shown).
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`--prefix=DIR'
Use DIR as the installation prefix. *note Installation Names::
for more details, including other options available for fine-tuning
the installation locations.
`--no-create'
`-n'
Run the configure checks, but stop before creating any output
files.
`configure' also accepts some other, not widely useful, options. Run
`configure --help' for more details.

View file

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

91
Lodel/__init__.py Normal file
View file

@ -0,0 +1,91 @@
## @mainpage
#
# @section Introduction
#
# For basics Lodel2 configuration & usage see README.md
#
# - @subpage lodel2_doc
# - Main packages
# - Lodel
# - Lodel.settings
# - EditorialModel
# - EditorialModel.fieldtypes
# - EditorialModel.backend
# - @ref leapi
# - DataSource
# - DataSource.MySQL
## @page lodel2_doc Lodel2 documentation
# @tableofcontents
#
# @section mainpage_docs Documentation
#
# - @subpage lodel2_arch
# - @subpage lodel_settings
# - @subpage api_user_side
# - @subpage lecrud_instanciation
# - @subpage lodel2_fieldtypes
#
#
## @page lodel2_arch Lodel2 architecture
#
# @tableofcontents
#
# @section lodel2_arch_basic Lodel2
#
# Lodel 2 is a CMS that allows to define content types ......
#
# @section lodel2_arch_edmod Lodel2 Editorial Model
#
# The editorial model is the part of lodel2 that allows to defines content types.
#
# To handle this the editorial model has 3 abstractions :
# - Classes
# - Types
# - Fields
#
# @warning An editorial model does NOT contains values.
#
# The editorial model is used to generate the dynamic part of the Lodel2 API ( see @ref leapi )
#
# @subsection lodel2_arch_edmod_classes Editorial model classes ( EmClass )
#
# An EmClass is basically a named collection of EmFields ( see @ref lodel2_arch_edmod_fields ) associated with a classtype.
#
# Classtypes are "family" of EmClasses. They defines allowed hierarchical relations between EmClass.
#
# @subsection lodel2_arch_edmod_types Editorial model types ( EmType )
#
# An EmType is a named EmClass specialization. In fact some EmField in an EmClass can be defined as optionnal and then be selected
# or not to be included in an EmType.
# 
# @subsection lodel2_arch_edmod_fields Editorial model fields ( EmField )
#
# EmFields defines what kind of datas can be stored in EmTypes. Actually its the associationg with an EmFieldtype ( see @ref lodel2_fieldtypes )
# that really defines what kind of datas can be stored.
#
# @section lodel2_arch_ui Lodel2 user interfaces
#
# All access to datas are made via UI. UI actions are composed with 3 or 4 elements :
# - user
# - action (Crud actions ?)
# - target
# - sometimes datas
#
# Each actions are send to Lodel2 ACL that check permissions and forward the actions to leapi. leapi send back a reply to the
# UI. And the response get formatted by UI templates. ( see @ref lodel2_arch_ui_fig )
#
# @section lodel2_arch_fig Figures
#
# @subsection lodel2_arch_edmod_fig_components Editorial model main components
# @image html graphviz/em_components.png
# @subsection lodel2_arch_edmod_fig_relations Editorial model relations between components
# @image html graphviz/em_relations.png
# @subsection lodel2_arch_edmod_fig_hierarchy Hierarchical relations between EmTypes given a classtype
# @image html graphviz/em_types_hierarch.png
# @subsection lodel2_arch_edmod_fig_em_example Example of editorial model
# @image html graphviz/example_em_graph.png
# @subsection lodel2_arch_ui_fig Lodel2 UI schema
# @image html graphviz/lodel2_ui.png

136
Lodel/settings.py Normal file
View file

@ -0,0 +1,136 @@
#-*- coding: utf-8 -*-
import types
import warnings
from . import settings_format
## @package Lodel.settings
#
# @brief Defines stuff to handles Lodel2 configuration (see @ref lodel_settings )
#
# To access the confs use the Lodel.settings.Settings SettingsHandler instance
## @brief A class designed to handles Lodel2 settings
#
# When instanciating a SettingsHandler, the new instance is filled with the content of settings.py (in the root directory of lodel2
#
# @important You don't have to instanciate this class, you can access to the global instance with the Settings variable in this module
# @todo Forbid module assignement in settings ! and disable tests about this
# @todo Implements a type checking of config value
# @todo Implements default values for config keys
class SettingsHandler(object):
## @brief Shortcut
_allowed = settings_format.ALLOWED + settings_format.MANDATORY
## @brief Shortcut
_mandatory = settings_format.MANDATORY
def __init__(self):
try:
import settings as default_settings
self._load_module(default_settings)
except ImportError:
warnings.warn("Unable to find global default settings")
## @brief A flag set to True when the instance is fully loaded
self._set_loaded(False if len(self._missings()) > 0 else True)
## @brief Compat wrapper for getattr
def get(self, name):
return getattr(self, name)
## @brief Compat wrapper for setattr
def set(self, name, value):
return setattr(self, name, value)
## @brief Load every module properties in the settings instance
#
# Load a module content into a SettingsHandler instance and checks that no mandatory settings are missing
# @note Example : <pre> import my_cool_settings;
# Settings._load_module(my_cool_settings);</pre>
# @param module module|None: a loaded module (if None just check for missing settings)
# @throw LookupError if invalid settings found or if mandatory settings are missing
def load_module(self, module = None):
if not(module is None):
self._load_module(module)
missings = self._missings()
if len(missings) > 0:
self._loaded = False
raise LookupError("Mandatory settings are missing : %s"%missings)
self._set_loaded(True)
## @brief supersede of default __setattr__ method
def __setattr__(self, name, value):
if not hasattr(self, name):
if name not in self._allowed:
raise LookupError("Invalid setting : %s"%name)
super().__setattr__(name, value)
## @brief This method do the job for SettingsHandler.load_module()
#
# @note The difference with SettingsHandler.load_module() is that it didn't check if some settings are missing
# @throw LokkupError if an invalid settings is given
# @param module : a loaded module
def _load_module(self, module):
errors = []
fatal_errors = []
conf_dict = {
name: getattr(module, name)
for name in dir(module)
if not name.startswith('__') and not isinstance(getattr(module, name), types.ModuleType)
}
for name, value in conf_dict.items():
try:
setattr(self, name, value)
except LookupError:
errors.append(name)
if len(errors) > 0:
err_msg = "Found invalid settings in %s : %s"%(module.__name__, errors)
raise LookupError(err_msg)
## @brief If some settings are missings return their names
# @return an array of string
def _missings(self):
return [ confname for confname in self._mandatory if not hasattr(self, confname) ]
def _set_loaded(self, value):
super().__setattr__('_loaded', bool(value))
Settings = SettingsHandler()
## @page lodel_settings Lodel SettingsHandler
#
# This page describe the way settings are handled in Lodel2.
#
# @section lodel_settings_files Lodel settings files
#
# - Lodel/settings.py defines the Lodel.settings package, the SettingsHandler class and the Lodel.settings.Settings instance
# - Lodel/settings_format.py defines the mandatory and allowed configurations keys lists
# - install/instance_settings.py is a model of the file that will be deployed in Lodel2 instances directories
#
# @section Using Lodel.settings.Settings SettingsHandler instance
#
# @subsection lodel_settings_without_loader Without loader
#
# Without any loader you can import Lodel.settings.Settings and acces its property with getattr (or . ) or with SettingsHandler.get() method.
# In the same way you can set a settings by standart affectation of a propery or with SettingsHandler.set() method.
#
# @subsection lodel_settings_loader With a loader in a lodel2 instance
#
# The loader will import Lodel.settings.Settings and then calls the SettingsHandler.load_module() method to load the content of the instance_settings.py file into the SettingsHandler instance
#
# @subsection lodel_settings_example Examples
#
# <pre>
# #!/usr/bin/python
# from Lodel.settings import Settings
# if Settings.debug:
# print("DEBUG")
# #or
# if Settings.get('debug'):
# print("DEBUG")
# Settings.debug = False
# #or
# Settings.set('debug', False)
# </pre>
#

24
Lodel/settings_format.py Normal file
View file

@ -0,0 +1,24 @@
#-*- coding: utf-8 -*-
## @package Lodel.settings_format Rules for settings
## @brief List mandatory configurations keys
MANDATORY = [
'debug',
'debug_sql',
'sitename',
'lodel2_lib_path',
'em_file',
'dynamic_code_file',
'ds_package',
'datasource',
'mh_classname',
'migration_options',
'base_path'
]
## @brief List allowed (but not mandatory) configurations keys
ALLOWED = [
'em_graph_output',
'em_graph_format',
'templates_base_dir'
]

0
Lodel/utils/__init__.py Normal file
View file

114
Lodel/utils/mlstring.py Normal file
View file

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
import json
import warnings
## Handle string with translations
# @todo define a default language that will be used in case the wanted language is not available for this string (gettext-like behavior)
class MlString(object):
default_lang = '___'
## Instanciate a new string with translation
#
# @param translations dict: With key = lang and value the translation
def __init__(self, translations=None, default_value = None):
if isinstance(translations, str):
try:
translations = json.loads(translations)
except ValueError:
if default_value is None:
default_value = str(translations)
translations = None
if translations is None:
translations = dict()
if not isinstance(translations, dict):
raise ValueError('Bad value given for translations argument on MlString instanciation')
else:
self.translations = translations
self.translations[self.default_lang] = '' if default_value is None else default_value
if default_value is None:
warnings.warn('No default value when isntanciating an MlString')
## Return a translation
# @param lang str: The lang
# @return An empty string if the wanted lang don't exist
# @warning Returns an empty string if the wanted translation didn't exists
# @todo if the asked language is not available, use the default one, defined as a class property
def get(self, lang = None):
if lang is None or lang not in self.translations:
lang = self.default_lang
return self.translations[lang]
## Set a translation for this MlString
# @param lang str: The language
# @param text str: The translation
# @todo check if the default language as a translation
def set(self, lang, text):
if not text:
if lang in self.translations:
del(self.translations[lang])
else:
self.translations[lang] = text
## @return Default translation
def get_default(self):
return self.get(self.default_lang)
## @brief Set default value
def set_default(self, text):
self.set(self.default_lang, text)
## String representation
# @return A json dump of the MlString::translations dict
def __str__(self):
return self.get_default()
## @brief Serialize the MlString in Json
def json_dumps(self):
if self.translations:
return json.dumps(self.translations, sort_keys=True)
else:
return ""
## @brief Equivalent to json_dumps
def dumps(self): return self.json_dumps()
## Test if two MlString instance are equivalent
# @param other MlString|str : Another MlString instance or a string (json formated)
# @return True or False
def __eq__(self, other):
if isinstance(other, str):
other = MlString(other)
if not isinstance(other, MlString):
return False
if not set(lng for lng in self.translations) == set( lng for lng in other.translations):
return False
for lng in self.translations:
if self.get(lng) != other.get(lng):
return False
return True
## @brief Allow [] access to translations
def __getitem__(self, langname): return self.get(langname)
## @brief Allow [] set
def __setitem__(self, langname, txt): return self.set(langname, txt)
## @brief Implements dict.keys() method
def keys(self): return self.translations.keys()
## @brief Implements dict.values() method
def values(self): return self.translations.values()
## @brief Implements dict.items() method
def items(self): return self.translations.items()
@staticmethod
## Instanciate a MlString from something
# @deprecated Equivalent to MlString(translations = arg, default_value = None)
def load(arg):
return MlString(arg)

40
Lodel/utils/mosql.py Normal file
View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
## @package Lodel.utils.mosql
# @brief Helper function to use mosql library
#
# define create_database and drop_database Query to manipulate databases
# define create, drop and alter Query to manipulate tables
# define create_user, delete_user and grant to manipulate user and rights
from mosql.util import Clause, Statement, Query, value, identifier, concat_by_comma, paren, raw
def col_def(cols):
print("cols", cols)
ret = []
for col in cols:
print("col", col)
name, definition = col
ret.append(identifier(name) + ' ' + raw(definition))
return ret
# chain definitions
column_list = (col_def, concat_by_comma, paren)
# Clauses
create_clause = Clause('create table', alias='create', no_argument=True)
if_not_exists = Clause('if not exists', alias='if_not_exists', no_argument=True, default=True)
table_clause = Clause('table', (identifier, ), alias='table', hidden=True)
character_clause = Clause('CHARACTER SET', (value, ), alias='character')
column_clause = Clause('column', column_list, alias='column', hidden=True)
alter_clause = Clause('alter table', (identifier, ), alias='table')
add_clause = Clause('add column', (col_def, ), alias='column')
# Statements
create_statement = Statement([create_clause, if_not_exists, table_clause, column_clause, character_clause])
alter_add_statement = Statement([alter_clause, add_clause])
# queries
create = Query(create_statement, ('table', 'column', 'if_not_exists'), {'create':True})
alter_add = Query(alter_add_statement, ('table', 'column'))

48
Makefile Normal file
View file

@ -0,0 +1,48 @@
all: check doc pip
# Running unit tests
check:
python -m unittest -v
# Rule to update libs
pip: cleanpycache
pip install --upgrade -r requirements.txt
#
# Documentation rules
#
graphviz_images_path = doc/img/graphviz
doc: cleandoc docimages refreshdyn
doxygen
# Generating graphviz images
docimages:
cd $(graphviz_images_path); make
refreshdyn:
python refreshdyn.py &>/dev/null
#
# Cleaning rules
#
.PHONY: check doc clean cleanpyc cleandoc cleanpycache cleandyn cleandocimages
clean: cleanpyc cleandoc cleanpycache cleandocimages cleandyn
# Documentation cleaning
cleandoc:
-rm -Rfv ./doc/html ./doc/doxygen_sqlite3.db
cleandocimages:
cd $(graphviz_images_path); make clean
# Python cleaning
cleanpyc:
-find ./ |grep -E "\.pyc$$" |xargs rm -fv 2>/dev/null
cleanpycache: cleanpyc
-find ./ -type d |grep '__pycache__' | xargs rmdir -fv 2>/dev/null
cleandyn:
-rm leapi/dyn.py

View file

@ -1,101 +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/>.
#
SUBDIRS = lodel progs lodelsites
EXTRA_DIST = runtest examples tests debian
CLEANFILES = runtest
lodel2_localstate_DATA =
lodel2_localstatedir=$(localstatedir)/lodel2
lodel2datadir=$(datadir)/lodel2
install_model_dir = $(lodel2datadir)/install_model
#python=/usr/bin/env python3
python=@PYTHON@
dyncode_filename='lodel/leapi/dyncode.py'
docdir=${datarootdir}/doc/lodel2
# Doxygen doc generation targets
doxygen:
test -z "@DOCOK@" && echo -e "\n\nUnable to generate documentation. See ./configure output for details\n\n" >&2 || make generate-doc
generate-doc: clean doc_graphviz
doxygen
doc_graphviz:
cd doc/img/graphviz; make
do_subst = sed -e 's,\[@\]INSTALLMODEL_DIR\[@\],$(install_model_dir),g'
runtest: ./runtest.sh
$(do_subst) < $(srcdir)/runtest.sh > runtest
chmod +x runtest
#Adding logdir creation on install
install-data-hook:
mkdir -p ${DESTDIR}$(lodel2_localstatedir); mkdir -p ${DESTDIR}$(docdir); cp README ${DESTDIR}$(docdir);
#Making debian package
deb: dist
mkdir debian_package;\
tar -xvf ${PACKAGE}-${VERSION}.tar.gz -C ./debian_package;\
cd debian_package/${PACKAGE}-${VERSION};\
dpkg-buildpackage -rfakeroot;
# Test em update ( examples/em_test.pickle )
em_test: em_test.py
$(python) em_test.py
# Test em update ( examples/em_test.pickle )
em_simple: editorial_models/em_simple.py
$(python) editorial_models/em_simple.py
# generate leapi dynamic code
dyncode: examples/em_test.pickle
$(python) scripts/refreshdyn.py examples/em_test.pickle $(dyncode_filename) && echo -e "\n\nCode generated in $(dyncode_filename)"
# generate leapi dynamic code
dyncode_simple: examples/em_simple.pickle
$(python) scripts/refreshdyn.py examples/em_simple.pickle $(dyncode_filename) && echo -e "\n\nCode generated in $(dyncode_filename)"
# run tests
checks: runtest
./runtest -v
unittest: checks
#Cleaning documentation and dyncode
clean-local: cleandoc
-rm -vR doc/html doc/doxygen_sqlite3.db
cleandoc:
-rm -v $(dyncode_filename)
#other cleans
distclean-local:
-rm -vR debian_package ${PACKAGE}-${VERSION}.tar.gz
-find . -name Makefile.in -delete
gitclean: distclean cleandoc
-rm -vR autom4te.cache/ aclocal.m4 install-sh missing py-compile configure; find ./ -name Makefile.in |xargs rm -v
.PHONY: cleandoc tests doc

216
README
View file

@ -1,216 +0,0 @@
Dependencies :
==============
lodel2 components :
-------------------
core :
python3 python3-lxml
build dependencies :
autoconf automake make
webui plugin :
python3-jinja2 python3-werkzeug uwsgi-plugin-python3
mongodb_datasource plugin :
python3-pymongo
lodel2 utils :
--------------
mass_deploy dependencies :
pwgen wamerican mongodb-clients
Build & install :
=================
./bootstrap.sh #generate the configure, refresh the Makefile.in files
#--prefix=/usr is mandatory on debian because of a python3.4.2 bug
./configure --sysconfdir=/etc --localstatedir=/var --prefix=/usr
#build lodel2 (actually substitute paths script and buildconf.py files)
make
#Install lodel2 on system
make install
#Uninstall lodel2
make uninstall
Creating debian package :
=========================
make deb
Other make targets :
====================
Doxygen documentation generation :
----------------------------------
make doxygen
Run unit tests :
----------------
make check
#or
make unittest
Cleaning to stop being annoyed when running git status :
--------------------------------------------------------
make gitclean
Lodel2 instance management utilities :
======================================
NOTE: You have to run in root to use this below
SLIM : Simple Lodel Instance Manager
------------------------------------
- create a new instance named foo :
slim -n foo -c
- list instances :
slim -l
- list instances with details :
slim -lv[v[v]]
- list instances with output formated for bash processing :
slim -l -b [-v[v[v]]
- run make all for instance foo :
slim -n foo -m
- run make for all existing instances :
slim --all -m
- start an instance in forground
slim -n foo --start -f
- start all instances :
slim -a --start
- generate a nginx conf for all instances :
slim -a --nginx-conf
- set logger to warning level and log in file :
slim -a -s --set-logger test:WARNING:/var/log/lodel2/%l.%s.log
- get some help for more options & actions :
slim -h
mass_deploy.sh :
----------------
Use for easy mass deployment of instances with webui & uwsgi
the script at $prefix/share/lodel2/scripts/mass_deploy.sh
Usage :
-------
#with N the number of instances to deploy
mass_deploy.sh N
Notes :
-------
- configuration can be found in /etc/lodel2/mass_deploy.cfg
to indicate mongodb admin credentials
- mass deploy create instances directories
in /tmp/lodel2_instances
Mass deployments tricks & tips:
===============================
Mass deployment on fresh install :
----------------------------------
#Install nginx & mongodb
apt-get install nginx mongodb-server mongodb-clients
#choose your credential
export mongoadmin="admin"; export mongopass="pass";
#Disable auth to be able to connect to mongodb
sed -ie 's/^#noauth = /noauth = /' /etc/mongodb.conf
#Add an admin user to mongodb
echo -e "use admin\ndb.addUser('$mongoadmin', '$mongopass')\nexit\n"|mongo
#Reactivate auth
sed -i -e 's/^noauth = /#noauth =/' -e 's/^#auth = /auth =/' /etc/mongodb.conf
#Test connection
echo "exit" | mongo --quiet -u $mongoadmin -p $mongopass admin && echo "Connection ok" || echo "connection fails"
#Indicate mongodb credentials to mass_deploy
echo -e "MONGODB_ADMIN_USER='$mongoadmin'\nMONGODB_ADMIN_PASSWORD='$mongopass'\n" >> /etc/lodel2/mass_deploy.cfg
#Deploying 50 instances
NINSTANCE=50
#Running mass_deploy
/usr/share/lodel2/scripts/mass_deploy $NINSTANCE
#FOLLOWING INSTRUCTIONS ARE FOR STARTING APPS WITH UWSGI
# for standalone instructions see bellow
#Updating nginx conf (delete /etc/nginx/sites-enabled/default if exists)
slim --nginx-conf -a > /etc/nginx/sites-enabled/lodel2
/etc/init.d/nginx reload
#Start all instances and check if they managed to start
slim --start -a && sleep 2 && slim -l
# FOLLOWING INSTRUCTIONS ARE FOR STANDALONE LODEL2 WEBSERVER
#Configure nginx & restart it
export lodel2_install_dir="/usr/lib/python3/dist-packages";
echo -e "server {\n\tlisten 80 default_server;\n\tlisten [::]:80 default_server;\n\tlocation / {\n\t\tproxy_pass http://localhost:1337/;\n\t}\n\t\n\tlocation /static/ {\n\t\talias $lodel2_install_dir/lodel/plugins/webui/templates/;\n\t}\n\t\n}\n" > /etc/nginx/sites-enabled/default
/etc/init.d/nginx restart
#Build dyncode for instances
slim -a -m
#Copy the multisite loader in lodel instances root folder
cp /usr/lib/python3/dist-packages/lodel/plugins/multisite/loader.py /tmp/lodel2_instances/
cd /tmp/lodel2_instances/
#Start the server
python3 loader.py
#Now you can access to the app in HTTP via nginx throught port 80
#or directly via the python server throught the port 1337
Cleaning mongodb + instances :
------------------------------
slim --purge && /usr/share/lodel2/scripts/mass_deploy purgedb ; slim -l
Debuging an instance in foreground (if it didn't manage to start) :
-------------------------------------------------------------------
slim --start -f -n INSTANCE_NAME
Instance operations :
=====================
- Two utils are available for instances operations :
* Make for op without parameters
* lodel_admin.py for op with parameters
- Makefile available target :
# Leapi dynamic code creation ( in leapi_dyncode.py in lodel2
#instance root dir)
make dyncode
# Call migration handlers to tell them to init all needed
#databases. (note : this target has dyncode as dependencie)
make init_db
make list_hooks # List all the hooks registered
- lodel_admin.py : see lodel_admin.py -h
##########################################
# Following informations can be obsolete #
##########################################
Dynamic code generation :
python3 scripts/refreshdyn.py examples/em_test.pickle OUTPUTFILE.py
Instance creation :
Use the script in scripts/create_instance.sh
Usage : scripts/create_instance.sh instance_name instance_dir [lodel_libdir]
Instance loader uppdate :
If the install/loader.py is updated you can update instance's loader.py using
scripts/create_instance.sh -u INSTANCE_PATH

44
README.md Normal file
View file

@ -0,0 +1,44 @@
- use python 3.4
** install dependencies
pip install -r requirements.txt
------
Creating a Lodel "instance":
use the lodel_init.sh script :
lodel_init.sh INSTANCE_NAME INSTANCE_WANTED_PATH [LODEL2_LIB_PATH]
cd INSTANCE_PATH
Create a database for your instance
mysql
> CREATE DATABASE `lodel2` CHARACTER SET utf8 COLLATE utf8_general_ci;
> GRANT ALL ON `lodel2`.* TO "lodel"@"localhost";
Edit instance_settings.py according to your database, install database and dynamic code
make
Once the instance is created you can run an interactive python interpreter using :
python loader.py
If you want to write a script that run is the instance env you have to use
from loader import *
-----
** Doxygen generation
Dependencies : doxygen graphviz doxypy
Generation : run make doc in the root folder
** create local config in settings.py
Copy settings.py.example to settings.py, change the conf to your local settings
** Tools
A Makefile is written with common operations :
- make clean : cleans doc and python pycache (and .pyc files)
- make pip : upgrade python libs according to requirements.txt
- make doc : generate the doxygen documentation
- make check : run the unit tests
- make : run check doc and pip

56
Template/Loader.py Normal file
View file

@ -0,0 +1,56 @@
#-*- coding: utf-8 -*-
import jinja2
import settings
from .api import api_lodel_templates
from .exceptions import NotAllowedCustomAPIKeyError
class TemplateLoader(object):
__reserved_template_keys = ['lodel']
## @brief Initializes a template loader
#
# @param search_path str : the base path from which the templates are searched. To use absolute paths, you can set
# it to the root "/". By default, it will be the root of the project, defined in the
# settings of the application
# @param follow_links bool : indicates whether or not to follow the symbolic links (default: True)
def __init__(self, search_path=settings.base_path, follow_links=True):
self.search_path = search_path
self.follow_links = follow_links
## @brief Renders a HTML content of a template
#
# @param template_file str : path to the template file (starting from the base path used to instanciate the
# TemplateLoader)
# @param template_vars dict : parameters to be used in the template
# @param template_extra list : list of tuples indicating the custom modules to import in the template (default: None).
# The modules are given as tuples with the format : ('name_to_use_in_the_template', module)
#
# @return str. String containing the HTML output of the processed templated
def render_to_html(self, template_file, template_vars={}, template_extra=None):
loader = jinja2.FileSystemLoader(searchpath=self.search_path, followlinks=self.follow_links)
environment = jinja2.Environment(loader=loader)
template = environment.get_template(template_file)
# Lodel2 default api is loaded
template.globals['lodel'] = api_lodel_templates
# Extra modules are loaded
if template_extra is not None:
for extra in template_extra:
if not self.__is_allowed_template_key(extra[0]):
raise NotAllowedCustomAPIKeyError("The name 'lodel' is a reserved one for the loaded APIs in templates")
template.globals[extra[0]] = extra[1]
return template.render(template_vars)
## @brief Checks if the key used for the template is allowed
#
# @param key str
# @return bool
def __is_allowed_template_key(self, key):
return False if key in self.__class__.__reserved_template_keys else True

0
Template/__init__.py Normal file
View file

0
Template/api/__init__.py Normal file
View file

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
# Lodel 2 templates API : loaded by default

View file

@ -0,0 +1,7 @@
#-*- coding: utf-8 -*-
class NotAllowedCustomAPIKeyError(Exception):
def __init__(self, message):
self.message = message

View file

@ -0,0 +1 @@
from .NotAllowedCustomAPIKeyError import NotAllowedCustomAPIKeyError

View file

@ -1,22 +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/>.
#
#!/bin/bash
autoreconf -fvi

View file

@ -1,113 +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/>.
#
AC_INIT([lodel], [0.1], [contact@openedition.org])
AM_INIT_AUTOMAKE
AC_CONFIG_FILES([Makefile \
lodel/buildconf.py.am \
lodel/Makefile \
lodel/auth/Makefile \
lodel/editorial_model/Makefile \
lodel/editorial_model/translator/Makefile \
lodel/leapi/Makefile \
lodel/leapi/datahandlers/Makefile \
lodel/plugin/Makefile \
lodel/settings/Makefile \
lodel/validator/Makefile \
lodel/mlnamedobject/Makefile \
lodel/utils/Makefile \
progs/Makefile \
progs/slim/Makefile \
lodel/plugins/Makefile \
lodelsites/Makefile \
])
###
#Kind of dangerous mix between default aclocal's macro and pyconfigure macros...
#
#pyconfigure macros are prefixed by PC_
###
AC_PROG_LN_S
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)
AM_PATH_PYTHON([3.4.2]) #init aclocal's default python support
PC_INIT([3.4.2], [3.6]) #init pyconfigure aclocal's macro
PC_PYTHON_VERIFY_VERSION([>=], python_min_ver, ,
[AC_MSG_ERROR(Python interpreter too old)])
AC_ARG_VAR([SITEPACKAGES], [If installed in /usr or /System* specify the name of the site directory (default value "site-packages"])
PC_PYTHON_CHECK_MODULE([lxml], ,
AC_MSG_ERROR([Module lxml not found but mandatory for lodel2]))
#Webui deps
AC_SUBST([WEBUI], 'True')
PC_PYTHON_CHECK_MODULE_VERSION([jinja2], [2.7.3], [], ,
AC_MSG_WARN([Module jinja2 not found but mandatory for default web interface])
AC_SUBST([WEBUI], 'False'))
PC_PYTHON_CHECK_MODULE_VERSION([werkzeug], [0.9], [], ,
AC_MSG_WARN([Module werkzeug not found but mandatory for default web interface])
AC_SUBST([WEBUI], 'False'))
#Mongo datasource deps
AC_SUBST([PYMONGO], 'True') #Can be found in lodel/buildconf.py
PC_PYTHON_CHECK_MODULE_VERSION([pymongo], [2.7], [2.8], ,
AC_MSG_WARN([Module pymongo not found. The mongodb datasource will not be able to work])
AC_SUBST([PYMONGO], 'False')
)
#Documentation checks
AC_SUBST([DOCOK], 'OK')
AC_CHECK_PROGS([DOXYGEN], [doxygen])
if test -z "$DOXYGEN";
then
AC_MSG_WARN([Doxygen not found - You will not be able to generate documentation])
AC_SUBST([DOCOK], '')
fi
AC_CHECK_PROGS([GRAPHVIZ], [dot neato twopi])
if test -z "$GRAPHVIZ";
then
AC_MSG_WARN([Graphviz not found (dot executable missing) - You will not be able to generate documentation])
AC_SUBST([DOCOK], '')
fi
AC_CHECK_PROGS([DOXYPY], [doxypy])
if test -z "$DOXYPY";
then
AC_MSG_WARN([Doxypy script not found (https://pypi.python.org/pypi/doxypy/0.3) - You will not be able to generate documentation])
AC_SUBST([DOCOK], '')
fi
AC_OUTPUT
if test -z "$DOCOK";
then AC_MSG_WARN([You will not be able to generate doxygen documentation. See up for reasons])
fi
if test "$WEBUI" = 'False';
then AC_MSG_WARN([Mandatory components are missing for running default web UI. See up for reasons])
fi
if test "$PYMONGO" = 'False';
then AC_MSG_WARN([You will not be able to user mongodb datasource. See up for reasons])
fi

5
debian/changelog vendored
View file

@ -1,5 +0,0 @@
lodel2 (0.1) UNRELEASED; urgency=medium
* First debian package
-- Yann Weber <yann.weber@openedition.org> Fri, 23 Sep 2016 12:09:29 +0200

1
debian/compat vendored
View file

@ -1 +0,0 @@
9

14
debian/control vendored
View file

@ -1,14 +0,0 @@
Source: lodel2
Section: python
Priority: optional
Maintainer: lodel2 dev team
Build-Depends: make, dpkg-dev, dh-make, python3, python3-lxml
Standards-Version: 0.1.1
Package: lodel2
Section: python
Description: lodel2 debian package
Architecture: any
Depends: python3, python3-lxml, python3-jinja2, python3-werkzeug, python3-pymongo, uwsgi-plugin-python3, make
Suggests: pwgen, wamerican, mongodb-server
Provides: lodel

20
debian/debhelper.log vendored
View file

@ -1,20 +0,0 @@
dh_auto_configure
dh_auto_build
dh_auto_test
dh_prep
dh_auto_install
dh_installdocs
dh_installchangelogs
dh_pysupport
dh_perl
dh_link
dh_compress
dh_fixperms
dh_strip
dh_makeshlibs
dh_shlibdeps
dh_installdeb
dh_gencontrol
dh_md5sums
dh_builddeb
dh_builddeb

1
debian/files vendored
View file

@ -1 +0,0 @@
lodel2_0.1_amd64.deb python optional

22
debian/rules vendored
View file

@ -1,22 +0,0 @@
#!/usr/bin/make -f
# consulter debhelper(7) (décommenter lorsque nécessaire)
# afficher chaque commande modifiant un fichier dans le système construit
#DH_VERBOSE = 1
# consulter EXAMPLES dans dpkg-buildflags(1) et lire /usr/share/dpkg/*
DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/default.mk
# consulter FEATURE AREAS dans dpkg-buildflags(1)
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# consulter ENVIRONMENT dans dpkg-buildflags(1)
# mainteneurs de paquet à ajouter à CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# mainteneurs de paquet à ajouter à append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
# principal script dempaquetage basé sur la syntaxe de dh7
%:
dh $@ --with python3

1
debian/substvars vendored
View file

@ -1 +0,0 @@
misc:Depends=

View file

@ -1,10 +0,0 @@
Package: lodel2
Version: 0.1
Architecture: amd64
Maintainer: lodel2 dev team
Installed-Size: 25
Depends: python3, python3-lxml, python3-jinja2, python3-werkzeug, python3-pymongo, uwsgi-plugin-python3
Suggests: pwgen, wamerican, mongodb-server
Provides: lodel
Section: python
Priority: optional

View file

@ -1 +0,0 @@
0377daa43e7232c06b702078dd86b850 usr/share/doc/lodel2/changelog.gz

Binary file not shown.

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

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

View file

@ -0,0 +1,13 @@
digraph editorial_model_components {
label="Editorial model components"
classtypes [ label="{ entity | person | index }", shape=record]
class [ label="<f0> Class| { <f2> fields|<f3> optionnal field }", shape=record ]
type [ label="<f0> Type| { <f2> fields| <f3> selected field }", shape=record ]
classtypes -> class [ label="Defines possibles hierarchy" ]
class:f0 -> type:f0 [ label="specialize", style="dotted" ]
class:f2 -> type:f2 [ label="inherit"]
class:f3 -> type:f3 [ label="select" ]
}

View file

@ -0,0 +1,26 @@
digraph editorial_model_relations {
label="Editorial Model relations"
classtypes
r_class1 [ label="<f0> Class 1| {STR |INTEGER(optionnal) | STR | REL_TO_TYPE}", shape=record ]
r_class2 [ label="<f0> Class 2| {STR |STR }", shape=record ]
r_type1 [ label="<f0> Type 1| { STR |INTEGER | STR |<fr>REL_TO_TYPE}}", shape=record ]
r_type2 [ label="<f0> Type 2|{ STR | STR |<fr>REL_TO_TYPE}", shape=record ]
r_type3 [ label="<f0> Type 3|{ STR |STR }", shape=record ]
classtypes -> r_class1
classtypes -> r_class2
r_class1 -> r_type1 [ label="inherit", style="dotted" ]
r_class1 -> r_type2 [ label="inherit", style="dotted" ]
r_class2 -> r_type3 [ label="inherit", style="dotted" ]
r_type1:fr -> r_class2 [ label="link to Class 2" ]
r_type2:fr -> r_class1 [ label="link to Class 1" ]
}

View file

@ -0,0 +1,77 @@
digraph em_types_hierarchy {
label="Editorial Model types hierarchy"
subgraph cluster_person {
label="Person"
h_person [ label="Person", shape="tripleoctagon" ]
hc3 [ label="Class 3", shape="doubleoctagon"]
hc4 [ label="Class 4", shape="doubleoctagon"]
ht6 [ label="Type 6", shape="octagon" ]
ht7 [ label="Type 7", shape="octagon" ]
ht8 [ label="Type 8", shape="octagon" ]
h_person -> {hc3, hc4}
hc3 -> { ht6,ht7 }
hc4 -> ht8
ht6 -> ht7 [ label="identity", color="blue" ]
ht6 -> ht8 [ label="identity", color="blue" ]
ht8 -> ht8 [ label="identity", color="blue" ]
}
subgraph cluster_entity {
label="Entity"
h_entity [ label="Entity", shape="tripleoctagon" ]
hc1 [ label="Class 1", shape="doubleoctagon"]
hc2 [ label="Class 2", shape="doubleoctagon"]
ht1 [ label="Type 1", shape="octagon" ]
ht2 [ label="Type 2", shape="octagon" ]
ht3 [ label="Type 3", shape="octagon" ]
ht4 [ label="Type 4", shape="octagon" ]
ht5 [ label="Type 5", shape="octagon" ]
h_entity -> { hc1, hc2 }
hc1 -> { ht1,ht2,ht3 }
hc2 -> { ht4, ht5 }
ht1 -> ht1 [label="translation", color="red"]
ht3 -> ht3 [label="translation", color="red"]
ht5 -> ht5 [label="translation", color="red"]
ht4 -> ht1 [ label="parent", color="green"]
ht2 -> ht1 [ label="parent", color="green"]
ht5 -> ht4 [ label="parent", color="green"]
ht3 -> ht4 [ label="parent", color="green"]
ht3 -> ht1 [ label="parent", color="green"]
}
subgraph cluster_entry {
label="Entry"
h_entry [ label="Entry", shape="tripleoctagon" ]
hc5 [ label="Class 5", shape="doubleoctagon"]
ht9 [ label="Type 9", shape="octagon" ]
ht10 [ label="Type 10", shape="octagon" ]
h_entry -> hc5
hc5 -> {ht9, ht10}
ht9 -> ht9 [ label="parent", color="green" ]
ht9 -> ht9 [ label="translation", color="red" ]
ht10 -> ht10 [ label="translation", color="red" ]
}
ht5 -> hc3 [ label="relation to type", style="dotted"]
ht2 -> hc5 [ label="relation to type", style="dotted"]
}

View file

@ -0,0 +1,39 @@
digraph G {
rankdir = BT
subgraph cluster_classtype {
style="invis"
ctentity [ label="classtype entity" shape="tripleoctagon" ]
ctentry [ label="classtype entry" shape="tripleoctagon" ]
ctperson [ label="classtype person" shape="tripleoctagon" ]
}
subgraph cluster_class {
style="invis"
emcomp1[ label="EmClass textes", shape="doubleoctagon" ]
emcomp2[ label="EmClass personnes", shape="doubleoctagon" ]
emcomp13[ label="EmClass publication", shape="doubleoctagon" ]
}
emcomp5[ label="EmType article | { titre|<f0> auteur|soustitre}" shape="record" color="blue" ]
emcomp5emcomp6 [ label="rel_to_type | { adresse}" shape="record"]
emcomp6[ label="EmType personne | { nom|prenom}" shape="record" color="blue" ]
emcomp14[ label="EmType rubrique | { titre}" shape="record" color="blue" ]
emcomp19[ label="EmType numero | { titre}" shape="record" color="blue" ]
emcomp1 -> ctentity [ style="dashed" ]
emcomp2 -> ctperson [ style="dashed" ]
emcomp13 -> ctentity [ style="dashed" ]
emcomp5:f0 -> emcomp5emcomp6 [ color="purple" dir="both" ]
emcomp5emcomp6 -> emcomp6 [color="purple" dir="both" ]
emcomp5 -> emcomp1 [ style="dotted" ]
emcomp5 -> emcomp14 [ label="parent" color="green" ]
emcomp6 -> emcomp2 [ style="dotted" ]
emcomp14 -> emcomp13 [ style="dotted" ]
emcomp14 -> emcomp14 [ label="parent" color="green" ]
emcomp14 -> emcomp19 [ label="parent" color="green" ]
emcomp19 -> emcomp13 [ style="dotted" ]
}

View file

@ -0,0 +1,19 @@
digraph Lodel2_architecture {
user [label="Lodel user"]
ui [label="<ui> UI|{web|cli|lodel2fs}|<tpl> templates", shape=record]
action [label="{user|action|target}|<datas> datas}", shape=record]
ACL
leapi [label="LeAPI"]
user -> ui
ui:tpl -> user
ui:ui -> action
action -> ACL
ACL -> leapi
action:datas -> leapi
leapi -> ui:tpl
leapi -> leDataSource [dir=both]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

View file

@ -1,716 +0,0 @@
#!/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/>.
#
import sys
import os, os.path
sys.path.append(os.path.dirname(os.getcwd()+'/..'))
from lodel.context import LodelContext
LodelContext.init()
from lodel.settings.settings import Settings as settings
settings('globconf.d')
from lodel.settings import Settings
from lodel.editorial_model.components import *
from lodel.editorial_model.exceptions import *
from lodel.editorial_model.model import EditorialModel
em = EditorialModel('simpleem', 'Simple editorial model')
base_group = em.new_group( 'base_group',
display_name = 'Base group',
help_text = 'Base group that implements base EM features (like classtype)'
)
####################
# Lodel Object #
####################
em_abstract = em.new_class( 'abstract_object',
display_name = 'Abstract lodel object',
help_text = 'For testing purpose',
group = base_group,
abstract = True)
em_object = em.new_class( 'object',
display_name = 'Object',
help_text = 'Main class for all Em objects',
group = base_group,
abstract = True,
parents = em_abstract,
)
em_object.new_field( 'lodel_id',
display_name = 'Lodel identifier',
help_text = 'Uniq ID that identify every lodel object',
group = base_group,
data_handler = 'uniqid',
internal = True,
)
em_object.new_field( 'date_create',
display_name = 'Creation date',
group = base_group,
data_handler = 'datetime',
now_on_create = True,
internal = True,
)
em_object.new_field( 'date_update',
display_name = 'Last update',
group = base_group,
data_handler = 'datetime',
now_on_update = True,
internal = True,
)
entitie = em.new_class( 'entitie',
display_name = 'entitie',
help_text = 'Replace old entity classtype',
abstract = True,
group = base_group,
parents = em_object,
)
########################
# Base group
########################
person = em.new_class( 'person',
display_name = 'Person',
help_text = 'Person type',
abstract = False,
group = base_group,
parents = em_object,
)
person.new_field( 'firstname',
display_name = {
'eng': 'Firstname',
'fre': 'Prénom',
},
data_handler = 'varchar',
group = base_group,
)
person.new_field( 'lastname',
display_name = {
'eng': 'Lastname',
'fre': 'Nom de famille',
},
data_handler = 'varchar',
group = base_group,
)
# person.new_field( 'role',
# display_name = {
# 'eng': 'Role',
# 'fre': 'Rôle',
# },
# data_handler = 'varchar',
# group = base_group,
# )
entry = em.new_class( 'entry',
display_name = 'Entry',
help_text = 'Entry type',
abstract = False,
group = base_group,
parents = em_object,
)
entry.new_field( 'name',
display_name = {
'eng': 'Name',
'fre': 'Nom',
},
data_handler = 'varchar',
group = base_group,
)
entry.new_field( 'description',
display_name = {
'eng': 'Description',
'fre': 'Description',
},
data_handler = 'text',
group = base_group,
)
entry.new_field( 'role',
display_name = {
'eng': 'Role',
'fre': 'Rôle',
},
data_handler = 'varchar',
group = base_group,
)
#####################
# Editorial classes #
#####################
editorial_group = em.new_group( 'editorial_abstract',
display_name = 'Editorial base',
help_text = {
'eng': 'Contains abstract class to handler editorial contents',
'fre': 'Contient les classes abstraites permetant la gestion de contenu éditorial'
},
depends = (base_group,)
)
################################ Texts #############################
# Classe texte
text = em.new_class( 'text',
display_name = 'Text',
help_text = 'Abstract class that represent texts',
group = editorial_group,
abstract = True,
parents = entitie,
)
text.new_field( 'title',
display_name = {'eng': 'Title', 'fre': 'Titre'},
group = editorial_group,
data_handler = 'text',
nullable = True,)
text.new_field( 'subtitle',
display_name = {
'eng': 'Subtitle',
'fre': 'Sous-titre',
},
group = editorial_group,
data_handler = 'text',
nullable = True,
default = None)
text.new_field( 'language',
display_name = {
'eng': 'Language',
'fre': 'Langue',
},
group = editorial_group,
data_handler = 'varchar',
nullable = True,
default = None)
text.new_field( 'text',
display_name = {
'eng': 'Text',
'fre': 'Texte',
},
group = editorial_group,
data_handler = 'text',
nullable = True,
default = None)
text.new_field( 'pub_date',
display_name = {
'eng': 'Publication date',
'fre': 'Date de publication',
},
group = editorial_group,
data_handler = 'datetime',
nullable = True,
default = None)
text.new_field( 'footnotes',
display_name = {
'eng': 'Footnotes',
'fre': 'Notes de bas de page',
},
group = editorial_group,
data_handler = 'text',
nullable = True,
default = None)
text.new_field( 'linked_entries',
display_name = {
'eng': 'Related entries',
'fre': 'Indices liés',
},
group = editorial_group,
data_handler = 'list',
nullable = True,
allowed_classes = [entry],
back_reference = ('entry', 'linked_texts'),
default = None
)
entry.new_field( 'linked_texts',
display_name = {
'eng': 'Related text',
'fre': 'Texte lié ',
},
data_handler = 'list',
nullable = True,
allowed_classes = [text],
group = editorial_group,
back_reference = ('text', 'linked_entries'),
default = None
)
# Classe article
article = em.new_class( 'article',
display_name = 'Article',
group = editorial_group,
abstract = False,
parents = text)
article.new_field( 'abstract',
display_name = {
'eng': 'Abstract',
'fre': 'Résumé',
},
group = editorial_group,
data_handler = 'text'
)
article.new_field( 'appendix',
display_name = {
'eng': 'Appendix',
'fre': 'Appendice',
},
group = editorial_group,
data_handler = 'text'
)
article.new_field( 'bibliography',
display_name = {
'eng': 'Bibliography',
'fre': 'Bibliographie',
},
group = editorial_group,
data_handler = 'text'
)
article.new_field( 'author_note',
display_name = {
'eng': 'Author note',
'fre': "Note de l'auteur",
},
group = editorial_group,
data_handler = 'text'
)
# Classe Review
review = em.new_class( 'review',
display_name = 'Review',
group = editorial_group,
abstract = False,
parents = text)
review.new_field( 'reference',
display_name = {
'eng': 'Reference',
'fre': "Référence",
},
group = editorial_group,
data_handler = 'text'
)
###################################### Containers ##################
# Classe container
container = em.new_class( 'container',
display_name = 'Container',
group = editorial_group,
abstract = True,
parents = entitie)
container.new_field( 'title',
display_name = 'Title',
group = editorial_group,
data_handler = 'text'
)
container.new_field( 'subtitle',
display_name = 'Subtitle',
group = editorial_group,
data_handler = 'text'
)
container.new_field( 'language',
display_name = {
'eng' : 'Language',
'fre' : 'Langue',
},
group = editorial_group,
data_handler = 'varchar'
)
container.new_field( 'linked_directors',
display_name = {
'eng': 'Directors',
'fre': 'Directeurs',
},
group = editorial_group,
data_handler = 'list',
nullable = True,
allowed_classes = [person],
back_reference = ('person', 'linked_containers'),
default = None
)
person.new_field( 'linked_containers',
display_name = {
'eng': 'Director of ',
'fre': 'Directeur de ',
},
group = editorial_group,
data_handler = 'list',
nullable = True,
allowed_classes = [container],
back_reference = ('container', 'linked_directors'),
default = None
)
container.new_field( 'description',
display_name = {
'eng': 'Description',
'fre': 'Description',
},
data_handler = 'text',
group = editorial_group,
)
container.new_field( 'publisher_note',
display_name = {
'eng': 'Publisher note',
'fre': "Note de l'éditeur",
},
data_handler = 'text',
group = editorial_group,
)
# Classe collection
collection = em.new_class( 'collection',
display_name = 'Collection',
group = editorial_group,
abstract = False,
parents = container)
collection.new_field( 'issn',
display_name = {
'eng': 'ISSN',
'fre': "ISSN",
},
data_handler = 'varchar',
group = editorial_group,
)
# Classe Publication : Pour gérer les back_references vers issue ou part
publication = em.new_class( 'publication',
display_name = 'Publication',
group = editorial_group,
abstract = True,
parents = container)
publication.new_field( 'linked_texts',
display_name = {
'eng': 'Text',
'fre': 'Texte',
},
data_handler = 'list',
nullable = True,
allowed_classes = [text],
group = editorial_group,
back_reference = ('text', 'linked_container'),
default = None
)
text.new_field( 'linked_container',
display_name = {
'eng': 'Container',
'fre': 'Conteneur',
},
data_handler = 'link',
nullable = True,
allowed_classes = [publication],
group = editorial_group,
back_reference = ('publication', 'linked_texts'),
default = None
)
# Classe Issue
issue = em.new_class( 'issue',
display_name = 'Issue',
group = editorial_group,
abstract = False,
parents = publication)
issue.new_field( 'isbn',
display_name = 'ISBN',
data_handler = 'varchar',
group = editorial_group,
)
issue.new_field( 'print_isbn',
display_name = {
'eng': 'Printed ISBN',
'fre': "ISBN imprimé",
},
data_handler = 'varchar',
group = editorial_group,
)
issue.new_field( 'number',
display_name = {
'eng': 'Number',
'fre': 'Numéro',
},
data_handler = 'varchar',
group = editorial_group,
)
issue.new_field( 'cover',
display_name = {
'eng': 'Cover',
'fre': 'Couverture',
},
data_handler = 'varchar',
group = editorial_group,
)
issue.new_field( 'print_pub_date',
display_name = {
'eng': 'Print date',
'fre': "Date d'impression",
},
data_handler = 'datetime',
group = editorial_group,
)
issue.new_field( 'e_pub_date',
display_name = {
'eng': 'Electronic publication date',
'fre': 'Date de publication électronique',
},
data_handler = 'datetime',
group = editorial_group,
)
issue.new_field( 'abstract',
display_name = {
'eng': 'Abstract',
'fre': 'Résumé',
},
data_handler = 'text',
group = editorial_group,
)
issue.new_field( 'collection',
display_name = {
'eng': 'Collection',
'fre': 'Collection',
},
data_handler = 'link',
nullable = True,
allowed_classes = [collection],
group = editorial_group,
back_reference = ('collection', 'linked_issues'),
default = None
)
collection.new_field( 'linked_issues',
display_name = {
'eng': 'Linked issues',
'fre': 'Numéros',
},
data_handler = 'hierarch',
back_reference = ('Issue', 'collection'),
group = editorial_group,
allowed_classes = [issue],
default = None,
nullable = True)
# Classe Part
part = em.new_class( 'part',
display_name = 'Part',
group = editorial_group,
abstract = False,
parents = publication,)
part.new_field( 'publication',
display_name = {
'eng': 'Publication',
'fre': 'Publication',
},
data_handler = 'link',
nullable = True,
allowed_classes = [publication],
group = editorial_group,
back_reference = ('publication', 'linked_parts'),
default = None
)
publication.new_field( 'linked_parts',
display_name = {
'eng': 'Parts',
'fre': 'Parties',
},
data_handler = 'hierarch',
nullable = True,
allowed_classes = [part],
group = editorial_group,
back_reference = ('part', 'publication'),
default = None
)
#####################
# Persons & authors #
#####################
editorial_person_group = em.new_group( 'editorial_person',
display_name = 'Editorial person',
help_text = {
'eng': 'Introduce the concept of editorial person (author, translator, director)',
'fre': 'Contient les classes servant à la gestion des personnes éditoriales (auteur, traducteur, directeur...)',
},
depends = (editorial_group,)
)
text_person = em.new_class( 'text_person',
display_name = {
'eng': 'TextPerson',
'fre': 'TextePersonne',
},
help_text = {
'eng': 'Represent a link between someone and a text',
'fre': 'Représente un lien entre une personne et un texte',
},
group = editorial_person_group,
abstract = True,
parents = entitie,
)
bref_textperson_text = text_person.new_field( 'text',
display_name = {
'eng': 'Linked text',
'fre': 'Texte lié',
},
data_handler = 'link',
allowed_classes = [text],
group = editorial_person_group
)
bref_textperson_person = text_person.new_field( 'person',
display_name = {
'eng': 'Linked person',
'fre': 'Personne liée',
},
data_handler = 'link',
allowed_classes = [person],
group = editorial_person_group,
)
text_person.new_field( 'role',
display_name = {
'eng': 'Person role',
'fre': 'Role de la personne',
},
data_handler = 'varchar',
group = editorial_person_group
)
# simple example of linked text / person
person.new_field( 'linked_texts',
display_name = {
'eng': 'Linked texts',
'fre': 'Textes liés',
},
data_handler = 'list',
back_reference = ('Text', 'linked_persons'),
group = editorial_person_group,
allowed_classes = [text],
default = None,
nullable = True)
text.new_field( 'linked_persons',
display_name = {
'eng': 'Linked persons',
'fre': 'Personnes liées',
},
data_handler = 'list',
back_reference = ('Person', 'linked_texts'),
group = editorial_person_group,
allowed_classes = [person],
default = None,
nullable = True)
#####################
# Index classes # <--- Note : using a different datasource for testing
##################### purpose
index_group = em.new_group( 'index_group',
display_name = 'Indexes',
help_text = {
'eng': 'EM class that represents indexes'},
depends = (editorial_group,))
index_abstract = em.new_class(
'indexAbs',
display_name = {'eng': 'Abstract Index'},
help_text = {'eng': 'Abstract class common to each Index classes'},
abstract = True,
group = index_group,
datasources = 'dummy2',
parents = em_object)
index_name = index_abstract.new_field(
'name',
display_name = {
'eng': 'name',
'fre': 'nom'},
data_handler = 'text',
group = index_group)
index_value = index_abstract.new_field(
'value',
display_name = {
'eng': 'value',
'fre': 'valeur'},
data_handler = 'varchar',
group = index_group)
text.new_field( 'indexes',
display_name = {
'eng': 'Indexes',
'fre': 'Indexes'},
data_handler = 'list',
back_reference = ('Indexabs', 'texts'),
allowed_classes = [index_abstract],
default = None,
nullable = True,
group = index_group)
index_abstract.new_field( 'texts',
display_name = {
'eng': 'Text referenced by this index',
'fre': 'Texte contenant cette index'},
data_handler = 'list',
back_reference = ('Text', 'indexes'),
allowed_classes = [text],
group = index_group)
index_theme = em.new_class(
'indexTheme',
display_name = {
'eng': 'Thematic index',
'fre': 'Index thématique'},
group = index_group,
datasources = 'dummy2',
parents = index_abstract)
index_theme_theme = index_abstract.new_field(
'theme',
display_name = {
'eng': 'theme'},
data_handler = 'varchar',
group = index_group)
#############
# USERS #
#############
user_group = em.new_group(
'users', display_name = 'Lodel users',
help_text = 'Group that handle users en perm')
user = em.new_class(
'user', display_name = 'Lodel user', help_text = 'Represent a lodel user',
group = user_group, abstract = False)
user.new_field(
'id', display_name = 'user identifier', help_text = 'Uniq ID',
group = user_group, data_handler = 'uniqid', internal = True)
user.new_field(
'firstname', display_name = 'Firstname',
group = user_group, data_handler = 'varchar', internal = False)
user.new_field(
'lastname', display_name = 'Lastname',
group = user_group, data_handler = 'varchar', internal = False)
user.new_field(
'login', display_name = 'user login', help_text = 'login',
group = user_group, data_handler = 'varchar', uniq = True, internal = False)
user.new_field(
'password', display_name = 'Password',
group = user_group, data_handler = 'password', internal = False)
em.save('xmlfile', filename = 'editorial_models/em_simple.xml')
pickle_file = 'examples/em_simple.pickle'
em.save('picklefile', filename = pickle_file)
print("Output written in %s" % pickle_file)

View file

@ -1,441 +0,0 @@
#!/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/>.
#
from lodel.context import LodelContext
LodelContext.init()
from lodel.settings.settings import Settings as settings
settings('globconf.d')
from lodel.settings import Settings
from lodel.editorial_model.components import *
from lodel.editorial_model.exceptions import *
from lodel.editorial_model.model import EditorialModel
em = EditorialModel('testem', 'Test editorial model')
base_group = em.new_group( 'base_group',
display_name = 'Base group',
help_text = 'Base group that implements base EM features (like classtype)'
)
####################
# Lodel Object #
####################
em_abstract = em.new_class( 'abstract_object',
display_name = 'Abstract lodel object',
help_text = 'For testing purpose',
group = base_group,
abstract = True)
em_object = em.new_class( 'object',
display_name = 'Object',
help_text = 'Main class for all Em objects',
group = base_group,
abstract = True,
parents = em_abstract,
)
em_object.new_field( 'lodel_id',
display_name = 'Lodel identifier',
help_text = 'Uniq ID that identify every lodel object',
group = base_group,
data_handler = 'uniqid',
internal = True,
)
em_object.new_field( 'help_text',
display_name = 'Help',
help_text = 'A short text that describe the object',
group = base_group,
internal = True,
data_handler = 'text',
)
em_object.new_field( 'date_create',
display_name = 'Creation date',
group = base_group,
data_handler = 'datetime',
now_on_create = True,
internal = True,
)
em_object.new_field( 'date_update',
display_name = 'Last update',
group = base_group,
data_handler = 'datetime',
now_on_update = True,
internal = True,
)
########################
# Lodel old classtypes #
########################
entitie = em.new_class( 'entitie',
display_name = 'entitie',
help_text = 'Replace old entity classtype',
abstract = True,
group = base_group,
parents = em_object,
)
person = em.new_class( 'person',
display_name = 'Person',
help_text = 'Replace old person classtype',
abstract = False,
group = base_group,
parents = em_object,
)
person.new_field( 'firstname',
display_name = {
'eng': 'Firstname',
'fre': 'Prénom',
},
data_handler = 'varchar',
group = base_group,
)
person.new_field( 'lastname',
display_name = {
'eng': 'Lastname',
'fre': 'Nom de famille',
},
data_handler = 'varchar',
group = base_group,
)
person.new_field( 'fullname',
display_name = {
'eng': 'Fullname',
'fre': 'Nom complet',
},
group = base_group,
data_handler = 'Concat',
field_list = ['firstname', 'lastname'],
immutable = True,
)
person.new_field( 'alias',
display_name = 'alias',
help_text = {
'eng': 'Link to other person class instance that represent the same person',
'fre': 'Lien vers un ensemble d\'instances de la classe personne représentant le même individu',
},
data_handler = 'set',
allowed_classes = [person],
default = None,
nullable = True,
group = base_group,
)
entry = em.new_class( 'entry',
display_name = 'Entry',
help_text = 'Replace olf entry classtype',
abstract = True,
group = base_group,
parents = em_object,
)
#####################
# Editorial classes #
#####################
editorial_group = em.new_group( 'editorial_abstract',
display_name = 'Editorial base',
help_text = {
'eng': 'Contains abstract class to handler editorial contents',
'fre': 'Contient les classes abstraites permetant la gestion de contenu éditorial'
},
depends = (base_group,)
)
# Classe texte
text = em.new_class( 'text',
display_name = 'Text',
help_text = 'Abstract class that represent texts',
group = editorial_group,
abstract = True,
parents = entitie,
)
text.new_field( 'title',
display_name = {'eng': 'Title', 'fre': 'Titre'},
group = editorial_group,
data_handler = 'varchar',
nullable = True,)
text.new_field( 'subtitle',
display_name = {
'eng': 'Subtitle',
'fre': 'Sous-titre',
},
group = editorial_group,
data_handler = 'varchar',
nullable = True,
default = None)
# Classe collection
collection = em.new_class( 'collection',
display_name = 'Collection',
group = editorial_group,
abstract = False,
parents = entitie)
collection.new_field( 'title',
display_name = 'Title',
group = editorial_group,
data_handler = 'varchar'
)
# Classe publication
publication = em.new_class( 'publication',
display_name = 'Publication',
group = editorial_group,
abstract = False,
parents = entitie,)
publication.new_field( 'collection',
display_name = 'Collection',
group = editorial_group,
data_handler = 'link',
default = None,
nullable = True,
allowed_classes = [collection],
back_reference = ('collection', 'publications'))
collection.new_field( 'publications',
display_name = 'Publications',
group = editorial_group,
data_handler = 'list',
allowed_classes = [publication],
back_reference = ('publication', 'collection'))
#########################
# Texte definition #
#########################
section = em.new_class( 'section',
display_name = 'Section',
group = editorial_group,
abstract = False,
parents = text)
subsection = em.new_class( 'subsection',
display_name = 'Subsection',
group = editorial_group,
abstract = False,
parents = section)
section.new_field( 'childs',
display_name = 'Next section',
group = editorial_group,
data_handler = 'hierarch',
allowed_classes = [subsection],
back_reference = ('subsection', 'parent'),
default = None,
nullable = True)
subsection.new_field( 'parent',
display_name = 'Parent',
group = editorial_group,
data_handler = 'link',
default = None,
nullable = True,
allowed_classes = [section],
back_reference = ('section', 'childs'))
#####################
# Persons & authors #
#####################
editorial_person_group = em.new_group( 'editorial_person',
display_name = 'Editorial person',
help_text = {
'eng': 'Introduce the concept of editorial person (authors, translator etc)',
'fre': 'Contient les classes servant à la gestion des personnes editorials (auteurs, traducteur...)',
},
depends = (editorial_group,)
)
text_person = em.new_class( 'text_person',
display_name = {
'eng': 'TextPerson',
'fre': 'TextePersonne',
},
help_text = {
'eng': 'Represent a link between someone and a text',
'fre': 'Représente un lien entre une personne et un texte',
},
group = editorial_person_group,
abstract = True,
parents = entitie,
)
bref_textperson_text = text_person.new_field( 'text',
display_name = {
'eng': 'Linked text',
'fre': 'Texte lié',
},
data_handler = 'link',
allowed_classes = [text],
group = editorial_person_group
)
bref_textperson_person = text_person.new_field( 'person',
display_name = {
'eng': 'Linked person',
'fre': 'Personne liée',
},
data_handler = 'link',
allowed_classes = [person],
group = editorial_person_group,
)
text_person.new_field( 'role',
display_name = {
'eng': 'Person role',
'fre': 'Role de la personne',
},
data_handler = 'varchar',
group = editorial_person_group
)
# simple example of linked text / person
person.new_field( 'linked_texts',
display_name = {
'eng': 'Linked texts',
'fre': 'Textes liés',
},
data_handler = 'list',
back_reference = ('Text', 'linked_persons'),
group = editorial_person_group,
allowed_classes = [text],
default = None,
nullable = True)
text.new_field( 'linked_persons',
display_name = {
'eng': 'Linked persons',
'fre': 'Personnes liées',
},
data_handler = 'list',
back_reference = ('Person', 'linked_texts'),
group = editorial_person_group,
allowed_classes = [person],
default = None,
nullable = True)
#####################
# Index classes # <--- Note : using a different datasource for testing
##################### purpose
index_group = em.new_group( 'index_group',
display_name = 'Indexes',
help_text = {
'eng': 'EM class that represents indexes'},
depends = (editorial_group,))
index_abstract = em.new_class(
'indexAbs',
display_name = {'eng': 'Abstract Index'},
help_text = {'eng': 'Abstract class common to each Index classes'},
abstract = True,
group = index_group,
datasources = 'dummy2',
parents = em_object)
index_name = index_abstract.new_field(
'name',
display_name = {
'eng': 'name',
'fre': 'nom'},
data_handler = 'varchar',
group = index_group)
index_value = index_abstract.new_field(
'value',
display_name = {
'eng': 'value',
'fre': 'valeur'},
data_handler = 'varchar',
group = index_group)
text.new_field( 'indexes',
display_name = {
'eng': 'Indexes',
'fre': 'Indexes'},
data_handler = 'list',
back_reference = ('Indexabs', 'texts'),
allowed_classes = [index_abstract],
default = None,
nullable = True,
group = index_group)
index_abstract.new_field( 'texts',
display_name = {
'eng': 'Text referenced by this index',
'fre': 'Texte contenant cette index'},
data_handler = 'list',
back_reference = ('Text', 'indexes'),
allowed_classes = [text],
group = index_group)
index_theme = em.new_class(
'indexTheme',
display_name = {
'eng': 'Thematic index',
'fre': 'Index thématique'},
group = index_group,
datasources = 'dummy2',
parents = index_abstract)
index_theme_theme = index_abstract.new_field(
'theme',
display_name = {
'eng': 'theme'},
data_handler = 'varchar',
group = index_group)
#############
# USERS #
#############
user_group = em.new_group(
'users', display_name = 'Lodel users',
help_text = 'Group that handle users en perm')
user = em.new_class(
'user', display_name = 'Lodel user', help_text = 'Represent a lodel user',
group = user_group, abstract = False)
user.new_field(
'id', display_name = 'user identifier', help_text = 'Uniq ID',
group = user_group, data_handler = 'uniqid', internal = True)
user.new_field(
'firstname', display_name = 'Firstname',
group = user_group, data_handler = 'varchar', internal = False)
user.new_field(
'lastname', display_name = 'Lastname',
group = user_group, data_handler = 'varchar', internal = False)
user.new_field(
'login', display_name = 'user login', help_text = 'login',
group = user_group, data_handler = 'varchar', uniq = True, internal = False)
user.new_field(
'password', display_name = 'Password',
group = user_group, data_handler = 'password', internal = False)
#em.save('xmlfile', filename = 'examples/em_test.xml')
pickle_file = 'examples/em_test.pickle'
em.save('picklefile', filename = pickle_file)
print("Output written in %s" % pickle_file)

Binary file not shown.

View file

@ -1,43 +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/>.
#
[lodel2]
debug = False
sitename = noname
datasource_connectors = dummy_datasource
session_handler = filesystem_session
[lodel2.datasources.default]
identifier = dummy_datasource.default
[lodel2.datasource.dummy_datasource.default]
dummy =
[lodel2.logging.stderr]
level = ERROR
filename = -
context = True
[lodel2.editorialmodel]
groups =
emfile = examples/em_test.pickle
dyncode = leapi_dyncode.py
editormode = True

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