Compare commits
12 Commits
Author | SHA1 | Date |
---|---|---|
Thomas NOËL | 2a8758f677 | |
Thomas NOËL | 63a6c51c7d | |
Thomas NOËL | 2ca7a789f5 | |
Thomas NOËL | 890903d96f | |
Jérôme Schneider | 3795559c98 | |
Jérôme Schneider | 72ea92c938 | |
Frédéric Péters | 26ec14b8a2 | |
Frédéric Péters | 4cc8376a74 | |
Frédéric Péters | 2ac222d051 | |
Jérôme Schneider | 4bb518ff98 | |
Jérôme Schneider | 3faac4f2b0 | |
Jérôme Schneider | 140d858ad7 |
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/>.
|
15
MANIFEST.in
15
MANIFEST.in
|
@ -1,15 +0,0 @@
|
|||
# locales
|
||||
recursive-include combo/locale *.po
|
||||
|
||||
# static
|
||||
recursive-include combo/manager/static *.css *.js *.ico *.gif *.png *.jpg
|
||||
recursive-include combo/public/static *.css *.js *.ico *.gif *.png *.jpg
|
||||
|
||||
# templates
|
||||
recursive-include combo/apps/wcs/templates *.html
|
||||
recursive-include combo/manager/templates *.html
|
||||
recursive-include combo/public/templates *.html
|
||||
|
||||
include COPYING README
|
||||
include MANIFEST.in
|
||||
include VERSION
|
117
README
117
README
|
@ -1,117 +0,0 @@
|
|||
Combo
|
||||
=====
|
||||
|
||||
Combo is a simple content management system, tailored to create simple
|
||||
websites, and with a specialization in aggregating contents from different
|
||||
sources.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Dependencies can be installed with pip,
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
It's then required to get the database configured (./manage.py syncdb); by
|
||||
default it will create a db.sqlite3 file.
|
||||
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
Combo manages content as a series of pages (objects of type 'Page'), that can
|
||||
be sorted ('order' attribute) and hierarchically ordered ('parent' attribute).
|
||||
|
||||
Every pages have a title and a "slug", that is used as the page URL; a page
|
||||
with 'index' as its slug will be served as the index page of the site.
|
||||
|
||||
The pages are set to use a template; the templates are defined in the settings
|
||||
file (COMBO_PUBLIC_TEMPLATES) and are made of a name, a template file, and a
|
||||
serie of "placeholders", identifying locations in the page (main content, side
|
||||
bar, footer...).
|
||||
|
||||
Example:
|
||||
|
||||
'standard': {
|
||||
'name': 'Standard',
|
||||
'template': 'combo/page_template.html',
|
||||
'placeholders': {
|
||||
'content': {
|
||||
'name': 'Content',
|
||||
},
|
||||
'footer': {
|
||||
'name': 'Footer',
|
||||
'acquired': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
The content of a page is defined as a serie of cells, of which there are
|
||||
various types (they are all subclasses of CellBase); each cell is also
|
||||
associated with a placeholder ('placeholder' attribute) and its order within
|
||||
('order' attribute).
|
||||
|
||||
A placeholder can be marked as 'acquired' (see "footer" in the example above),
|
||||
this makes its cells automatically inherited from a parent page; the presence
|
||||
of a cell of type 'Unlock Marker' will stop this acquisition mechanism, this
|
||||
makes it possible to have a different content for those placeholders in
|
||||
specific pages or sections have the site.
|
||||
|
||||
Note: in the case of placeholder acquisition the site index page will be
|
||||
inserted as the top of the page hierarchy.
|
||||
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
Default settings are loaded from settings.py, they can be overloaded by a
|
||||
local_settings.py file set in the same directory, or by a file referenced
|
||||
in the COMBO_SETTINGS_FILE environment variable.
|
||||
|
||||
SAML authentication can be enabled by adding 'mellon' to INSTALLED_APPS and
|
||||
'mellon.backends.SAMLBackend' to AUTHENTICATION_BACKENDS, this requires
|
||||
django-mellon to be installed, and further files and settings are required:
|
||||
|
||||
- public and private keys (in cert.pem and key.cert in the current working
|
||||
directory, or from files defined in the MELLON_PUBLIC_KEYS and
|
||||
MELLON_PRIVATE_KEY settings)
|
||||
- metadata of the identity provider (in idp-metadata.xml, or defined using
|
||||
the MELLON_IDENTITY_PROVIDERS settings)
|
||||
|
||||
Details on these options and additional SAML settings are available in the
|
||||
documentation of django-mellon.
|
||||
|
||||
|
||||
Blurps (from cmsplugin-blurp module) can be used to define additional cell
|
||||
types, the CMS_PLUGIN_BLURP_RENDERERS variable is used to hold them, details
|
||||
can be found in the cmsplugin-blurp documentation.
|
||||
|
||||
In addition to common attributes Combo supports a 'private' attribute, that can
|
||||
be used to exclude the given blurp from selectable cells. The blurp itself can
|
||||
still be referenced manually in a template file. This is useful to avoid
|
||||
overwhelming the UI with blurps used in non-editable parts of the pages.
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Unit tests are written using py.test, and its pytest-django support library.
|
||||
|
||||
DJANGO_SETTINGS_MODULE=combo.settings COMBO_SETTINGS_FILE=tests/settings.py py.test
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
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/>.
|
|
@ -1,19 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015 Entr'ouvert
|
||||
#
|
||||
# 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 . import plugins
|
||||
|
||||
plugins.init()
|
|
@ -1,11 +0,0 @@
|
|||
Combo/wcs integration
|
||||
=====================
|
||||
|
||||
INSTALLED_APPS += ('combo.apps.wcs',)
|
||||
|
||||
COMBO_WCS_SITES = {
|
||||
'default': {'title': 'wcs', 'url': 'http://wcs/'},
|
||||
}
|
||||
|
||||
It is also possible to load the module but to disable the cell types with
|
||||
WCS_CELLS_ENABLE = False.
|
|
@ -1,53 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014-2015 Entr'ouvert
|
||||
#
|
||||
# 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 django import forms
|
||||
from django.conf import settings
|
||||
|
||||
from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell
|
||||
from .utils import get_wcs_options
|
||||
|
||||
class WcsFormCellForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = WcsFormCell
|
||||
fields = ('formdef_reference',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WcsFormCellForm, self).__init__(*args, **kwargs)
|
||||
formdef_references = get_wcs_options('json')
|
||||
self.fields['formdef_reference'].widget = forms.Select(choices=formdef_references)
|
||||
|
||||
|
||||
class WcsCategoryCellForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = WcsCategoryCell
|
||||
fields = ('category_reference', 'link_page')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WcsCategoryCellForm, self).__init__(*args, **kwargs)
|
||||
references = get_wcs_options('categories')
|
||||
self.fields['category_reference'].widget = forms.Select(choices=references)
|
||||
|
||||
|
||||
class WcsFormsOfCategoryCellForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = WcsFormsOfCategoryCell
|
||||
fields = ('category_reference', 'ordering', 'limit')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WcsFormsOfCategoryCellForm, self).__init__(*args, **kwargs)
|
||||
references = get_wcs_options('categories')
|
||||
self.fields['category_reference'].widget = forms.Select(choices=references)
|
|
@ -1,255 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014-2015 Entr'ouvert
|
||||
#
|
||||
# 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 django import template
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.forms import models as model_forms
|
||||
from django.forms import Select
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import cmsplugin_blurp.utils
|
||||
|
||||
from combo.data.models import CellBase
|
||||
from combo.data.library import register_cell_class
|
||||
|
||||
from .utils import get_wcs_json
|
||||
|
||||
|
||||
def is_wcs_enabled(cls):
|
||||
return not hasattr(settings, 'WCS_CELLS_ENABLE') or settings.WCS_CELLS_ENABLE
|
||||
|
||||
@register_cell_class
|
||||
class WcsFormCell(CellBase):
|
||||
formdef_reference = models.CharField(_('Form'), max_length=100)
|
||||
|
||||
cached_title = models.CharField(_('Title'), max_length=50)
|
||||
cached_url = models.URLField(_('URL'))
|
||||
|
||||
is_enabled = classmethod(is_wcs_enabled)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Form Link')
|
||||
|
||||
def get_default_form_class(self):
|
||||
from .forms import WcsFormCellForm
|
||||
return WcsFormCellForm
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.formdef_reference:
|
||||
wcs_key, form_slug = self.formdef_reference.split(':')
|
||||
wcs_site = settings.COMBO_WCS_SITES.get(wcs_key)
|
||||
forms_response_json = get_wcs_json(wcs_site.get('url') + 'json')
|
||||
for form in forms_response_json:
|
||||
slug = form.get('slug')
|
||||
if slug == form_slug:
|
||||
self.cached_title = form.get('title')
|
||||
self.cached_url = form.get('url')
|
||||
return super(WcsFormCell, self).save(*args, **kwargs)
|
||||
|
||||
def render(self, context):
|
||||
formdef_template = template.loader.get_template('combo/wcs/form.html')
|
||||
context['slug'] = self.formdef_reference.split(':')[-1]
|
||||
context['title'] = self.cached_title
|
||||
context['url'] = self.cached_url
|
||||
return formdef_template.render(context)
|
||||
|
||||
def get_additional_label(self):
|
||||
if not self.cached_title:
|
||||
return
|
||||
return self.cached_title
|
||||
|
||||
|
||||
class WcsCommonCategoryCell(CellBase):
|
||||
is_enabled = classmethod(is_wcs_enabled)
|
||||
category_reference = models.CharField(_('Category'), max_length=100)
|
||||
|
||||
cached_title = models.CharField(_('Title'), max_length=50)
|
||||
cached_description = models.TextField(_('Description'), blank=True)
|
||||
cached_url = models.URLField(_('Cached URL'))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.category_reference:
|
||||
wcs_key, category_slug = self.category_reference.split(':')
|
||||
wcs_site = settings.COMBO_WCS_SITES.get(wcs_key)
|
||||
categories_response_json = get_wcs_json(wcs_site.get('url') + 'categories')
|
||||
for category in categories_response_json.get('data'):
|
||||
slug = category.get('slug')
|
||||
if slug == category_slug:
|
||||
self.cached_title = category.get('title')
|
||||
self.cached_description = category.get('description') or ''
|
||||
self.cached_url = category.get('url')
|
||||
return super(WcsCommonCategoryCell, self).save(*args, **kwargs)
|
||||
|
||||
def get_additional_label(self):
|
||||
if not self.cached_title:
|
||||
return
|
||||
return self.cached_title
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class WcsCategoryCell(WcsCommonCategoryCell):
|
||||
link_page = models.ForeignKey('data.Page', related_name='link', null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Category Link')
|
||||
|
||||
def get_default_form_class(self):
|
||||
from .forms import WcsCategoryCellForm
|
||||
return WcsCategoryCellForm
|
||||
|
||||
def render(self, context):
|
||||
category_template = template.loader.get_template('combo/wcs/category.html')
|
||||
context['slug'] = self.category_reference.split(':')[-1]
|
||||
context['title'] = self.cached_title
|
||||
context['description'] = self.cached_description
|
||||
if self.link_page:
|
||||
context['url'] = self.link_page.get_online_url()
|
||||
else:
|
||||
context['url'] = self.cached_url
|
||||
return category_template.render(context)
|
||||
|
||||
|
||||
class WcsBlurpMixin(object):
|
||||
is_enabled = classmethod(is_wcs_enabled)
|
||||
|
||||
def get_blurp_renderer(self, context):
|
||||
if self.wcs_site:
|
||||
wcs_site = settings.COMBO_WCS_SITES.get(self.wcs_site)
|
||||
else:
|
||||
wcs_site = settings.COMBO_WCS_SITES.values()[0]
|
||||
|
||||
source = {
|
||||
'slug': self.variable_name,
|
||||
'parser_type': 'json',
|
||||
'verify_certificate': False,
|
||||
'allow_redirects': False,
|
||||
}
|
||||
|
||||
url = wcs_site.get('url')
|
||||
url += self.api_url + '?format=json'
|
||||
if context.get('user') and hasattr(context['user'], 'email') and context['user'].email:
|
||||
url += '&orig=%s' % wcs_site.get('orig')
|
||||
url += '&email=' + context['user'].email
|
||||
auth_mech = 'hmac-sha1'
|
||||
source['auth_mech'] = auth_mech
|
||||
source['signature_key'] = wcs_site.get('secret')
|
||||
|
||||
source['url'] = url
|
||||
|
||||
renderer = cmsplugin_blurp.utils.create_renderer(self.variable_name, {
|
||||
'name': self._meta.verbose_name,
|
||||
'class': 'cmsplugin_blurp.renderers.data_source.Renderer',
|
||||
'sources': [source],
|
||||
'template_name': self.template_name,
|
||||
'refresh': 0,
|
||||
'ajax': False,
|
||||
})
|
||||
|
||||
return renderer
|
||||
|
||||
|
||||
class WcsDataBaseCell(CellBase, WcsBlurpMixin):
|
||||
is_enabled = classmethod(is_wcs_enabled)
|
||||
wcs_site = models.CharField(_('Site'), max_length=50)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_default_form_class(self):
|
||||
if len(settings.COMBO_WCS_SITES) == 1:
|
||||
return None
|
||||
combo_wcs_sites = [(x, y.get('title')) for x, y in settings.COMBO_WCS_SITES.items()]
|
||||
return model_forms.modelform_factory(self.__class__,
|
||||
fields=['wcs_site'],
|
||||
widgets={'wcs_site': Select(choices=combo_wcs_sites)})
|
||||
|
||||
def render(self, context):
|
||||
renderer = self.get_blurp_renderer(context)
|
||||
template = renderer.render_template()
|
||||
context = renderer.render(context)
|
||||
return template.render(context)
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class WcsCurrentFormsCell(WcsDataBaseCell):
|
||||
api_url = 'myspace/forms'
|
||||
variable_name = 'current_forms'
|
||||
template_name = 'combo/wcs/current_forms.html'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Current Forms')
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class WcsCurrentDraftsCell(WcsDataBaseCell):
|
||||
api_url = 'myspace/drafts'
|
||||
variable_name = 'current_drafts'
|
||||
template_name = 'combo/wcs/current_drafts.html'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Current Drafts')
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class WcsFormsOfCategoryCell(WcsCommonCategoryCell, WcsBlurpMixin):
|
||||
ordering = models.CharField(_('Order'), max_length=20,
|
||||
default='', blank=True,
|
||||
choices=[('', _('Default')),
|
||||
('alpha', _('Alphabetical')),
|
||||
('popularity', _('Popularity'))])
|
||||
limit = models.PositiveSmallIntegerField(_('Limit'),
|
||||
null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Forms of Category')
|
||||
|
||||
variable_name = 'forms'
|
||||
template_name = 'combo/wcs/forms_of_category.html'
|
||||
|
||||
def get_default_form_class(self):
|
||||
from .forms import WcsFormsOfCategoryCellForm
|
||||
return WcsFormsOfCategoryCellForm
|
||||
|
||||
@property
|
||||
def wcs_site(self):
|
||||
return self.category_reference.split(':')[0]
|
||||
|
||||
@property
|
||||
def api_url(self):
|
||||
return self.category_reference.split(':')[1] + '/json'
|
||||
|
||||
def render(self, context):
|
||||
renderer = self.get_blurp_renderer(context)
|
||||
template = renderer.render_template()
|
||||
context = renderer.render(context)
|
||||
context['slug'] = self.category_reference.split(':')[-1]
|
||||
context['title'] = self.cached_title
|
||||
context['description'] = self.cached_description
|
||||
if self.ordering == 'alpha':
|
||||
context['forms'] = sorted(context['forms'], lambda x, y: cmp(x.get('title'), y.get('title')))
|
||||
elif self.ordering == 'popularity':
|
||||
context['forms'] = sorted(context['forms'], lambda x, y: -cmp(x.get('count'), y.get('count')))
|
||||
|
||||
if self.limit:
|
||||
if len(context['forms']) > self.limit:
|
||||
context['more_forms'] = True
|
||||
context['forms'] = context['forms'][:self.limit]
|
||||
|
||||
return template.render(context)
|
|
@ -1,4 +0,0 @@
|
|||
<div class="wcs-category-{{slug}}">
|
||||
<h2><a href="{{ url }}">{{ title }}</a></h2>
|
||||
{{ description|safe }}
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% if current_drafts %}
|
||||
<h2>{% trans 'Current Drafts' %}</h2>
|
||||
<ul>
|
||||
{% for data in current_drafts %}
|
||||
<li><a href="{{ data.url }}">{{ data.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
|
@ -1,11 +0,0 @@
|
|||
{% load i18n %}
|
||||
<h2>{% trans 'Current Forms' %}</h2>
|
||||
{% if current_forms %}
|
||||
<ul>
|
||||
{% for data in current_forms %}
|
||||
{% if data.url and data.title and not data.form_status_is_endpoint %}
|
||||
<li><a href="{{ data.url }}">{{ data.title }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
|
@ -1 +0,0 @@
|
|||
<div class="wcs-form-{{slug}}"><a href="{{ url }}">{{ title }}</a></div>
|
|
@ -1,10 +0,0 @@
|
|||
<div class="wcs-forms-of-category-{{slug}}">
|
||||
<h2>{{ title }}</h2>
|
||||
{{ description|safe }}
|
||||
<ul>
|
||||
{% for form in forms %}
|
||||
<li><a href="{{ form.url }}">{{ form.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014-2015 Entr'ouvert
|
||||
#
|
||||
# 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 requests
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
def get_wcs_json(url):
|
||||
response_json = cache.get(url)
|
||||
if response_json is None:
|
||||
response_json = requests.get(url, headers={'accept': 'application/json'}).json()
|
||||
cache.set(url, response_json)
|
||||
return response_json
|
||||
|
||||
def get_wcs_options(url):
|
||||
references = []
|
||||
for wcs_key in settings.COMBO_WCS_SITES:
|
||||
wcs_site = settings.COMBO_WCS_SITES.get(wcs_key)
|
||||
site_title = wcs_site.get('title')
|
||||
response_json = get_wcs_json(wcs_site.get('url') + url)
|
||||
if type(response_json) is dict:
|
||||
response_json = response_json.get('data')
|
||||
for element in response_json:
|
||||
slug = element.get('slug')
|
||||
title = element.get('title')
|
||||
if len(settings.COMBO_WCS_SITES) == 1:
|
||||
label = title
|
||||
else:
|
||||
label = '%s : %s' % (site_title, title)
|
||||
reference = '%s:%s' % (wcs_key, slug)
|
||||
references.append((reference, label))
|
||||
references.sort(lambda x, y: cmp(x[1], y[1]))
|
||||
return references
|
|
@ -1,47 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django.contrib.contenttypes.models import ContentType
|
||||
|
||||
class Library(object):
|
||||
"""Singleton object that serves as a registry of the classes
|
||||
providing page cells."""
|
||||
|
||||
def __init__(self):
|
||||
self.classes = []
|
||||
self.classes_by_content_id = None
|
||||
|
||||
def get_cell_classes(self):
|
||||
return self.classes
|
||||
|
||||
def get_cell_class(self, content_type_id):
|
||||
if self.classes_by_content_id is None:
|
||||
# initialize the dictionary
|
||||
self.classes_by_content_id = {}
|
||||
for klass in self.classes:
|
||||
content_type = ContentType.objects.get_for_model(klass)
|
||||
self.classes_by_content_id[content_type.id] = klass
|
||||
return self.classes_by_content_id[int(content_type_id)]
|
||||
|
||||
def register_cell_class(self, klass):
|
||||
self.classes.append(klass)
|
||||
return klass
|
||||
|
||||
|
||||
library = Library() # singleton object
|
||||
register_cell_class = library.register_cell_class
|
||||
get_cell_classes = library.get_cell_classes
|
||||
get_cell_class = library.get_cell_class
|
|
@ -1,344 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.forms import models as model_forms
|
||||
from django.utils.html import strip_tags
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ckeditor.fields import RichTextField
|
||||
import cmsplugin_blurp.utils
|
||||
|
||||
from .library import register_cell_class, get_cell_classes, get_cell_class
|
||||
|
||||
|
||||
def element_is_visible(element, user=None):
|
||||
if element.public:
|
||||
return True
|
||||
if user is None:
|
||||
return False
|
||||
if user.is_anonymous():
|
||||
return False
|
||||
page_groups = element.groups.all()
|
||||
if not page_groups:
|
||||
# user is logged in, no group restriction in place
|
||||
return True
|
||||
return len(set(page_groups).intersection(user.groups.all())) > 0
|
||||
|
||||
|
||||
class Page(models.Model):
|
||||
title = models.CharField(_('Title'), max_length=50)
|
||||
slug = models.SlugField(_('Slug'))
|
||||
template_name = models.CharField(_('Template'), max_length=50)
|
||||
parent = models.ForeignKey('self', null=True, blank=True)
|
||||
order = models.PositiveIntegerField()
|
||||
exclude_from_navigation = models.BooleanField(_('Exclude from navigation'), default=False)
|
||||
redirect_url = models.CharField(_('Redirect URL'), max_length=100, blank=True)
|
||||
|
||||
public = models.BooleanField(_('Public'), default=True)
|
||||
groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
|
||||
|
||||
_level = None
|
||||
_children = None
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.order:
|
||||
max_order = Page.objects.all().aggregate(Max('order')).get('order__max') or 0
|
||||
self.order = max_order + 1
|
||||
return super(Page, self).save(*args, **kwargs)
|
||||
|
||||
def get_online_url(self):
|
||||
parts = [self.slug]
|
||||
page = self
|
||||
while page.parent_id:
|
||||
page = page.parent
|
||||
parts.append(page.slug)
|
||||
return '/' + '/'.join(reversed(parts))
|
||||
|
||||
def get_page_of_level(self, level):
|
||||
'''Return page of given level in the page hierarchy.'''
|
||||
parts = [self]
|
||||
page = self
|
||||
while page.parent_id:
|
||||
page = page.parent
|
||||
parts.append(page)
|
||||
parts.reverse()
|
||||
try:
|
||||
return parts[level]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def get_siblings(self):
|
||||
return Page.objects.filter(parent=self.parent)
|
||||
|
||||
def get_children(self):
|
||||
return Page.objects.filter(parent_id=self.id)
|
||||
|
||||
def has_children(self):
|
||||
return Page.objects.filter(parent_id=self.id).exists()
|
||||
|
||||
@classmethod
|
||||
def get_as_reordered_flat_hierarchy(cls, object_list):
|
||||
reordered = []
|
||||
def fill_list(object_sublist, level=0, parent=None):
|
||||
for page in object_sublist:
|
||||
page._children = [x for x in object_list if x.parent_id == page.id]
|
||||
if page.parent == parent:
|
||||
page.level = level
|
||||
reordered.append(page)
|
||||
fill_list(object_sublist, level=level+1, parent=page)
|
||||
fill_list(object_list)
|
||||
return reordered
|
||||
|
||||
def get_unlocked_placeholders(self, cells=None):
|
||||
combo_template = settings.COMBO_PUBLIC_TEMPLATES.get(self.template_name)
|
||||
if self.slug == 'index':
|
||||
# on the site index page, there are no unlocked placeholder.
|
||||
return combo_template['placeholders'].keys()
|
||||
|
||||
if cells is None:
|
||||
cells = CellBase.get_cells(page_id=self.id)
|
||||
|
||||
# on the other page sites, look for unlock markers
|
||||
unlocked_placeholders = []
|
||||
for cell in cells:
|
||||
if not isinstance(cell, UnlockMarkerCell):
|
||||
continue
|
||||
if cell.page_id == self.id:
|
||||
unlocked_placeholders.append(cell.placeholder)
|
||||
return unlocked_placeholders
|
||||
|
||||
def get_locked_placeholders(self, cells=None):
|
||||
combo_template = settings.COMBO_PUBLIC_TEMPLATES.get(self.template_name)
|
||||
lockable_placeholders = [x for x in combo_template['placeholders'] if (
|
||||
combo_template['placeholders'].get(x).get('acquired'))]
|
||||
unlocked_placeholders = self.get_unlocked_placeholders(cells)
|
||||
locked_placeholders = set(lockable_placeholders) - set(unlocked_placeholders)
|
||||
return list(locked_placeholders)
|
||||
|
||||
def visibility(self):
|
||||
if self.public:
|
||||
return _('Public')
|
||||
return _('Private (%s)') % ', '.join([x.name for x in self.groups.all()])
|
||||
|
||||
def is_visible(self, user=None):
|
||||
return element_is_visible(self, user=user)
|
||||
|
||||
|
||||
class CellBase(models.Model):
|
||||
page = models.ForeignKey(Page)
|
||||
placeholder = models.CharField(max_length=20)
|
||||
order = models.PositiveIntegerField()
|
||||
|
||||
public = models.BooleanField(_('Public'), default=True)
|
||||
groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
|
||||
|
||||
default_form_class = None
|
||||
visible = True
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __unicode__(self):
|
||||
label = unicode(self.get_verbose_name())
|
||||
additional_label = self.get_additional_label()
|
||||
if label and additional_label:
|
||||
return '%s (%s)' % (label, additional_label)
|
||||
else:
|
||||
return label
|
||||
|
||||
@classmethod
|
||||
def get_verbose_name(cls):
|
||||
return cls._meta.verbose_name
|
||||
|
||||
def get_additional_label(self):
|
||||
return ''
|
||||
|
||||
@property
|
||||
def css_class_name(self):
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
@classmethod
|
||||
def get_cell_content_types(cls):
|
||||
content_types = []
|
||||
for klass in get_cell_classes():
|
||||
if not klass.is_enabled():
|
||||
continue
|
||||
if klass.visible is False:
|
||||
continue
|
||||
content_types.extend(klass.get_content_types())
|
||||
return content_types
|
||||
|
||||
@classmethod
|
||||
def get_cells(cls, **kwargs):
|
||||
"""Returns the list of cells of various classes matching **kwargs"""
|
||||
cells = []
|
||||
for klass in get_cell_classes():
|
||||
cells.extend(klass.objects.filter(**kwargs))
|
||||
cells.sort(lambda x, y: cmp(x.order, y.order))
|
||||
return cells
|
||||
|
||||
def get_reference(self):
|
||||
"Returns a string that can serve as a unique reference to a cell"""
|
||||
return '%s-%s' % (ContentType.objects.get_for_model(self).id, self.id)
|
||||
|
||||
@classmethod
|
||||
def get_cell(cls, reference, **kwargs):
|
||||
"""Returns the cell matching reference, and eventual **kwargs"""
|
||||
content_id, cell_id = reference.split('-')
|
||||
try:
|
||||
klass = get_cell_class(content_id)
|
||||
except KeyError:
|
||||
raise ObjectDoesNotExist()
|
||||
return klass.objects.get(id=cell_id, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_content_types(cls):
|
||||
return [{
|
||||
'name': cls.get_verbose_name(),
|
||||
'content_type': ContentType.objects.get_for_model(cls),
|
||||
'variant': 'default',
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
def is_enabled(cls):
|
||||
"""Defines if the cell type is enabled for the given site; this is used
|
||||
to selectively enable cells from extension modules."""
|
||||
return True
|
||||
|
||||
def set_variant(self, variant):
|
||||
pass
|
||||
|
||||
def get_label(self):
|
||||
return self.get_verbose_name()
|
||||
|
||||
def get_default_form_class(self):
|
||||
if self.default_form_class:
|
||||
return self.default_form_class
|
||||
|
||||
fields = [x.name for x in self._meta.local_concrete_fields
|
||||
if x.name not in ('id', 'page', 'placeholder', 'order',
|
||||
'public', 'groups')]
|
||||
if not fields:
|
||||
return None
|
||||
|
||||
return model_forms.modelform_factory(self.__class__, fields=fields)
|
||||
|
||||
def get_visibility_form_class(self):
|
||||
return model_forms.modelform_factory(self.__class__,
|
||||
fields=['public', 'groups'])
|
||||
|
||||
def is_visible(self, user=None):
|
||||
return element_is_visible(self, user=user)
|
||||
|
||||
def is_relevant(self, context):
|
||||
'''Return whether it's relevant to render this cell in the page
|
||||
context.'''
|
||||
return True
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class TextCell(CellBase):
|
||||
text = RichTextField(_('Text'), null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Text')
|
||||
|
||||
def render(self, context):
|
||||
return mark_safe(self.text or '')
|
||||
|
||||
def get_additional_label(self):
|
||||
if not self.text:
|
||||
return None
|
||||
def ellipsize(text):
|
||||
if text < 50:
|
||||
return text
|
||||
return text[:40] + '...'
|
||||
return ellipsize(strip_tags(self.text))
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class FortuneCell(CellBase):
|
||||
ajax_refresh = 30
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Fortune')
|
||||
|
||||
def render(self, context):
|
||||
import subprocess
|
||||
return subprocess.check_output(['fortune'])
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class UnlockMarkerCell(CellBase):
|
||||
"""Marks an 'acquired' placeholder as unlocked."""
|
||||
visible = False
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Unlock Marker')
|
||||
|
||||
def render(self, context):
|
||||
return ''
|
||||
|
||||
@register_cell_class
|
||||
class BlurpCell(CellBase):
|
||||
blurp_key = models.CharField(max_length=50)
|
||||
|
||||
@classmethod
|
||||
def get_content_types(cls):
|
||||
try:
|
||||
blurp_renderers = settings.CMS_PLUGIN_BLURP_RENDERERS
|
||||
except AttributeError:
|
||||
return []
|
||||
l = []
|
||||
base_content_type = ContentType.objects.get_for_model(cls)
|
||||
for blurp_key, blurp_value in blurp_renderers.items():
|
||||
if blurp_value.get('private'):
|
||||
continue
|
||||
l.append({
|
||||
'name': blurp_value.get('name'),
|
||||
'content_type': base_content_type,
|
||||
'variant': blurp_key,
|
||||
})
|
||||
l.sort(lambda x, y: cmp(x.get('name'), y.get('name')))
|
||||
return l
|
||||
|
||||
def get_label(self):
|
||||
return settings.CMS_PLUGIN_BLURP_RENDERERS[self.blurp_key]['name']
|
||||
|
||||
def set_variant(self, variant):
|
||||
self.blurp_key = variant
|
||||
|
||||
def render(self, context):
|
||||
renderer = cmsplugin_blurp.utils.resolve_renderer(self.blurp_key)
|
||||
template = renderer.render_template()
|
||||
context = renderer.render(context)
|
||||
return template.render(context)
|
||||
|
||||
def get_default_form_class(self):
|
||||
return None
|
|
@ -1,200 +0,0 @@
|
|||
# Combo French Translation.
|
||||
# Copyright (C) 2015 Entr'ouvert
|
||||
# This file is distributed under the same license as the Combo package.
|
||||
# Frederic Peters <fpeters@entrouvert.com>, 2015.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: combo 0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-17 19:13+0100\n"
|
||||
"PO-Revision-Date: 2015-01-17 19:13+0100\n"
|
||||
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
|
||||
"Language: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: apps/wcs/models.py:30
|
||||
msgid "Form"
|
||||
msgstr "Formulaire"
|
||||
|
||||
#: apps/wcs/models.py:32 data/models.py:49
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: apps/wcs/models.py:33
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: apps/wcs/models.py:36
|
||||
msgid "Form Link"
|
||||
msgstr "Lien vers un formulaire"
|
||||
|
||||
#: data/models.py:50
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: data/models.py:51
|
||||
msgid "Template"
|
||||
msgstr "Template"
|
||||
|
||||
#: data/models.py:54
|
||||
msgid "Exclude from navigation"
|
||||
msgstr "Exclure de la navigation"
|
||||
|
||||
#: data/models.py:55
|
||||
msgid "Redirect URL"
|
||||
msgstr "URL de redirection"
|
||||
|
||||
#: data/models.py:57 data/models.py:143 data/models.py:155
|
||||
msgid "Public"
|
||||
msgstr "Publique"
|
||||
|
||||
#: data/models.py:58 data/models.py:156
|
||||
msgid "Groups"
|
||||
msgstr "Groupes"
|
||||
|
||||
#: data/models.py:144
|
||||
#, python-format
|
||||
msgid "Private (%s)"
|
||||
msgstr "Privée (%s)"
|
||||
|
||||
#: data/models.py:251 data/models.py:254
|
||||
msgid "Text"
|
||||
msgstr "Texte"
|
||||
|
||||
#: data/models.py:272
|
||||
msgid "Fortune"
|
||||
msgstr "Fortune du jour"
|
||||
|
||||
#: data/models.py:285
|
||||
msgid "Unlock Marker"
|
||||
msgstr "Marqueur de déblocage"
|
||||
|
||||
#: manager/forms.py:40
|
||||
msgid "Slug must be unique"
|
||||
msgstr "Le slug doit être unique"
|
||||
|
||||
#: manager/templates/combo/cell_form.html:7
|
||||
msgid "There are no options for this cell."
|
||||
msgstr "Il n'y a pas d'option pour cette cellule."
|
||||
|
||||
#: manager/templates/combo/cell_form.html:10
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: manager/templates/combo/cell_form.html:11
|
||||
msgid "Visibility"
|
||||
msgstr "Visibilité"
|
||||
|
||||
#: manager/templates/combo/cell_form.html:12
|
||||
msgid "Close"
|
||||
msgstr "Fermer"
|
||||
|
||||
#: manager/templates/combo/cell_form.html:14
|
||||
#: manager/templates/combo/cell_visibility.html:14
|
||||
#: manager/templates/combo/page_add.html:18
|
||||
msgid "Save"
|
||||
msgstr "Enregistrer"
|
||||
|
||||
#: manager/templates/combo/cell_visibility.html:5
|
||||
msgid "Cell Visibility"
|
||||
msgstr "Visibilité de la cellule"
|
||||
|
||||
#: manager/templates/combo/cell_visibility.html:16
|
||||
#: manager/templates/combo/cell_visibility.html:18
|
||||
#: manager/templates/combo/generic_confirm_delete.html:14
|
||||
#: manager/templates/combo/page_add.html:20
|
||||
#: manager/templates/combo/page_add.html:22
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: manager/templates/combo/generic_confirm_delete.html:11
|
||||
msgid "Are you sure you want to delete this?"
|
||||
msgstr "Êtes-vous sûr de vouloir supprimer ceci ?"
|
||||
|
||||
#: manager/templates/combo/generic_confirm_delete.html:13
|
||||
msgid "Confirm Deletion"
|
||||
msgstr "Confirmation de suppression"
|
||||
|
||||
#: manager/templates/combo/manager_base.html:10
|
||||
msgid "Management"
|
||||
msgstr "Gestion"
|
||||
|
||||
#: manager/templates/combo/manager_home.html:5
|
||||
msgid "Pages"
|
||||
msgstr "Pages"
|
||||
|
||||
#: manager/templates/combo/manager_home.html:6
|
||||
msgid "New"
|
||||
msgstr "Nouvelle"
|
||||
|
||||
#: manager/templates/combo/manager_home.html:21
|
||||
msgid ""
|
||||
"\n"
|
||||
" This site doesn't have any page yet. Click on the \"New\" button in the "
|
||||
"top\n"
|
||||
" right of the page to add a first one.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Ce site n'a pas encore de pages. Cliquez sur le bouton « Nouvelle » "
|
||||
"dans le coin supérieur droit de la page pour en ajouter une première. "
|
||||
|
||||
#: manager/templates/combo/page_add.html:6
|
||||
msgid "Edit Page"
|
||||
msgstr "Modification de la page"
|
||||
|
||||
#: manager/templates/combo/page_add.html:8
|
||||
msgid "New Page"
|
||||
msgstr "Nouvelle page"
|
||||
|
||||
#: manager/templates/combo/page_view.html:6
|
||||
msgid "Page"
|
||||
msgstr "Page"
|
||||
|
||||
#: manager/templates/combo/page_view.html:7
|
||||
msgid "export"
|
||||
msgstr "exporter"
|
||||
|
||||
#: manager/templates/combo/page_view.html:8
|
||||
msgid "edit"
|
||||
msgstr "modifier"
|
||||
|
||||
#: manager/templates/combo/page_view.html:9
|
||||
msgid "delete"
|
||||
msgstr "supprimer"
|
||||
|
||||
#: manager/templates/combo/page_view.html:14
|
||||
msgid "See online"
|
||||
msgstr "Voir en ligne"
|
||||
|
||||
#: manager/templates/combo/page_view.html:20
|
||||
msgid "Current template:"
|
||||
msgstr "Template actuel :"
|
||||
|
||||
#: manager/templates/combo/page_view.html:22
|
||||
msgid "Visibility:"
|
||||
msgstr "Visibilité :"
|
||||
|
||||
#: manager/templates/combo/page_view.html:23
|
||||
msgid "change"
|
||||
msgstr "modifier"
|
||||
|
||||
#: manager/templates/combo/page_view.html:29
|
||||
msgid "Available cells"
|
||||
msgstr "Cellules disponibles"
|
||||
|
||||
#: manager/templates/combo/page_view.html:63
|
||||
msgid "This page redirects to:"
|
||||
msgstr "Cette page redirige vers :"
|
||||
|
||||
#: manager/templates/registration/login.html:8
|
||||
msgid "Log in"
|
||||
msgstr "S'identifier"
|
||||
|
||||
#: manager/views.py:57
|
||||
msgid "Home"
|
||||
msgstr "Accueil"
|
|
@ -1,51 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from combo.data.models import Page
|
||||
|
||||
class PageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = ('title', 'slug', 'template_name')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PageForm, self).__init__(*args, **kwargs)
|
||||
templates = [(x[0], x[1]['name']) for x in settings.COMBO_PUBLIC_TEMPLATES.items()]
|
||||
templates.sort(lambda x, y: cmp(x[1], y[1]))
|
||||
self.fields['template_name'].widget = forms.Select(choices=templates)
|
||||
|
||||
def clean_slug(self):
|
||||
value = self.cleaned_data.get('slug')
|
||||
if self.instance.slug == value:
|
||||
return value
|
||||
if Page.objects.filter(slug=value).count() > 0:
|
||||
raise ValidationError(_('Slug must be unique'), code='unique')
|
||||
return value
|
||||
|
||||
class PageEditForm(PageForm):
|
||||
class Meta:
|
||||
model = Page
|
||||
exclude = ('order', 'public', 'groups')
|
||||
|
||||
class PageVisibilityForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = ('public', 'groups')
|
|
@ -1,165 +0,0 @@
|
|||
div#meta {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div#sidebar {
|
||||
width: 15em;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div#page-content {
|
||||
padding-left: 17em;
|
||||
min-height: 5em;
|
||||
}
|
||||
|
||||
div.placeholder {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
div.cell-list > div {
|
||||
border: 1px solid #eee;
|
||||
margin: 1ex 0;
|
||||
box-shadow: rgba(0, 0, 0, 0.04) 0px 1px 1px 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.cell-list > div > div form {
|
||||
padding: 1ex;
|
||||
}
|
||||
|
||||
div.cell-list > div > div {
|
||||
display: none;
|
||||
transition: max-height linear 0.2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.cell-list > div.untoggled > div {
|
||||
display: block;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
div.cell-list > div.toggled > div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.cell h3 {
|
||||
background: #fafafa;
|
||||
margin: 0;
|
||||
padding: 1ex;
|
||||
min-width: 10em;
|
||||
color: #222;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
div.cell h3 span.additional-label {
|
||||
font-size: 80%;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
div.cell-list div h3:after {
|
||||
font-family: FontAwesome;
|
||||
content: "\f107"; /* angle-down */
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
div.cell-list div.toggled h3:after {
|
||||
content: "\f106"; /* angle-up */
|
||||
}
|
||||
|
||||
div.cell-list button.save {
|
||||
position: relative;
|
||||
right: 2ex;
|
||||
bottom: 1ex;
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
div.cell div.buttons {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 1ex;
|
||||
}
|
||||
|
||||
div.cell-list .empty-cell {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid #eee;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
div.cell-list {
|
||||
min-height: 3em;
|
||||
}
|
||||
|
||||
#available-cells ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 1ex 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
li.cell-type input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li.cell-type label {
|
||||
display: block;
|
||||
margin: 1ex 0;
|
||||
}
|
||||
|
||||
li.cell-type li {
|
||||
margin: 1ex 0;
|
||||
}
|
||||
|
||||
li.cell-type input + span {
|
||||
display: block;
|
||||
background: #fafafa;
|
||||
padding: 1ex;
|
||||
box-shadow: rgba(0, 0, 0, 0.04) 0px 1px 1px 0px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
li.cell-type input ~ ul {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
transition: max-height linear 0.2s;
|
||||
}
|
||||
|
||||
li.cell-type input:checked + span {
|
||||
border-color: #888;
|
||||
}
|
||||
|
||||
li.cell-type input:checked ~ ul {
|
||||
display: block;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.view-online {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.icon-eye-open:before { content: "\f06e "; }
|
||||
|
||||
div#pages-list > div {
|
||||
border: 1px solid #eee;
|
||||
margin: 0.5ex 0;
|
||||
box-shadow: rgba(0, 0, 0, 0.04) 0px 1px 1px 0px;
|
||||
padding: 1ex;
|
||||
}
|
||||
|
||||
div#pages-list > div.level-1 {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
div#pages-list > div.level-2 {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
p#redirection {
|
||||
display: block;
|
||||
background: #fafafa;
|
||||
padding: 3ex 1ex;
|
||||
box-shadow: rgba(0, 0, 0, 0.04) 0px 1px 1px 0px;
|
||||
border: 1px solid #eee;
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
function init_pages_list()
|
||||
{
|
||||
if ($('#pages-list').length == 0)
|
||||
return;
|
||||
var list_offset = $('#pages-list').offset().left;
|
||||
$('#pages-list').sortable({
|
||||
stop: function(event, ui) {
|
||||
var moved_page_id = ui.item.data('page-id');
|
||||
/* 25 is the per-level margin-left applied to pages, it needs to be kept
|
||||
* in sync with the css file */
|
||||
var item_offset = ui.offset.left - list_offset + parseInt($(ui.item).data('level'))*25;
|
||||
var new_level = Math.abs(Math.round(item_offset / 25));
|
||||
console.log(new_level);
|
||||
if (new_level <= 0) {
|
||||
new_level = 0;
|
||||
} else {
|
||||
var previous_page = $('#pages-list div[data-page-id=' + moved_page_id + ']').prev();
|
||||
var previous_page_level = parseInt($(previous_page).data('level'));
|
||||
if (new_level > previous_page_level+1) {
|
||||
new_level = previous_page_level+1;
|
||||
}
|
||||
}
|
||||
|
||||
var new_parent = null;
|
||||
if (new_level != 0) {
|
||||
new_parent = $($(ui.item).prevAll('[data-level='+(new_level-1)+']')[0]).data('page-id');
|
||||
}
|
||||
/* remove classes and add new values */
|
||||
$(ui.item).removeClass('level-0').removeClass('level-1').removeClass('level-2');
|
||||
$(ui.item).addClass('level-' + new_level);
|
||||
$(ui.item).data('level', new_level).attr('data-level', new_level);
|
||||
|
||||
var new_order = $('#pages-list div').map(function() { return $(this).data('page-id'); }).get().join();
|
||||
|
||||
$.ajax({
|
||||
url: $('#pages-list').data('page-order-url'),
|
||||
data: {'new-order': new_order,
|
||||
'moved-page-id': moved_page_id,
|
||||
'moved-page-new-parent': new_parent
|
||||
},
|
||||
success: function(data, status) {
|
||||
$('#pages-list').replaceWith($(data).find('#pages-list'));
|
||||
init_pages_list();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('div.cell-list h3').on('click', function() {
|
||||
$(this).parent().toggleClass('toggled').toggleClass('untoggled');
|
||||
});
|
||||
$('div.cell-list a.close-button').on('click', function() {
|
||||
$(this).parents('.toggled').toggleClass('toggled').toggleClass('untoggled');
|
||||
return false;
|
||||
});
|
||||
init_pages_list();
|
||||
$('.cell-list').sortable({
|
||||
connectWith: '.cell-list',
|
||||
handle: 'h3',
|
||||
helper: 'clone',
|
||||
placeholder: 'empty-cell',
|
||||
update: function(event, ui) {
|
||||
var new_order = Object();
|
||||
$('.cell').each(function(i, x) {
|
||||
var cell_suffix = $(x).data('cell-reference');
|
||||
new_order['pos_' + cell_suffix] = i;
|
||||
new_placeholder = $(x).closest('.placeholder').data('placeholder-key');
|
||||
new_order['ph_' + cell_suffix] = new_placeholder;
|
||||
});
|
||||
$.ajax({
|
||||
url: $('#placeholders').data('cell-order-url'),
|
||||
data: new_order
|
||||
});
|
||||
}
|
||||
});
|
||||
$('#sidebar button').on('click', function() {
|
||||
window.location = $(this).data('add-url');
|
||||
return false;
|
||||
});
|
||||
$('div.cell button.save').on('click', function(event) {
|
||||
var $button = $(this);
|
||||
var $form = $(this).closest('form');
|
||||
var button_label = $button.text();
|
||||
for (instance in CKEDITOR.instances) {
|
||||
CKEDITOR.instances[instance].updateElement();
|
||||
}
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
data: $form.serialize(),
|
||||
type: 'POST',
|
||||
beforeSend: function() { $button.attr('disabled', 'disabled'); },
|
||||
success: function() { $button.attr('disabled', null); }
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('li.cell-type span').on('click', function() {
|
||||
var $checked_input = $(this).parent().find(':checked');
|
||||
if ($checked_input.length) {
|
||||
$checked_input.prop('checked', null);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/* hack against height:0 → height:auto not animating, this gets the height of
|
||||
* the elements and dynamically generate css rules setting the max-height property
|
||||
* to an appropriate height.
|
||||
*/
|
||||
$('li.cell-type').each(function(i, x) {
|
||||
$(this).attr('id', 'cell-type-'+i);
|
||||
var h = $(this).find('ul').height();
|
||||
$(this).find('ul').css('display', 'block').css('position', 'relative').css('visibility', 'visible');
|
||||
var style = '<style>li#cell-type-'+i+' input ~ ul { max-height: 0px; }\n'+
|
||||
'li#cell-type-'+i+' input:checked ~ ul { max-height: '+h+'px; }</style>';
|
||||
$(style).appendTo('head');
|
||||
});
|
||||
|
||||
$('div.cell').each(function(i, x) {
|
||||
$(this).attr('id', 'div-cell-'+i);
|
||||
var h = $(this).find('h3 + div').height() + 10;
|
||||
h += $(this).find('.django-ckeditor-widget').length * 200;
|
||||
var style = '<style>div#div-cell-'+i+'.toggled h3 + div { max-height: '+h+'px; }</style>';
|
||||
$(style).appendTo('head');
|
||||
$(this).addClass('untoggled');
|
||||
});
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
{% load i18n %}
|
||||
<form action="{{ url }}" method="post">
|
||||
{% csrf_token %}
|
||||
{% if form %}
|
||||
{{ form.as_p }}
|
||||
{% else %}
|
||||
<p>{% trans "There are no options for this cell." %}</p>
|
||||
{% endif %}
|
||||
<div class="buttons">
|
||||
<a rel="popup" href="{% url 'combo-manager-page-delete-cell' page_pk=page.id cell_reference=cell.get_reference %}">{% trans 'Delete' %}</a> |
|
||||
<a rel="popup" href="{% url 'combo-manager-page-visibility-cell' page_pk=page.id cell_reference=cell.get_reference %}">{% trans 'Visibility' %}</a> |
|
||||
<a class="close-button" href="#">{% trans 'Close' %}</a>
|
||||
{% if form %}
|
||||
<button class="save">{% trans 'Save' %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
|
@ -1,22 +0,0 @@
|
|||
{% extends "combo/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Cell Visibility' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button>{% trans "Save" %}</button>
|
||||
{% if object.id %}
|
||||
<a class="cancel" href="{{ object.get_absolute_url }}">{% trans 'Cancel' %}</a>
|
||||
{% else %}
|
||||
<a class="cancel" href="{% url 'combo-manager-homepage' %}">{% trans 'Cancel' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,17 +0,0 @@
|
|||
{% extends "combo/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{{ view.model.get_verbose_name }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% blocktrans %}Are you sure you want to delete this?{% endblocktrans %}
|
||||
<div class="buttons">
|
||||
<button>{% trans 'Confirm Deletion' %}</button>
|
||||
<a class="cancel" href="{{ object.get_absolute_url }}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||
{% extends "gadjo/base.html" %}
|
||||
{% load staticfiles i18n %}
|
||||
|
||||
{% block css %}
|
||||
<link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/combo.manager.css"/>
|
||||
{% endblock %}
|
||||
{% block page-title %}Combo{% endblock %}
|
||||
{% block site-title %}Combo{% endblock %}
|
||||
{% block footer %}Combo — Copyright © Entr'ouvert{% endblock %}
|
||||
|
||||
{% block homepage-url %}{% url 'combo-manager-homepage' %}{% endblock %}
|
||||
{% block logout-url %}{% url 'auth_logout' %}{% endblock %}
|
||||
{% block homepage-title %}{% trans 'Management' %}{% endblock %}
|
||||
|
||||
{% block extrascripts %}
|
||||
<script src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
|
||||
<script src="{% static "js/combo.manager.js" %}"></script>
|
||||
{% endblock %}
|
|
@ -1,28 +0,0 @@
|
|||
{% extends "combo/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Pages' %}</h2>
|
||||
<a rel="popup" href="{% url 'combo-manager-page-add' %}">{% trans 'New' %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if object_list %}
|
||||
<div id="pages-list" data-page-order-url="{% url 'combo-manager-page-order' %}">
|
||||
{% for page in object_list %}
|
||||
<div class="level-{{page.level}}" data-page-id="{{page.id}}" data-level="{{page.level}}">
|
||||
<a href="{% url 'combo-manager-page-view' pk=page.id %}">{{ page.title }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans %}
|
||||
This site doesn't have any page yet. Click on the "New" button in the top
|
||||
right of the page to add a first one.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
|||
{% extends "combo/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
{% if object.id %}
|
||||
<h2>{% trans "Edit Page" %}</h2>
|
||||
{% else %}
|
||||
<h2>{% trans "New Page" %}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button>{% trans "Save" %}</button>
|
||||
{% if object.id %}
|
||||
<a class="cancel" href="{{ object.get_absolute_url }}">{% trans 'Cancel' %}</a>
|
||||
{% else %}
|
||||
<a class="cancel" href="{% url 'combo-manager-homepage' %}">{% trans 'Cancel' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,92 +0,0 @@
|
|||
{% extends "combo/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load cells %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Page' %} - {{ object.title }}</h2>
|
||||
<a href="{% url 'combo-manager-page-export' pk=object.id %}">{% trans 'export' %}</a>
|
||||
<a rel="popup" href="{% url 'combo-manager-page-edit' pk=object.id %}">{% trans 'edit' %}</a>
|
||||
<a rel="popup" href="{% url 'combo-manager-page-delete' pk=object.id %}">{% trans 'delete' %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block more-user-links %}
|
||||
{{ block.super }}
|
||||
<a href="{{ object.get_online_url }}" class="view-online icon-eye-open">{% trans 'See online' %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="meta">
|
||||
<label>{% trans 'Current template:' %} </label> {{ object.template_name }}
|
||||
/
|
||||
<label>{% trans 'Visibility:' %} </label> {{ object.visibility }}
|
||||
(<a rel="popup" href="{% url 'combo-manager-page-visibility' pk=object.id %}">{% trans 'change' %}</a>)
|
||||
</div>
|
||||
|
||||
<div id="sidebar">
|
||||
|
||||
<div id="available-cells">
|
||||
<h2>{% trans 'Available cells' %}</h2>
|
||||
<ul>
|
||||
{% for cell_type in cell_types %}
|
||||
<li class="cell-type">
|
||||
<label><input type="radio" name="cell_type" value="{{ cell_type.content_type.id }}"/>
|
||||
<span>{{ cell_type.name }}</span>
|
||||
<ul>
|
||||
{% for placeholder in placeholders %}
|
||||
<li><button data-add-url="{% url 'combo-manager-page-add-cell' page_pk=object.id cell_type=cell_type.content_type.id variant=cell_type.variant ph_key=placeholder.key %}">→ {{ placeholder.name }}</button></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</label></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if extra_placeholders %}
|
||||
<div id="extra-placeholders">
|
||||
<h2>Extra blocks</h2>
|
||||
<ul>
|
||||
{% for placeholder in extra_placeholders %}
|
||||
<li><button data-add-url="{% url 'combo-manager-page-add-cell' page_pk=object.id cell_type=unlock.content_type.id variant=unlock.variant ph_key=placeholder.key %}">→ {{ placeholder.name }}</button></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div> <!-- #sidebar -->
|
||||
|
||||
<div id="page-content">
|
||||
{% if object.redirect_url %}
|
||||
<div>
|
||||
<h2>Redirection</h2>
|
||||
<p id="redirection">
|
||||
{% trans 'This page redirects to:' %} <a href="{{ object.redirect_url }}">{{ object.redirect_url }}</a>.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="placeholders"
|
||||
data-cell-order-url="{% url 'combo-manager-cell-order' page_pk=object.id %}"
|
||||
>
|
||||
{% for placeholder in placeholders %}
|
||||
<div class="placeholder" data-placeholder-key="{{ placeholder.key }}">
|
||||
<h2>{{ placeholder.name }}</h2>
|
||||
<div class="cell-list">
|
||||
{% for cell in placeholder.cells %}
|
||||
<div class="cell" data-cell-reference="{{ cell.get_reference }}">
|
||||
<h3>{{ cell.get_label }}
|
||||
{% if cell.get_additional_label %}
|
||||
<span class="additional-label">
|
||||
<i>{{cell.get_additional_label}}</i></span>{% endif %}
|
||||
</h3>
|
||||
<div>{% cell_form cell %}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "combo/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="{% trans 'Log in' %}" />
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,32 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def cell_form(context, cell):
|
||||
context['url'] = reverse('combo-manager-page-edit-cell', kwargs={
|
||||
'page_pk': cell.page.id, 'cell_reference': cell.get_reference()})
|
||||
form_class = cell.get_default_form_class()
|
||||
if form_class:
|
||||
context['form'] = form_class(instance=cell, prefix='c%s' % cell.get_reference())
|
||||
else:
|
||||
context['form'] = None
|
||||
cell_form_template = template.loader.get_template('combo/cell_form.html')
|
||||
return cell_form_template.render(context)
|
|
@ -1,50 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django.conf.urls import patterns, url, include
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = patterns('combo.views',
|
||||
url(r'^$', views.homepage, name='combo-manager-homepage'),
|
||||
url(r'^pages/add/$', views.page_add, name='combo-manager-page-add'),
|
||||
url(r'^pages/(?P<pk>\w+)/$', views.page_view,
|
||||
name='combo-manager-page-view'),
|
||||
url(r'^pages/(?P<pk>\w+)/edit$', views.page_edit,
|
||||
name='combo-manager-page-edit'),
|
||||
url(r'^pages/(?P<pk>\w+)/visibility$', views.page_visibility,
|
||||
name='combo-manager-page-visibility'),
|
||||
url(r'^pages/(?P<pk>\w+)/delete$', views.page_delete,
|
||||
name='combo-manager-page-delete'),
|
||||
url(r'^pages/(?P<pk>\w+)/export$', views.page_export,
|
||||
name='combo-manager-page-export'),
|
||||
url(r'^pages/(?P<page_pk>\w+)/add-cell-to-(?P<ph_key>\w+)/(?P<cell_type>\w+)/(?P<variant>[\w-]+)/$',
|
||||
views.page_add_cell,
|
||||
name='combo-manager-page-add-cell'),
|
||||
url(r'^pages/(?P<page_pk>\w+)/cell/(?P<cell_reference>[\w-]+)/$', views.page_edit_cell,
|
||||
name='combo-manager-page-edit-cell'),
|
||||
url(r'^pages/(?P<page_pk>\w+)/cell/(?P<cell_reference>[\w-]+)/delete$', views.page_delete_cell,
|
||||
name='combo-manager-page-delete-cell'),
|
||||
url(r'^pages/(?P<page_pk>\w+)/cell/(?P<cell_reference>[\w-]+)/visibility$',
|
||||
views.page_cell_visibility,
|
||||
name='combo-manager-page-visibility-cell'),
|
||||
url(r'^pages/(?P<page_pk>\w+)/order$', views.cell_order,
|
||||
name='combo-manager-cell-order'),
|
||||
url(r'^pages/order$', views.page_order,
|
||||
name='combo-manager-page-order'),
|
||||
(r'^ckeditor/', include('ckeditor.urls')),
|
||||
)
|
|
@ -1,229 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.csrf import requires_csrf_token
|
||||
from django.views.generic import (RedirectView, DetailView,
|
||||
CreateView, UpdateView, ListView, DeleteView)
|
||||
|
||||
from combo.data.models import Page, CellBase, UnlockMarkerCell
|
||||
|
||||
from .forms import PageForm, PageEditForm, PageVisibilityForm
|
||||
|
||||
|
||||
class HomepageView(ListView):
|
||||
model = Page
|
||||
template_name = 'combo/manager_home.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.object_list = Page.get_as_reordered_flat_hierarchy(self.object_list)
|
||||
context = super(HomepageView, self).get_context_data(**kwargs)
|
||||
return context
|
||||
|
||||
homepage = HomepageView.as_view()
|
||||
|
||||
|
||||
class PageAddView(CreateView):
|
||||
model = Page
|
||||
template_name = 'combo/page_add.html'
|
||||
form_class = PageForm
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(PageAddView, self).get_initial()
|
||||
if Page.objects.count() == 0:
|
||||
# first page
|
||||
initial['title'] = _('Home')
|
||||
initial['slug'] = 'index'
|
||||
return initial
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('combo-manager-page-view', kwargs={'pk': self.object.id})
|
||||
|
||||
page_add = PageAddView.as_view()
|
||||
|
||||
|
||||
class PageEditView(UpdateView):
|
||||
model = Page
|
||||
template_name = 'combo/page_add.html'
|
||||
form_class = PageEditForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('combo-manager-page-view', kwargs={'pk': self.object.id})
|
||||
|
||||
page_edit = PageEditView.as_view()
|
||||
|
||||
class PageVisibilityView(PageEditView):
|
||||
form_class = PageVisibilityForm
|
||||
|
||||
page_visibility = PageVisibilityView.as_view()
|
||||
|
||||
|
||||
class PageView(DetailView):
|
||||
model = Page
|
||||
template_name = 'combo/page_view.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(PageView, self).get_context_data(**kwargs)
|
||||
context['cell_types'] = CellBase.get_cell_content_types()
|
||||
cells = CellBase.get_cells(page_id=self.object.id)
|
||||
template = self.object.template_name
|
||||
placeholders = []
|
||||
extra_placeholders = []
|
||||
combo_template = settings.COMBO_PUBLIC_TEMPLATES.get(template)
|
||||
|
||||
unlocked_placeholders = self.object.get_unlocked_placeholders(cells)
|
||||
|
||||
for placeholder_key, placeholder in combo_template['placeholders'].items():
|
||||
placeholder_dict = {
|
||||
'key': placeholder_key,
|
||||
'name': placeholder['name'],
|
||||
}
|
||||
if placeholder.get('acquired') and not placeholder_key in unlocked_placeholders:
|
||||
extra_placeholders.append(placeholder_dict)
|
||||
else:
|
||||
placeholder_dict['cells'] = [x for x in cells if (
|
||||
x.placeholder == placeholder_key)]
|
||||
placeholders.append(placeholder_dict)
|
||||
|
||||
context['unlock'] = UnlockMarkerCell.get_content_types()[0]
|
||||
context['placeholders'] = placeholders
|
||||
context['extra_placeholders'] = extra_placeholders
|
||||
return context
|
||||
|
||||
page_view = requires_csrf_token(PageView.as_view())
|
||||
|
||||
|
||||
class PageDeleteView(DeleteView):
|
||||
model = Page
|
||||
template_name = 'combo/generic_confirm_delete.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('combo-manager-homepage')
|
||||
|
||||
page_delete = PageDeleteView.as_view()
|
||||
|
||||
|
||||
class PageExportView(DetailView):
|
||||
model = Page
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
response = HttpResponse(content_type='application/json')
|
||||
cells = CellBase.get_cells(page_id=self.object.id)
|
||||
serialized_page = json.loads(serializers.serialize('json', [self.object]))[0]
|
||||
serialized_page['cells'] = json.loads(serializers.serialize('json', cells))
|
||||
json.dump(serialized_page, response, indent=2)
|
||||
return response
|
||||
|
||||
page_export = PageExportView.as_view()
|
||||
|
||||
class PageAddCellView(RedirectView):
|
||||
def get_redirect_url(self, page_pk, cell_type, variant, ph_key):
|
||||
cell_class = ContentType.objects.get(id=cell_type).model_class()
|
||||
cell = cell_class(page_id=page_pk, placeholder=ph_key)
|
||||
cell.set_variant(variant)
|
||||
orders = [x.order for x in CellBase.get_cells(page_id=page_pk)]
|
||||
if orders:
|
||||
cell.order = max(orders)+1
|
||||
else:
|
||||
cell.order = 1
|
||||
cell.save()
|
||||
return reverse('combo-manager-page-view', kwargs={'pk': page_pk})
|
||||
|
||||
page_add_cell = PageAddCellView.as_view()
|
||||
|
||||
|
||||
class PageEditCellView(UpdateView):
|
||||
def get_object(self, queryset=None):
|
||||
page_pk = self.kwargs.get('page_pk')
|
||||
cell_reference = self.kwargs.get('cell_reference')
|
||||
try:
|
||||
return CellBase.get_cell(cell_reference, page_id=page_pk)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
def get_prefix(self):
|
||||
return 'c%s' % self.kwargs.get('cell_reference')
|
||||
|
||||
def get_form_class(self):
|
||||
return self.object.get_default_form_class()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')})
|
||||
|
||||
page_edit_cell = PageEditCellView.as_view()
|
||||
|
||||
|
||||
class PageDeleteCellView(DeleteView):
|
||||
template_name = 'combo/generic_confirm_delete.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
page_pk = self.kwargs.get('page_pk')
|
||||
cell_reference = self.kwargs.get('cell_reference')
|
||||
try:
|
||||
return CellBase.get_cell(cell_reference, page_id=page_pk)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('combo-manager-page-view', kwargs={'pk': self.kwargs.get('page_pk')})
|
||||
|
||||
page_delete_cell = PageDeleteCellView.as_view()
|
||||
|
||||
|
||||
class PageCellVisibilityView(PageEditCellView):
|
||||
template_name = 'combo/cell_visibility.html'
|
||||
|
||||
def get_form_class(self):
|
||||
return self.object.get_visibility_form_class()
|
||||
|
||||
page_cell_visibility = PageCellVisibilityView.as_view()
|
||||
|
||||
|
||||
def cell_order(request, page_pk):
|
||||
for cell in CellBase.get_cells(page_id=page_pk):
|
||||
old_order = cell.order
|
||||
old_placeholder = cell.placeholder
|
||||
key_suffix = cell.get_reference()
|
||||
new_order = int(request.GET.get('pos_' + key_suffix))
|
||||
new_placeholder = request.GET.get('ph_' + key_suffix)
|
||||
if new_order != old_order or new_placeholder != old_placeholder:
|
||||
cell.order = new_order
|
||||
cell.placeholder = new_placeholder
|
||||
cell.save()
|
||||
return HttpResponse(status=206)
|
||||
|
||||
|
||||
def page_order(request):
|
||||
new_order = [int(x) for x in request.GET['new-order'].split(',')]
|
||||
moved_page = Page.objects.get(id=request.GET['moved-page-id'])
|
||||
if request.GET['moved-page-new-parent']:
|
||||
moved_page.parent_id = request.GET['moved-page-new-parent']
|
||||
else:
|
||||
moved_page.parent_id = None
|
||||
moved_page.save()
|
||||
for page in Page.objects.filter(parent_id=moved_page.parent_id):
|
||||
page.order = new_order.index(page.id)+1
|
||||
page.save()
|
||||
return redirect(reverse('combo-manager-homepage'))
|
|
@ -1,75 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015 Entr'ouvert
|
||||
#
|
||||
# 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 pkg_resources import iter_entry_points
|
||||
import logging
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PLUGIN_GROUP_NAME = 'combo.plugin'
|
||||
|
||||
class PluginError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_plugins(*args, **kwargs):
|
||||
plugins = []
|
||||
for entrypoint in iter_entry_points(PLUGIN_GROUP_NAME):
|
||||
try:
|
||||
plugin = entrypoint.load()
|
||||
except Exception, e:
|
||||
logger.exception('failed to load entrypoint %s', entrypoint)
|
||||
raise PluginError('failed to load entrypoint %s' % entrypoint, e)
|
||||
plugins.append(plugin(*args, **kwargs))
|
||||
return plugins
|
||||
|
||||
def register_plugins_urls(urlpatterns):
|
||||
pre_urls = []
|
||||
post_urls = []
|
||||
for plugin in get_plugins():
|
||||
if hasattr(plugin, 'get_before_urls'):
|
||||
pre_urls.append(url('^', include(plugin.get_before_urls())))
|
||||
if hasattr(plugin, 'get_after_urls'):
|
||||
post_urls.append(url('^', include(plugin.get_after_urls())))
|
||||
pre_patterns = patterns('', *pre_urls)
|
||||
post_patterns = patterns('', *post_urls)
|
||||
return pre_patterns + urlpatterns + post_patterns
|
||||
|
||||
def register_plugins_apps(installed_apps):
|
||||
for plugin in get_plugins():
|
||||
if hasattr(plugin, 'get_apps'):
|
||||
apps = plugin.get_apps()
|
||||
for app in apps:
|
||||
if app not in installed_apps:
|
||||
installed_apps += (app, )
|
||||
return installed_apps
|
||||
|
||||
def register_plugins_middleware(middlewares):
|
||||
for plugin in get_plugins():
|
||||
if hasattr(plugin, 'get_before_middleware'):
|
||||
pre_middleware = plugin.get_before_middleware()
|
||||
middlewares = tuple(set(pre_middleware + middlewares))
|
||||
if hasattr(plugin, 'get_after_middleware'):
|
||||
post_middleware = plugin.get_after_middleware()
|
||||
middlewares = tuple(set(post_middleware + middlewares))
|
||||
return middlewares
|
||||
|
||||
def init():
|
||||
for plugin in get_plugins():
|
||||
if hasattr(plugin, 'init'):
|
||||
plugin.init()
|
|
@ -1,116 +0,0 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
div#title {
|
||||
background: linear-gradient(to bottom, #0036f1 5%, #0078ff 30%) top right no-repeat;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div#title:before {
|
||||
content: "Combo";
|
||||
position: absolute;
|
||||
font-size: 300%;
|
||||
right: 0;
|
||||
bottom: -0.45ex;
|
||||
color: #0068ef;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div#title h1 {
|
||||
font-size: 300%;
|
||||
margin: 0;
|
||||
padding: 0.5ex 1ex 0.5ex 1ex;
|
||||
text-shadow: black 0px -1px 2px;
|
||||
}
|
||||
|
||||
div.cell {
|
||||
margin: 1em;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
div.fortunecell {
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
width: 80%;
|
||||
background: #eee;
|
||||
min-height: 5em;
|
||||
}
|
||||
|
||||
div.fortunecell div {
|
||||
padding: 2em 4em 1em 4em;
|
||||
font-size: 110%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.fortunecell:before {
|
||||
position: absolute;
|
||||
content: "« ";
|
||||
top: -1ex;
|
||||
font-size: 500%;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.fortunecell:after {
|
||||
color: #444;
|
||||
position: absolute;
|
||||
bottom: -1ex;
|
||||
right: 0;
|
||||
content: " »";
|
||||
font-size: 500%;
|
||||
}
|
||||
|
||||
#content {
|
||||
position: relative;
|
||||
margin-bottom: 5em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
background: #333;
|
||||
color: white;
|
||||
padding: 1em 0;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#main-content {
|
||||
margin-right: 20%;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
div#sidebar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0em;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
div#menu {
|
||||
}
|
||||
|
||||
div#menu ul {
|
||||
margin: 0;
|
||||
margin-left: 2em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#menu li {
|
||||
display: inline-block;
|
||||
background: #0078ff;
|
||||
padding: 1ex 1em;
|
||||
}
|
||||
|
||||
div#menu li.selected {
|
||||
background: linear-gradient(to top, #0036f1 5%, #0078ff 90%) top right no-repeat;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
div#menu li a {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
$(function() {
|
||||
$('[data-ajax-cell-refresh]').each(function(idx, elem) {
|
||||
var $elem = $(elem);
|
||||
function refresh() {
|
||||
$elem.find('> div').load($elem.data('ajax-cell-url'));
|
||||
}
|
||||
$elem.timeout_id = setInterval(refresh, $elem.data('ajax-cell-refresh')*1000);
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
{% if menuitems %}
|
||||
<ul>
|
||||
{% for menuitem in menuitems %}
|
||||
<li {% if menuitem.selected %}class="selected"{% endif %}><a
|
||||
href="{{ menuitem.page.get_online_url }}">{{ menuitem.page.title }}</a></li>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
|
@ -1,24 +0,0 @@
|
|||
{% load combo gadjo %}<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Combo - {{ page.title }}</title>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/combo-style.css"/>
|
||||
<script src="{% xstatic 'jquery' 'jquery.min.js' %}"></script>
|
||||
<script src="{{ STATIC_URL }}js/combo.public.js"></script>
|
||||
</head>
|
||||
<body class="page-{{ page.slug }}">
|
||||
<div id="title"><h1>{{ page.title }}</h1></div>
|
||||
<div id="menu">{% block menu %}{% show_menu %}{% endblock %}</div>
|
||||
<div id="content">
|
||||
{% block combo-content %}
|
||||
{% placeholder "content" %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div id="footer">
|
||||
{% block footer %}
|
||||
{% placeholder "footer" %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
{% extends "combo/page_template.html" %}
|
||||
{% load combo %}
|
||||
|
||||
{% block combo-content %}
|
||||
<div id="main-content">
|
||||
{% placeholder "content" %}
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<h2>Sidebar</h2>
|
||||
{% show_menu 1 %}
|
||||
{% placeholder "sidebar" %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,8 +0,0 @@
|
|||
{% load combo %}
|
||||
{% for cell in cells %}
|
||||
<div class="cell {{ cell.css_class_name }}"
|
||||
{% if cell.ajax_refresh %}
|
||||
data-ajax-cell-url="{% url 'combo-public-ajax-page-cell' page_pk=page.id cell_reference=cell.get_reference %}"
|
||||
data-ajax-cell-refresh="{{ cell.ajax_refresh }}"
|
||||
{% endif %}><div>{% render_cell cell %}</div></div>
|
||||
{% endfor %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends "combo/page_template.html" %}
|
||||
|
||||
{% block menu %}{% endblock %}
|
||||
|
||||
{% block combo-content %}
|
||||
{% block mellon_content %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{% endblock %}
|
|
@ -1,55 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.inclusion_tag('combo/placeholder.html', takes_context=True)
|
||||
def placeholder(context, placeholder_name):
|
||||
context['cells'] = [x for x in context['page_cells'] if
|
||||
x.placeholder == placeholder_name and x.is_relevant(context)]
|
||||
return context
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def render_cell(context, cell):
|
||||
return cell.render(context)
|
||||
|
||||
@register.inclusion_tag('combo/menu.html', takes_context=True)
|
||||
def show_menu(context, level=0):
|
||||
current_page = context['page']
|
||||
page_of_level = current_page.get_page_of_level(level)
|
||||
if page_of_level is None:
|
||||
if level > 0:
|
||||
parent_page = current_page.get_page_of_level(level-1)
|
||||
siblings = parent_page.get_children()
|
||||
else:
|
||||
context['menuitems'] = []
|
||||
return context
|
||||
else:
|
||||
siblings = page_of_level.get_siblings()
|
||||
menuitems = []
|
||||
for sibling in siblings:
|
||||
if not sibling.is_visible(context['request'].user):
|
||||
continue
|
||||
if sibling.exclude_from_navigation:
|
||||
continue
|
||||
menuitem = {'page': sibling}
|
||||
if sibling == page_of_level:
|
||||
menuitem['selected'] = True
|
||||
menuitems.append(menuitem)
|
||||
context['menuitems'] = menuitems
|
||||
return context
|
|
@ -1,25 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django.conf.urls import patterns, url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = patterns('combo.publicviews',
|
||||
url(r'^ajax/cell/(?P<page_pk>\w+)/(?P<cell_reference>[\w-]+)/$',
|
||||
views.ajax_page_cell, name='combo-public-ajax-page-cell'),
|
||||
(r'', views.page),
|
||||
)
|
|
@ -1,120 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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 django.conf import settings
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render, resolve_url
|
||||
from django.template import RequestContext
|
||||
|
||||
from combo.data.models import CellBase, Page
|
||||
|
||||
|
||||
def login(request, *args, **kwargs):
|
||||
if 'mellon.backends.SAMLBackend' in settings.AUTHENTICATION_BACKENDS:
|
||||
if not 'next' in request.GET:
|
||||
return HttpResponseRedirect(resolve_url('mellon_login'))
|
||||
return HttpResponseRedirect(resolve_url('mellon_login') + '?next=' + request.GET.get('next'))
|
||||
return auth_views.login(request, *args, **kwargs)
|
||||
|
||||
def logout(request, next_page=None):
|
||||
if 'mellon.backends.SAMLBackend' in settings.AUTHENTICATION_BACKENDS:
|
||||
return HttpResponseRedirect(resolve_url('mellon_logout'))
|
||||
auth_logout(request)
|
||||
if next_page is not None:
|
||||
next_page = resolve_url(next_page)
|
||||
else:
|
||||
next_page = '/'
|
||||
return HttpResponseRedirect(next_page)
|
||||
|
||||
def ajax_page_cell(request, page_pk, cell_reference):
|
||||
try:
|
||||
page = Page.objects.get(id=page_pk)
|
||||
except Page.DoesNotExist:
|
||||
raise Http404()
|
||||
if not page.is_visible(request.user):
|
||||
raise PermissionDenied()
|
||||
try:
|
||||
cell = CellBase.get_cell(cell_reference, page_id=page_pk)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
context = RequestContext(request, {
|
||||
'page': page,
|
||||
'request': request,
|
||||
})
|
||||
|
||||
return HttpResponse(cell.render(context), content_type='text/html')
|
||||
|
||||
|
||||
def page(request):
|
||||
parts = [x for x in request.path_info.strip('/').split('/') if x]
|
||||
if not parts:
|
||||
parts = ['index']
|
||||
pages = [get_object_or_404(Page, slug=x) for x in parts]
|
||||
for i, page in enumerate(pages[1:]):
|
||||
if page.parent_id != pages[i].id:
|
||||
raise Http404()
|
||||
|
||||
page = pages[-1]
|
||||
|
||||
if not page.is_visible(request.user):
|
||||
raise PermissionDenied()
|
||||
|
||||
if page.redirect_url:
|
||||
return HttpResponseRedirect(page.redirect_url)
|
||||
|
||||
combo_template = settings.COMBO_PUBLIC_TEMPLATES[page.template_name]
|
||||
|
||||
cells = CellBase.get_cells(page_id=page.id)
|
||||
locked_placeholders = page.get_locked_placeholders(cells)
|
||||
|
||||
if locked_placeholders:
|
||||
# there are some acquired placeholders, look in parent pages for
|
||||
# appropriate content.
|
||||
try:
|
||||
# add the site index page as ultimate parent
|
||||
pages.insert(0, Page.objects.get(slug='index'))
|
||||
except Page.DoesNotExist:
|
||||
pass
|
||||
unlocker_cells = CellBase.get_cells(page_id__in=[x.id for x in pages])
|
||||
found_placeholders = {}
|
||||
for parent_page in reversed(pages[:-1]):
|
||||
for placeholder in parent_page.get_unlocked_placeholders(unlocker_cells):
|
||||
if not placeholder in locked_placeholders:
|
||||
continue
|
||||
if not placeholder in found_placeholders:
|
||||
found_placeholders[placeholder] = parent_page.id
|
||||
if len(found_placeholders) == len(locked_placeholders):
|
||||
break
|
||||
|
||||
# add found cells to the page cells
|
||||
for placeholder_key, page_id in found_placeholders.items():
|
||||
cells.extend([x for x in unlocker_cells if x.page_id == page_id and
|
||||
x.placeholder == placeholder_key])
|
||||
|
||||
cells = [x for x in cells if x.is_visible(user=request.user)]
|
||||
|
||||
ctx = {
|
||||
'page': page,
|
||||
'page_cells': cells,
|
||||
'request': request,
|
||||
}
|
||||
|
||||
template_name = combo_template['template']
|
||||
return render(request, template_name, ctx)
|
|
@ -1,194 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Django settings file; it loads the default settings, and local settings
|
||||
(from a local_settings.py file, or a configuration file set in the
|
||||
COMBO_SETTINGS_FILE environment variable).
|
||||
|
||||
The local settings file should exist, at least to set a suitable SECRET_KEY,
|
||||
and to disable DEBUG mode in production.
|
||||
"""
|
||||
|
||||
import os
|
||||
from django.conf import global_settings
|
||||
|
||||
from . import plugins
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'r^(w+o4*txe1=t+0w*w3*9%idij!yeq1#axpsi4%5*u#3u&)1t'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'ckeditor',
|
||||
'sekizai',
|
||||
'gadjo',
|
||||
'cmsplugin_blurp',
|
||||
'combo.data',
|
||||
'combo.manager',
|
||||
'combo.public',
|
||||
)
|
||||
|
||||
INSTALLED_APPS = plugins.register_plugins_apps(INSTALLED_APPS)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = plugins.register_plugins_middleware(MIDDLEWARE_CLASSES)
|
||||
|
||||
# Serve xstatic files, required for gadjo
|
||||
STATICFILES_FINDERS = global_settings.STATICFILES_FINDERS + \
|
||||
('gadjo.finders.XStaticFinder',)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + \
|
||||
('sekizai.context_processors.sekizai',)
|
||||
|
||||
ROOT_URLCONF = 'combo.urls'
|
||||
|
||||
WSGI_APPLICATION = 'combo.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
LOCALE_PATHS = (os.path.join(BASE_DIR, 'combo', 'locale'), )
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
CKEDITOR_UPLOAD_PATH = 'uploads/'
|
||||
|
||||
CKEDITOR_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar_Own': [['Source', 'Format', '-', 'Bold', 'Italic'],
|
||||
['NumberedList', 'BulletedList'],
|
||||
['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
|
||||
['Link', 'Unlink'],
|
||||
['Image',],
|
||||
['RemoveFormat',]],
|
||||
'toolbar': 'Own',
|
||||
},
|
||||
}
|
||||
|
||||
COMBO_PUBLIC_TEMPLATES = {
|
||||
'standard': {
|
||||
'name': 'Standard',
|
||||
'template': 'combo/page_template.html',
|
||||
'placeholders': {
|
||||
'content': {
|
||||
'name': 'Content',
|
||||
},
|
||||
'footer': {
|
||||
'name': 'Footer',
|
||||
'acquired': True,
|
||||
},
|
||||
}
|
||||
},
|
||||
'standard-sidebar': {
|
||||
'name': 'Standard + sidebar',
|
||||
'template': 'combo/page_template_sidebar.html',
|
||||
'placeholders': {
|
||||
'content': {
|
||||
'name': 'Content',
|
||||
},
|
||||
'sidebar': {
|
||||
'name': 'Sidebar',
|
||||
},
|
||||
'footer': {
|
||||
'name': 'Footer',
|
||||
'acquired': True,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# Authentication settings
|
||||
LOGIN_URL = '/login/'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGOUT_URL = '/logout/'
|
||||
|
||||
MELLON_ATTRIBUTE_MAPPING = {
|
||||
'username': '{attributes[username][0]}',
|
||||
'email': '{attributes[email][0]}',
|
||||
'first_name': '{attributes[first_name][0]}',
|
||||
'last_name': '{attributes[last_name][0]}',
|
||||
}
|
||||
|
||||
MELLON_USERNAME_TEMPLATE = '{attributes[username][0]}'
|
||||
|
||||
MELLON_PUBLIC_KEYS = [os.path.join(BASE_DIR, 'cert.pem')]
|
||||
MELLON_PRIVATE_KEY = os.path.join(BASE_DIR, 'key.cert')
|
||||
MELLON_IDENTITY_PROVIDERS = [
|
||||
{'METADATA': os.path.join(BASE_DIR, 'idp-metadata.xml'),
|
||||
'GROUP_ATTRIBUTE': 'role'},
|
||||
]
|
||||
|
||||
local_settings_file = os.environ.get('COMBO_SETTINGS_FILE',
|
||||
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
|
||||
if os.path.exists(local_settings_file):
|
||||
execfile(local_settings_file)
|
|
@ -1,49 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015 Entr'ouvert
|
||||
#
|
||||
# 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 django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
|
||||
from .urls_utils import decorated_includes, manager_required
|
||||
|
||||
from .public.views import login, logout
|
||||
from .manager.urls import urlpatterns as combo_manager_urls
|
||||
|
||||
from . import plugins
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^manage/', decorated_includes(manager_required,
|
||||
include(combo_manager_urls))),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^logout/$', logout, name='auth_logout'),
|
||||
url(r'^login/$', login, name='auth_login'),
|
||||
)
|
||||
|
||||
if 'mellon' in settings.INSTALLED_APPS:
|
||||
urlpatterns += patterns('', url(r'^accounts/mellon/', include('mellon.urls')))
|
||||
|
||||
# static and media files
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
from django.conf.urls.static import static
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
urlpatterns = plugins.register_plugins_urls(urlpatterns)
|
||||
|
||||
# other URLs are handled as public URLs
|
||||
urlpatterns += patterns('', url(r'', include('combo.public.urls')))
|
|
@ -1,62 +0,0 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015 Entr'ouvert
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
# Decorating URL includes, <https://djangosnippets.org/snippets/2532/>
|
||||
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import RegexURLPattern, RegexURLResolver
|
||||
|
||||
class DecoratedURLPattern(RegexURLPattern):
|
||||
def resolve(self, *args, **kwargs):
|
||||
result = super(DecoratedURLPattern, self).resolve(*args, **kwargs)
|
||||
if result:
|
||||
result.func = self._decorate_with(result.func)
|
||||
return result
|
||||
|
||||
class DecoratedRegexURLResolver(RegexURLResolver):
|
||||
def resolve(self, *args, **kwargs):
|
||||
result = super(DecoratedRegexURLResolver, self).resolve(*args, **kwargs)
|
||||
if result:
|
||||
result.func = self._decorate_with(result.func)
|
||||
return result
|
||||
|
||||
def decorated_includes(func, includes, *args, **kwargs):
|
||||
urlconf_module, app_name, namespace = includes
|
||||
|
||||
for item in urlconf_module:
|
||||
if isinstance(item, RegexURLPattern):
|
||||
item.__class__ = DecoratedURLPattern
|
||||
item._decorate_with = func
|
||||
|
||||
elif isinstance(item, RegexURLResolver):
|
||||
item.__class__ = DecoratedRegexURLResolver
|
||||
item._decorate_with = func
|
||||
|
||||
return urlconf_module, app_name, namespace
|
||||
|
||||
def manager_required(function=None, login_url=None):
|
||||
def check_manager(user):
|
||||
if user and user.is_staff:
|
||||
return True
|
||||
if user and not user.is_anonymous():
|
||||
raise PermissionDenied()
|
||||
# As the last resort, show the login form
|
||||
return False
|
||||
actual_decorator = user_passes_test(check_manager, login_url=login_url)
|
||||
if function:
|
||||
return actual_decorator(function)
|
||||
return actual_decorator
|
|
@ -1,14 +0,0 @@
|
|||
"""
|
||||
WSGI config for combo project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "combo.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
|
@ -0,0 +1,5 @@
|
|||
combo (0.1-1) unstable; urgency=low
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Jérôme Schneider <jschneider@entrouvert.com> Tue, 17 Feb 2015 15:50:44 +0100
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
NAME=combo
|
||||
MANAGE=/usr/lib/$NAME/manage.py
|
||||
|
||||
# load Debian default configuration
|
||||
export COMBO_SETTINGS_FILE=/usr/lib/$NAME/debian_config.py
|
||||
|
||||
# check user
|
||||
if test x$1 = x"--forceuser"
|
||||
then
|
||||
shift
|
||||
elif test $(id -un) != "$NAME"
|
||||
then
|
||||
echo "error: must use $0 with user ${NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test $# -eq 0
|
||||
then
|
||||
python ${MANAGE} help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python ${MANAGE} "$@"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/etc/combo
|
||||
/usr/lib/combo
|
||||
/var/lib/combo/collectstatic
|
||||
/var/lib/combo/tenants
|
||||
/var/log/combo
|
|
@ -0,0 +1,3 @@
|
|||
COPYING
|
||||
README
|
||||
debian/nginx-example.conf
|
|
@ -0,0 +1,193 @@
|
|||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: combo
|
||||
# Required-Start: $network $local_fs
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Portal Management System
|
||||
# Description: Portal Management System
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Entr'ouvert <info@entrouvert.com>
|
||||
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Portal Management System"
|
||||
NAME=combo
|
||||
DAEMON=/usr/bin/gunicorn
|
||||
RUN_DIR=/run/$NAME
|
||||
PIDFILE=$RUN_DIR/$NAME.pid
|
||||
LOG_DIR=/var/log/$NAME
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
BIND=unix:$RUN_DIR/$NAME.sock
|
||||
WORKERS=5
|
||||
TIMEOUT=30
|
||||
|
||||
COMBO_SETTINGS_FILE=/usr/lib/$NAME/debian_config.py
|
||||
MANAGE_SCRIPT="/usr/bin/$NAME-manage"
|
||||
|
||||
USER=$NAME
|
||||
GROUP=$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x $MANAGE_SCRIPT ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
DAEMON_ARGS=${DAEMON_ARGS:-"--pid $PIDFILE \
|
||||
--user $USER --group $GROUP \
|
||||
--daemon \
|
||||
--access-logfile $LOG_DIR/gunicorn-access.log \
|
||||
--log-file $LOG_DIR/gunicorn-error.log \
|
||||
--bind=$BIND \
|
||||
--workers=$WORKERS \
|
||||
--worker-class=sync \
|
||||
--timeout=$TIMEOUT \
|
||||
--name $NAME \
|
||||
$NAME.wsgi:application"}
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Create /run directory
|
||||
if [ ! -d $RUN_DIR ]; then
|
||||
install -d -m 755 -o $USER -g $GROUP $RUN_DIR
|
||||
fi
|
||||
|
||||
# environment for wsgi
|
||||
export COMBO_SETTINGS_FILE
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name `basename $DAEMON`
|
||||
return 0
|
||||
}
|
||||
|
||||
do_migrate() {
|
||||
log_action_msg "Applying migrations (migrate_schemas).."
|
||||
su $USER -p -c "$MANAGE_SCRIPT migrate_schemas"
|
||||
log_action_msg "done"
|
||||
}
|
||||
|
||||
do_collectstatic() {
|
||||
log_action_msg "Collect static files (collectstatic).."
|
||||
su $USER -p -c "$MANAGE_SCRIPT collectstatic --noinput"
|
||||
log_action_msg "done"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
log_daemon_msg "Starting $DESC " "$NAME"
|
||||
do_migrate
|
||||
do_collectstatic
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
do_collectstatic
|
||||
do_migrate
|
||||
do_reload
|
||||
log_end_msg $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_migrate
|
||||
do_collectstatic
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
|
@ -0,0 +1,3 @@
|
|||
debian/combo-manage /usr/bin
|
||||
debian/settings.py /etc/combo
|
||||
debian/debian_config.py /usr/lib/combo
|
|
@ -0,0 +1,44 @@
|
|||
#! /bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
NAME="combo"
|
||||
USER=$NAME
|
||||
GROUP=$NAME
|
||||
CONFIG_DIR="/etc/$NAME"
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
|
||||
# make sure the administrative user exists
|
||||
if ! getent passwd $USER >/dev/null; then
|
||||
adduser --disabled-password --quiet --system \
|
||||
--no-create-home --home /var/lib/$NAME \
|
||||
--gecos "$NAME user" --group $USER
|
||||
fi
|
||||
# ensure dirs ownership
|
||||
chown $USER:$GROUP /var/log/$NAME
|
||||
chown $USER:$GROUP /var/lib/$NAME/collectstatic
|
||||
chown $USER:$GROUP /var/lib/$NAME/tenants
|
||||
# create a secret file
|
||||
SECRET_FILE=$CONFIG_DIR/secret
|
||||
if [ ! -f $SECRET_FILE ]; then
|
||||
echo -n "Generating Django secret..." >&2
|
||||
cat /dev/urandom | tr -dc [:alnum:]-_\!\%\^:\; | head -c70 > $SECRET_FILE
|
||||
chown root:$GROUP $SECRET_FILE
|
||||
chmod 0440 $SECRET_FILE
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -0,0 +1 @@
|
|||
7
|
|
@ -0,0 +1,28 @@
|
|||
Source: combo
|
||||
Maintainer: Jérôme Schneider <jschneider@entrouvert.com>
|
||||
Section: python
|
||||
Priority: optional
|
||||
Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 7)
|
||||
Standards-Version: 3.9.6
|
||||
X-Python-Version: >= 2.7
|
||||
|
||||
Package: python-combo
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${python:Depends},
|
||||
python-django (>= 1.7),
|
||||
python-gadjo,
|
||||
python-django-cmsplugin-blurp
|
||||
Recommends: python-django-mellon
|
||||
Description: Portal Management System (Python module)
|
||||
|
||||
Package: combo
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends},
|
||||
python-combo (= ${binary:Version}),
|
||||
python-hobo,
|
||||
python-django-tenant-schemas,
|
||||
python-psycopg2,
|
||||
python-django-mellon,
|
||||
gunicorn
|
||||
Recommends: nginx, postgresql
|
||||
Description: Portal Management System
|
|
@ -0,0 +1,18 @@
|
|||
# This file is sourced by "execfile" from combo.settings
|
||||
|
||||
import os
|
||||
|
||||
PROJECT_NAME = 'combo'
|
||||
|
||||
# SAML2 authentication
|
||||
INSTALLED_APPS += ('mellon',)
|
||||
|
||||
#
|
||||
# hobotization (multitenant)
|
||||
#
|
||||
execfile('/usr/lib/hobo/debian_config_common.py')
|
||||
|
||||
#
|
||||
# local settings
|
||||
#
|
||||
execfile(os.path.join(ETC_DIR, 'settings.py'))
|
|
@ -0,0 +1,58 @@
|
|||
server {
|
||||
listen 443;
|
||||
server_name *-combo.example.org;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||
|
||||
access_log /var/log/nginx/combo.example.org-access.log combined;
|
||||
error_log /var/log/nginx/combo.example.org-error.log;
|
||||
|
||||
location ~ ^/static/(.+)$ {
|
||||
root /;
|
||||
try_files /var/lib/combo/tenants/$host/static/$1
|
||||
/var/lib/combo/collectstatic/$1
|
||||
=404;
|
||||
}
|
||||
|
||||
location ~ ^/media/(.+)$ {
|
||||
alias /var/lib/combo/tenants/$host/media/$1;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/var/run/combo/combo.sock;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-SSL on;
|
||||
proxy_set_header X-Forwarded-Protocol ssl;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name *-combo.example.org;
|
||||
|
||||
access_log /var/log/nginx/combo.example.org-access.log combined;
|
||||
error_log /var/log/nginx/combo.example.org-error.log;
|
||||
|
||||
location ~ ^/static/(.+)$ {
|
||||
root /;
|
||||
try_files /var/lib/combo/tenants/$host/static/$1
|
||||
/var/lib/combo/collectstatic/$1
|
||||
=404;
|
||||
}
|
||||
|
||||
location ~ ^/media/(.+)$ {
|
||||
alias /var/lib/combo/tenants/$host/media/$1;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/var/run/combo/combo.sock;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
django_ckeditor python-django-ckeditor
|
|
@ -0,0 +1 @@
|
|||
/usr/lib/combo
|
|
@ -0,0 +1,2 @@
|
|||
COPYING
|
||||
README
|
|
@ -0,0 +1,2 @@
|
|||
usr/bin/manage.py /usr/lib/combo
|
||||
usr/lib/python2*/*-packages
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
%:
|
||||
dh $@ --with python2
|
|
@ -0,0 +1,55 @@
|
|||
# Configuration for combo.
|
||||
# You can override Combo default settings here
|
||||
|
||||
# Combo is a Django application: for the full list of settings and their
|
||||
# values, see https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
# For more information on settings see
|
||||
# https://docs.djangoproject.com/en/1.7/topics/settings/
|
||||
|
||||
# WARNING! Quick-start development settings unsuitable for production!
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
# This file is sourced by "execfile" from /usr/lib/combo/debian_config.py
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = False
|
||||
|
||||
#ADMINS = (
|
||||
# # ('User 1', 'watchdog@example.net'),
|
||||
# # ('User 2', 'janitor@example.net'),
|
||||
#)
|
||||
|
||||
# ALLOWED_HOSTS must be correct in production!
|
||||
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = [
|
||||
'*',
|
||||
]
|
||||
|
||||
# Databases
|
||||
# Default: a local database named "combo"
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
# Warning: don't change ENGINE
|
||||
# DATABASES['default']['NAME'] = 'combo'
|
||||
# DATABASES['default']['USER'] = 'combo'
|
||||
# DATABASES['default']['PASSWORD'] = '******'
|
||||
# DATABASES['default']['HOST'] = 'localhost'
|
||||
# DATABASES['default']['PORT'] = '5432'
|
||||
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
TIME_ZONE = 'Europe/Paris'
|
||||
|
||||
# Email configuration
|
||||
# EMAIL_SUBJECT_PREFIX = '[combo] '
|
||||
# SERVER_EMAIL = 'root@combo.example.org'
|
||||
# DEFAULT_FROM_EMAIL = 'webmaster@combo.example.org'
|
||||
|
||||
# SMTP configuration
|
||||
# EMAIL_HOST = 'localhost'
|
||||
# EMAIL_HOST_USER = ''
|
||||
# EMAIL_HOST_PASSWORD = ''
|
||||
# EMAIL_PORT = 25
|
||||
|
||||
# HTTPS Security
|
||||
# CSRF_COOKIE_SECURE = True
|
||||
# SESSION_COOKIE_SECURE = True
|
10
manage.py
10
manage.py
|
@ -1,10 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "combo.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
|
@ -1,4 +0,0 @@
|
|||
Django==1.7
|
||||
django-ckeditor
|
||||
-e git+http://repos.entrouvert.org/gadjo.git/#egg=gadjo
|
||||
django-cmsplugin-blurp
|
66
setup.py
66
setup.py
|
@ -1,66 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from distutils.command.sdist import sdist
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
class eo_sdist(sdist):
|
||||
def run(self):
|
||||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
version = get_version()
|
||||
version_file = open('VERSION', 'w')
|
||||
version_file.write(version)
|
||||
version_file.close()
|
||||
sdist.run(self)
|
||||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
|
||||
def get_version():
|
||||
if os.path.exists('VERSION'):
|
||||
version_file = open('VERSION', 'r')
|
||||
version = version_file.read()
|
||||
version_file.close()
|
||||
return version
|
||||
if os.path.exists('.git'):
|
||||
p = subprocess.Popen(['git', 'describe', '--dirty', '--match=v*'], stdout=subprocess.PIPE)
|
||||
result = p.communicate()[0]
|
||||
if p.returncode == 0:
|
||||
version = result.split()[0][1:]
|
||||
version = version.replace('-', '.')
|
||||
return version
|
||||
return '0'
|
||||
|
||||
|
||||
setup(
|
||||
name='combo',
|
||||
version=get_version(),
|
||||
description='Content Manager',
|
||||
author='Frederic Peters',
|
||||
author_email='fpeters@entrouvert.com',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
scripts=('manage.py',),
|
||||
url='https://dev.entrouvert.org/projects/combo/',
|
||||
classifiers=[
|
||||
'Development Status :: 2 - Pre-Alpha',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
],
|
||||
install_requires=['django == 1.7',
|
||||
'django-ckeditor',
|
||||
'gadjo',
|
||||
'django-cmsplugin-blurp',
|
||||
],
|
||||
zip_safe=False,
|
||||
)
|
|
@ -1 +0,0 @@
|
|||
LANGUAGE_CODE = 'en-us'
|
|
@ -1,18 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from combo.data.models import Page, CellBase, TextCell
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_cell_reference():
|
||||
page = Page()
|
||||
page.save()
|
||||
|
||||
cell = TextCell()
|
||||
cell.page = page
|
||||
cell.text = 'foobar'
|
||||
cell.order = 0
|
||||
cell.save()
|
||||
|
||||
assert CellBase.get_cell(cell.get_reference()) == cell
|
|
@ -1,101 +0,0 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
import pytest
|
||||
from webtest import TestApp
|
||||
|
||||
from combo.data.models import Page, CellBase, TextCell
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@pytest.fixture
|
||||
def admin_user():
|
||||
try:
|
||||
user = User.objects.get(username='admin')
|
||||
except User.DoesNotExist:
|
||||
user = User.objects.create_superuser('admin', email=None, password='admin')
|
||||
return user
|
||||
|
||||
def login(app, username='admin', password='admin'):
|
||||
login_page = app.get('/login/')
|
||||
login_form = login_page.forms[0]
|
||||
login_form['username'] = username
|
||||
login_form['password'] = password
|
||||
resp = login_form.submit()
|
||||
assert resp.status_int == 302
|
||||
return app
|
||||
|
||||
def test_unlogged_access():
|
||||
# connect while not being logged in
|
||||
app = TestApp(get_wsgi_application())
|
||||
assert app.get('/manage/', status=302).location == 'http://localhost:80/login/?next=/manage/'
|
||||
|
||||
def test_access(admin_user):
|
||||
app = login(TestApp(get_wsgi_application()))
|
||||
resp = app.get('/manage/', status=200)
|
||||
assert 'Pages' in resp.body
|
||||
assert "This site doesn't have any page yet." in resp.body
|
||||
|
||||
def test_add_page(admin_user):
|
||||
app = login(TestApp(get_wsgi_application()))
|
||||
resp = app.get('/manage/', status=200)
|
||||
resp = resp.click('New')
|
||||
assert resp.forms[0]['title'].value == 'Home' # default title for first page
|
||||
assert resp.forms[0]['slug'].value == 'index' # default slug for first page
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://localhost:80/manage/pages/1/'
|
||||
|
||||
def test_add_second_page(admin_user):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='One', slug='one')
|
||||
page.save()
|
||||
app = login(TestApp(get_wsgi_application()))
|
||||
resp = app.get('/manage/', status=200)
|
||||
resp = resp.click('New')
|
||||
# assert there's no defaul title or slug for the second page
|
||||
assert resp.forms[0]['title'].value == ''
|
||||
assert resp.forms[0]['slug'].value == ''
|
||||
|
||||
def test_delete_page(admin_user):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='One', slug='one', template_name='standard')
|
||||
page.save()
|
||||
app = login(TestApp(get_wsgi_application()))
|
||||
resp = app.get('/manage/pages/%s/' % page.id)
|
||||
resp = resp.click('delete')
|
||||
assert 'Confirm Deletion' in resp.body
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://localhost:80/manage/'
|
||||
assert Page.objects.count() == 0
|
||||
|
||||
def test_export_page(admin_user):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='One', slug='one', template_name='standard')
|
||||
page.save()
|
||||
app = login(TestApp(get_wsgi_application()))
|
||||
resp = app.get('/manage/pages/%s/' % page.id)
|
||||
resp = resp.click('export')
|
||||
assert resp.headers['content-type'] == 'application/json'
|
||||
assert resp.json.get('fields').get('slug') == 'one'
|
||||
|
||||
def test_add_edit_cell(admin_user):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='One', slug='one', template_name='standard')
|
||||
page.save()
|
||||
app = login(TestApp(get_wsgi_application()))
|
||||
resp = app.get('/manage/pages/%s/' % page.id)
|
||||
# click on first button link, this should add a text cell
|
||||
resp = app.get(resp.html.find('button').get('data-add-url'))
|
||||
assert resp.location == 'http://localhost:80/manage/pages/1/'
|
||||
|
||||
cells = CellBase.get_cells(page_id=page.id)
|
||||
assert len(cells) == 1
|
||||
assert isinstance(cells[0], TextCell)
|
||||
|
||||
resp = app.get('/manage/pages/%s/' % page.id)
|
||||
assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.body
|
||||
resp.forms[0]['c%s-text' % cells[0].get_reference()].value = 'Hello world'
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://localhost:80/manage/pages/1/'
|
||||
|
||||
resp = app.get('/manage/pages/%s/' % page.id)
|
||||
assert resp.forms[0]['c%s-text' % cells[0].get_reference()].value == 'Hello world'
|
|
@ -1,114 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from combo.data.models import Page
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
def test_page_order():
|
||||
page = Page()
|
||||
page.title = 'foo'
|
||||
page.save()
|
||||
page2 = Page()
|
||||
page2.title = 'bar'
|
||||
page2.save()
|
||||
|
||||
assert Page.objects.get(id=page.id).order < Page.objects.get(id=page2.id)
|
||||
|
||||
def test_page_url():
|
||||
page = Page()
|
||||
page.slug = 'foo'
|
||||
page.save()
|
||||
assert page.get_online_url() == '/foo'
|
||||
page2 = Page()
|
||||
page2.slug = 'bar'
|
||||
page2.parent = page
|
||||
assert page2.get_online_url() == '/foo/bar'
|
||||
|
||||
def test_page_of_level():
|
||||
parent_page = None
|
||||
for i in range(10):
|
||||
page = Page()
|
||||
page.slug = 'level%d' % i
|
||||
if parent_page:
|
||||
page.parent = parent_page
|
||||
page.save()
|
||||
parent_page = page
|
||||
|
||||
assert page.get_page_of_level(2).slug == 'level2'
|
||||
assert page.get_page_of_level(4).slug == 'level4'
|
||||
assert page.get_page_of_level(14) is None
|
||||
|
||||
def test_page_siblings():
|
||||
Page.objects.all().delete()
|
||||
page = Page()
|
||||
page.slug = 'foo'
|
||||
page.save()
|
||||
page2 = Page()
|
||||
page2.slug = 'bar'
|
||||
page2.parent = page
|
||||
page2.save()
|
||||
page3 = Page()
|
||||
page3.slug = 'baz'
|
||||
page3.parent = page
|
||||
page3.save()
|
||||
assert [x.slug for x in page3.get_siblings()] == ['bar', 'baz']
|
||||
|
||||
assert page.has_children()
|
||||
assert not page2.has_children()
|
||||
|
||||
def test_flat_hierarchy():
|
||||
Page.objects.all().delete()
|
||||
page = Page()
|
||||
page.slug = 'foo'
|
||||
page.save()
|
||||
page2 = Page()
|
||||
page2.slug = 'bar'
|
||||
page2.parent = page
|
||||
page2.save()
|
||||
page3 = Page()
|
||||
page3.slug = 'baz'
|
||||
page3.parent = page
|
||||
page3.save()
|
||||
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
assert [x.slug for x in pages] == ['foo', 'bar', 'baz']
|
||||
|
||||
page.order = 17
|
||||
page.save()
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
assert [x.slug for x in pages] == ['foo', 'bar', 'baz']
|
||||
|
||||
page2.parent = None
|
||||
page2.save()
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
assert [x.slug for x in pages] == ['bar', 'foo', 'baz']
|
||||
|
||||
page.parent = page2
|
||||
page.save()
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
assert [x.slug for x in pages] == ['bar', 'foo', 'baz']
|
||||
|
||||
def test_page_visibility():
|
||||
page = Page()
|
||||
assert page.is_visible()
|
||||
|
||||
page.public = False
|
||||
page.save()
|
||||
assert not page.is_visible()
|
||||
|
||||
group = Group(name='foobar')
|
||||
group.save()
|
||||
user1 = User(username='foo')
|
||||
user1.save()
|
||||
user1.groups = [group]
|
||||
user2 = User(username='bar')
|
||||
user2.save()
|
||||
|
||||
assert page.is_visible(user1)
|
||||
assert page.is_visible(user2)
|
||||
|
||||
page.groups = [group]
|
||||
assert not page.is_visible()
|
||||
assert page.is_visible(user1)
|
||||
assert not page.is_visible(user2)
|
|
@ -1,54 +0,0 @@
|
|||
from django.core.wsgi import get_wsgi_application
|
||||
from webtest import TestApp
|
||||
import pytest
|
||||
|
||||
from combo.data.models import Page, CellBase, TextCell
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
def test_missing_index():
|
||||
Page.objects.all().delete()
|
||||
app = TestApp(get_wsgi_application())
|
||||
resp = app.get('/', status=404)
|
||||
|
||||
def test_index():
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard')
|
||||
page.save()
|
||||
app = TestApp(get_wsgi_application())
|
||||
resp = app.get('/index', status=200)
|
||||
resp = app.get('/', status=200)
|
||||
|
||||
def test_page_contents():
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard')
|
||||
page.save()
|
||||
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
|
||||
cell.save()
|
||||
app = TestApp(get_wsgi_application())
|
||||
resp = app.get('/', status=200)
|
||||
assert 'Foobar' in resp.body
|
||||
|
||||
def test_page_footer_acquisition():
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard')
|
||||
page.save()
|
||||
cell = TextCell(page=page, placeholder='footer', text='BARFOO', order=0)
|
||||
cell.save()
|
||||
app = TestApp(get_wsgi_application())
|
||||
resp = app.get('/', status=200)
|
||||
assert 'BARFOO' in resp.body
|
||||
|
||||
page = Page(title='Second', slug='second', template_name='standard')
|
||||
page.save()
|
||||
resp = app.get('/second', status=200)
|
||||
assert 'BARFOO' in resp.body
|
||||
|
||||
def test_page_redirect():
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Elsewhere', slug='elsewhere', template_name='standard',
|
||||
redirect_url='http://example.net')
|
||||
page.save()
|
||||
app = TestApp(get_wsgi_application())
|
||||
resp = app.get('/elsewhere', status=302)
|
||||
assert resp.location == 'http://example.net'
|
Loading…
Reference in New Issue