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