Merge branch 'release/0.12.0' into production
This commit is contained in:
commit
000391a808
160 changed files with 10716 additions and 2289 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -44,3 +44,15 @@ htmlcov/
|
|||
tags
|
||||
_build/
|
||||
*.mo
|
||||
.vagrant/
|
||||
gnuviechadmin/assets/
|
||||
coverage-report/
|
||||
.idea/
|
||||
|
||||
.env
|
||||
|
||||
/docker/django_media
|
||||
/docker/django_static
|
||||
!/docker/django_media/.empty
|
||||
!/docker/django_static/.empty
|
||||
/static/
|
||||
|
|
661
COPYING
Normal file
661
COPYING
Normal file
|
@ -0,0 +1,661 @@
|
|||
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/>.
|
56
Dockerfile
Normal file
56
Dockerfile
Normal file
|
@ -0,0 +1,56 @@
|
|||
ARG DEBIAN_RELEASE=buster
|
||||
FROM debian:$DEBIAN_RELEASE
|
||||
LABEL maintainer="Jan Dittberner <jan@dittberner.info>"
|
||||
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
dumb-init \
|
||||
gettext \
|
||||
git \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-virtualenv \
|
||||
python3-wheel \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*.*
|
||||
|
||||
RUN python3 -m pip install --prefix=/usr/local pipenv
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
libpq-dev \
|
||||
postgresql-client \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*.*
|
||||
|
||||
ARG GVAGID=2000
|
||||
ARG GVAUID=2000
|
||||
|
||||
ARG GVAAPP=gva
|
||||
|
||||
VOLUME /srv/$GVAAPP/media /srv/$GVAAPP/static
|
||||
|
||||
WORKDIR /srv/$GVAAPP
|
||||
|
||||
COPY Pipfile Pipfile.lock /srv/$GVAAPP/
|
||||
|
||||
RUN addgroup --gid $GVAGID $GVAAPP ; \
|
||||
adduser --home /home/$GVAAPP --shell /bin/bash --uid $GVAUID --gid $GVAGID --disabled-password --gecos "User for gnuviechadmin component $GVAAPP" $GVAAPP
|
||||
|
||||
USER $GVAAPP
|
||||
RUN python3 -m virtualenv --python=python3 /home/$GVAAPP/$GVAAPP-venv ; \
|
||||
/home/$GVAAPP/$GVAAPP-venv/bin/python3 -m pip install -U pip ; \
|
||||
VIRTUAL_ENV=/home/$GVAAPP/$GVAAPP-venv pipenv install --deploy --ignore-pipfile --dev
|
||||
|
||||
VOLUME /srv/$GVAAPP
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
COPY gva.sh /srv/
|
||||
|
||||
ENTRYPOINT ["dumb-init", "/srv/gva.sh"]
|
22
LICENSE.txt
22
LICENSE.txt
|
@ -1,22 +0,0 @@
|
|||
Copyright (c) 2014, 2015 Jan Dittberner.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
33
Pipfile
Normal file
33
Pipfile
Normal file
|
@ -0,0 +1,33 @@
|
|||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[[source]]
|
||||
url = "https://pypi.gnuviech-server.de/simple"
|
||||
verify_ssl = true
|
||||
name = "gnuviech"
|
||||
|
||||
[packages]
|
||||
"psycopg2" = "*"
|
||||
Django = "<3"
|
||||
celery = "*"
|
||||
django-allauth = "*"
|
||||
django-braces = "*"
|
||||
django-crispy-forms = "*"
|
||||
django-model-utils = "*"
|
||||
gvacommon = {version = "*",index = "gnuviech"}
|
||||
passlib = "*"
|
||||
redis = "*"
|
||||
requests-oauthlib = "*"
|
||||
|
||||
[dev-packages]
|
||||
coverage = "*"
|
||||
django-debug-toolbar = "*"
|
||||
sphinx = "*"
|
||||
releases = "*"
|
||||
sphinxcontrib-blockdiag = "*"
|
||||
pylama = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
617
Pipfile.lock
generated
Normal file
617
Pipfile.lock
generated
Normal file
|
@ -0,0 +1,617 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "1c0b7bdab385f10279c852fa7fe7ae2c022dc1c4495d0e55fd407aea947bc976"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
},
|
||||
{
|
||||
"name": "gnuviech",
|
||||
"url": "https://pypi.gnuviech-server.de/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"amqp": {
|
||||
"hashes": [
|
||||
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8",
|
||||
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"
|
||||
],
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede",
|
||||
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"
|
||||
],
|
||||
"version": "==3.6.3.0"
|
||||
},
|
||||
"celery": {
|
||||
"hashes": [
|
||||
"sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f",
|
||||
"sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.4.2"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
|
||||
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
|
||||
],
|
||||
"version": "==2020.4.5.1"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:69897097095f336d5aeef45b4103dceae51c00afa6d3ae198a2a18e519791b7a",
|
||||
"sha256:6ecd229e1815d4fc5240fc98f1cca78c41e7a8cd3e3f2eefadc4735031077916"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.12"
|
||||
},
|
||||
"django-allauth": {
|
||||
"hashes": [
|
||||
"sha256:7ab91485b80d231da191d5c7999ba93170ef1bf14ab6487d886598a1ad03e1d8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.41.0"
|
||||
},
|
||||
"django-braces": {
|
||||
"hashes": [
|
||||
"sha256:83705b78948de00804bfacf40c315d001bb39630f35bbdd8588211c2d5b4d43f",
|
||||
"sha256:a6d9b34cf3e4949635e54884097c30410d7964fc7bec7231445ea7079b8c5722"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.14.0"
|
||||
},
|
||||
"django-crispy-forms": {
|
||||
"hashes": [
|
||||
"sha256:50032184708ce351e3c9f0008ac35d659d9d5973fa2db218066f2e0a76eb41d9",
|
||||
"sha256:67e73ac863d3159500029fbbcdcb788f287a3fd357becebc1a0b51f73896dce3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"django-model-utils": {
|
||||
"hashes": [
|
||||
"sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c",
|
||||
"sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"gvacommon": {
|
||||
"hashes": [
|
||||
"sha256:adf1ebc824433196d112764c61d9ca869481d33f612818c2840069f57ab42c25"
|
||||
],
|
||||
"index": "gnuviech",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||
],
|
||||
"version": "==2.9"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
|
||||
"sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"kombu": {
|
||||
"hashes": [
|
||||
"sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76",
|
||||
"sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"
|
||||
],
|
||||
"version": "==4.6.8"
|
||||
},
|
||||
"oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
|
||||
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
|
||||
],
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"passlib": {
|
||||
"hashes": [
|
||||
"sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177",
|
||||
"sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.7.2"
|
||||
},
|
||||
"psycopg2": {
|
||||
"hashes": [
|
||||
"sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535",
|
||||
"sha256:2327bf42c1744a434ed8ed0bbaa9168cac7ee5a22a9001f6fc85c33b8a4a14b7",
|
||||
"sha256:27c633f2d5db0fc27b51f1b08f410715b59fa3802987aec91aeb8f562724e95c",
|
||||
"sha256:2c0afb40cfb4d53487ee2ebe128649028c9a78d2476d14a67781e45dc287f080",
|
||||
"sha256:2df2bf1b87305bd95eb3ac666ee1f00a9c83d10927b8144e8e39644218f4cf81",
|
||||
"sha256:440a3ea2c955e89321a138eb7582aa1d22fe286c7d65e26a2c5411af0a88ae72",
|
||||
"sha256:6a471d4d2a6f14c97a882e8d3124869bc623f3df6177eefe02994ea41fd45b52",
|
||||
"sha256:6b306dae53ec7f4f67a10942cf8ac85de930ea90e9903e2df4001f69b7833f7e",
|
||||
"sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf",
|
||||
"sha256:ac5b23d0199c012ad91ed1bbb971b7666da651c6371529b1be8cbe2a7bf3c3a9",
|
||||
"sha256:acf56d564e443e3dea152efe972b1434058244298a94348fc518d6dd6a9fb0bb",
|
||||
"sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055",
|
||||
"sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8.5"
|
||||
},
|
||||
"python3-openid": {
|
||||
"hashes": [
|
||||
"sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa",
|
||||
"sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"
|
||||
],
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f",
|
||||
"sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.4.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
||||
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
||||
],
|
||||
"version": "==2.23.0"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
|
||||
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
|
||||
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
|
||||
],
|
||||
"version": "==0.3.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||
],
|
||||
"version": "==1.25.8"
|
||||
},
|
||||
"vine": {
|
||||
"hashes": [
|
||||
"sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87",
|
||||
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
|
||||
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
|
||||
],
|
||||
"version": "==3.1.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
|
||||
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
|
||||
],
|
||||
"version": "==0.7.12"
|
||||
},
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
|
||||
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
|
||||
],
|
||||
"version": "==3.2.7"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
|
||||
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
|
||||
],
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"blockdiag": {
|
||||
"hashes": [
|
||||
"sha256:16a69dd9f3b44c9e0869999ce82aa968586698febc86ece9ca0c902dba772397",
|
||||
"sha256:fa0b47cf25bfc4d546b7fc284c70c3bac875a066e744b4a6b1d9ba457e4ed077"
|
||||
],
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
|
||||
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
|
||||
],
|
||||
"version": "==2020.4.5.1"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0",
|
||||
"sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30",
|
||||
"sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b",
|
||||
"sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0",
|
||||
"sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823",
|
||||
"sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe",
|
||||
"sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037",
|
||||
"sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6",
|
||||
"sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31",
|
||||
"sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd",
|
||||
"sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892",
|
||||
"sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1",
|
||||
"sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78",
|
||||
"sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac",
|
||||
"sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006",
|
||||
"sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014",
|
||||
"sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2",
|
||||
"sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7",
|
||||
"sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8",
|
||||
"sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7",
|
||||
"sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9",
|
||||
"sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1",
|
||||
"sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307",
|
||||
"sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a",
|
||||
"sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435",
|
||||
"sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0",
|
||||
"sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5",
|
||||
"sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441",
|
||||
"sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732",
|
||||
"sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de",
|
||||
"sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.0.4"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:69897097095f336d5aeef45b4103dceae51c00afa6d3ae198a2a18e519791b7a",
|
||||
"sha256:6ecd229e1815d4fc5240fc98f1cca78c41e7a8cd3e3f2eefadc4735031077916"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.12"
|
||||
},
|
||||
"django-debug-toolbar": {
|
||||
"hashes": [
|
||||
"sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943",
|
||||
"sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||
],
|
||||
"version": "==0.16"
|
||||
},
|
||||
"funcparserlib": {
|
||||
"hashes": [
|
||||
"sha256:b7992eac1a3eb97b3d91faa342bfda0729e990bd8a43774c1592c091e563c91d"
|
||||
],
|
||||
"version": "==0.3.6"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||
],
|
||||
"version": "==2.9"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
|
||||
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
|
||||
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
|
||||
],
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
|
||||
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
|
||||
],
|
||||
"version": "==20.3"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:04a10558320eba9137d6a78ca6fc8f4a5801f1b971152938851dc4629d903579",
|
||||
"sha256:0f89ddc77cf421b8cd34ae852309501458942bf370831b4a9b406156b599a14e",
|
||||
"sha256:251e5618125ec12ac800265d7048f5857a8f8f1979db9ea3e11382e159d17f68",
|
||||
"sha256:291bad7097b06d648222b769bbfcd61e40d0abdfe10df686d20ede36eb8162b6",
|
||||
"sha256:2f0b52a08d175f10c8ea36685115681a484c55d24d0933f9fd911e4111c04144",
|
||||
"sha256:3713386d1e9e79cea1c5e6aaac042841d7eef838cc577a3ca153c8bedf570287",
|
||||
"sha256:433bbc2469a2351bea53666d97bb1eb30f0d56461735be02ea6b27654569f80f",
|
||||
"sha256:4510c6b33277970b1af83c987277f9a08ec2b02cc20ac0f9234e4026136bb137",
|
||||
"sha256:50a10b048f4dd81c092adad99fa5f7ba941edaf2f9590510109ac2a15e706695",
|
||||
"sha256:670e58d3643971f4afd79191abd21623761c2ebe61db1c2cb4797d817c4ba1a7",
|
||||
"sha256:6c1924ed7dbc6ad0636907693bbbdd3fdae1d73072963e71f5644b864bb10b4d",
|
||||
"sha256:721c04d3c77c38086f1f95d1cd8df87f2f9a505a780acf8575912b3206479da1",
|
||||
"sha256:8d5799243050c2833c2662b824dfb16aa98e408d2092805edea4300a408490e7",
|
||||
"sha256:90cd441a1638ae176eab4d8b6b94ab4ec24b212ed4c3fbee2a6e74672481d4f8",
|
||||
"sha256:a5dc9f28c0239ec2742d4273bd85b2aa84655be2564db7ad1eb8f64b1efcdc4c",
|
||||
"sha256:b2f3e8cc52ecd259b94ca880fea0d15f4ebc6da2cd3db515389bb878d800270f",
|
||||
"sha256:b7453750cf911785009423789d2e4e5393aae9cbb8b3f471dab854b85a26cb89",
|
||||
"sha256:b99b2607b6cd58396f363b448cbe71d3c35e28f03e442ab00806463439629c2c",
|
||||
"sha256:cd47793f7bc9285a88c2b5551d3f16a2ddd005789614a34c5f4a598c2a162383",
|
||||
"sha256:d6bf085f6f9ec6a1724c187083b37b58a8048f86036d42d21802ed5d1fae4853",
|
||||
"sha256:da737ab273f4d60ae552f82ad83f7cbd0e173ca30ca20b160f708c92742ee212",
|
||||
"sha256:eb84e7e5b07ff3725ab05977ac56d5eeb0c510795aeb48e8b691491be3c5745b"
|
||||
],
|
||||
"version": "==7.1.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||
],
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pydocstyle": {
|
||||
"hashes": [
|
||||
"sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
|
||||
"sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
|
||||
],
|
||||
"version": "==5.0.2"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
|
||||
"sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
||||
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
|
||||
],
|
||||
"version": "==2.6.1"
|
||||
},
|
||||
"pylama": {
|
||||
"hashes": [
|
||||
"sha256:9bae53ef9c1a431371d6a8dca406816a60d547147b60a4934721898f553b7d8f",
|
||||
"sha256:fd61c11872d6256b019ef1235be37b77c922ef37ac9797df6bd489996dddeb15"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.7.1"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"releases": {
|
||||
"hashes": [
|
||||
"sha256:555ae4c97a671a420281c1c782e9236be25157b449fdf20b4c4b293fe93db2f1",
|
||||
"sha256:cb3435ba372a6807433800fbe473760cfa781171513f670f3c4b76983ac80f18"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.3"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
||||
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
||||
],
|
||||
"version": "==2.23.0"
|
||||
},
|
||||
"semantic-version": {
|
||||
"hashes": [
|
||||
"sha256:2a4328680073e9b243667b201119772aefc5fc63ae32398d6afafff07c4f54c0",
|
||||
"sha256:2d06ab7372034bcb8b54f2205370f4aa0643c133b7e6dbd129c5200b83ab394b"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
|
||||
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
|
||||
],
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:6a099e6faffdc3ceba99ca8c2d09982d43022245e409249375edf111caf79ed3",
|
||||
"sha256:b63a0c879c4ff9a4dffcb05217fa55672ce07abdeb81e33c73303a563f8d8901"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
|
||||
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-blockdiag": {
|
||||
"hashes": [
|
||||
"sha256:51ce7cff8d25dfd4c8a753d5aa5491e6dbf280004719c49e8001e583ecda7d91",
|
||||
"sha256:91fd35b64f1f25db59d80b8a5196ed4ffadf57a81f63ee207e34d53ec36d8f97"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"sphinxcontrib-devhelp": {
|
||||
"hashes": [
|
||||
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
|
||||
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-htmlhelp": {
|
||||
"hashes": [
|
||||
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
|
||||
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
|
||||
],
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-jsmath": {
|
||||
"hashes": [
|
||||
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
||||
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"sphinxcontrib-qthelp": {
|
||||
"hashes": [
|
||||
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
|
||||
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
|
||||
],
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-serializinghtml": {
|
||||
"hashes": [
|
||||
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
|
||||
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
|
||||
],
|
||||
"version": "==1.1.4"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
|
||||
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
|
||||
],
|
||||
"version": "==0.3.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||
],
|
||||
"version": "==1.25.8"
|
||||
},
|
||||
"webcolors": {
|
||||
"hashes": [
|
||||
"sha256:76f360636957d1c976db7466bc71dcb713bb95ac8911944dffc55c01cb516de6",
|
||||
"sha256:b8cd5d865a25c51ff1218f0c90d0c0781fc64312a49b746b320cf50de1648f6e"
|
||||
],
|
||||
"version": "==1.11.1"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ Gnuviechadmin is based on Django_ and Celery_
|
|||
.. _Django: https://djangoproject.com/
|
||||
.. _Celery: http://www.celeryproject.com/
|
||||
|
||||
The project page for gnuviechadmin is at http://dev.gnuviech-server.de/gva. If
|
||||
you find some problem or have some feature suggestions you can post a new
|
||||
ticket in our issue tracker on the project page.
|
||||
The project page for gnuviechadmin is at
|
||||
http://git.dittberner.info/gnuviech/gva. If you find some problem or have some
|
||||
feature suggestions you can post a new ticket in our issue tracker on the
|
||||
project page.
|
||||
|
|
112
docker-compose.yml
Normal file
112
docker-compose.yml
Normal file
|
@ -0,0 +1,112 @@
|
|||
version: "3"
|
||||
services:
|
||||
db:
|
||||
image: gnuviech/pgsql:buster
|
||||
ports:
|
||||
- "15432:5432"
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "pg_data:/var/lib/postgresql/11/main"
|
||||
mq:
|
||||
image: gnuviech/mq:buster
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "mq_data:/var/lib/rabbitmq/mnesia"
|
||||
redis:
|
||||
image: gnuviech/redis:buster
|
||||
env_file: .env
|
||||
volumes:
|
||||
- "redis_data:/var/lib/redis"
|
||||
gva:
|
||||
image: gnuviech/gva:buster
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
- mq
|
||||
- redis
|
||||
env_file: .env
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: gnuviechadmin.settings
|
||||
GVA_DOMAIN_NAME: localhost
|
||||
GVA_SITE_NAME: localhost
|
||||
volumes:
|
||||
- "./docker/django_media:/srv/gva/media"
|
||||
- "./docker/django_static:/srv/gva/static"
|
||||
- ".:/srv/gva"
|
||||
web:
|
||||
image: gnuviech/gvaweb:buster
|
||||
build:
|
||||
context: ../gvaweb
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvaweb/.env
|
||||
volumes:
|
||||
- "../gvaweb:/srv/gvaweb"
|
||||
ldap:
|
||||
image: gnuviech/gvaldap:buster
|
||||
build:
|
||||
context: ../gvaldap
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvaldap/.env
|
||||
volumes:
|
||||
- "../gvaldap:/srv/gvaldap"
|
||||
file:
|
||||
image: gnuviech/gvafile:buster
|
||||
build:
|
||||
context: ../gvafile
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvafile/.env
|
||||
volumes:
|
||||
- "../gvafile:/srv/gvafile"
|
||||
pgsql:
|
||||
image: gnuviech/gvapgsql:buster
|
||||
build:
|
||||
context: ../gvapgsql
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvapgsql/.env
|
||||
volumes:
|
||||
- "../gvapgsql:/srv/gvapgsql"
|
||||
mysql:
|
||||
image: gnuviech/gvamysql:buster
|
||||
build:
|
||||
context: ../gvamysql
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvamysql/.env
|
||||
volumes:
|
||||
- "../gvamysql:/srv/gvamysql"
|
||||
volumes:
|
||||
django_media:
|
||||
django_static:
|
||||
pg_data:
|
||||
redis_data:
|
||||
mq_data:
|
0
docker/django_static/.empty
Normal file
0
docker/django_static/.empty
Normal file
|
@ -1,6 +1,33 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
* :release:`0.12.0 <2020-04-10>`
|
||||
* :support:`-` add architecture diagramm for documentation
|
||||
* :support:`-` drop environment specific settings
|
||||
* :support:`-` update to Python 3
|
||||
* :support:`-` use Pipenv for dependency management
|
||||
* :support:`-` switch result backend to Redis
|
||||
* :support:`-` use separate test vhost for celery queues
|
||||
* :support:`-` switch licensing to AGPLv3+
|
||||
* :support:`-` add a Vagrant setup to ease development
|
||||
* :support:`-` add Docker setup for local development
|
||||
* :feature:`-` let all celery tasks run asynchronously and move task processing
|
||||
to signal handlers
|
||||
* :feature:`-` add unit tests for all the code
|
||||
* :feature:`-` add proper configuration for coverage, flake8 and pep8
|
||||
* :feature:`-` update to Django 2.2.12
|
||||
* :support:`-` use gvacommon from separate repository
|
||||
* :support:`-` update documentation
|
||||
|
||||
* :release:`0.11.6 <2020-02-14>`
|
||||
* :support:`-` Update dependencies to versions that work with Debian Stretch
|
||||
|
||||
* :release:`0.11.5 <2018-12-26>`
|
||||
* :support:`-` Remove Xing support from settings and templates
|
||||
|
||||
* :release:`0.11.4 <2016-12-31>`
|
||||
* :bug:`-` fix wrong tag in password reset done template
|
||||
|
||||
* :release:`0.11.3 <2015-02-21>`
|
||||
* :bug:`-` fix handling of OpenSSH formatted keys with whitespace in comments
|
||||
* :bug:`-` the ssh key list does not show SSH keys of other users anymore
|
||||
|
|
|
@ -35,28 +35,4 @@ The project module :py:mod:`gnuviechadmin`
|
|||
-------------------------------------------
|
||||
|
||||
.. automodule:: gnuviechadmin.settings
|
||||
|
||||
|
||||
:py:mod:`base <gnuviechadmin.settings.base>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.base
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`local <gnuviechadmin.settings.local>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.local
|
||||
|
||||
|
||||
:py:mod:`production <gnuviechadmin.settings.production>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.production
|
||||
|
||||
|
||||
:py:mod:`test <gnuviechadmin.settings.test>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.test
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
.. automodule:: osusers.models
|
||||
:members:
|
||||
|
||||
:py:mod:`signals <osusers.signals>`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: osusers.signals
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`urls <osusers.urls>`
|
||||
-----------------------------
|
||||
|
|
|
@ -30,6 +30,12 @@
|
|||
.. automodule:: userdbs.models
|
||||
:members:
|
||||
|
||||
:py:mod:`signals <userdbs.signals>`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: userdbs.signals
|
||||
:members:
|
||||
|
||||
:py:mod:`templatetags <userdbs.templatetags>`
|
||||
---------------------------------------------
|
||||
|
||||
|
|
16
docs/conf.py
16
docs/conf.py
|
@ -22,6 +22,7 @@ import django
|
|||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath(os.path.join('..', 'gnuviechadmin')))
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'gnuviechadmin.settings'
|
||||
os.environ['GVA_SITE_ADMINMAIL'] = 'admin@gva.example.org'
|
||||
|
||||
django.setup()
|
||||
|
@ -33,11 +34,16 @@ django.setup()
|
|||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx']
|
||||
extensions = [
|
||||
'releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx',
|
||||
'sphinxcontrib.blockdiag']
|
||||
|
||||
# configuration for releases extension
|
||||
releases_issue_uri = 'https://dev.gnuviech-server.de/gva/ticket/%s'
|
||||
releases_release_uri = 'https://dev.gnuviech-server.de/gva/milestone/%s'
|
||||
releases_issue_uri = 'https://git.dittberner.info/gnuviech/gva/issues/%s'
|
||||
releases_release_uri = 'https://git.dittberner.info/gnuviech/gva/src/tag/%s'
|
||||
|
||||
# configuration for blockdiag extension
|
||||
blockdiag_fontpath = '/usr/share/fonts/truetype/dejavu/'
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
@ -53,7 +59,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'gnuviechadmin'
|
||||
copyright = u'2014, 2015 Jan Dittberner'
|
||||
copyright = u'2014-2020, Jan Dittberner'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
@ -104,7 +110,7 @@ pygments_style = 'sphinx'
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
|
1
docs/gnuviechadmin_architecture.drawio
Normal file
1
docs/gnuviechadmin_architecture.drawio
Normal file
|
@ -0,0 +1 @@
|
|||
<mxfile host="app.diagrams.net" modified="2020-04-05T12:20:31.231Z" agent="5.0 (X11)" etag="7DgPwm3LEHCQ2g0Mvyzq" version="12.9.7" type="device"><diagram id="NsmcGlYAI3yPeXriofTy" name="Page-1">7Vpbb6M6EP41eQziDnls2mbPSl2pPX3Y06fIAYd4FzA1Jk32159xMAnEtElbcqu2qlo8tmE833zj8aVnXSeLbwxlsx80xHHP1MNFz7rpmaZhm2ZP/OrhspT4ulUKIkZC2WgjeCR/sBTqUlqQEOeNhpzSmJOsKQxomuKAN2SIMfrSbDalcfOrGYqwIngMUKxKf5KQz+QoTG8j/weTaFZ92XAHZU2CqsZyJPkMhfSlJrJue9Y1o5SXT8niGsfCeJVdyn6jV2rXijGc8n06PD48osH3+6uh9fw8mt4/3ekPP/vyLXMUF3LA/+KQ5FJjvqzMgPKstO6ULDC8cDjjSQxFAx4zSlK+MrMz7Dk3IEExiVIQBKAaZiAgycrMwylNucTYMDfyG5JEoHhMJvA3yQOE4f81CmZ4vFJnfM9oWARcy+cR9JI6Y8bx4lVjGGsTg29immDOltBEdhh4tua5ZS/pmbZjaE4pedkgPXA00y6lsxrOvgQVSfeK1l/YICDGUoLwDkBMX7E9DsEjZTGlqbAjo0UaChxudCj9KpKsaoBYABLK+IxGNEXxHaWZxOkX5nwprY8KTpsoTkkcX9OYstVHrRBhfypelXNGf+NajRv4eDIVNZVDCx0CmpBAPpcjEGq/jQ6MkhYswG+ZQ1IesQjzXX6sos1wjDiZN/XoHjOFRNEcZVH+HCtY0oLHJAVzVtFK2CtE+WwNpnBrAvHnDk1wfE9zwgkVXJpQzmlSa3AlScYFwCrlatACUJn4frKIRJDW0EtuaRnNecTwGLQckzTnKA2w6gbmrWcNfZBDz5DAu6s66YgdUNFYx3pJRMfUPJWIrq6y0LUOhaj3l4U1c1iXwEKrjYXJ8rxZuNJvHE7WFBybpyGhfn4ktBVAzxjIswmnpm0305oKs5PFUuf9sfQDkVMNv+cZS51LiKVOWywFe2IFyp38emvl0M69Jj7SPxRCtlJ/m5oB4YwsNKH5OMdsvurZAclc3WuSTOWYZ6gcG3iH4ph9FI6dJ6PcS2CU28aoOETZZTIqJAzaULbslFb2VhLSQivXUWnl+9XCvXtiuX8XAjVzeJdANa+Nai94cplMA8U75ZivD3ZOXa7WwrLBoba8DHXy+vRMBcZgy/9EfxiKLD7J160KN4tGaVkv3WNGYGjC4l0TyN+TQPYpCWSo6V+O07BnujHoPJwweIp4GWUSJCqOkHvgBeEloJ4ji0+1qg2eotCAs+yl601H0HTrQnzB0E/qDGo4zQrRbdsZGM6LmKtHBwdxhhquVhNWy/gIrOc53e7tIW9GfLCR6/uNoC8V2NuD5LvvxUlPrQmdTnNQbNvF1ip83Ot8xekqhwvJvPI4mNYrKXykVqH4YDW3VudQO+fxnalBLCqGKPgdrZy55iHT1U9PHm5dVSmE3pZPVOdfM87FeeaVsKM5CsJU10hA0ykBmjANvEuMD3EY7kjIc/Ff5BIp5n2RSUDZMQQK36V0fAfSvu71DdPXsjTqIHHY3qZfl2uZg1EdX9UTh7Ww+9hkdZ85dEhfS9+Tv/4nA/wWPdeH703SW6an6Y7pWobj+QPb21ozlcOR7+ie0cbgyGmC4dq1PGGdGrTnCVA44Exv7OsI7iln+vVFhWMhBA5ZR0jTDfciUHJOipJxFJROYVfvpHZVT5e+il2tk9pVnaO/il3NU9q10rJ+dQtNJoT/eFAMvjOb3ZkPK1tYrRtdW6soXR/ejsyWrS30p2BYey5wgccRTiGki7XW6h4ZZrdzXF4n28D6qbzVGWxtKrcciBptpzXuwW4qfCCE11eYnWwwN9avHbLHHBwp6f1cVBoo7GlZYF6FCRGLra+5xjR3rjFxngP+BMX9FzwR4W7kAHdGRY5ZH4VhP4uLvB8wjDiGMtiqw+Wm3WStr5LWb1lr+odaalpquFU8oHVbX1x5rd8AscyR+OxrN0C2L4xUzVvj7aFvumxf112V5Xhbfa0thLdcl7E14UJ5N/F9y1Esr+VAo8VTvPd7ChQ317XLdfHm0rt1+z8=</diagram></mxfile>
|
BIN
docs/gnuviechadmin_architecture.png
Normal file
BIN
docs/gnuviechadmin_architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
3
docs/gnuviechadmin_architecture.svg
Normal file
3
docs/gnuviechadmin_architecture.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 59 KiB |
|
@ -9,27 +9,31 @@ Welcome to gnuviechadmin's documentation!
|
|||
|
||||
.. include:: ../README.rst
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
gnuviechadmin is licensed under the terms of the MIT license:
|
||||
|
||||
.. include:: ../LICENSE.txt
|
||||
:literal:
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 3
|
||||
|
||||
install
|
||||
deploy
|
||||
tests
|
||||
code
|
||||
ideas
|
||||
task_flows
|
||||
changelog
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
gnuviechadmin 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.
|
||||
|
||||
.. include:: ../COPYING
|
||||
:literal:
|
||||
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
|
|
@ -1,45 +1,22 @@
|
|||
Installation
|
||||
============
|
||||
.. index:: installation
|
||||
|
||||
You have several options in setting up your working environment. We recommend
|
||||
using virtualenv to separate the dependencies of your project from your
|
||||
system's python environment. If on Linux or Mac OS X, you can also use
|
||||
virtualenvwrapper to help manage multiple virtualenvs across different
|
||||
projects.
|
||||
=======
|
||||
Install
|
||||
=======
|
||||
|
||||
Virtualenv Only
|
||||
---------------
|
||||
Working Environment
|
||||
===================
|
||||
|
||||
First, make sure you are using virtualenv (http://www.virtualenv.org). Once
|
||||
that's installed, create your virtualenv::
|
||||
To get a running work environment use `pipenv`_.
|
||||
|
||||
$ virtualenv --distribute gnuviechadmin
|
||||
.. _pipenv: https://pipenv.kennethreitz.org/en/latest/
|
||||
|
||||
You will also need to ensure that the virtualenv has the project directory
|
||||
added to the path. Adding the project directory will allow `django-admin.py` to
|
||||
be able to change settings using the `--settings` flag.
|
||||
To get started install `pip` and `pipenv` and use `pipenv install --dev`:
|
||||
|
||||
Virtualenv with virtualenvwrapper
|
||||
------------------------------------
|
||||
.. code-block:: sh
|
||||
|
||||
In Linux and Mac OSX, you can install virtualenvwrapper
|
||||
(http://virtualenvwrapper.readthedocs.org/en/latest/), which will take care of
|
||||
managing your virtual environments and adding the project path to the
|
||||
`site-directory` for you::
|
||||
$ apt install python3-pip
|
||||
$ python3 -m pip install --user -U pipenv
|
||||
$ pipenv install --dev
|
||||
|
||||
$ mkdir gnuviechadmin
|
||||
$ mkvirtualenv -a gnuviechadmin gnuviechadmin-dev
|
||||
$ cd gnuviechadmin && add2virtualenv `pwd`
|
||||
|
||||
Installation of Dependencies
|
||||
=============================
|
||||
|
||||
Depending on where you are installing dependencies:
|
||||
|
||||
In development::
|
||||
|
||||
$ pip install -r requirements/local.txt
|
||||
|
||||
For production::
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
|
|
198
docs/pdns.local.gva_queries.conf
Normal file
198
docs/pdns.local.gva_queries.conf
Normal file
|
@ -0,0 +1,198 @@
|
|||
# Regular queries
|
||||
gpgsql-basic-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \
|
||||
FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND type='%s' AND name=E'%s'
|
||||
gpgsql-id-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \
|
||||
FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND type='%s' AND name=E'%s' AND domain_id=%d
|
||||
gpgsql-any-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \
|
||||
FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND name=E'%s'
|
||||
gpgsql-any-id-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \
|
||||
FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND name=E'%s' AND domain_id=%d
|
||||
gpgsql-list-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \
|
||||
FROM domains_dnsrecord \
|
||||
WHERE (disabled=false OR %d::bool) AND domain_id='%d' \
|
||||
ORDER BY name, type
|
||||
|
||||
# Master/slave queries
|
||||
gpgsql-master-zone-query=SELECT master \
|
||||
FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' AND type='SLAVE'
|
||||
gpgsql-info-zone-query=SELECT id, domain, master, last_check, notified_serial, type \
|
||||
FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s'
|
||||
gpgsql-info-all-slaves-query=SELECT id, domain, master, last_check, type \
|
||||
FROM domains_dnsdomain \
|
||||
WHERE type='SLAVE'
|
||||
gpgsql-supermaster-query=SELECT customer \
|
||||
FROM domains_dnssupermaster \
|
||||
WHERE ip='%s' AND nameserver=E'%s'
|
||||
gpgsql-insert-slave-query=INSERT INTO domains_dnsdomain \
|
||||
(type, domain, master, account) \
|
||||
VALUES ('SLAVE', E'%s', E'%s', E'%s')
|
||||
gpgsql-insert-record-query=INSERT INTO domains_dnsrecord \
|
||||
(content, ttl, prio, type, domain_id, disabled, name, auth) \
|
||||
VALUES (E'%s', %d, %d, '%s', %d, %d::bool, E'%s', '%d')
|
||||
gpgsql-update-serial-query=UPDATE domains_dnsdomain \
|
||||
SET notified_serial=%d \
|
||||
WHERE id=%d
|
||||
gpgsql-update-lastcheck-query=UPDATE domains_dnsdomain \
|
||||
SET last_check=%d \
|
||||
WHERE id=%d
|
||||
gpgsql-info-all-master-query=SELECT id, domain, master, last_check, notified_serial, type \
|
||||
FROM domains_dnsdomain \
|
||||
WHERE type='MASTER'
|
||||
gpgsql-delete-zone-query=DELETE FROM domains_dnsrecord \
|
||||
WHERE domain_id=%d
|
||||
|
||||
# Comment queries
|
||||
gpgsql-list-comments-query=SELECT domain_id, name, type, modified_at, customer, comment \
|
||||
FROM domains_dnscomment \
|
||||
WHERE domain_id=%d
|
||||
gpgsql-insert-comment-query=INSERT INTO domains_dnscomment \
|
||||
(domain_id, name, type, modified_at, customer, comment) \
|
||||
VALUES (%d, E'%s', E'%s', %d, E'%s', E'%s')
|
||||
gpgsql-delete-comment-rrset-query=DELETE FROM domains_dnscomment \
|
||||
WHERE domain_id=%d AND name=E'%s' AND type=E'%s'
|
||||
gpgsql-delete-comments-query=DELETE FROM domains_dnscomment \
|
||||
WHERE domain_id=%d
|
||||
|
||||
# Crypto key queries
|
||||
gpgsql-activate-domain-key-query=UPDATE domains_dnscryptokey \
|
||||
SET active=true \
|
||||
WHERE domain_id=( \
|
||||
SELECT id \
|
||||
FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' \
|
||||
) AND domains_dnscryptokey.id=%d
|
||||
gpgsql-add-domain-key-query=INSERT INTO domains_dnscryptokey \
|
||||
(domain_id, flags, active, content) \
|
||||
SELECT id, %d, (%d = 1), '%s' FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s'
|
||||
gpgsql-clear-domain-all-keys-query=DELETE FROM domains_dnscryptokey \
|
||||
WHERE domain_id=( \
|
||||
SELECT id FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' \
|
||||
)
|
||||
gpgsql-deactivate-domain-key-query=UPDATE domains_dnscryptokey \
|
||||
SET active=false \
|
||||
WHERE domain_id=( \
|
||||
SELECT id FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' \
|
||||
) AND domains_dnscryptokey.id=%d
|
||||
gpgsql-list-domain-keys-query=SELECT domains_dnscryptokey.id, flags, CASE WHEN active THEN 1 ELSE 0 END AS active, content \
|
||||
FROM domains_dnsdomain, domains_cryptokey \
|
||||
WHERE domains_dnscryptokey.domain_id=domains_dnsdomain.id AND domain=E'%s'
|
||||
gpgsql-remove-domain-key-query=DELETE FROM domains_dnscryptokey \
|
||||
WHERE domain_id=( \
|
||||
SELECT id FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' \
|
||||
) AND domains_dnscryptokey.id=%d
|
||||
|
||||
# TSIG key queries
|
||||
gpgsql-delete-tsig-key-query=DELETE FROM domains_dnstsigkey \
|
||||
WHERE name='%s'
|
||||
gpgsql-get-tsig-key-query=SELECT algorithm, secret \
|
||||
FROM domains_dnstsigkey \
|
||||
WHERE name=E'%s'
|
||||
gpgsql-get-tsig-keys-query=SELECT name, algorithm, secret \
|
||||
FROM domains_dnstsigkey
|
||||
gpgsql-set-tsig-key-query=INSERT INTO domains_dnstsigkey \
|
||||
(name, algorithm, secret) \
|
||||
VALUES ('%s', '%s', '%s')
|
||||
|
||||
# Metadata queries
|
||||
gpgsql-clear-domain-all-metadata-query=DELETE FROM domains_dnsdomainmetadata \
|
||||
WHERE domain_id=( \
|
||||
SELECT id FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' \
|
||||
)
|
||||
gpgsql-clear-domain-metadata-query=DELETE FROM domains_dnsdomainmetadata \
|
||||
WHERE domain_id=( \
|
||||
SELECT id FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s' \
|
||||
) AND domains_dnsdomainmetadata.kind=E'%s'
|
||||
gpgsql-get-all-domain-metadata-query=SELECT kind, content \
|
||||
FROM domains_dnsdomain, domains_dnsdomainmetadata \
|
||||
WHERE domains_dnsdomainmetadata.domain_id=domains_dnsdomain.id AND domain=E'%s'
|
||||
gpgsql-get-domain-metadata-query=SELECT content \
|
||||
FROM domains_dnsdomain, domains_dnsdomainmetadata \
|
||||
WHERE domains_dnsdomainmetadata.domain_id=domains_dnsdomain.id AND domain=E'%s' AND domains_dnsdomainmetadata.kind=E'%s'
|
||||
gpgsql-set-domain-metadata-query=INSERT INTO domains_dnsdomainmetadata \
|
||||
(domain_id, kind, content) \
|
||||
SELECT id, '%s', '%s' FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s'
|
||||
|
||||
# Record queries
|
||||
gpgsql-delete-empty-non-terminal-query=DELETE FROM domains_dnsrecord \
|
||||
WHERE domain_id='%d' AND name='%s' AND type IS NULL
|
||||
gpgsql-delete-names-query=DELETE FROM domains_dnsrecord \
|
||||
WHERE domain_id=%d AND name=E'%s'
|
||||
gpgsql-delete-rrset-query=DELETE FROM domains_dnsrecord \
|
||||
WHERE domain_id=%d AND name=E'%s' AND type=E'%s'
|
||||
gpgsql-get-order-after-query=SELECT ordername FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND ordername ~>~ E'%s' AND domain_id=%d AND ordername IS NOT NULL \
|
||||
ORDER BY 1 USING ~<~ LIMIT 1
|
||||
gpgsql-get-order-before-query=SELECT ordername, name FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND ordername ~<=~ E'%s' AND domain_id=%d AND ordername IS NOT NULL \
|
||||
ORDER BY 1 USING ~>~ LIMIT 1
|
||||
gpgsql-get-order-first-query=SELECT ordername, name FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND domain_id=%d AND ordername IS NOT NULL \
|
||||
ORDER BY 1 USING ~<~ LIMIT 1
|
||||
gpgsql-get-order-last-query=SELECT ordername, name FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND ordername != '' AND domain_id=%d AND ordername IS NOT NULL \
|
||||
ORDER BY 1 USING ~>~ LIMIT 1
|
||||
gpgsql-insert-empty-non-terminal-query=INSERT INTO domains_dnsrecord \
|
||||
(domain_id, name, type, disabled, auth) \
|
||||
VALUES ('%d', '%s', null, false, true)
|
||||
gpgsql-insert-ent-order-query=INSERT INTO domains_dnsrecord \
|
||||
(type, domain_id, disabled, name, ordername, auth) \
|
||||
VALUES (null, '%d', false, E'%s', E'%s', '%d')
|
||||
gpgsql-insert-ent-query=INSERT INTO domains_dnsrecord \
|
||||
(type, domain_id, disabled, name, auth) \
|
||||
VALUES (null, '%d', false, E'%s', '%d')
|
||||
gpgsql-insert-record-order-query=INSERT INTO domains_dnsrecord \
|
||||
(content, ttl, prio, type, domain_id, disabled, name, ordername, auth) \
|
||||
VALUES (E'%s', %d, %d, '%s', %d, %d::bool, E'%s', E'%s', '%d')
|
||||
gpgsql-list-subzone-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \
|
||||
FROM domains_dnsrecord \
|
||||
WHERE disabled=false AND (name=E'%s' OR name like E'%s') AND domain_id='%d'
|
||||
gpgsql-nullify-ordername-and-auth-query=UPDATE domains_dnsrecord \
|
||||
SET ordername=NULL, auth=false \
|
||||
WHERE name=E'%s' AND type=E'%s' AND domain_id='%d' AND disabled=false
|
||||
gpgsql-nullify-ordername-and-update-auth-query=UPDATE domains_dnsrecord \
|
||||
SET ordername=NULL, auth=%d::bool \
|
||||
WHERE domain_id='%d' AND name='%s' AND disabled=false
|
||||
gpgsql-remove-empty-non-terminals-from-zone-query=DELETE FROM domains_dnsrecord \
|
||||
WHERE domain_id='%d' AND type IS NULL
|
||||
gpgsql-set-auth-on-ds-record-query=UPDATE domains_dnsrecord \
|
||||
SET auth=true \
|
||||
WHERE domain_id='%d' AND name='%s' AND type='DS' AND disabled=false
|
||||
gpgsql-set-order-and-auth-query=UPDATE domains_dnsrecord \
|
||||
SET ordername=E'%s', auth=%d::bool \
|
||||
WHERE name=E'%s' AND domain_id='%d' AND disabled=false
|
||||
gpgsql-zone-lastchange-query=SELECT MAX(change_date) FROM domains_dnsrecord \
|
||||
WHERE domain_id=%d
|
||||
|
||||
# Domain queries
|
||||
gpgsql-delete-domain-query=DELETE FROM domains_dnsdomain \
|
||||
WHERE domain=E'%s'
|
||||
gpgsql-insert-zone-query=INSERT INTO domains_dnsdomain \
|
||||
(type, domain) \
|
||||
VALUES ('NATIVE', E'%s')
|
||||
gpgsql-update-kind-query=UPDATE domains_dnsdomain \
|
||||
SET type='%s' \
|
||||
WHERE domain='%s'
|
||||
gpgsql-update-master-query=UPDATE domains_dnsdomain \
|
||||
SET master='%s' \
|
||||
WHERE domain='%s'
|
||||
|
||||
# Mixed queries
|
||||
gpgsql-get-all-domains-query=SELECT domains_dnsdomain.id, domains_dnsdomain.domain, domains_dnsrecord.content, \
|
||||
domains_dnsdomain.type, domains_dnsdomain.master, domains_dnsdomain.notified_serial, domains_dnsdomain.last_check \
|
||||
FROM domains_dnsdomain \
|
||||
LEFT JOIN domains_dnsrecord \
|
||||
ON domains_dnsrecord.domain_id=domains_dnsdomain.id AND domains_dnsrecord.type='SOA' AND domains_dnsrecord.name=domains_dnsdomain.domain \
|
||||
WHERE domains_dnsrecord.disabled=false OR %d::bool
|
35
docs/task_flows.rst
Normal file
35
docs/task_flows.rst
Normal file
|
@ -0,0 +1,35 @@
|
|||
**********
|
||||
Task Flows
|
||||
**********
|
||||
|
||||
gva uses Celery tasks to trigger actions on several servers, this chapter lists
|
||||
the code parts that start tasks. See the code documentation for details on the
|
||||
information flow.
|
||||
|
||||
:py:mod:`osusers.admin`
|
||||
=======================
|
||||
|
||||
* :py:meth:`osusers.admin.SshPublicKeyAdmin.perform_delete_selected`
|
||||
|
||||
|
||||
:py:mod:`osusers.signals`
|
||||
=========================
|
||||
|
||||
* :py:func:`osusers.signals.handle_group_created`
|
||||
* :py:func:`osusers.signals.handle_group_deleted`
|
||||
* :py:func:`osusers.signals.handle_ssh_keys_changed`
|
||||
* :py:func:`osusers.signals.handle_user_added_to_group`
|
||||
* :py:func:`osusers.signals.handle_user_created`
|
||||
* :py:func:`osusers.signals.handle_user_deleted`
|
||||
* :py:func:`osusers.signals.handle_user_password_set`
|
||||
* :py:func:`osusers.signals.handle_user_removed_from_group`
|
||||
|
||||
|
||||
:py:mod:`userdbs.signals`
|
||||
=========================
|
||||
|
||||
* :py:func:`userdbs.signals.handle_dbuser_created`
|
||||
* :py:func:`userdbs.signals.handle_dbuser_deleted`
|
||||
* :py:func:`userdbs.signals.handle_dbuser_deleted`
|
||||
* :py:func:`userdbs.signals.handle_dbuser_password_set`
|
||||
* :py:func:`userdbs.signals.handle_userdb_created`
|
227
fixtures/default_hostingpackages.json
Normal file
227
fixtures/default_hostingpackages.json
Normal file
|
@ -0,0 +1,227 @@
|
|||
[
|
||||
{
|
||||
"fields": {
|
||||
"description": "",
|
||||
"diskspace": 256,
|
||||
"created": "2015-01-18T10:50:38.392Z",
|
||||
"modified": "2015-01-18T10:50:38.409Z",
|
||||
"diskspace_unit": 0,
|
||||
"mailboxcount": 10,
|
||||
"name": "Basispaket I"
|
||||
},
|
||||
"model": "hostingpackages.hostingpackagetemplate",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"description": "",
|
||||
"diskspace": 512,
|
||||
"created": "2015-01-18T10:50:55.413Z",
|
||||
"modified": "2015-01-18T10:50:55.425Z",
|
||||
"diskspace_unit": 0,
|
||||
"mailboxcount": 25,
|
||||
"name": "Basispaket II"
|
||||
},
|
||||
"model": "hostingpackages.hostingpackagetemplate",
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"description": "",
|
||||
"diskspace": 1,
|
||||
"created": "2015-01-18T10:51:13.988Z",
|
||||
"modified": "2015-01-18T10:51:13.999Z",
|
||||
"diskspace_unit": 1,
|
||||
"mailboxcount": 50,
|
||||
"name": "Basispaket III"
|
||||
},
|
||||
"model": "hostingpackages.hostingpackagetemplate",
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"description": "Hostingpaket, dass ausschlie\u00dflich Mailweiterleitungen und Web-Redirects enth\u00e4lt",
|
||||
"diskspace": 0,
|
||||
"created": "2015-01-28T20:45:20.598Z",
|
||||
"modified": "2015-01-28T20:45:20.616Z",
|
||||
"diskspace_unit": 0,
|
||||
"mailboxcount": 0,
|
||||
"name": "Weiterleitung an externe Services"
|
||||
},
|
||||
"model": "hostingpackages.hostingpackagetemplate",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"diskspace_unit": 0,
|
||||
"diskspace": 256
|
||||
},
|
||||
"model": "hostingpackages.diskspaceoption",
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"diskspace_unit": 0,
|
||||
"diskspace": 512
|
||||
},
|
||||
"model": "hostingpackages.diskspaceoption",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"diskspace_unit": 1,
|
||||
"diskspace": 1
|
||||
},
|
||||
"model": "hostingpackages.diskspaceoption",
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"diskspace_unit": 1,
|
||||
"diskspace": 2
|
||||
},
|
||||
"model": "hostingpackages.diskspaceoption",
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"diskspace_unit": 1,
|
||||
"diskspace": 5
|
||||
},
|
||||
"model": "hostingpackages.diskspaceoption",
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"diskspace_unit": 1,
|
||||
"diskspace": 10
|
||||
},
|
||||
"model": "hostingpackages.diskspaceoption",
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"db_type": 0,
|
||||
"number": 1
|
||||
},
|
||||
"model": "hostingpackages.userdatabaseoption",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"db_type": 1,
|
||||
"number": 1
|
||||
},
|
||||
"model": "hostingpackages.userdatabaseoption",
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"number": 10
|
||||
},
|
||||
"model": "hostingpackages.mailboxoption",
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"number": 25
|
||||
},
|
||||
"model": "hostingpackages.mailboxoption",
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"number": 50
|
||||
},
|
||||
"model": "hostingpackages.mailboxoption",
|
||||
"pk": 11
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:51:38.298Z",
|
||||
"created": "2015-01-18T10:51:38.286Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:51:41.804Z",
|
||||
"created": "2015-01-18T10:51:41.792Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:03.193Z",
|
||||
"created": "2015-01-18T10:52:03.181Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:08.430Z",
|
||||
"created": "2015-01-18T10:52:08.418Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:14.153Z",
|
||||
"created": "2015-01-18T10:52:14.134Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:19.151Z",
|
||||
"created": "2015-01-18T10:52:19.138Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:24.461Z",
|
||||
"created": "2015-01-18T10:52:24.448Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:30.821Z",
|
||||
"created": "2015-01-18T10:52:30.807Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:53.657Z",
|
||||
"created": "2015-01-18T10:52:53.646Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:52:56.079Z",
|
||||
"created": "2015-01-18T10:52:56.064Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"modified": "2015-01-18T10:53:01.634Z",
|
||||
"created": "2015-01-18T10:53:01.622Z"
|
||||
},
|
||||
"model": "hostingpackages.hostingoption",
|
||||
"pk": 11
|
||||
}
|
||||
]
|
46
fixtures/default_osuser_groups.json
Normal file
46
fixtures/default_osuser_groups.json
Normal file
|
@ -0,0 +1,46 @@
|
|||
[
|
||||
{
|
||||
"fields": {
|
||||
"created": "2014-06-03T10:13:59.796Z",
|
||||
"descr": "SFTP users",
|
||||
"groupname": "sftponly",
|
||||
"modified": "2014-06-03T10:13:59.804Z",
|
||||
"passwd": ""
|
||||
},
|
||||
"model": "osusers.group",
|
||||
"pk": 2000
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"created": "2014-06-03T10:14:14.353Z",
|
||||
"descr": "",
|
||||
"groupname": "wwwusers",
|
||||
"modified": "2014-06-03T10:14:14.360Z",
|
||||
"passwd": ""
|
||||
},
|
||||
"model": "osusers.group",
|
||||
"pk": 2001
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"created": "2014-06-03T10:14:31.853Z",
|
||||
"descr": "",
|
||||
"groupname": "webserver",
|
||||
"modified": "2014-06-03T10:14:31.860Z",
|
||||
"passwd": ""
|
||||
},
|
||||
"model": "osusers.group",
|
||||
"pk": 2002
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"created": "2016-06-18T10:32:26.490Z",
|
||||
"descr": "Group for git only access",
|
||||
"groupname": "gitonly",
|
||||
"modified": "2016-06-18T10:32:26.498Z",
|
||||
"passwd": ""
|
||||
},
|
||||
"model": "osusers.group",
|
||||
"pk": 2003
|
||||
}
|
||||
]
|
|
@ -1,5 +0,0 @@
|
|||
[run]
|
||||
source = gnuviechadmin,contact_form,dashboard,domains,gvawebcore,managemails,osusers,taskresults,userdbs
|
||||
|
||||
[report]
|
||||
omit = */migrations/*,*/tests/*.py,*/tests.py,gnuviechadmin/settings/local.py,gnuviechadmin/settings/production.py
|
|
@ -7,13 +7,13 @@ from __future__ import absolute_import, unicode_literals
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import RequestContext
|
||||
from django.template import loader
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.contrib.sites.models import RequestSite
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites.requests import RequestSite
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
|
@ -52,11 +52,16 @@ class ContactForm(forms.Form):
|
|||
self.request, dict(self.cleaned_data, site=site))
|
||||
|
||||
def message(self):
|
||||
return loader.render_to_string(self.template_name, self.get_context())
|
||||
context = self.get_context()
|
||||
template_context = context.flatten()
|
||||
template_context.update({
|
||||
'remote_ip': context.request.META['REMOTE_ADDR']
|
||||
})
|
||||
return loader.render_to_string(self.template_name, template_context)
|
||||
|
||||
def subject(self):
|
||||
subject = loader.render_to_string(
|
||||
self.subject_template_name, self.get_context())
|
||||
context = self.get_context().flatten()
|
||||
subject = loader.render_to_string(self.subject_template_name, context)
|
||||
return ''.join(subject.splitlines())
|
||||
|
||||
def save(self, fail_silently=False):
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: contact_form\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-02-01 19:02+0100\n"
|
||||
"POT-Creation-Date: 2016-01-29 11:04+0100\n"
|
||||
"PO-Revision-Date: 2015-02-01 19:03+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
|
@ -19,18 +19,18 @@ msgstr ""
|
|||
"X-Generator: Poedit 1.6.10\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: forms.py:27
|
||||
#: contact_form/forms.py:27
|
||||
msgid "Your name"
|
||||
msgstr "Ihr Name"
|
||||
|
||||
#: forms.py:28
|
||||
#: contact_form/forms.py:28
|
||||
msgid "Your email address"
|
||||
msgstr "Ihre E-Mailadresse"
|
||||
|
||||
#: forms.py:29
|
||||
#: contact_form/forms.py:29
|
||||
msgid "Your message"
|
||||
msgstr "Ihre Nachricht"
|
||||
|
||||
#: forms.py:41
|
||||
#: contact_form/forms.py:41
|
||||
msgid "Send message"
|
||||
msgstr "Nachricht senden"
|
||||
|
|
4
gnuviechadmin/contact_form/tests/__init__.py
Normal file
4
gnuviechadmin/contact_form/tests/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Tests for the :py:mod:`contact_form` app.
|
||||
|
||||
"""
|
83
gnuviechadmin/contact_form/tests/test_forms.py
Normal file
83
gnuviechadmin/contact_form/tests/test_forms.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
"""
|
||||
Tests for :py:mod:`contact_form.forms`.
|
||||
|
||||
"""
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from contact_form.forms import ContactForm
|
||||
|
||||
TEST_DATA = {"name": "Test User", "email": "test@example.org", "body": "Test message"}
|
||||
|
||||
|
||||
class ContactFormTest(TestCase):
|
||||
def test_constructor_needs_request(self):
|
||||
with self.assertRaises(KeyError):
|
||||
ContactForm()
|
||||
|
||||
def test_constructor(self):
|
||||
request = MagicMock()
|
||||
form = ContactForm(request=request)
|
||||
self.assertTrue(hasattr(form, "request"))
|
||||
self.assertEqual(form.request, request)
|
||||
self.assertTrue(hasattr(form, "helper"))
|
||||
self.assertEqual(form.helper.form_action, reverse("contact_form"))
|
||||
self.assertEqual(len(form.helper.inputs), 1)
|
||||
self.assertEqual(form.helper.inputs[0].name, "submit")
|
||||
|
||||
def test_constructor_fields(self):
|
||||
request = MagicMock()
|
||||
form = ContactForm(request=request)
|
||||
self.assertEqual(len(form.fields), 3)
|
||||
self.assertIn("email", form.fields)
|
||||
self.assertIn("name", form.fields)
|
||||
self.assertIn("body", form.fields)
|
||||
self.assertEqual(len(form.data), 0)
|
||||
|
||||
def test_get_context_invalid(self):
|
||||
request = MagicMock()
|
||||
form = ContactForm(request=request)
|
||||
with self.assertRaisesMessage(
|
||||
ValueError, "Cannot generate context from invalid contact form"
|
||||
):
|
||||
form.get_context()
|
||||
|
||||
def test_get_context_valid_site_installed(self):
|
||||
request = MagicMock()
|
||||
form = ContactForm(request=request, data=TEST_DATA)
|
||||
context = form.get_context()
|
||||
self.assertIn("site", context)
|
||||
self.assertIn("name", context)
|
||||
self.assertIn("email", context)
|
||||
self.assertIn("body", context)
|
||||
|
||||
def test_get_context_valid_site_not_installed(self):
|
||||
request = MagicMock()
|
||||
form = ContactForm(request=request, data=TEST_DATA)
|
||||
with patch("contact_form.forms.Site") as sitemock:
|
||||
sitemock._meta.installed = False
|
||||
context = form.get_context()
|
||||
self.assertIn("site", context)
|
||||
self.assertIn("name", context)
|
||||
self.assertIn("email", context)
|
||||
self.assertIn("body", context)
|
||||
|
||||
def test_message(self):
|
||||
request = Mock()
|
||||
request.META = {"REMOTE_ADDR": "127.0.0.1"}
|
||||
form = ContactForm(request=request, data=TEST_DATA)
|
||||
message = form.message()
|
||||
self.assertIn(TEST_DATA["name"], message)
|
||||
self.assertIn(TEST_DATA["email"], message)
|
||||
self.assertIn(TEST_DATA["body"], message)
|
||||
self.assertIn("127.0.0.1", message)
|
||||
|
||||
def test_subject(self):
|
||||
request = Mock()
|
||||
form = ContactForm(request=request, data=TEST_DATA)
|
||||
subject = form.subject()
|
||||
self.assertIn(Site.objects.get_current().name, subject)
|
||||
self.assertIn(TEST_DATA["name"], subject)
|
123
gnuviechadmin/contact_form/tests/test_views.py
Normal file
123
gnuviechadmin/contact_form/tests/test_views.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
"""
|
||||
Tests for :py:mod:`contact_form.views`.
|
||||
|
||||
"""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import mail
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
TEST_USER = "test"
|
||||
TEST_PASSWORD = "secret"
|
||||
TEST_EMAIL = "test@example.org"
|
||||
TEST_NAME = "Example Tester".split()
|
||||
TEST_MESSAGE = """
|
||||
This is a really unimportant test message.
|
||||
"""
|
||||
|
||||
|
||||
class ContactFormViewTest(TestCase):
|
||||
def _setup_user(self, **kwargs):
|
||||
return User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD, **kwargs
|
||||
)
|
||||
|
||||
def test_get_contact_form_template(self):
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
self.assertTemplateUsed(response, "contact_form/contact_form.html")
|
||||
|
||||
def test_get_contact_form_anonymous_status(self):
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_contact_form_anonymous_has_empty_form(self):
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
self.assertIn("form", response.context)
|
||||
form = response.context["form"]
|
||||
self.assertEqual(len(form.initial), 0)
|
||||
|
||||
def test_get_contact_form_fields_anonymous(self):
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
for name in ("name", "email", "body"):
|
||||
self.assertIn(name, response.context["form"].fields)
|
||||
|
||||
def test_post_empty_form_template(self):
|
||||
response = self.client.post(reverse("contact_form"), {})
|
||||
self.assertTemplateUsed(response, "contact_form/contact_form.html")
|
||||
|
||||
def test_post_empty_form_status(self):
|
||||
response = self.client.post(reverse("contact_form"), {})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_empty_form_validation_errors(self):
|
||||
response = self.client.post(reverse("contact_form"), {})
|
||||
self.assertIn("form", response.context)
|
||||
form = response.context["form"]
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEqual(len(form.errors), 3)
|
||||
|
||||
def test_post_empty_form_no_mail(self):
|
||||
self.client.post(reverse("contact_form"), {})
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
def test_get_contact_form_logged_in_no_fullname_initial(self):
|
||||
self._setup_user()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
self.assertIn("form", response.context)
|
||||
form = response.context["form"]
|
||||
self.assertEqual(form.initial, {"name": TEST_USER, "email": TEST_EMAIL})
|
||||
|
||||
def test_get_contact_form_logged_in_fullname_initial(self):
|
||||
self._setup_user(first_name=TEST_NAME[0], last_name=TEST_NAME[1])
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
self.assertIn("form", response.context)
|
||||
form = response.context["form"]
|
||||
self.assertEqual(
|
||||
form.initial, {"name": " ".join(TEST_NAME), "email": TEST_EMAIL}
|
||||
)
|
||||
|
||||
def test_post_filled_form_anonymous_redirects(self):
|
||||
response = self.client.post(
|
||||
reverse("contact_form"),
|
||||
{"name": TEST_USER, "email": TEST_EMAIL, "body": TEST_MESSAGE},
|
||||
)
|
||||
self.assertRedirects(response, reverse("contact_success"))
|
||||
|
||||
def test_post_filled_form_anonymous_mail(self):
|
||||
self.client.post(
|
||||
reverse("contact_form"),
|
||||
{"name": TEST_USER, "email": TEST_EMAIL, "body": TEST_MESSAGE},
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
def test_post_filled_form_logged_in_redirects(self):
|
||||
self._setup_user(first_name=TEST_NAME[0], last_name=TEST_NAME[1])
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse("contact_form"),
|
||||
{"name": " ".join(TEST_NAME), "email": TEST_EMAIL, "body": TEST_MESSAGE},
|
||||
)
|
||||
self.assertRedirects(response, reverse("contact_success"))
|
||||
|
||||
def test_post_filled_form_logged_in_mail(self):
|
||||
self._setup_user(first_name=TEST_NAME[0], last_name=TEST_NAME[1])
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
self.client.post(
|
||||
reverse("contact_form"),
|
||||
{"name": " ".join(TEST_NAME), "email": TEST_EMAIL, "body": TEST_MESSAGE},
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
|
||||
class ContactSuccessViewTest(TestCase):
|
||||
def test_get_template(self):
|
||||
response = self.client.get(reverse("contact_success"))
|
||||
self.assertTemplateUsed(response, "contact_form/contact_success.html")
|
||||
|
||||
def test_get_status(self):
|
||||
response = self.client.get(reverse("contact_success"))
|
||||
self.assertEqual(response.status_code, 200)
|
|
@ -4,7 +4,7 @@ URL patterns for the contact_form views.
|
|||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import (
|
||||
ContactFormView,
|
||||
|
@ -12,8 +12,7 @@ from .views import (
|
|||
)
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(r'^$', ContactFormView.as_view(), name='contact_form'),
|
||||
url(r'^success/$', ContactSuccessView.as_view(), name='contact_success'),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -5,7 +5,7 @@ This module defines the views of the contact_form app.
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import (
|
||||
FormView,
|
||||
TemplateView,
|
||||
|
@ -31,7 +31,7 @@ class ContactFormView(FormView):
|
|||
def get_initial(self):
|
||||
initial = super(ContactFormView, self).get_initial()
|
||||
currentuser = self.request.user
|
||||
if currentuser.is_authenticated():
|
||||
if currentuser.is_authenticated:
|
||||
initial['name'] = (
|
||||
currentuser.get_full_name() or currentuser.username)
|
||||
initial['email'] = currentuser.email
|
||||
|
|
4
gnuviechadmin/dashboard/tests/__init__.py
Normal file
4
gnuviechadmin/dashboard/tests/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Tests for :py:mod:`dashboard`.
|
||||
|
||||
"""
|
67
gnuviechadmin/dashboard/tests/test_views.py
Normal file
67
gnuviechadmin/dashboard/tests/test_views.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
"""
|
||||
Tests for :py:mod:`dashboard.views`.
|
||||
|
||||
"""
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
TEST_USER = "test"
|
||||
TEST_PASSWORD = "secret"
|
||||
|
||||
|
||||
class IndexViewTest(TestCase):
|
||||
def test_index_view(self):
|
||||
response = self.client.get(reverse("dashboard"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "dashboard/index.html")
|
||||
|
||||
|
||||
class UserDashboardViewTest(TestCase):
|
||||
def _create_test_user(self):
|
||||
self.user = User.objects.create(username=TEST_USER)
|
||||
self.user.set_password(TEST_PASSWORD)
|
||||
self.user.save()
|
||||
|
||||
def test_user_dashboard_view_no_user(self):
|
||||
response = self.client.get(
|
||||
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_user_dashboard_view_anonymous(self):
|
||||
User.objects.create(username=TEST_USER)
|
||||
response = self.client.get(
|
||||
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_user_dashboard_view_logged_in_ok(self):
|
||||
self._create_test_user()
|
||||
self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD))
|
||||
response = self.client.get(
|
||||
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_user_dashboard_view_logged_in_template(self):
|
||||
self._create_test_user()
|
||||
self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD))
|
||||
response = self.client.get(
|
||||
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
|
||||
)
|
||||
self.assertTemplateUsed(response, "dashboard/user_dashboard.html")
|
||||
|
||||
def test_user_dashboard_view_logged_in_context_fresh(self):
|
||||
self._create_test_user()
|
||||
self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD))
|
||||
response = self.client.get(
|
||||
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
|
||||
)
|
||||
self.assertIn("dashboard_user", response.context)
|
||||
self.assertEqual(self.user, response.context["dashboard_user"])
|
||||
self.assertIn("hosting_packages", response.context)
|
||||
self.assertEqual(len(response.context["hosting_packages"]), 0)
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import (
|
||||
IndexView,
|
||||
|
@ -8,9 +8,8 @@ from .views import (
|
|||
)
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(r'^$', IndexView.as_view(), name='dashboard'),
|
||||
url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$',
|
||||
UserDashboardView.as_view(), name='customer_dashboard'),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -6,9 +6,23 @@ with the django admin site.
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import (
|
||||
MailDomain,
|
||||
DNSComment,
|
||||
DNSCryptoKey,
|
||||
DNSDomain,
|
||||
DNSDomainMetadata,
|
||||
DNSRecord,
|
||||
DNSSupermaster,
|
||||
DNSTSIGKey,
|
||||
HostingDomain,
|
||||
MailDomain,
|
||||
)
|
||||
|
||||
admin.site.register(MailDomain)
|
||||
admin.site.register(HostingDomain)
|
||||
admin.site.register(DNSComment)
|
||||
admin.site.register(DNSCryptoKey)
|
||||
admin.site.register(DNSDomain)
|
||||
admin.site.register(DNSDomainMetadata)
|
||||
admin.site.register(DNSRecord)
|
||||
admin.site.register(DNSSupermaster)
|
||||
admin.site.register(DNSTSIGKey)
|
||||
|
|
|
@ -7,7 +7,7 @@ from __future__ import absolute_import, unicode_literals
|
|||
import re
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
|
@ -21,7 +21,9 @@ from .models import HostingDomain
|
|||
|
||||
def relative_domain_validator(value):
|
||||
"""
|
||||
This validator ensures that the given value is a valid lowercase
|
||||
This validator ensures that the given value is a valid lowercase domain
|
||||
name.
|
||||
|
||||
"""
|
||||
if len(value) > 254:
|
||||
raise forms.ValidationError(
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gnuviechadmin domains\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"PO-Revision-Date: 2015-01-27 19:06+0100\n"
|
||||
"POT-Creation-Date: 2016-01-29 11:04+0100\n"
|
||||
"PO-Revision-Date: 2015-11-08 12:02+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\n"
|
||||
|
@ -16,57 +16,151 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
"X-Generator: Poedit 1.8.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: domains/apps.py:17
|
||||
msgid "Domains"
|
||||
msgstr "Domains"
|
||||
|
||||
#: domains/forms.py:28
|
||||
#: domains/forms.py:30 domains/tests/test_forms.py:24
|
||||
msgid "host name too long"
|
||||
msgstr "zu langer Hostname"
|
||||
|
||||
#: domains/forms.py:31
|
||||
#: domains/forms.py:33 domains/tests/test_forms.py:29
|
||||
#: domains/tests/test_forms.py:34 domains/tests/test_forms.py:39
|
||||
#: domains/tests/test_forms.py:44
|
||||
msgid "invalid domain name"
|
||||
msgstr "ungültiger Domainname"
|
||||
|
||||
#: domains/forms.py:54
|
||||
#: domains/forms.py:56
|
||||
msgid "Add Hosting Domain"
|
||||
msgstr "Hostingdomain hinzufügen"
|
||||
|
||||
#: domains/models.py:21
|
||||
#: domains/models.py:17
|
||||
msgid "Master"
|
||||
msgstr "Master"
|
||||
|
||||
#: domains/models.py:18
|
||||
msgid "Slave"
|
||||
msgstr "Slave"
|
||||
|
||||
#: domains/models.py:19
|
||||
msgid "Native"
|
||||
msgstr "Native"
|
||||
|
||||
#: domains/models.py:44
|
||||
msgid "HMAC MD5"
|
||||
msgstr "HMAC MD5"
|
||||
|
||||
#: domains/models.py:45
|
||||
msgid "HMAC SHA1"
|
||||
msgstr "HMAC SHA1"
|
||||
|
||||
#: domains/models.py:46
|
||||
msgid "HMAC SHA224"
|
||||
msgstr "HMAC SHA224"
|
||||
|
||||
#: domains/models.py:47
|
||||
msgid "HMAC SHA256"
|
||||
msgstr "HMAC SHA256"
|
||||
|
||||
#: domains/models.py:48
|
||||
msgid "HMAC SHA384"
|
||||
msgstr "HMAC SHA384"
|
||||
|
||||
#: domains/models.py:49
|
||||
msgid "HMAC SHA512"
|
||||
msgstr "HMAC SHA512"
|
||||
|
||||
#: domains/models.py:58
|
||||
msgid "domain name"
|
||||
msgstr "Domainname"
|
||||
|
||||
#: domains/models.py:23
|
||||
#: domains/models.py:60 domains/models.py:258 domains/models.py:308
|
||||
msgid "customer"
|
||||
msgstr "Kunde"
|
||||
|
||||
#: domains/models.py:42
|
||||
#: domains/models.py:76
|
||||
msgid "Mail domain"
|
||||
msgstr "E-Maildomain"
|
||||
|
||||
#: domains/models.py:43
|
||||
#: domains/models.py:77
|
||||
msgid "Mail domains"
|
||||
msgstr "E-Maildomains"
|
||||
|
||||
#: domains/models.py:87
|
||||
#: domains/models.py:121
|
||||
msgid "mail domain"
|
||||
msgstr "E-Maildomain"
|
||||
|
||||
#: domains/models.py:88
|
||||
#: domains/models.py:122
|
||||
msgid "assigned mail domain for this domain"
|
||||
msgstr "zugeordnete E-Maildomain für diese Domain"
|
||||
|
||||
#: domains/models.py:94
|
||||
#: domains/models.py:128
|
||||
msgid "Hosting domain"
|
||||
msgstr "Hostingdomain"
|
||||
|
||||
#: domains/models.py:95
|
||||
#: domains/models.py:129
|
||||
msgid "Hosting domains"
|
||||
msgstr "Hostingdomains"
|
||||
|
||||
#: domains/models.py:169
|
||||
msgid "DNS domain"
|
||||
msgstr "DNS-Domain"
|
||||
|
||||
#: domains/models.py:170
|
||||
msgid "DNS domains"
|
||||
msgstr "DNS-Domains"
|
||||
|
||||
#: domains/models.py:226
|
||||
msgid "DNS record"
|
||||
msgstr "DNS-Record"
|
||||
|
||||
#: domains/models.py:227
|
||||
msgid "DNS records"
|
||||
msgstr "DNS-Records"
|
||||
|
||||
#: domains/models.py:261
|
||||
msgid "DNS supermaster"
|
||||
msgstr "DNS-Supermaster"
|
||||
|
||||
#: domains/models.py:262
|
||||
msgid "DNS supermasters"
|
||||
msgstr "DNS-Supermasters"
|
||||
|
||||
#: domains/models.py:313
|
||||
msgid "DNS comment"
|
||||
msgstr "DNS-Kommentar"
|
||||
|
||||
#: domains/models.py:314
|
||||
msgid "DNS comments"
|
||||
msgstr "DNS-Kommentare"
|
||||
|
||||
#: domains/models.py:351
|
||||
msgid "DNS domain metadata item"
|
||||
msgstr "DNS-Domainmetadaten-Eintrag"
|
||||
|
||||
#: domains/models.py:352
|
||||
msgid "DNS domain metadata items"
|
||||
msgstr "DNS-Domainmetadaten-Einträge"
|
||||
|
||||
#: domains/models.py:385
|
||||
msgid "DNS crypto key"
|
||||
msgstr "DNS-Cryposchlüssel"
|
||||
|
||||
#: domains/models.py:386
|
||||
msgid "DNS crypto keys"
|
||||
msgstr "DNS-Cryptoschlüssel"
|
||||
|
||||
#: domains/models.py:420
|
||||
msgid "DNS TSIG key"
|
||||
msgstr "DNS-TSIG-Schlüssel"
|
||||
|
||||
#: domains/models.py:421
|
||||
msgid "DNS TSIG keys"
|
||||
msgstr "DNS-TSIG-Schlüssel"
|
||||
|
||||
#: domains/views.py:58
|
||||
#, python-brace-format
|
||||
msgid "Successfully created domain {domainname}"
|
||||
|
|
|
@ -8,7 +8,6 @@ import model_utils.fields
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('domains', '0001_initial'),
|
||||
|
@ -18,12 +17,31 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='HostingDomain',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('domain', models.CharField(unique=True, max_length=128, verbose_name='domain name')),
|
||||
('customer', models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('maildomain', models.OneToOneField(null=True, to='domains.MailDomain', blank=True, help_text='assigned mail domain for this domain', verbose_name='mail domain')),
|
||||
('id',
|
||||
models.AutoField(verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('created',
|
||||
model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified',
|
||||
model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('domain',
|
||||
models.CharField(
|
||||
unique=True, max_length=128, verbose_name='domain name')),
|
||||
('customer',
|
||||
models.ForeignKey(
|
||||
verbose_name='customer', blank=True,
|
||||
to=settings.AUTH_USER_MODEL, null=True,
|
||||
on_delete=models.CASCADE)),
|
||||
('maildomain',
|
||||
models.OneToOneField(
|
||||
null=True, to='domains.MailDomain', blank=True,
|
||||
help_text='assigned mail domain for this domain',
|
||||
verbose_name='mail domain',
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hosting domain',
|
||||
|
@ -34,13 +52,17 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='maildomain',
|
||||
name='customer',
|
||||
field=models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='customer', blank=True,
|
||||
to=settings.AUTH_USER_MODEL, null=True,
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='maildomain',
|
||||
name='domain',
|
||||
field=models.CharField(unique=True, max_length=128, verbose_name='domain name'),
|
||||
field=models.CharField(
|
||||
unique=True, max_length=128, verbose_name='domain name'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
199
gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py
Normal file
199
gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py
Normal file
|
@ -0,0 +1,199 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
import model_utils.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('domains', '0002_auto_20150124_1909'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DNSComment',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('commenttype',
|
||||
models.CharField(max_length=10, db_column='type')),
|
||||
('modified_at', models.IntegerField()),
|
||||
('comment', models.CharField(max_length=65535)),
|
||||
('customer', models.ForeignKey(
|
||||
verbose_name='customer',
|
||||
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
migrations.RunSQL(
|
||||
'''ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name
|
||||
CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DNSCryptoKey',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('flags', models.IntegerField()),
|
||||
('active', models.BooleanField(default=True)),
|
||||
('content', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DNSDomain',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('domain', models.CharField(
|
||||
unique=True, max_length=255, verbose_name='domain name')),
|
||||
('master',
|
||||
models.CharField(max_length=128, null=True, blank=True)),
|
||||
('last_check', models.IntegerField(null=True)),
|
||||
('domaintype', models.CharField(
|
||||
max_length=6, db_column='type',
|
||||
choices=[('MASTER', 'Master'),
|
||||
('SLAVE', 'Slave'),
|
||||
('NATIVE', 'Native')])),
|
||||
('notified_serial', models.IntegerField(null=True)),
|
||||
('customer', models.ForeignKey(
|
||||
verbose_name='customer', blank=True,
|
||||
to=settings.AUTH_USER_MODEL, null=True,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'DNS domain',
|
||||
'verbose_name_plural': 'DNS domains',
|
||||
},
|
||||
),
|
||||
migrations.RunSQL(
|
||||
'''ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name
|
||||
CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))'''
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DNSDomainMetadata',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('kind', models.CharField(max_length=32)),
|
||||
('content', models.TextField()),
|
||||
('domain', models.ForeignKey(
|
||||
to='domains.DNSDomain', on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DNSRecord',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(
|
||||
db_index=True, max_length=255, null=True, blank=True)),
|
||||
('recordtype', models.CharField(
|
||||
max_length=10, null=True, db_column='type', blank=True)),
|
||||
('content', models.CharField(
|
||||
max_length=65535, null=True, blank=True)),
|
||||
('ttl', models.IntegerField(null=True)),
|
||||
('prio', models.IntegerField(null=True)),
|
||||
('change_date', models.IntegerField(null=True)),
|
||||
('disabled', models.BooleanField(default=False)),
|
||||
('ordername', models.CharField(max_length=255)),
|
||||
('auth', models.BooleanField(default=True)),
|
||||
('domain', models.ForeignKey(
|
||||
to='domains.DNSDomain', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'DNS record',
|
||||
'verbose_name_plural': 'DNS records',
|
||||
},
|
||||
),
|
||||
migrations.RunSQL(
|
||||
'''ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name
|
||||
CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
|
||||
),
|
||||
migrations.RunSQL(
|
||||
'''CREATE INDEX recordorder ON domains_dnsrecord (domain_id,
|
||||
ordername text_pattern_ops)'''
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DNSSupermaster',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('ip', models.GenericIPAddressField()),
|
||||
('nameserver', models.CharField(max_length=255)),
|
||||
('customer', models.ForeignKey(
|
||||
verbose_name='customer', to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DNSTSIGKey',
|
||||
fields=[
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('algorithm', models.CharField(max_length=50)),
|
||||
('secret', models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.RunSQL(
|
||||
'''ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name
|
||||
CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hostingdomain',
|
||||
name='domain',
|
||||
field=models.CharField(
|
||||
unique=True, max_length=255, verbose_name='domain name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='maildomain',
|
||||
name='domain',
|
||||
field=models.CharField(
|
||||
unique=True, max_length=255, verbose_name='domain name'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dnscryptokey',
|
||||
name='domain',
|
||||
field=models.ForeignKey(
|
||||
to='domains.DNSDomain', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dnscomment',
|
||||
name='domain',
|
||||
field=models.ForeignKey(
|
||||
to='domains.DNSDomain', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dnssupermaster',
|
||||
unique_together=set([('ip', 'nameserver')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dnstsigkey',
|
||||
unique_together=set([('name', 'algorithm')]),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='dnsrecord',
|
||||
index_together=set([('name', 'recordtype')]),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='dnscomment',
|
||||
index_together={('name', 'commenttype'), ('domain', 'modified_at')},
|
||||
),
|
||||
]
|
44
gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py
Normal file
44
gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('domains', '0003_auto_20151105_2133'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='dnscomment',
|
||||
options={'verbose_name': 'DNS comment', 'verbose_name_plural': 'DNS comments'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='dnscryptokey',
|
||||
options={'verbose_name': 'DNS crypto key', 'verbose_name_plural': 'DNS crypto keys'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='dnsdomainmetadata',
|
||||
options={'verbose_name': 'DNS domain metadata item', 'verbose_name_plural': 'DNS domain metadata items'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='dnssupermaster',
|
||||
options={'verbose_name': 'DNS supermaster', 'verbose_name_plural': 'DNS supermasters'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='dnstsigkey',
|
||||
options={'verbose_name': 'DNS TSIG key', 'verbose_name_plural': 'DNS TSIG keys'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dnsdomainmetadata',
|
||||
name='kind',
|
||||
field=models.CharField(max_length=32, choices=[('ALLOW-DNSUPDATE-FROM', 'ALLOW-DNSUPDATE-FROM'), ('ALSO-NOTIFY', 'ALSO-NOTIFY'), ('AXFR-MASTER-TSIG', 'AXFR-MASTER-TSIG'), ('AXFR-SOURCE', 'AXFR-SOURCE'), ('FORWARD-DNSUPDATE', 'FORWARD-DNSUPDATE'), ('GSS-ACCEPTOR-PRINCIPAL', 'GSS-ACCEPTOR-PRINCIPAL'), ('GSS-ALLOW-AXFR-PRINCIPAL', 'GSS-ALLOW-AXFR-PRINCIPAL'), ('LUA-AXFR-SCRIPT', 'LUA-AXFR-SCRIPT'), ('NSEC3NARROW', 'NSEC3NARROW'), ('NSEC3PARAM', 'NSEC3PARAM'), ('PRESIGNED', 'PRESIGNED'), ('PUBLISH_CDNSKEY', 'PUBLISH_CDNSKEY'), ('PUBLISH_CDS', 'PUBLISH_CDS'), ('SOA-EDIT', 'SOA-EDIT'), ('SOA-EDIT-DNSUPDATE', 'SOA-EDIT-DNSUPDATE'), ('TSIG-ALLOW-AXFR', 'TSIG-ALLOW-AXFR'), ('TSIG-ALLOW-DNSUPDATE', 'TSIG-ALLOW-DNSUPDATE')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dnstsigkey',
|
||||
name='algorithm',
|
||||
field=models.CharField(max_length=50, choices=[('hmac-md5', 'HMAC MD5'), ('hmac-sha1', 'HMAC SHA1'), ('hmac-sha224', 'HMAC SHA224'), ('hmac-sha256', 'HMAC SHA256'), ('hmac-sha384', 'HMAC SHA384'), ('hmac-sha512', 'HMAC SHA512')]),
|
||||
),
|
||||
]
|
|
@ -10,25 +10,59 @@ from django.utils.encoding import python_2_unicode_compatible
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
from model_utils.models import TimeStampedModel
|
||||
from model_utils import Choices
|
||||
|
||||
|
||||
DNS_DOMAIN_TYPES = Choices(
|
||||
('MASTER', _('Master')),
|
||||
('SLAVE', _('Slave')),
|
||||
('NATIVE', _('Native')),
|
||||
)
|
||||
|
||||
# see https://doc.powerdns.com/md/authoritative/domainmetadata/
|
||||
DNS_DOMAIN_METADATA_KINDS = Choices(
|
||||
'ALLOW-DNSUPDATE-FROM',
|
||||
'ALSO-NOTIFY',
|
||||
'AXFR-MASTER-TSIG',
|
||||
'AXFR-SOURCE',
|
||||
'FORWARD-DNSUPDATE',
|
||||
'GSS-ACCEPTOR-PRINCIPAL',
|
||||
'GSS-ALLOW-AXFR-PRINCIPAL',
|
||||
'LUA-AXFR-SCRIPT',
|
||||
'NSEC3NARROW',
|
||||
'NSEC3PARAM',
|
||||
'PRESIGNED',
|
||||
'PUBLISH_CDNSKEY',
|
||||
'PUBLISH_CDS',
|
||||
'SOA-EDIT',
|
||||
'SOA-EDIT-DNSUPDATE',
|
||||
'TSIG-ALLOW-AXFR',
|
||||
'TSIG-ALLOW-DNSUPDATE',
|
||||
)
|
||||
|
||||
DNS_TSIG_KEY_ALGORITHMS = Choices(
|
||||
('hmac-md5', _('HMAC MD5')),
|
||||
('hmac-sha1', _('HMAC SHA1')),
|
||||
('hmac-sha224', _('HMAC SHA224')),
|
||||
('hmac-sha256', _('HMAC SHA256')),
|
||||
('hmac-sha384', _('HMAC SHA384')),
|
||||
('hmac-sha512', _('HMAC SHA512')),
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DomainBase(TimeStampedModel):
|
||||
"""
|
||||
This is the base model for domains.
|
||||
|
||||
"""
|
||||
domain = models.CharField(_('domain name'), max_length=128, unique=True)
|
||||
domain = models.CharField(_('domain name'), max_length=255, unique=True)
|
||||
customer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True,
|
||||
null=True)
|
||||
null=True, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class MailDomain(DomainBase):
|
||||
|
@ -85,7 +119,8 @@ class HostingDomain(DomainBase):
|
|||
"""
|
||||
maildomain = models.OneToOneField(
|
||||
MailDomain, verbose_name=_('mail domain'), blank=True, null=True,
|
||||
help_text=_('assigned mail domain for this domain')
|
||||
help_text=_('assigned mail domain for this domain'),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
objects = HostingDomainManager()
|
||||
|
@ -96,3 +131,301 @@ class HostingDomain(DomainBase):
|
|||
|
||||
def __str__(self):
|
||||
return self.domain
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSDomain(DomainBase):
|
||||
"""
|
||||
This model represents a DNS zone. The model is similar to the domain table
|
||||
in the PowerDNS schema specified in
|
||||
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE domains (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
master VARCHAR(128) DEFAULT NULL,
|
||||
last_check INT DEFAULT NULL,
|
||||
type VARCHAR(6) NOT NULL,
|
||||
notified_serial INT DEFAULT NULL,
|
||||
account VARCHAR(40) DEFAULT NULL,
|
||||
CONSTRAINT c_lowercase_name CHECK (
|
||||
((name)::TEXT = LOWER((name)::TEXT)))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX name_index ON domains(name);
|
||||
|
||||
"""
|
||||
# name is represented by domain
|
||||
master = models.CharField(max_length=128, blank=True, null=True)
|
||||
last_check = models.IntegerField(null=True)
|
||||
domaintype = models.CharField(
|
||||
max_length=6, choices=DNS_DOMAIN_TYPES, db_column='type')
|
||||
notified_serial = models.IntegerField(null=True)
|
||||
# account is represented by customer_id
|
||||
# check constraint is added via RunSQL in migration
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS domain')
|
||||
verbose_name_plural = _('DNS domains')
|
||||
|
||||
def __str__(self):
|
||||
return self.domain
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSRecord(models.Model):
|
||||
"""
|
||||
This model represents a DNS record. The model is similar to the record
|
||||
table in the PowerDNS schema specified in
|
||||
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE records (
|
||||
id SERIAL PRIMARY KEY,
|
||||
domain_id INT DEFAULT NULL,
|
||||
name VARCHAR(255) DEFAULT NULL,
|
||||
type VARCHAR(10) DEFAULT NULL,
|
||||
content VARCHAR(65535) DEFAULT NULL,
|
||||
ttl INT DEFAULT NULL,
|
||||
prio INT DEFAULT NULL,
|
||||
change_date INT DEFAULT NULL,
|
||||
disabled BOOL DEFAULT 'f',
|
||||
ordername VARCHAR(255),
|
||||
auth BOOL DEFAULT 't',
|
||||
CONSTRAINT domain_exists
|
||||
FOREIGN KEY(domain_id) REFERENCES domains(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT c_lowercase_name CHECK (
|
||||
((name)::TEXT = LOWER((name)::TEXT)))
|
||||
);
|
||||
|
||||
CREATE INDEX rec_name_index ON records(name);
|
||||
CREATE INDEX nametype_index ON records(name,type);
|
||||
CREATE INDEX domain_id ON records(domain_id);
|
||||
CREATE INDEX recordorder ON records (
|
||||
domain_id, ordername text_pattern_ops);
|
||||
|
||||
"""
|
||||
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
|
||||
name = models.CharField(
|
||||
max_length=255, blank=True, null=True, db_index=True)
|
||||
recordtype = models.CharField(
|
||||
max_length=10, blank=True, null=True, db_column='type')
|
||||
content = models.CharField(max_length=65535, blank=True, null=True)
|
||||
ttl = models.IntegerField(null=True)
|
||||
prio = models.IntegerField(null=True)
|
||||
change_date = models.IntegerField(null=True)
|
||||
disabled = models.BooleanField(default=False)
|
||||
ordername = models.CharField(max_length=255)
|
||||
auth = models.BooleanField(default=True)
|
||||
# check constraint and index recordorder are added via RunSQL in migration
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS record')
|
||||
verbose_name_plural = _('DNS records')
|
||||
index_together = [
|
||||
['name', 'recordtype']
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "{name} IN {type} {content}".format(
|
||||
name=self.name, type=self.recordtype, content=self.content)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSSupermaster(models.Model):
|
||||
"""
|
||||
This model represents the supermasters table in the PowerDNS schema
|
||||
specified in
|
||||
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE supermasters (
|
||||
ip INET NOT NULL,
|
||||
nameserver VARCHAR(255) NOT NULL,
|
||||
account VARCHAR(40) NOT NULL,
|
||||
PRIMARY KEY(ip, nameserver)
|
||||
);
|
||||
|
||||
"""
|
||||
ip = models.GenericIPAddressField()
|
||||
nameserver = models.CharField(max_length=255)
|
||||
# account is replaced by customer
|
||||
customer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, verbose_name=_('customer'),
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS supermaster')
|
||||
verbose_name_plural = _('DNS supermasters')
|
||||
unique_together = (
|
||||
('ip', 'nameserver')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "{ip} {nameserver}".format(
|
||||
ip=self.ip, nameserver=self.nameserver)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSComment(models.Model):
|
||||
"""
|
||||
This model represents the comments table in the PowerDNS schema specified
|
||||
in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The
|
||||
comments table is used to store user comments related to individual DNS
|
||||
records.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE comments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
domain_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(10) NOT NULL,
|
||||
modified_at INT NOT NULL,
|
||||
account VARCHAR(40) DEFAULT NULL,
|
||||
comment VARCHAR(65535) NOT NULL,
|
||||
CONSTRAINT domain_exists
|
||||
FOREIGN KEY(domain_id) REFERENCES domains(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT c_lowercase_name CHECK (
|
||||
((name)::TEXT = LOWER((name)::TEXT)))
|
||||
);
|
||||
|
||||
CREATE INDEX comments_domain_id_idx ON comments (domain_id);
|
||||
CREATE INDEX comments_name_type_idx ON comments (name, type);
|
||||
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
|
||||
|
||||
"""
|
||||
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
commenttype = models.CharField(max_length=10, db_column='type')
|
||||
modified_at = models.IntegerField()
|
||||
# account is replaced by customer
|
||||
customer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, verbose_name=_('customer'),
|
||||
on_delete=models.CASCADE)
|
||||
comment = models.CharField(max_length=65535)
|
||||
# check constraint is added via RunSQL in migration
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS comment')
|
||||
verbose_name_plural = _('DNS comments')
|
||||
index_together = [
|
||||
['name', 'commenttype'],
|
||||
['domain', 'modified_at']
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "{name} IN {type}: {comment}".format(
|
||||
name=self.name, type=self.commenttype, comment=self.comment)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSDomainMetadata(models.Model):
|
||||
"""
|
||||
This model represents the domainmetadata table in the PowerDNS schema
|
||||
specified in
|
||||
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
|
||||
The domainmetadata table is used to store domain meta data as described in
|
||||
https://doc.powerdns.com/md/authoritative/domainmetadata/.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE domainmetadata (
|
||||
id SERIAL PRIMARY KEY,
|
||||
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
|
||||
kind VARCHAR(32),
|
||||
content TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);
|
||||
|
||||
"""
|
||||
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
|
||||
kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS)
|
||||
content = models.TextField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS domain metadata item')
|
||||
verbose_name_plural = _('DNS domain metadata items')
|
||||
|
||||
def __str__(self):
|
||||
return "{domain} {kind} {content}".format(
|
||||
domain=self.domain.domain, kind=self.kind, content=self.content)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSCryptoKey(models.Model):
|
||||
"""
|
||||
This model represents the cryptokeys table in the PowerDNS schema
|
||||
specified in
|
||||
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE cryptokeys (
|
||||
id SERIAL PRIMARY KEY,
|
||||
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
|
||||
flags INT NOT NULL,
|
||||
active BOOL,
|
||||
content TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX domainidindex ON cryptokeys(domain_id);
|
||||
|
||||
"""
|
||||
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
|
||||
flags = models.IntegerField()
|
||||
active = models.BooleanField(default=True)
|
||||
content = models.TextField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS crypto key')
|
||||
verbose_name_plural = _('DNS crypto keys')
|
||||
|
||||
def __str__(self):
|
||||
return "{domain} {content}".format(
|
||||
domain=self.domain.domain, content=self.content)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DNSTSIGKey(models.Model):
|
||||
"""
|
||||
This model represents the tsigkeys table in the PowerDNS schema specified
|
||||
in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE tsigkeys (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
algorithm VARCHAR(50),
|
||||
secret VARCHAR(255),
|
||||
CONSTRAINT c_lowercase_name CHECK (
|
||||
((name)::TEXT = LOWER((name)::TEXT)))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
|
||||
|
||||
"""
|
||||
name = models.CharField(max_length=255)
|
||||
algorithm = models.CharField(
|
||||
max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS)
|
||||
secret = models.CharField(max_length=255)
|
||||
# check constraint is added via RunSQL in migration
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('DNS TSIG key')
|
||||
verbose_name_plural = _('DNS TSIG keys')
|
||||
unique_together = [
|
||||
['name', 'algorithm']
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "{name} {algorithm} XXXX".format(
|
||||
name=self.name, algorithm=self.algorithm)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class TestMailDomainAdmin(TestCase):
|
||||
def test_admin_for_maildomain(self):
|
||||
admin_url = reverse('admin:domains_maildomain_changelist')
|
||||
admin_url = reverse("admin:domains_maildomain_changelist")
|
||||
self.assertIsNotNone(admin_url)
|
||||
|
|
99
gnuviechadmin/domains/tests/test_forms.py
Normal file
99
gnuviechadmin/domains/tests/test_forms.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
"""
|
||||
Tests for :py:mod:`domains.forms`.
|
||||
|
||||
"""
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from django.forms import ValidationError
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from domains.forms import relative_domain_validator, CreateHostingDomainForm
|
||||
|
||||
|
||||
class RelativeDomainValidatorTest(TestCase):
|
||||
def test_valid_domainname(self):
|
||||
relative_domain_validator("example.org")
|
||||
|
||||
def test_domain_name_too_long(self):
|
||||
with self.assertRaisesMessage(ValidationError, _("host name too long")):
|
||||
relative_domain_validator("e" * 255)
|
||||
|
||||
def test_domain_name_part_too_long(self):
|
||||
with self.assertRaisesMessage(ValidationError, _("invalid domain name")):
|
||||
relative_domain_validator("a" * 64 + ".org")
|
||||
|
||||
def test_domain_name_illegal_characters(self):
|
||||
with self.assertRaisesMessage(ValidationError, _("invalid domain name")):
|
||||
relative_domain_validator("eXampl3.org")
|
||||
|
||||
def test_domain_name_starts_with_dash(self):
|
||||
with self.assertRaisesMessage(ValidationError, _("invalid domain name")):
|
||||
relative_domain_validator("-example.org")
|
||||
|
||||
def test_domain_name_ends_with_dash(self):
|
||||
with self.assertRaisesMessage(ValidationError, _("invalid domain name")):
|
||||
relative_domain_validator("example-.org")
|
||||
|
||||
|
||||
class CreateHostingDomainFormTest(TestCase):
|
||||
def test_constructor_needs_hostingpackage(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
CreateHostingDomainForm(instance)
|
||||
|
||||
def test_constructor(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock()
|
||||
form = CreateHostingDomainForm(instance, hostingpackage=hostingpackage)
|
||||
self.assertTrue(hasattr(form, "hosting_package"))
|
||||
self.assertEqual(form.hosting_package, hostingpackage)
|
||||
self.assertTrue(hasattr(form, "helper"))
|
||||
self.assertEqual(
|
||||
form.helper.form_action,
|
||||
reverse("create_hosting_domain", kwargs={"package": 42}),
|
||||
)
|
||||
self.assertEqual(len(form.helper.layout.fields), 2)
|
||||
self.assertEqual(form.helper.layout.fields[1].name, "submit")
|
||||
|
||||
def test_domain_field_has_relative_domain_validator(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock()
|
||||
form = CreateHostingDomainForm(instance, hostingpackage=hostingpackage)
|
||||
self.assertIn(relative_domain_validator, form.fields["domain"].validators)
|
||||
|
||||
def test_clean(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock()
|
||||
form = CreateHostingDomainForm(
|
||||
instance, hostingpackage=hostingpackage, data={"domain": "example.org"}
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertIn("hosting_package", form.cleaned_data)
|
||||
self.assertEqual(hostingpackage, form.cleaned_data["hosting_package"])
|
||||
|
||||
def test_save(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock()
|
||||
form = CreateHostingDomainForm(
|
||||
instance, hostingpackage=hostingpackage, data={"domain": "example.org"}
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
with patch("domains.forms.HostingDomain") as domain:
|
||||
form.save()
|
||||
domain.objects.create_for_hosting_package.assert_called_with(
|
||||
commit=True, **form.cleaned_data
|
||||
)
|
||||
form.save(commit=False)
|
||||
domain.objects.create_for_hosting_package.assert_called_with(
|
||||
commit=False, **form.cleaned_data
|
||||
)
|
||||
|
||||
def test_save_m2m(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock()
|
||||
form = CreateHostingDomainForm(
|
||||
instance, hostingpackage=hostingpackage, data={"domain": "example.org"}
|
||||
)
|
||||
form.save_m2m()
|
|
@ -1,9 +1,125 @@
|
|||
from django.test import TestCase
|
||||
"""
|
||||
Tests for :py:mod:`domains.models`.
|
||||
|
||||
from domains.models import MailDomain
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from domains.models import (
|
||||
DNSComment,
|
||||
DNSCryptoKey,
|
||||
DNSDomain,
|
||||
DNSDomainMetadata,
|
||||
DNSRecord,
|
||||
DNSSupermaster,
|
||||
DNSTSIGKey,
|
||||
HostingDomain,
|
||||
MailDomain,
|
||||
)
|
||||
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
TEST_USER = "test"
|
||||
|
||||
|
||||
class MailDomainTest(TestCase):
|
||||
def test__str__(self):
|
||||
md = MailDomain.objects.create(domain='example.org')
|
||||
self.assertEqual(str(md), 'example.org')
|
||||
def test___str__(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
self.assertEqual(str(md), "example.org")
|
||||
|
||||
def test_get_mailaddresses(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
from managemails.models import MailAddress
|
||||
|
||||
addrmock = MailAddress.objects.create(localpart="info", domain=md)
|
||||
self.assertIn(addrmock, md.get_mailaddresses())
|
||||
self.assertIn(addrmock, md.mailaddresses)
|
||||
|
||||
|
||||
class HostingDomainManagerTest(TestCase):
|
||||
def _setup_hosting_package(self):
|
||||
template = HostingPackageTemplate.objects.create(
|
||||
name="testpackagetemplate", mailboxcount=0, diskspace=1, diskspace_unit=0
|
||||
)
|
||||
customer = User.objects.create_user(username=TEST_USER)
|
||||
package = CustomerHostingPackage.objects.create_from_template(
|
||||
customer, template, "testpackage"
|
||||
)
|
||||
with patch("hostingpackages.models.settings") as hmsettings:
|
||||
hmsettings.OSUSER_DEFAULT_GROUPS = []
|
||||
package.save()
|
||||
return package
|
||||
|
||||
def test_create_for_hosting_package_with_commit(self):
|
||||
package = self._setup_hosting_package()
|
||||
hostingdomain = HostingDomain.objects.create_for_hosting_package(
|
||||
package, "example.org", True
|
||||
)
|
||||
|
||||
self.assertIsNotNone(hostingdomain)
|
||||
self.assertTrue(hostingdomain.customer, package.customer)
|
||||
|
||||
def test_create_for_hosting_package_no_commit(self):
|
||||
package = self._setup_hosting_package()
|
||||
hostingdomain = HostingDomain.objects.create_for_hosting_package(
|
||||
package, "example.org", False
|
||||
)
|
||||
|
||||
self.assertIsNotNone(hostingdomain)
|
||||
self.assertTrue(hostingdomain.customer, package.customer)
|
||||
|
||||
|
||||
class HostingDomainTest(TestCase):
|
||||
def test___str__(self):
|
||||
hostingdomain = HostingDomain(domain="test")
|
||||
self.assertEqual(str(hostingdomain), "test")
|
||||
|
||||
|
||||
class DNSDomainTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnsdomain = DNSDomain(domain="test")
|
||||
self.assertEqual(str(dnsdomain), "test")
|
||||
|
||||
|
||||
class DNSRecordTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnsrecord = DNSRecord(name="localhost", recordtype="A", content="127.0.0.1")
|
||||
self.assertEqual(str(dnsrecord), "localhost IN A 127.0.0.1")
|
||||
|
||||
|
||||
class DNSSupermasterTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnssupermaster = DNSSupermaster(ip="127.0.0.1", nameserver="dns.example.org")
|
||||
self.assertEqual(str(dnssupermaster), "127.0.0.1 dns.example.org")
|
||||
|
||||
|
||||
class DNSCommentTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnscomment = DNSComment(name="localhost", commenttype="A", comment="good stuff")
|
||||
self.assertEqual(str(dnscomment), "localhost IN A: good stuff")
|
||||
|
||||
|
||||
class DNSDomainMetadataTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnsdomain = DNSDomain(domain="test")
|
||||
dnsdomainmetadata = DNSDomainMetadata(
|
||||
domain=dnsdomain, kind="SOA-EDIT", content="INCEPTION"
|
||||
)
|
||||
self.assertEqual(str(dnsdomainmetadata), "test SOA-EDIT INCEPTION")
|
||||
|
||||
|
||||
class DNSCryptoKeyTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnsdomain = DNSDomain(domain="test")
|
||||
dnscryptokey = DNSCryptoKey(domain=dnsdomain, content="testvalue")
|
||||
self.assertEqual(str(dnscryptokey), "test testvalue")
|
||||
|
||||
|
||||
class DNSTSIGKeyTest(TestCase):
|
||||
def test___str__(self):
|
||||
dnstsigkey = DNSTSIGKey(name="testkey", algorithm="hmac-md5", secret="dummykey")
|
||||
self.assertEqual(str(dnstsigkey), "testkey hmac-md5 XXXX")
|
||||
|
|
153
gnuviechadmin/domains/tests/test_views.py
Normal file
153
gnuviechadmin/domains/tests/test_views.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
"""
|
||||
Tests for :py:mod:`domains.views`.
|
||||
|
||||
"""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from domains.views import CreateHostingDomain
|
||||
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
TEST_USER = "test"
|
||||
TEST_PASSWORD = "secret"
|
||||
TEST_EMAIL = "test@example.org"
|
||||
TEST_NAME = "Example Tester".split()
|
||||
|
||||
|
||||
class CreateHostingDomainTest(TestCase):
|
||||
def _setup_hosting_package(self, customer):
|
||||
template = HostingPackageTemplate.objects.create(
|
||||
name="testpackagetemplate", mailboxcount=0, diskspace=1, diskspace_unit=0
|
||||
)
|
||||
package = CustomerHostingPackage.objects.create_from_template(
|
||||
customer, template, "testpackage"
|
||||
)
|
||||
with patch("hostingpackages.models.settings") as hmsettings:
|
||||
hmsettings.OSUSER_DEFAULT_GROUPS = []
|
||||
package.save()
|
||||
return package
|
||||
|
||||
def test_get_anonymous(self):
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": 1})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_regular_user(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_staff_user(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_template(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id})
|
||||
)
|
||||
self.assertTemplateUsed(response, "domains/hostingdomain_create.html")
|
||||
|
||||
def test_get_no_package_found(self):
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": 1})
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_get_form_kwargs(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = CreateHostingDomain(
|
||||
request=MagicMock(), kwargs={"package": str(package.id)}
|
||||
)
|
||||
the_kwargs = view.get_form_kwargs()
|
||||
self.assertIn("hostingpackage", the_kwargs)
|
||||
self.assertEqual(the_kwargs["hostingpackage"], package)
|
||||
|
||||
def test_get_context_data_has_hosting_package(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id})
|
||||
)
|
||||
self.assertIn("hostingpackage", response.context)
|
||||
self.assertEqual(response.context["hostingpackage"], package)
|
||||
|
||||
def test_get_context_data_has_customer(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id})
|
||||
)
|
||||
self.assertIn("customer", response.context)
|
||||
self.assertEqual(response.context["customer"], customer)
|
||||
|
||||
def test_form_valid_redirect(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id}),
|
||||
data={"domain": "example.org"},
|
||||
)
|
||||
self.assertRedirects(response, package.get_absolute_url())
|
||||
|
||||
def test_form_valid_message(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse("create_hosting_domain", kwargs={"package": package.id}),
|
||||
follow=True,
|
||||
data={"domain": "example.org"},
|
||||
)
|
||||
messages = list(response.context["messages"])
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual("Successfully created domain example.org", str(messages[0]))
|
|
@ -4,13 +4,12 @@ This module defines the URL patterns for domain related views.
|
|||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import CreateHostingDomain
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(r'^(?P<package>\d+)/create$', CreateHostingDomain.as_view(),
|
||||
name='create_hosting_domain'),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -4,58 +4,50 @@ This module defines views related to domains.
|
|||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.views.generic.edit import CreateView
|
||||
from django.utils.translation import ugettext as _
|
||||
from braces.views import StaffuserRequiredMixin
|
||||
from django.contrib import messages
|
||||
|
||||
from braces.views import (
|
||||
LoginRequiredMixin,
|
||||
StaffuserRequiredMixin,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.edit import CreateView
|
||||
|
||||
from hostingpackages.models import CustomerHostingPackage
|
||||
|
||||
from .forms import CreateHostingDomainForm
|
||||
from .models import HostingDomain
|
||||
|
||||
|
||||
class CreateHostingDomain(
|
||||
LoginRequiredMixin, StaffuserRequiredMixin, CreateView
|
||||
):
|
||||
class CreateHostingDomain(StaffuserRequiredMixin, CreateView):
|
||||
"""
|
||||
This view is used for creating a new HostingDomain instance for an existing
|
||||
hosting package.
|
||||
|
||||
"""
|
||||
|
||||
model = HostingDomain
|
||||
raise_exception = True
|
||||
template_name_suffix = '_create'
|
||||
template_name_suffix = "_create"
|
||||
form_class = CreateHostingDomainForm
|
||||
|
||||
def _get_hosting_package(self):
|
||||
return get_object_or_404(
|
||||
CustomerHostingPackage, pk=int(self.kwargs['package']))
|
||||
return get_object_or_404(CustomerHostingPackage, pk=int(self.kwargs["package"]))
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(CreateHostingDomain, self).get_form_kwargs()
|
||||
kwargs['hostingpackage'] = self._get_hosting_package()
|
||||
kwargs["hostingpackage"] = self._get_hosting_package()
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateHostingDomain, self).get_context_data(**kwargs)
|
||||
hosting_package = self._get_hosting_package()
|
||||
context.update({
|
||||
'hostingpackage': hosting_package,
|
||||
'customer': hosting_package.customer,
|
||||
})
|
||||
context.update(
|
||||
{"hostingpackage": hosting_package, "customer": hosting_package.customer}
|
||||
)
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
hostingdomain = form.save()
|
||||
messages.success(
|
||||
self.request,
|
||||
_('Successfully created domain {domainname}').format(
|
||||
domainname=hostingdomain.domain)
|
||||
_("Successfully created domain {domainname}").format(
|
||||
domainname=hostingdomain.domain
|
||||
),
|
||||
)
|
||||
return redirect(self._get_hosting_package())
|
||||
|
|
|
@ -9,127 +9,388 @@ from celery import shared_task
|
|||
|
||||
|
||||
@shared_task
|
||||
def setup_file_sftp_userdir(username):
|
||||
def setup_file_sftp_userdir(username, *args, **kwargs):
|
||||
"""
|
||||
This task creates the home directory for an SFTP user if it does not exist
|
||||
yet.
|
||||
|
||||
:param str username: the user name
|
||||
:param str username: the username
|
||||
:raises Exception: if the SFTP directory of the user cannot be created
|
||||
:return: the created directory name
|
||||
:rtype: str
|
||||
:return: a dictionary with the key :py:const:`username` set to the
|
||||
username value and a new key :py:const:`sftp_directory` set to the
|
||||
path of the created SFTP directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`fileservertasks.tasks.setup_file_sftp_userdir_chained`
|
||||
at other positions in the task chain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_sftp_userdir(username):
|
||||
def setup_file_sftp_userdir_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task creates the home directory for an SFTP user if it does not exist
|
||||
yet.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` key
|
||||
:raises Exception: if the SFTP directory of the user cannot be created
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`sftp_directory` key set to the path of the created SFTP
|
||||
directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_sftp_userdir(username, *args, **kwargs):
|
||||
"""
|
||||
This task recursively deletes the home directory of an SFTP user if it
|
||||
does not exist yet.
|
||||
exists.
|
||||
|
||||
:param str username: the user name
|
||||
:param str username: the username
|
||||
:raises Exception: if the SFTP directory of the user cannot be removed
|
||||
:return: the removed directory name
|
||||
:rtype: str
|
||||
:return: a dictionary with the key :py:const:`username` set to the username
|
||||
value and the new key :py:const:`sftp_directory` set to the path of the
|
||||
deleted SFTP directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`fileservertasks.tasks.delete_file_sftp_userdir_chained`
|
||||
at other positions in the task chain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def setup_file_mail_userdir(username):
|
||||
def delete_file_sftp_userdir_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task recursively deletes the home directory of an SFTP user if it
|
||||
exists.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` key
|
||||
:raises Exception: if the SFTP directory of the user cannot be removed
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`sftp_directory` key set to the path of the removed SFTP
|
||||
directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def setup_file_mail_userdir(username, *args, **kwargs):
|
||||
"""
|
||||
This task creates the mail base directory for a user if it does not exist
|
||||
yet.
|
||||
|
||||
:param str username: the user name
|
||||
:param str username: the username
|
||||
:raises Exception: if the mail base directory for the user cannot be
|
||||
created
|
||||
:return: the created directory name
|
||||
:rtype: str
|
||||
:return: a dictionary with the key :py:const:`username` set to the
|
||||
username value and a new key :py:const:`mail_directory` set to the path
|
||||
of the created mail directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`fileservertasks.tasks.setup_file_mail_userdir_chained`
|
||||
at other positions in the task chain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mail_userdir(username):
|
||||
def setup_file_mail_userdir_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task creates the mail base directory for a user if it does not exist
|
||||
yet.
|
||||
|
||||
:param dict previous_result: a dictionary containing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` key
|
||||
:raises Exception: if the mail base directory for the user cannot be
|
||||
created
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`mail_directory` key set to the path of the created mail
|
||||
directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mail_userdir(username, *args, **kwargs):
|
||||
"""
|
||||
This task recursively deletes the mail base directory for a user if it
|
||||
does not exist yet.
|
||||
|
||||
:param str username: the user name
|
||||
:raises Exception: if the mail base directory of the user cannot be removed
|
||||
:return: the removed directory name
|
||||
:param str username: the username
|
||||
:raises Exception: if the mail base directory of the user cannot be deleted
|
||||
:return: a dictionary with the key :py:const:`username` set to the
|
||||
username value and a new key :py:const:`mail_directory` set to the path
|
||||
of the deleted mail directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`fileservertasks.tasks.delete_file_mail_userdir_chained`
|
||||
at other positions in the task chain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mail_userdir_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task recursively deletes the mail base directory for a user if it
|
||||
does not exist yet.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` key
|
||||
:raises Exception: if the mail base directory of the user cannot be deleted
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`mail_directory` key set to the path of the deleted mail
|
||||
directory
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_file_mailbox(username, mailboxname):
|
||||
def create_file_mailbox(username, mailboxname, *args, **kwargs):
|
||||
"""
|
||||
This task creates a new mailbox directory for the given user and mailbox
|
||||
name.
|
||||
|
||||
:param str username: the user name
|
||||
:param str username: the username
|
||||
:param str mailboxname: the mailbox name
|
||||
:raises Exception: if the mailbox directory cannot be created
|
||||
:return: the created mailbox directory name
|
||||
:rtype: str
|
||||
:return: a dictionary with the keys :py:const:`username` and
|
||||
:py:const:`mailboxname` set to the values of username and mailboxname
|
||||
and a new key :py:const:`mailbox_directory` set to the path of the
|
||||
created mailbox directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`fileservertasks.tasks.create_file_mailbox_chained` at
|
||||
other positions in the task chain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mailbox(username, mailboxname):
|
||||
def create_file_mailbox_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task creates a new mailbox directory for the given user and mailbox
|
||||
name.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` and a :py:const:`mailboxname` key
|
||||
:raises Exception: if the mailbox directory cannot be created
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`mailbox_directory` key set to the path of the created
|
||||
mailbox directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mailbox(username, mailboxname, *args, **kwargs):
|
||||
"""
|
||||
This task deletes the given mailbox of the given user.
|
||||
|
||||
:param str username: the user name
|
||||
:param str username: the username
|
||||
:param str mailboxname: the mailbox name
|
||||
:raises Exception: if the mailbox directory cannot be deleted
|
||||
:return: the deleted mailbox directory name
|
||||
:rtype: str
|
||||
:return: a dictionary with the keys :py:const:`username` and
|
||||
:py:const:`mailboxname` set to the values of username and mailboxname
|
||||
and a new key :py:const:`mailbox_directory` set to the path of the
|
||||
deleted mailbox directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`fileservertasks.tasks.delete_file_mailbox_chained` for
|
||||
other positions in the task chain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_file_website_hierarchy(username, sitename):
|
||||
def delete_file_mailbox_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task deletes the given mailbox of the given user.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` and a :py:const:`mailboxname` key
|
||||
:raises Exception: if the mailbox directory cannot be deleted
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`mailbox_directory` key set to the path of the deleted
|
||||
mailbox directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_file_website_hierarchy(username, sitename, *args, **kwargs):
|
||||
"""
|
||||
This task creates the directory hierarchy for a website.
|
||||
|
||||
:param str username: the user name
|
||||
:param str sitename: name of the website
|
||||
:return: the directory name
|
||||
:rtype: str
|
||||
:param str username: the username
|
||||
:param str sitename: the sitename
|
||||
:raises Exception: if the website directory hierarchy directory cannot be
|
||||
created
|
||||
:return: a dictionary with the keys :py:const:`username` and
|
||||
:py:const:`sitename` set to the values of username and sitename and a
|
||||
new key :py:const:`website_directory` set to the path of the created
|
||||
website directory
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use
|
||||
:py:func:`fileservertasks.tasks.create_file_website_hierarchy_chained`
|
||||
at other positions in the task chain
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_website_hierarchy(username, sitename):
|
||||
def create_file_website_hierarchy_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task creates the directory hierarchy for a website.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` and a :py:const:`sitename` key
|
||||
:raises Exception: if the website directory hierarchy directory cannot be
|
||||
created
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`website_directory` key set to the path of the created
|
||||
website directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_website_hierarchy(username, sitename, *args, **kwargs):
|
||||
"""
|
||||
This task deletes a website hierarchy recursively.
|
||||
|
||||
:param str username: a username
|
||||
:param str sitename: a site name
|
||||
:return: a dictionary with the keys :py:const:`username` and
|
||||
:py:const:`sitename` set to their original values and a new key
|
||||
:py:const:`website_directory` set to the path of the deleted website
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use
|
||||
:py:func:`fileservertasks.tasks.delete_file_website_hierarchy_chained`
|
||||
at other positions in the task chain
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_website_hierarchy_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task deletes the website hierarchy recursively.
|
||||
|
||||
:param str username: the user name
|
||||
:param str sitename: name of the website
|
||||
:return: the directory name
|
||||
:rtype: str
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` and a :py:const:`sitename` key
|
||||
:raises Exception: if the website directory hierarchy directory cannot be
|
||||
deleted
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`website_directory` set to the path of the deleted website
|
||||
directory
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_file_ssh_authorized_keys(username, ssh_keys):
|
||||
def set_file_ssh_authorized_keys(username, ssh_keys, *args, **kwargs):
|
||||
"""
|
||||
This task set the authorized keys for ssh logins.
|
||||
|
||||
:param str username: a username
|
||||
:param list ssh_keys: a list of ssh keys
|
||||
:raises Exception: if the update of the creation or update of ssh
|
||||
authorized_keys failed
|
||||
:return: a dictionary with the keys :py:const:`username` and
|
||||
:py:const:`ssh_keys` set to their original values and a new key
|
||||
:py:const:`ssh_authorized_keys` set to the path of the SSH
|
||||
authorized_keys file
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use
|
||||
:py:func:`fileservertasks.tasks.set_file_ssh_authorized_keys_chained`
|
||||
at other positions in the task chain
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_file_ssh_authorized_keys_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task sets the authorized keys for ssh logins.
|
||||
|
||||
:param str username: the user name
|
||||
:param list ssh_key: an ssh_key
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` and a :py:const:`ssh_keys` key
|
||||
:raises Exception: if the update of the creation or update of ssh
|
||||
authorized_keys failed
|
||||
:return: the name of the authorized_keys file
|
||||
:rtype: str
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`ssh_authorized_keys` set to the path of the SSH
|
||||
authorized_keys file
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from gnuviechadmin.celery import app as celery_app
|
||||
# import celery_app to initialize it
|
||||
from gnuviechadmin.celery import app as celery_app # NOQA
|
||||
|
||||
__version__ = '0.11.3'
|
||||
__version__ = '0.12.0'
|
||||
|
|
|
@ -12,5 +12,9 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE',
|
|||
|
||||
app = Celery('gnuviechadmin')
|
||||
|
||||
|
||||
def get_installed_apps():
|
||||
return settings.INSTALLED_APPS
|
||||
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
app.autodiscover_tasks(get_installed_apps)
|
||||
|
|
|
@ -13,7 +13,6 @@ from gnuviechadmin import __version__ as gvaversion
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
def navigation(request):
|
||||
"""
|
||||
Add navigation items to the request context.
|
||||
|
@ -50,7 +49,7 @@ def navigation(request):
|
|||
request.path.endswith('/impressum/')
|
||||
):
|
||||
context['active_item'] = 'imprint'
|
||||
else:
|
||||
elif not viewmodule.startswith('django.contrib.admin'):
|
||||
_LOGGER.debug(
|
||||
'no special handling for view %s in module %s, fallback to '
|
||||
'default active menu item %s',
|
||||
|
|
496
gnuviechadmin/gnuviechadmin/settings.py
Normal file
496
gnuviechadmin/gnuviechadmin/settings.py
Normal file
|
@ -0,0 +1,496 @@
|
|||
# -*- python -*-
|
||||
# pymode:lint_ignore=E501
|
||||
"""
|
||||
Common settings and globals.
|
||||
|
||||
"""
|
||||
|
||||
from os.path import abspath, basename, dirname, join, normpath
|
||||
|
||||
from django.contrib.messages import constants as messages
|
||||
|
||||
from gvacommon.settings_utils import get_env_variable
|
||||
|
||||
|
||||
# ######### PATH CONFIGURATION
|
||||
# Absolute filesystem path to the Django project directory:
|
||||
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
|
||||
|
||||
# Absolute filesystem path to the top-level project folder:
|
||||
SITE_ROOT = dirname(DJANGO_ROOT)
|
||||
|
||||
# Site name:
|
||||
SITE_NAME = basename(DJANGO_ROOT)
|
||||
|
||||
# ######### END PATH CONFIGURATION
|
||||
|
||||
|
||||
# ######### DEBUG CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = False
|
||||
# ######### END DEBUG CONFIGURATION
|
||||
|
||||
|
||||
# ######### MANAGER CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
|
||||
ADMINS = (
|
||||
(
|
||||
get_env_variable("GVA_ADMIN_NAME", default="Admin"),
|
||||
get_env_variable("GVA_ADMIN_EMAIL", default="admin@example.org"),
|
||||
),
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
|
||||
MANAGERS = ADMINS
|
||||
# ######### END MANAGER CONFIGURATION
|
||||
|
||||
|
||||
# ######### DATABASE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": get_env_variable("GVA_PGSQL_DATABASE", default="gnuviechadmin"),
|
||||
"USER": get_env_variable("GVA_PGSQL_USER", default="gnuviechadmin"),
|
||||
"PASSWORD": get_env_variable("GVA_PGSQL_PASSWORD"),
|
||||
"HOST": get_env_variable("GVA_PGSQL_HOSTNAME", default="db"),
|
||||
"PORT": get_env_variable("GVA_PGSQL_PORT", int, default=5432),
|
||||
}
|
||||
}
|
||||
# ######### END DATABASE CONFIGURATION
|
||||
|
||||
|
||||
# ######### GENERAL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
|
||||
TIME_ZONE = "Europe/Berlin"
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
||||
SITE_ID = 1
|
||||
SITES_DOMAIN_NAME = get_env_variable("GVA_DOMAIN_NAME")
|
||||
SITES_SITE_NAME = get_env_variable("GVA_SITE_NAME")
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
|
||||
USE_I18N = True
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
|
||||
USE_L10N = True
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
|
||||
USE_TZ = True
|
||||
# ######### END GENERAL CONFIGURATION
|
||||
|
||||
|
||||
LOCALE_PATHS = (normpath(join(SITE_ROOT, "gnuviechadmin", "locale")),)
|
||||
|
||||
|
||||
# ######### MEDIA CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
||||
MEDIA_ROOT = normpath(join(SITE_ROOT, "media"))
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
||||
MEDIA_URL = "/media/"
|
||||
# ######### END MEDIA CONFIGURATION
|
||||
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # noqa
|
||||
STATICFILES_DIRS = (normpath(join(SITE_ROOT, "gnuviechadmin", "static")),)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa
|
||||
STATICFILES_FINDERS = (
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
)
|
||||
# ######### END STATIC FILE CONFIGURATION
|
||||
|
||||
|
||||
# ######### SECRET CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||
# Note: This key should only be used for development and testing.
|
||||
SECRET_KEY = get_env_variable("GVA_SITE_SECRET")
|
||||
# ######### END SECRET CONFIGURATION
|
||||
|
||||
|
||||
# ######### SITE CONFIGURATION
|
||||
# Hosts/domain names that are valid for this site
|
||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = []
|
||||
# ######### END SITE CONFIGURATION
|
||||
|
||||
|
||||
# ######### FIXTURE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS # noqa
|
||||
FIXTURE_DIRS = (normpath(join(SITE_ROOT, "fixtures")),)
|
||||
# ######### END FIXTURE CONFIGURATION
|
||||
|
||||
|
||||
# ######### TEMPLATE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/1.9/ref/settings/#std:setting-TEMPLATES # noqa
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [normpath(join(DJANGO_ROOT, "templates"))],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"django.template.context_processors.request",
|
||||
# custom context processors
|
||||
"gnuviechadmin.context_processors.navigation",
|
||||
"gnuviechadmin.context_processors.version_info",
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
# ######### END TEMPLATE CONFIGURATION
|
||||
|
||||
|
||||
# ######### MIDDLEWARE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes
|
||||
MIDDLEWARE = [
|
||||
# Default Django middleware.
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
# uncomment next line to enable translation to browser locale
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
# ######### END MIDDLEWARE CONFIGURATION
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
# Needed to login by username in Django admin, regardless of `allauth`
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
# `allauth` specific authentication methods, such as login by e-mail
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
)
|
||||
|
||||
|
||||
# ######### URL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
|
||||
ROOT_URLCONF = "%s.urls" % SITE_NAME
|
||||
# ######### END URL CONFIGURATION
|
||||
|
||||
|
||||
# ######### TEST RUNNER CONFIGURATION
|
||||
TEST_RUNNER = "django.test.runner.DiscoverRunner"
|
||||
# ######### END TEST RUNNER CONFIGURATION
|
||||
|
||||
|
||||
# ######### APP CONFIGURATION
|
||||
DJANGO_APPS = (
|
||||
# Default Django apps:
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# Useful template tags:
|
||||
"django.contrib.humanize",
|
||||
# Admin panel and documentation:
|
||||
"django.contrib.admin",
|
||||
# Flatpages for about page
|
||||
"django.contrib.flatpages",
|
||||
"crispy_forms",
|
||||
)
|
||||
|
||||
ALLAUTH_APPS = (
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"allauth.socialaccount.providers.google",
|
||||
"allauth.socialaccount.providers.linkedin_oauth2",
|
||||
"allauth.socialaccount.providers.twitter",
|
||||
)
|
||||
|
||||
# Apps specific for this project go here.
|
||||
LOCAL_APPS = (
|
||||
"dashboard",
|
||||
"taskresults",
|
||||
"ldaptasks",
|
||||
"mysqltasks",
|
||||
"pgsqltasks",
|
||||
"fileservertasks",
|
||||
"webtasks",
|
||||
"domains",
|
||||
"osusers",
|
||||
"managemails",
|
||||
"userdbs",
|
||||
"hostingpackages",
|
||||
"websites",
|
||||
"contact_form",
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
INSTALLED_APPS = DJANGO_APPS + ALLAUTH_APPS + LOCAL_APPS
|
||||
|
||||
MESSAGE_TAGS = {
|
||||
messages.DEBUG: "",
|
||||
messages.ERROR: "alert-danger",
|
||||
messages.INFO: "alert-info",
|
||||
messages.SUCCESS: "alert-success",
|
||||
messages.WARNING: "alert-warning",
|
||||
}
|
||||
# ######### END APP CONFIGURATION
|
||||
|
||||
|
||||
# ######### ALLAUTH CONFIGURATION
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
SOCIALACCOUNT_QUERY_EMAIL = True
|
||||
# ######### END ALLAUTH CONFIGURATION
|
||||
|
||||
|
||||
# ######### CRISPY FORMS CONFIGURATION
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap3"
|
||||
# ######### END CRISPY_FORMS CONFIGURATION
|
||||
|
||||
|
||||
# ######### LOGGING CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
||||
# A sample logging configuration. The only tangible logging
|
||||
# performed by this configuration is to send an email to
|
||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "%(levelname)s %(asctime)s %(name)s "
|
||||
"%(module)s:%(lineno)d %(process)d %(thread)d %(message)s"
|
||||
},
|
||||
"simple": {"format": "%(levelname)s %(name)s:%(lineno)d %(message)s"},
|
||||
},
|
||||
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
|
||||
"handlers": {
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"django.request": {
|
||||
"handlers": ["mail_admins"],
|
||||
"level": "ERROR",
|
||||
"propagate": True,
|
||||
}
|
||||
},
|
||||
}
|
||||
# ######### END LOGGING CONFIGURATION
|
||||
|
||||
|
||||
# ######### WSGI CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
||||
WSGI_APPLICATION = "%s.wsgi.application" % SITE_NAME
|
||||
# ######### END WSGI CONFIGURATION
|
||||
|
||||
|
||||
# ######### CELERY CONFIGURATION
|
||||
BROKER_URL = get_env_variable(
|
||||
"GVA_BROKER_URL", default="amqp://gnuviechadmin:gnuviechadmin@mq/gnuviechadmin"
|
||||
)
|
||||
BROKER_TRANSPORT_OPTIONS = {
|
||||
"max_retries": 3,
|
||||
"interval_start": 0,
|
||||
"interval_step": 0.2,
|
||||
"interval_max": 0.2,
|
||||
}
|
||||
CELERY_RESULT_BACKEND = get_env_variable(
|
||||
"GVA_RESULTS_REDIS_URL", default="redis://:gnuviechadmin@redis:6379/0"
|
||||
)
|
||||
CELERY_TASK_RESULT_EXPIRES = None
|
||||
CELERY_ROUTES = ("gvacommon.celeryrouters.GvaRouter",)
|
||||
CELERY_TIMEZONE = "Europe/Berlin"
|
||||
CELERY_ENABLE_UTC = True
|
||||
CELERY_ACCEPT_CONTENT = ["json"]
|
||||
CELERY_TASK_SERIALIZER = "json"
|
||||
CELERY_RESULT_SERIALIZER = "json"
|
||||
# ######### END CELERY CONFIGURATION
|
||||
|
||||
|
||||
# ######### CUSTOM APP CONFIGURATION
|
||||
OSUSER_MINUID = get_env_variable("GVA_MIN_OS_UID", int, default=10000)
|
||||
OSUSER_MINGID = get_env_variable("GVA_MIN_OS_GID", int, default=10000)
|
||||
OSUSER_USERNAME_PREFIX = get_env_variable("GVA_OSUSER_PREFIX", default="usr")
|
||||
OSUSER_HOME_BASEPATH = get_env_variable("GVA_OSUSER_HOME_BASEPATH", default="/home")
|
||||
OSUSER_DEFAULT_SHELL = get_env_variable(
|
||||
"GVA_OSUSER_DEFAULT_SHELL", default="/usr/bin/rssh"
|
||||
)
|
||||
OSUSER_SFTP_GROUP = "sftponly"
|
||||
OSUSER_SSH_GROUP = "sshusers"
|
||||
OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP]
|
||||
OSUSER_UPLOAD_SERVER = get_env_variable("GVA_OSUSER_UPLOADSERVER", default="file")
|
||||
|
||||
GVA_LINK_WEBMAIL = get_env_variable(
|
||||
"GVA_WEBMAIL_URL", default="https://webmail.example.org/"
|
||||
)
|
||||
GVA_LINK_PHPMYADMIN = get_env_variable(
|
||||
"GVA_PHPMYADMIN_URL", default="https://phpmyadmin.example.org/"
|
||||
)
|
||||
GVA_LINK_PHPPGADMIN = get_env_variable(
|
||||
"GVA_PHPPGADMIN_URL", default="https://phppgadmin.example.org/"
|
||||
)
|
||||
# ######### END CUSTOM APP CONFIGURATION
|
||||
|
||||
GVA_ENVIRONMENT = get_env_variable("GVA_ENVIRONMENT", default="prod")
|
||||
|
||||
# ######### STATIC FILE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
||||
STATIC_ROOT = "/srv/gva/static/"
|
||||
|
||||
|
||||
def show_debug_toolbar(request):
|
||||
return DEBUG and GVA_ENVIRONMENT == "local"
|
||||
|
||||
|
||||
if GVA_ENVIRONMENT == "local":
|
||||
# ######### DEBUG CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = True
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
|
||||
TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
|
||||
# ######### END DEBUG CONFIGURATION
|
||||
|
||||
# ######### EMAIL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
# ######### END EMAIL CONFIGURATION
|
||||
|
||||
# ######### CACHE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
|
||||
# ######### END CACHE CONFIGURATION
|
||||
|
||||
# ######### TOOLBAR CONFIGURATION
|
||||
# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa
|
||||
INSTALLED_APPS += ("debug_toolbar",)
|
||||
|
||||
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
|
||||
|
||||
LOGGING["handlers"].update(
|
||||
{
|
||||
"console": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "simple",
|
||||
}
|
||||
}
|
||||
)
|
||||
LOGGING["loggers"].update(
|
||||
dict(
|
||||
[
|
||||
(key, {"handlers": ["console"], "level": "DEBUG", "propagate": True})
|
||||
for key in [
|
||||
"dashboard",
|
||||
"domains",
|
||||
"fileservertasks",
|
||||
"gvacommon",
|
||||
"gvawebcore",
|
||||
"hostingpackages",
|
||||
"ldaptasks",
|
||||
"managemails",
|
||||
"mysqltasks",
|
||||
"osusers",
|
||||
"pgsqltasks",
|
||||
"taskresults",
|
||||
"userdbs",
|
||||
"websites",
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
DEBUG_TOOLBAR_PATCH_SETTINGS = False
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
"SHOW_TOOLBAR_CALLBACK": "gnuviechadmin.settings.show_debug_toolbar"
|
||||
}
|
||||
|
||||
# ######### END TOOLBAR CONFIGURATION
|
||||
elif GVA_ENVIRONMENT == "test":
|
||||
ALLOWED_HOSTS = ["localhost"]
|
||||
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
|
||||
LOGGING["handlers"].update(
|
||||
{
|
||||
"console": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "simple",
|
||||
}
|
||||
}
|
||||
)
|
||||
LOGGING["loggers"].update(
|
||||
dict(
|
||||
[
|
||||
(key, {"handlers": ["console"], "level": "ERROR", "propagate": True})
|
||||
for key in [
|
||||
"dashboard",
|
||||
"domains",
|
||||
"fileservertasks",
|
||||
"gvacommon",
|
||||
"gvawebcore",
|
||||
"hostingpackages",
|
||||
"ldaptasks",
|
||||
"managemails",
|
||||
"mysqltasks",
|
||||
"osusers",
|
||||
"pgsqltasks",
|
||||
"taskresults",
|
||||
"userdbs",
|
||||
"websites",
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
BROKER_URL = BROKER_URL + "_test"
|
||||
CELERY_RESULT_PERSISTENT = False
|
||||
else:
|
||||
# ######### HOST CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production # noqa
|
||||
ALLOWED_HOSTS = [SITES_DOMAIN_NAME]
|
||||
# ######### END HOST CONFIGURATION
|
||||
|
||||
# ######### EMAIL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
|
||||
EMAIL_SUBJECT_PREFIX = "[%s] " % SITE_NAME
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
|
||||
DEFAULT_FROM_EMAIL = get_env_variable(
|
||||
"GVA_SITE_ADMINMAIL", default="admin@example.org"
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
|
||||
SERVER_EMAIL = get_env_variable("GVA_SITE_ADMINMAIL", default="admin@example.org")
|
||||
# ######### END EMAIL CONFIGURATION
|
||||
|
||||
# ######### CACHE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||
# CACHES = {}
|
||||
# ######### END CACHE CONFIGURATION
|
||||
|
||||
# ######### ALLAUTH PRODUCTION CONFIGURATION
|
||||
ACCOUNT_EMAIL_SUBJECT_PREFIX = "[Jan Dittberner IT-Consulting & -Solutions] "
|
||||
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
|
||||
# ######### END ALLAUTH PRODUCTION CONFIGURATION
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,380 +0,0 @@
|
|||
# -*- python -*-
|
||||
# pymode:lint_ignore=E501
|
||||
"""Common settings and globals."""
|
||||
|
||||
|
||||
from os import environ
|
||||
from os.path import abspath, basename, dirname, join, normpath
|
||||
from sys import path
|
||||
|
||||
# Normally you should not import ANYTHING from Django directly
|
||||
# into your settings, but ImproperlyConfigured is an exception.
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.contrib.messages import constants as messages
|
||||
|
||||
|
||||
def get_env_variable(var_name):
|
||||
"""
|
||||
Get a setting from an environment variable.
|
||||
|
||||
:param str var_name: variable name
|
||||
|
||||
"""
|
||||
try:
|
||||
return environ[var_name]
|
||||
except KeyError:
|
||||
error_msg = "Set the %s environment variable" % var_name
|
||||
raise ImproperlyConfigured(error_msg)
|
||||
|
||||
|
||||
########## PATH CONFIGURATION
|
||||
# Absolute filesystem path to the Django project directory:
|
||||
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
|
||||
|
||||
# Absolute filesystem path to the top-level project folder:
|
||||
SITE_ROOT = dirname(DJANGO_ROOT)
|
||||
|
||||
# Site name:
|
||||
SITE_NAME = basename(DJANGO_ROOT)
|
||||
|
||||
# Add our project to our pythonpath, this way we don't need to type our project
|
||||
# name in our dotted import paths:
|
||||
path.append(DJANGO_ROOT)
|
||||
########## END PATH CONFIGURATION
|
||||
|
||||
|
||||
########## DEBUG CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = False
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
########## END DEBUG CONFIGURATION
|
||||
|
||||
|
||||
########## MANAGER CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
|
||||
ADMINS = (
|
||||
(get_env_variable('GVA_ADMIN_NAME'), get_env_variable('GVA_ADMIN_EMAIL')),
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
|
||||
MANAGERS = ADMINS
|
||||
########## END MANAGER CONFIGURATION
|
||||
|
||||
|
||||
########## DATABASE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': get_env_variable('GVA_PGSQL_DATABASE'),
|
||||
'USER': get_env_variable('GVA_PGSQL_USER'),
|
||||
'PASSWORD': get_env_variable('GVA_PGSQL_PASSWORD'),
|
||||
'HOST': get_env_variable('GVA_PGSQL_HOSTNAME'),
|
||||
'PORT': get_env_variable('GVA_PGSQL_PORT'),
|
||||
}
|
||||
}
|
||||
########## END DATABASE CONFIGURATION
|
||||
|
||||
|
||||
########## GENERAL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
||||
SITE_ID = 1
|
||||
SITES_DOMAIN_NAME = get_env_variable('GVA_DOMAIN_NAME')
|
||||
SITES_SITE_NAME = get_env_variable('GVA_SITE_NAME')
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
|
||||
USE_I18N = True
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
|
||||
USE_L10N = True
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
|
||||
USE_TZ = True
|
||||
########## END GENERAL CONFIGURATION
|
||||
|
||||
|
||||
LOCALE_PATHS = (
|
||||
normpath(join(SITE_ROOT, 'locale')),
|
||||
)
|
||||
|
||||
|
||||
########## MEDIA CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
||||
MEDIA_ROOT = normpath(join(SITE_ROOT, 'media'))
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
||||
MEDIA_URL = '/media/'
|
||||
########## END MEDIA CONFIGURATION
|
||||
|
||||
|
||||
########## STATIC FILE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
||||
STATIC_ROOT = normpath(join(SITE_ROOT, 'assets'))
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
||||
STATICFILES_DIRS = (
|
||||
normpath(join(SITE_ROOT, 'static')),
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
)
|
||||
########## END STATIC FILE CONFIGURATION
|
||||
|
||||
|
||||
########## SECRET CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||
# Note: This key should only be used for development and testing.
|
||||
SECRET_KEY = get_env_variable('GVA_SITE_SECRET')
|
||||
########## END SECRET CONFIGURATION
|
||||
|
||||
|
||||
########## SITE CONFIGURATION
|
||||
# Hosts/domain names that are valid for this site
|
||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = []
|
||||
########## END SITE CONFIGURATION
|
||||
|
||||
|
||||
########## FIXTURE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
|
||||
FIXTURE_DIRS = (
|
||||
normpath(join(SITE_ROOT, 'fixtures')),
|
||||
)
|
||||
########## END FIXTURE CONFIGURATION
|
||||
|
||||
|
||||
########## TEMPLATE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.static',
|
||||
'django.core.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.core.context_processors.request',
|
||||
# allauth specific context processors
|
||||
'allauth.account.context_processors.account',
|
||||
'allauth.socialaccount.context_processors.socialaccount',
|
||||
# custom context processors
|
||||
'gnuviechadmin.context_processors.navigation',
|
||||
'gnuviechadmin.context_processors.version_info',
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
|
||||
TEMPLATE_DIRS = (
|
||||
normpath(join(SITE_ROOT, 'templates')),
|
||||
)
|
||||
########## END TEMPLATE CONFIGURATION
|
||||
|
||||
|
||||
########## MIDDLEWARE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes
|
||||
MIDDLEWARE_CLASSES = (
|
||||
# Default Django middleware.
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
# uncomment next line to enable translation to browser locale
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
########## END MIDDLEWARE CONFIGURATION
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
# Needed to login by username in Django admin, regardless of `allauth`
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
|
||||
# `allauth` specific authentication methods, such as login by e-mail
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
)
|
||||
|
||||
|
||||
########## URL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
|
||||
ROOT_URLCONF = '%s.urls' % SITE_NAME
|
||||
########## END URL CONFIGURATION
|
||||
|
||||
|
||||
########## TEST RUNNER CONFIGURATION
|
||||
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
||||
########## END TEST RUNNER CONFIGURATION
|
||||
|
||||
|
||||
########## APP CONFIGURATION
|
||||
DJANGO_APPS = (
|
||||
# Default Django apps:
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# Useful template tags:
|
||||
'django.contrib.humanize',
|
||||
|
||||
# Admin panel and documentation:
|
||||
'django.contrib.admin',
|
||||
|
||||
# Flatpages for about page
|
||||
'django.contrib.flatpages',
|
||||
|
||||
'crispy_forms',
|
||||
)
|
||||
|
||||
ALLAUTH_APPS = (
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth.socialaccount.providers.google',
|
||||
'allauth.socialaccount.providers.linkedin_oauth2',
|
||||
'allauth.socialaccount.providers.twitter',
|
||||
)
|
||||
|
||||
# Apps specific for this project go here.
|
||||
LOCAL_APPS = (
|
||||
'dashboard',
|
||||
'taskresults',
|
||||
'mysqltasks',
|
||||
'pgsqltasks',
|
||||
'fileservertasks',
|
||||
'webtasks',
|
||||
'domains',
|
||||
'osusers',
|
||||
'managemails',
|
||||
'userdbs',
|
||||
'hostingpackages',
|
||||
'websites',
|
||||
'contact_form',
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
INSTALLED_APPS = DJANGO_APPS + ALLAUTH_APPS + LOCAL_APPS
|
||||
|
||||
MESSAGE_TAGS = {
|
||||
messages.DEBUG: '',
|
||||
messages.ERROR: 'alert-danger',
|
||||
messages.INFO: 'alert-info',
|
||||
messages.SUCCESS: 'alert-success',
|
||||
messages.WARNING: 'alert-warning',
|
||||
}
|
||||
########## END APP CONFIGURATION
|
||||
|
||||
|
||||
########## ALLAUTH CONFIGURATION
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
SOCIALACCOUNT_QUERY_EMAIL = True
|
||||
########## END ALLAUTH CONFIGURATION
|
||||
|
||||
|
||||
########## CRISPY FORMS CONFIGURATION
|
||||
CRISPY_TEMPLATE_PACK = 'bootstrap3'
|
||||
########## END CRISPY_FORMS CONFIGURATION
|
||||
|
||||
|
||||
########## LOGGING CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
||||
# A sample logging configuration. The only tangible logging
|
||||
# performed by this configuration is to send an email to
|
||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '%(levelname)s %(asctime)s %(name)s '
|
||||
'%(module)s:%(lineno)d %(process)d %(thread)d %(message)s',
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(name)s:%(lineno)d %(message)s',
|
||||
},
|
||||
},
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
########## END LOGGING CONFIGURATION
|
||||
|
||||
|
||||
########## WSGI CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
||||
WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME
|
||||
########## END WSGI CONFIGURATION
|
||||
|
||||
|
||||
########## CELERY CONFIGURATION
|
||||
BROKER_URL = get_env_variable('GVA_BROKER_URL')
|
||||
CELERY_RESULT_BACKEND = 'amqp'
|
||||
CELERY_RESULT_PERSISTENT = True
|
||||
CELERY_TASK_RESULT_EXPIRES = None
|
||||
CELERY_ROUTES = (
|
||||
'gvacommon.celeryrouters.GvaRouter',
|
||||
)
|
||||
CELERY_TIMEZONE = 'Europe/Berlin'
|
||||
CELERY_ENABLE_UTC = True
|
||||
CELERY_ACCEPT_CONTENT = ['json']
|
||||
CELERY_TASK_SERIALIZER = 'json'
|
||||
CELERY_RESULT_SERIALIZER = 'json'
|
||||
########## END CELERY CONFIGURATION
|
||||
|
||||
|
||||
########## CUSTOM APP CONFIGURATION
|
||||
OSUSER_MINUID = int(get_env_variable('GVA_MIN_OS_UID'))
|
||||
OSUSER_MINGID = int(get_env_variable('GVA_MIN_OS_GID'))
|
||||
OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX')
|
||||
OSUSER_HOME_BASEPATH = get_env_variable('GVA_OSUSER_HOME_BASEPATH')
|
||||
OSUSER_DEFAULT_SHELL = get_env_variable('GVA_OSUSER_DEFAULT_SHELL')
|
||||
OSUSER_SFTP_GROUP = 'sftponly'
|
||||
OSUSER_SSH_GROUP = 'sshusers'
|
||||
OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP]
|
||||
OSUSER_UPLOAD_SERVER = get_env_variable('GVA_OSUSER_UPLOADSERVER')
|
||||
|
||||
GVA_LINK_WEBMAIL = get_env_variable('GVA_WEBMAIL_URL')
|
||||
GVA_LINK_PHPMYADMIN = get_env_variable('GVA_PHPMYADMIN_URL')
|
||||
GVA_LINK_PHPPGADMIN = get_env_variable('GVA_PHPPGADMIN_URL')
|
||||
########## END CUSTOM APP CONFIGURATION
|
|
@ -1,81 +0,0 @@
|
|||
# -*- python -*-
|
||||
# pymode:lint_ignore=W0401,E501
|
||||
"""Development settings and globals."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .base import *
|
||||
|
||||
|
||||
########## DEBUG CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = True
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
########## END DEBUG CONFIGURATION
|
||||
|
||||
|
||||
########## EMAIL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
########## END EMAIL CONFIGURATION
|
||||
|
||||
|
||||
########## CACHE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
}
|
||||
}
|
||||
########## END CACHE CONFIGURATION
|
||||
|
||||
|
||||
########## TOOLBAR CONFIGURATION
|
||||
# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup
|
||||
INSTALLED_APPS += (
|
||||
'debug_toolbar',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES += (
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
)
|
||||
|
||||
LOGGING['handlers'].update({
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
}
|
||||
})
|
||||
LOGGING['loggers'].update({
|
||||
'gnuviechadmin': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'dashboard': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'domains': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'gvacommon': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'gvawebcore': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'hostingpackages': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'managemails': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'osusers': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'taskresults': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'userdbs': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
'websites': {
|
||||
'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,},
|
||||
})
|
||||
|
||||
DEBUG_TOOLBAR_PATCH_SETTINGS = False
|
||||
|
||||
# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
|
||||
INTERNAL_IPS = ('127.0.0.1', '10.0.2.2')
|
||||
########## END TOOLBAR CONFIGURATION
|
|
@ -1,36 +0,0 @@
|
|||
# -*- python -*-
|
||||
# pymode:lint_ignore=W0401,E501
|
||||
"""Production settings and globals."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .base import *
|
||||
|
||||
########## HOST CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production
|
||||
ALLOWED_HOSTS = [SITES_DOMAIN_NAME]
|
||||
########## END HOST CONFIGURATION
|
||||
|
||||
########## EMAIL CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
|
||||
EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
|
||||
DEFAULT_FROM_EMAIL = get_env_variable('GVA_SITE_ADMINMAIL')
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
|
||||
SERVER_EMAIL = get_env_variable('GVA_SITE_ADMINMAIL')
|
||||
########## END EMAIL CONFIGURATION
|
||||
|
||||
########## CACHE CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||
#CACHES = {}
|
||||
########## END CACHE CONFIGURATION
|
||||
|
||||
########## ALLAUTH PRODUCTION CONFIGURATION
|
||||
ACCOUNT_EMAIL_SUBJECT_PREFIX = '[Jan Dittberner IT-Consulting & -Solutions] '
|
||||
ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https'
|
||||
########## END ALLAUTH PRODUCTION CONFIGURATION
|
|
@ -1,3 +0,0 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from .base import *
|
|
@ -1,28 +0,0 @@
|
|||
import os
|
||||
from unittest import TestCase
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from gnuviechadmin.settings.base import get_env_variable
|
||||
|
||||
|
||||
class GetEnvVariableTest(TestCase):
|
||||
|
||||
def test_get_existing_env_variable(self):
|
||||
os.environ['testvariable'] = 'myvalue'
|
||||
self.assertEqual(get_env_variable('testvariable'), 'myvalue')
|
||||
|
||||
def test_get_missing_env_variable(self):
|
||||
if 'missingvariable' in os.environ:
|
||||
del os.environ['missingvariable']
|
||||
with self.assertRaises(ImproperlyConfigured) as e:
|
||||
get_env_variable('missingvariable')
|
||||
self.assertEqual(
|
||||
str(e.exception), 'Set the missingvariable environment variable')
|
||||
|
||||
|
||||
class WSGITest(TestCase):
|
||||
|
||||
def test_wsgi_application(self):
|
||||
from gnuviechadmin import wsgi
|
||||
self.assertIsNotNone(wsgi.application)
|
2
gnuviechadmin/gnuviechadmin/tests/__init__.py
Normal file
2
gnuviechadmin/gnuviechadmin/tests/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
# -*- python -*-
|
||||
# -*- coding: utf-8 -*-
|
10
gnuviechadmin/gnuviechadmin/tests/test_celery.py
Normal file
10
gnuviechadmin/gnuviechadmin/tests/test_celery.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from gnuviechadmin.celery import get_installed_apps
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class GetInstalledAppsTest(TestCase):
|
||||
|
||||
def test_get_installed_apps(self):
|
||||
self.assertEqual(get_installed_apps(), settings.INSTALLED_APPS)
|
111
gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py
Normal file
111
gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
# -*- python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module contains tests for :py:mod:`gnuviechadmin.context_processors`.
|
||||
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from gnuviechadmin import __version__ as gvaversion
|
||||
from gnuviechadmin.context_processors import navigation
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class NavigationContextProcessorTest(TestCase):
|
||||
|
||||
EXPECTED_ITEMS = ("webmail_url", "phpmyadmin_url", "phppgadmin_url", "active_item")
|
||||
|
||||
def test_ajax_request(self):
|
||||
response = self.client.get("/", HTTP_X_REQUESTED_WITH="XMLHttpRequest")
|
||||
for item in self.EXPECTED_ITEMS:
|
||||
self.assertNotIn(item, response.context)
|
||||
|
||||
def _check_static_urls(self, context):
|
||||
self.assertEqual(context["webmail_url"], settings.GVA_LINK_WEBMAIL)
|
||||
self.assertEqual(context["phpmyadmin_url"], settings.GVA_LINK_PHPMYADMIN)
|
||||
self.assertEqual(context["phppgadmin_url"], settings.GVA_LINK_PHPPGADMIN)
|
||||
|
||||
def test_index_page_context(self):
|
||||
response = self.client.get("/")
|
||||
for item in self.EXPECTED_ITEMS:
|
||||
self.assertIn(item, response.context)
|
||||
self._check_static_urls(response.context)
|
||||
self.assertEqual(response.context["active_item"], "dashboard")
|
||||
|
||||
def test_contact_page_context(self):
|
||||
response = self.client.get(reverse("contact_form"))
|
||||
for item in self.EXPECTED_ITEMS:
|
||||
self.assertIn(item, response.context)
|
||||
self._check_static_urls(response.context)
|
||||
self.assertEqual(response.context["active_item"], "contact")
|
||||
|
||||
def test_hostingpackage_page_context(self):
|
||||
User.objects.create_user("test", password="test")
|
||||
self.client.login(username="test", password="test")
|
||||
response = self.client.get(reverse("hosting_packages", kwargs={"user": "test"}))
|
||||
for item in self.EXPECTED_ITEMS:
|
||||
self.assertIn(item, response.context)
|
||||
self._check_static_urls(response.context)
|
||||
self.assertEqual(response.context["active_item"], "hostingpackage")
|
||||
|
||||
def _test_page_context_by_viewmodule(self, viewmodule, expecteditem):
|
||||
request = HttpRequest()
|
||||
request.resolver_match = MagicMock()
|
||||
request.resolver_match.func.__module__ = viewmodule
|
||||
context = navigation(request)
|
||||
for item in self.EXPECTED_ITEMS:
|
||||
self.assertIn(item, context)
|
||||
self._check_static_urls(context)
|
||||
self.assertEqual(context["active_item"], expecteditem)
|
||||
|
||||
def test_osusers_page_context(self):
|
||||
self._test_page_context_by_viewmodule("osusers.views", "hostingpackage")
|
||||
|
||||
def test_userdbs_page_context(self):
|
||||
self._test_page_context_by_viewmodule("userdbs.views", "hostingpackage")
|
||||
|
||||
def test_managemails_page_context(self):
|
||||
self._test_page_context_by_viewmodule("managemails.views", "hostingpackage")
|
||||
|
||||
def test_websites_page_context(self):
|
||||
self._test_page_context_by_viewmodule("websites.views", "hostingpackage")
|
||||
|
||||
def test_domains_page_context(self):
|
||||
self._test_page_context_by_viewmodule("domains.views", "hostingpackage")
|
||||
|
||||
def test_allauth_account_page_context(self):
|
||||
self._test_page_context_by_viewmodule("allauth.account.views", "account")
|
||||
|
||||
def test_allauth_socialaccount_page_context(self):
|
||||
self._test_page_context_by_viewmodule("allauth.socialaccount.views", "account")
|
||||
|
||||
def test_imprint_page_context(self):
|
||||
response = self.client.get(reverse("imprint"))
|
||||
for item in self.EXPECTED_ITEMS:
|
||||
self.assertIn(item, response.context)
|
||||
self._check_static_urls(response.context)
|
||||
self.assertEqual(response.context["active_item"], "imprint")
|
||||
|
||||
def test_no_resolver_match(self):
|
||||
request = HttpRequest()
|
||||
context = navigation(request)
|
||||
self._check_static_urls(context)
|
||||
self.assertEqual(context["active_item"], "dashboard")
|
||||
|
||||
def test_admin_module(self):
|
||||
self._test_page_context_by_viewmodule("django.contrib.admin.foo", "dashboard")
|
||||
|
||||
|
||||
class VersionInfoContextProcessorTest(TestCase):
|
||||
def test_version_info_in_context(self):
|
||||
response = self.client.get("/")
|
||||
self.assertIn("gnuviechadmin_version", response.context)
|
||||
self.assertEqual(response.context["gnuviechadmin_version"], gvaversion)
|
10
gnuviechadmin/gnuviechadmin/tests/test_wsgi.py
Normal file
10
gnuviechadmin/gnuviechadmin/tests/test_wsgi.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
# -*- python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class WSGITest(TestCase):
|
||||
|
||||
def test_wsgi_application(self):
|
||||
from gnuviechadmin import wsgi
|
||||
self.assertIsNotNone(wsgi.application)
|
|
@ -1,13 +1,15 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import include, url
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.flatpages import views
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(r'', include('dashboard.urls')),
|
||||
url(r'^accounts/', include('allauth.urls')),
|
||||
url(r'^database/', include('userdbs.urls')),
|
||||
|
@ -16,19 +18,19 @@ urlpatterns = patterns(
|
|||
url(r'^website/', include('websites.urls')),
|
||||
url(r'^mail/', include('managemails.urls')),
|
||||
url(r'^osuser/', include('osusers.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^contact/', include('contact_form.urls')),
|
||||
)
|
||||
urlpatterns += patterns(
|
||||
'django.contrib.flatpages.views',
|
||||
url(r'^impressum/$', 'flatpage', {'url': '/impressum/'}, name='imprint'),
|
||||
)
|
||||
url(r'^impressum/$', views.flatpage, {
|
||||
'url': '/impressum/'
|
||||
}, name='imprint'),
|
||||
]
|
||||
|
||||
# Uncomment the next line to serve media files in dev.
|
||||
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG: # pragma: no cover
|
||||
import debug_toolbar
|
||||
urlpatterns += patterns('',
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
)
|
||||
] + staticfiles_urlpatterns() + urlpatterns
|
||||
|
|
|
@ -24,12 +24,13 @@ path.append(SITE_ROOT)
|
|||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "jajaja.settings"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.production")
|
||||
os.environ.setdefault(
|
||||
"DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.production")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from django.core.wsgi import get_wsgi_application # noqa
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
|
|
3
gnuviechadmin/gvacommon/.gitignore
vendored
3
gnuviechadmin/gvacommon/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
.*.swp
|
||||
*.pyc
|
||||
.ropeproject/
|
|
@ -1,15 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class GvaRouter(object):
|
||||
|
||||
def route_for_task(self, task, args=None, kwargs=None):
|
||||
for route in ['ldap', 'file', 'mysql', 'pgsql', 'web']:
|
||||
if route in task:
|
||||
return {
|
||||
'exchange': route,
|
||||
'exchange_type': 'direct',
|
||||
'queue': route,
|
||||
}
|
||||
return None
|
|
@ -1,24 +0,0 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gvacommon\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"PO-Revision-Date: 2015-01-24 18:25+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\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"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: gvacommon/viewmixins.py:29
|
||||
msgid "You are not allowed to view this page."
|
||||
msgstr "Sie haben nicht die nötigen Berechtigungen um diese Seite zu sehen."
|
|
@ -1,42 +0,0 @@
|
|||
"""
|
||||
This module defines mixins for gnuviechadmin views.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from braces.views import LoginRequiredMixin
|
||||
|
||||
|
||||
class StaffOrSelfLoginRequiredMixin(LoginRequiredMixin):
|
||||
"""
|
||||
Mixin that makes sure that a user is logged in and matches the current
|
||||
customer or is a staff user.
|
||||
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if (
|
||||
request.user.is_staff or
|
||||
request.user == self.get_customer_object()
|
||||
):
|
||||
return super(StaffOrSelfLoginRequiredMixin, self).dispatch(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
return HttpResponseForbidden(
|
||||
_('You are not allowed to view this page.')
|
||||
)
|
||||
|
||||
def get_customer_object(self):
|
||||
"""
|
||||
Views based on this mixin have to implement this method to return
|
||||
the customer that must be an object of the same class as the
|
||||
django.contrib.auth user type.
|
||||
|
||||
:return: customer
|
||||
:rtype: settings.AUTH_USER_MODEL
|
||||
|
||||
"""
|
||||
raise NotImplemented("subclass has to implement get_customer_object")
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gvawebcore\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"POT-Creation-Date: 2016-01-29 11:04+0100\n"
|
||||
"PO-Revision-Date: 2015-01-25 11:49+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
|
|
0
gnuviechadmin/gvawebcore/tests/__init__.py
Normal file
0
gnuviechadmin/gvawebcore/tests/__init__.py
Normal file
30
gnuviechadmin/gvawebcore/tests/test_forms.py
Normal file
30
gnuviechadmin/gvawebcore/tests/test_forms.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
This module contains tests for :py:mod:`gvawebcore.forms`.
|
||||
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from gvawebcore.forms import PASSWORD_MISMATCH_ERROR, PasswordModelFormMixin
|
||||
|
||||
|
||||
class PasswordModelFormMixinTest(TestCase):
|
||||
def test_form_properties(self):
|
||||
form = PasswordModelFormMixin()
|
||||
self.assertIn("password1", form.fields)
|
||||
self.assertIn("password2", form.fields)
|
||||
|
||||
def test_clean_password_same(self):
|
||||
form = PasswordModelFormMixin(
|
||||
data={"password1": "secret", "password2": "secret"}
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual("secret", form.clean_password2())
|
||||
|
||||
def test_clean_password_different(self):
|
||||
form = PasswordModelFormMixin(
|
||||
data={"password1": "onesecret", "password2": "other"}
|
||||
)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("password2", form.errors)
|
||||
self.assertIn(PASSWORD_MISMATCH_ERROR, form.errors["password2"])
|
32
gnuviechadmin/gvawebcore/tests/test_views.py
Normal file
32
gnuviechadmin/gvawebcore/tests/test_views.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""
|
||||
This model contains tests for :py:mod:`gvawebcore.views`.
|
||||
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from gvawebcore.views import HostingPackageAndCustomerMixin
|
||||
|
||||
|
||||
class HostingPackageAndCustomerMixinTest(TestCase):
|
||||
class TestView(HostingPackageAndCustomerMixin):
|
||||
|
||||
kwargs = {"package": "1"}
|
||||
|
||||
@patch("gvawebcore.views.get_object_or_404")
|
||||
def test_get_hosting_package(self, get_object_or_404):
|
||||
get_object_or_404.return_value = "A package"
|
||||
view = self.TestView()
|
||||
self.assertEqual("A package", view.get_hosting_package())
|
||||
|
||||
def test_get_hosting_package_cached(self):
|
||||
view = self.TestView()
|
||||
view.hostingpackage = "Cached package"
|
||||
self.assertEqual("Cached package", view.get_hosting_package())
|
||||
|
||||
@patch("gvawebcore.views.get_object_or_404")
|
||||
def test_get_customer_object(self, get_object_or_404):
|
||||
get_object_or_404.return_value = Mock(customer="A customer")
|
||||
view = self.TestView()
|
||||
self.assertEqual("A customer", view.get_customer_object())
|
|
@ -5,7 +5,7 @@ This module contains the form classes related to hosting packages.
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gnuviechadmin hostingpackages\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"POT-Creation-Date: 2016-01-29 11:04+0100\n"
|
||||
"PO-Revision-Date: 2015-01-25 15:49+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
|
@ -167,67 +167,67 @@ msgstr "Kundenhostingpakete"
|
|||
msgid "{name} for {customer}"
|
||||
msgstr "{name} für {customer}"
|
||||
|
||||
#: hostingpackages/models.py:405 hostingpackages/models.py:427
|
||||
#: hostingpackages/models.py:404 hostingpackages/models.py:426
|
||||
msgid "hosting package"
|
||||
msgstr "Hostingpaket"
|
||||
|
||||
#: hostingpackages/models.py:408
|
||||
#: hostingpackages/models.py:407
|
||||
msgid "hosting domain"
|
||||
msgstr "Hostingdomain"
|
||||
|
||||
#: hostingpackages/models.py:430
|
||||
#: hostingpackages/models.py:429
|
||||
msgid "customer hosting option"
|
||||
msgstr "kundenspezifische Hostingoption"
|
||||
|
||||
#: hostingpackages/models.py:431
|
||||
#: hostingpackages/models.py:430
|
||||
msgid "customer hosting options"
|
||||
msgstr "kundenspezifische Hostingoptionen"
|
||||
|
||||
#: hostingpackages/models.py:443
|
||||
#: hostingpackages/models.py:442
|
||||
msgid "disk space option template"
|
||||
msgstr "Speicherplatzoptionsvorlage"
|
||||
|
||||
#: hostingpackages/models.py:445
|
||||
#: hostingpackages/models.py:444
|
||||
msgid "The disk space option template that this disk space option is based on"
|
||||
msgstr ""
|
||||
"Die Speicherplatzoptionsvorlage auf der diese Speicherplatzoption aufgebaut "
|
||||
"ist"
|
||||
|
||||
#: hostingpackages/models.py:459
|
||||
#: hostingpackages/models.py:458
|
||||
msgid "user database option template"
|
||||
msgstr "Nutzerdatenbankoptionsvorlage"
|
||||
|
||||
#: hostingpackages/models.py:461
|
||||
#: hostingpackages/models.py:460
|
||||
msgid "The user database option template that this database option is based on"
|
||||
msgstr ""
|
||||
"Die Nutzerdatenbankoptionsvorlage auf der diese Datenbankoption aufgebaut ist"
|
||||
|
||||
#: hostingpackages/models.py:475
|
||||
#: hostingpackages/models.py:474
|
||||
msgid "mailbox option template"
|
||||
msgstr "Postfachoptionsvorlage"
|
||||
|
||||
#: hostingpackages/models.py:477
|
||||
#: hostingpackages/models.py:476
|
||||
msgid "The mailbox option template that this mailbox option is based on"
|
||||
msgstr "Die Postfachoptionsvorlage auf der diese Postfachoption aufgebaut ist"
|
||||
|
||||
#: hostingpackages/views.py:60 hostingpackages/views.py:93
|
||||
#: hostingpackages/views.py:60 hostingpackages/views.py:94
|
||||
#, python-brace-format
|
||||
msgid "Started setup of new hosting package {name}."
|
||||
msgstr "Einrichtung des Hostingpakets {name} wurde gestartet."
|
||||
|
||||
#: hostingpackages/views.py:152
|
||||
#: hostingpackages/views.py:186
|
||||
msgid "Disk space"
|
||||
msgstr "Speicherplatz"
|
||||
|
||||
#: hostingpackages/views.py:155
|
||||
#: hostingpackages/views.py:189
|
||||
msgid "Mailboxes"
|
||||
msgstr "Postfächer"
|
||||
|
||||
#: hostingpackages/views.py:158
|
||||
#: hostingpackages/views.py:192
|
||||
msgid "Databases"
|
||||
msgstr "Datenbanken"
|
||||
|
||||
#: hostingpackages/views.py:228
|
||||
#: hostingpackages/views.py:262
|
||||
#, python-brace-format
|
||||
msgid "Successfully added option {option} to hosting package {package}."
|
||||
msgstr "Option {option} erfolgreich zum Hostingpaket {package} hinzugefügt."
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
import model_utils.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
@ -17,15 +16,30 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerHostingPackage',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('name', models.CharField(
|
||||
unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(
|
||||
verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(
|
||||
verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
help_text='disk space for the hosting package',
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('customer', models.ForeignKey(
|
||||
verbose_name='customer', to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'customer hosting package',
|
||||
|
@ -36,9 +50,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerHostingPackageOption',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'customer hosting option',
|
||||
|
@ -49,9 +69,16 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerDiskSpaceOption',
|
||||
fields=[
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
|
||||
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False,
|
||||
to='hostingpackages.CustomerHostingPackageOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['diskspace_unit', 'diskspace'],
|
||||
|
@ -59,13 +86,19 @@ class Migration(migrations.Migration):
|
|||
'verbose_name': 'Disk space option',
|
||||
'verbose_name_plural': 'Disk space options',
|
||||
},
|
||||
bases=('hostingpackages.customerhostingpackageoption', models.Model),
|
||||
bases=(
|
||||
'hostingpackages.customerhostingpackageoption', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomerMailboxOption',
|
||||
fields=[
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
|
||||
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False,
|
||||
to='hostingpackages.CustomerHostingPackageOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
unique=True, verbose_name='number of mailboxes')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['number'],
|
||||
|
@ -73,14 +106,22 @@ class Migration(migrations.Migration):
|
|||
'verbose_name': 'Mailbox option',
|
||||
'verbose_name_plural': 'Mailbox options',
|
||||
},
|
||||
bases=('hostingpackages.customerhostingpackageoption', models.Model),
|
||||
bases=(
|
||||
'hostingpackages.customerhostingpackageoption', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomerUserDatabaseOption',
|
||||
fields=[
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
|
||||
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False,
|
||||
to='hostingpackages.CustomerHostingPackageOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(
|
||||
verbose_name='database type',
|
||||
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['db_type', 'number'],
|
||||
|
@ -88,14 +129,21 @@ class Migration(migrations.Migration):
|
|||
'verbose_name': 'Database option',
|
||||
'verbose_name_plural': 'Database options',
|
||||
},
|
||||
bases=('hostingpackages.customerhostingpackageoption', models.Model),
|
||||
bases=(
|
||||
'hostingpackages.customerhostingpackageoption', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HostingOption',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hosting option',
|
||||
|
@ -106,9 +154,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='DiskSpaceOption',
|
||||
fields=[
|
||||
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
|
||||
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('hostingoption_ptr', models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False, to='hostingpackages.HostingOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['diskspace_unit', 'diskspace'],
|
||||
|
@ -121,14 +175,27 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='HostingPackageTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('name', models.CharField(
|
||||
unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(
|
||||
verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(
|
||||
verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
help_text='disk space for the hosting package',
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hosting package',
|
||||
|
@ -139,8 +206,12 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MailboxOption',
|
||||
fields=[
|
||||
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
|
||||
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
|
||||
('hostingoption_ptr', models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False, to='hostingpackages.HostingOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
unique=True, verbose_name='number of mailboxes')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['number'],
|
||||
|
@ -153,9 +224,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='UserDatabaseOption',
|
||||
fields=[
|
||||
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
|
||||
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
('hostingoption_ptr', models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False, to='hostingpackages.HostingOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(
|
||||
verbose_name='database type',
|
||||
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['db_type', 'number'],
|
||||
|
@ -167,48 +244,71 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userdatabaseoption',
|
||||
unique_together=set([('number', 'db_type')]),
|
||||
unique_together={('number', 'db_type')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='diskspaceoption',
|
||||
unique_together=set([('diskspace', 'diskspace_unit')]),
|
||||
unique_together={('diskspace', 'diskspace_unit')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customeruserdatabaseoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this hosting option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='user database option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The user database option template that this '
|
||||
'hosting option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customeruserdatabaseoption',
|
||||
unique_together=set([('number', 'db_type')]),
|
||||
unique_together={('number', 'db_type')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customermailboxoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this hosting option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='mailbox option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The mailbox option template that this hosting '
|
||||
'option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerhostingpackageoption',
|
||||
name='hosting_package',
|
||||
field=models.ForeignKey(verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='hosting package',
|
||||
to='hostingpackages.CustomerHostingPackage',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerhostingpackage',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on.'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='hosting package template',
|
||||
to='hostingpackages.HostingPackageTemplate',
|
||||
help_text='The hosting package template that this hosting '
|
||||
'package is based on.',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerdiskspaceoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this hosting option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='disk space option template',
|
||||
to='hostingpackages.DiskSpaceOption',
|
||||
help_text='The disk space option template that this hosting '
|
||||
'option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customerdiskspaceoption',
|
||||
unique_together=set([('diskspace', 'diskspace_unit')]),
|
||||
unique_together={('diskspace', 'diskspace_unit')},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
import model_utils.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('hostingpackages', '0001_initial'), ('hostingpackages', '0002_auto_20150118_1149'), ('hostingpackages', '0003_auto_20150118_1221'), ('hostingpackages', '0004_customerhostingpackage_osuser'), ('hostingpackages', '0005_auto_20150118_1303')]
|
||||
replaces = [('hostingpackages', '0001_initial'),
|
||||
('hostingpackages', '0002_auto_20150118_1149'),
|
||||
('hostingpackages', '0003_auto_20150118_1221'),
|
||||
('hostingpackages', '0004_customerhostingpackage_osuser'),
|
||||
('hostingpackages', '0005_auto_20150118_1303')]
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
|
@ -20,15 +23,30 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerHostingPackage',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('name', models.CharField(
|
||||
unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(
|
||||
verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(
|
||||
verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
help_text='disk space for the hosting package',
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('customer', models.ForeignKey(
|
||||
verbose_name='customer',
|
||||
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'customer hosting package',
|
||||
|
@ -39,9 +57,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerHostingPackageOption',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'customer hosting option',
|
||||
|
@ -52,9 +76,17 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerDiskSpaceOption',
|
||||
fields=[
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
|
||||
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('customerhostingpackageoption_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False,
|
||||
to='hostingpackages.CustomerHostingPackageOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['diskspace_unit', 'diskspace'],
|
||||
|
@ -62,13 +94,20 @@ class Migration(migrations.Migration):
|
|||
'verbose_name': 'Disk space option',
|
||||
'verbose_name_plural': 'Disk space options',
|
||||
},
|
||||
bases=('hostingpackages.customerhostingpackageoption', models.Model),
|
||||
bases=(
|
||||
'hostingpackages.customerhostingpackageoption', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomerMailboxOption',
|
||||
fields=[
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
|
||||
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
|
||||
('customerhostingpackageoption_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False,
|
||||
to='hostingpackages.CustomerHostingPackageOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
unique=True, verbose_name='number of mailboxes')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['number'],
|
||||
|
@ -76,14 +115,23 @@ class Migration(migrations.Migration):
|
|||
'verbose_name': 'Mailbox option',
|
||||
'verbose_name_plural': 'Mailbox options',
|
||||
},
|
||||
bases=('hostingpackages.customerhostingpackageoption', models.Model),
|
||||
bases=(
|
||||
'hostingpackages.customerhostingpackageoption', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomerUserDatabaseOption',
|
||||
fields=[
|
||||
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
|
||||
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
('customerhostingpackageoption_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False,
|
||||
to='hostingpackages.CustomerHostingPackageOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(
|
||||
verbose_name='database type',
|
||||
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['db_type', 'number'],
|
||||
|
@ -91,14 +139,21 @@ class Migration(migrations.Migration):
|
|||
'verbose_name': 'Database option',
|
||||
'verbose_name_plural': 'Database options',
|
||||
},
|
||||
bases=('hostingpackages.customerhostingpackageoption', models.Model),
|
||||
bases=(
|
||||
'hostingpackages.customerhostingpackageoption', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HostingOption',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hosting option',
|
||||
|
@ -109,9 +164,16 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='DiskSpaceOption',
|
||||
fields=[
|
||||
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
|
||||
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('hostingoption_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False, to='hostingpackages.HostingOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['diskspace_unit', 'diskspace'],
|
||||
|
@ -124,14 +186,27 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='HostingPackageTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('name', models.CharField(
|
||||
unique=True, max_length=128, verbose_name='name')),
|
||||
('description', models.TextField(
|
||||
verbose_name='description', blank=True)),
|
||||
('mailboxcount', models.PositiveIntegerField(
|
||||
verbose_name='mailbox count')),
|
||||
('diskspace', models.PositiveIntegerField(
|
||||
help_text='disk space for the hosting package',
|
||||
verbose_name='disk space')),
|
||||
('diskspace_unit', models.PositiveSmallIntegerField(
|
||||
verbose_name='unit of disk space',
|
||||
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hosting package',
|
||||
|
@ -142,8 +217,13 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MailboxOption',
|
||||
fields=[
|
||||
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
|
||||
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
|
||||
('hostingoption_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False, to='hostingpackages.HostingOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
unique=True, verbose_name='number of mailboxes')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['number'],
|
||||
|
@ -156,9 +236,17 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='UserDatabaseOption',
|
||||
fields=[
|
||||
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
|
||||
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
|
||||
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
('hostingoption_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True, auto_created=True, primary_key=True,
|
||||
serialize=False, to='hostingpackages.HostingOption',
|
||||
on_delete=models.CASCADE)),
|
||||
('number', models.PositiveIntegerField(
|
||||
default=1, verbose_name='number of databases')),
|
||||
('db_type',
|
||||
models.PositiveSmallIntegerField(
|
||||
verbose_name='database type',
|
||||
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
|
||||
],
|
||||
options={
|
||||
'ordering': ['db_type', 'number'],
|
||||
|
@ -170,60 +258,93 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userdatabaseoption',
|
||||
unique_together=set([('number', 'db_type')]),
|
||||
unique_together={('number', 'db_type')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='diskspaceoption',
|
||||
unique_together=set([('diskspace', 'diskspace_unit')]),
|
||||
unique_together={('diskspace', 'diskspace_unit')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customeruserdatabaseoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this hosting option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='user database option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The user database option template that this '
|
||||
'hosting option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customeruserdatabaseoption',
|
||||
unique_together=set([('number', 'db_type')]),
|
||||
unique_together={('number', 'db_type')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customermailboxoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this mailbox option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='mailbox option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The mailbox option template that this mailbox '
|
||||
'option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerhostingpackageoption',
|
||||
name='hosting_package',
|
||||
field=models.ForeignKey(verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='hosting package',
|
||||
to='hostingpackages.CustomerHostingPackage',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerhostingpackage',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='hosting package template',
|
||||
to='hostingpackages.HostingPackageTemplate',
|
||||
help_text='The hosting package template that this hosting '
|
||||
'package is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerdiskspaceoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this hosting option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='disk space option template',
|
||||
to='hostingpackages.DiskSpaceOption',
|
||||
help_text='The disk space option template that this hosting '
|
||||
'option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customerdiskspaceoption',
|
||||
unique_together=set([('diskspace', 'diskspace_unit')]),
|
||||
unique_together={('diskspace', 'diskspace_unit')},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customerdiskspaceoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this disk space option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='disk space option template',
|
||||
to='hostingpackages.DiskSpaceOption',
|
||||
help_text='The disk space option template that this disk '
|
||||
'space option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customeruserdatabaseoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this database option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='user database option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The user database option template that this '
|
||||
'database option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
@ -234,12 +355,14 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customerhostingpackage',
|
||||
unique_together=set([('customer', 'name')]),
|
||||
unique_together={('customer', 'name')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerhostingpackage',
|
||||
name='osuser',
|
||||
field=models.OneToOneField(null=True, blank=True, to='osusers.User', verbose_name='Operating system user'),
|
||||
field=models.OneToOneField(
|
||||
null=True, blank=True, to='osusers.User',
|
||||
verbose_name='Operating system user', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hostingpackages', '0001_initial'),
|
||||
]
|
||||
|
@ -14,25 +13,45 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='customerdiskspaceoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this disk space option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='disk space option template',
|
||||
to='hostingpackages.DiskSpaceOption',
|
||||
help_text='The disk space option template that this disk '
|
||||
'space option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customerhostingpackage',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='hosting package template',
|
||||
to='hostingpackages.HostingPackageTemplate',
|
||||
help_text='The hosting package template that this hosting '
|
||||
'package is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customermailboxoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this mailbox option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='mailbox option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The mailbox option template that this mailbox '
|
||||
'option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customeruserdatabaseoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this database option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='user database option template',
|
||||
to='hostingpackages.UserDatabaseOption',
|
||||
help_text='The user database option template that this '
|
||||
'database option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hostingpackages', '0002_auto_20150118_1319'),
|
||||
]
|
||||
|
@ -14,7 +13,12 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='customermailboxoption',
|
||||
name='template',
|
||||
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.MailboxOption', help_text='The mailbox option template that this mailbox option is based on'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='mailbox option template',
|
||||
to='hostingpackages.MailboxOption',
|
||||
help_text='The mailbox option template that this mailbox '
|
||||
'option is based on',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('osusers', '0004_auto_20150104_1751'),
|
||||
('hostingpackages', '0003_auto_20150118_1221'),
|
||||
|
@ -15,7 +14,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='customerhostingpackage',
|
||||
name='osuser',
|
||||
field=models.ForeignKey(verbose_name='Operating system user', blank=True, to='osusers.User', null=True),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='Operating system user', blank=True,
|
||||
to='osusers.User', null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('domains', '0002_auto_20150124_1909'),
|
||||
('hostingpackages', '0003_auto_20150118_1407'),
|
||||
|
@ -17,11 +16,22 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CustomerHostingPackageDomain',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('domain', models.OneToOneField(verbose_name='hosting domain', to='domains.HostingDomain')),
|
||||
('hosting_package', models.ForeignKey(related_name='domains', verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage')),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('domain', models.OneToOneField(
|
||||
verbose_name='hosting domain', to='domains.HostingDomain',
|
||||
on_delete=models.CASCADE)),
|
||||
('hosting_package', models.ForeignKey(
|
||||
related_name='domains', verbose_name='hosting package',
|
||||
to='hostingpackages.CustomerHostingPackage',
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hostingpackages', '0004_customerhostingpackage_osuser'),
|
||||
]
|
||||
|
@ -14,7 +13,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='customerhostingpackage',
|
||||
name='osuser',
|
||||
field=models.OneToOneField(null=True, blank=True, to='osusers.User', verbose_name='Operating system user'),
|
||||
field=models.OneToOneField(
|
||||
null=True, blank=True, to='osusers.User',
|
||||
verbose_name='Operating system user', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,9 +5,9 @@ This module contains the hosting package models.
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||
|
||||
|
@ -16,38 +16,24 @@ from model_utils.models import TimeStampedModel
|
|||
|
||||
from domains.models import HostingDomain
|
||||
from managemails.models import Mailbox
|
||||
from osusers.models import (
|
||||
AdditionalGroup,
|
||||
Group,
|
||||
User as OsUser,
|
||||
)
|
||||
from userdbs.models import (
|
||||
DB_TYPES,
|
||||
UserDatabase,
|
||||
)
|
||||
from osusers.models import AdditionalGroup, Group, User as OsUser
|
||||
from userdbs.models import DB_TYPES, UserDatabase
|
||||
|
||||
DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("TiB")))
|
||||
|
||||
DISK_SPACE_UNITS = Choices(
|
||||
(0, 'M', _('MiB')),
|
||||
(1, 'G', _('GiB')),
|
||||
(2, 'T', _('TiB')),
|
||||
)
|
||||
|
||||
DISK_SPACE_FACTORS = (
|
||||
(1, None, None),
|
||||
(1024, 1, None),
|
||||
(1024 * 1024, 1024, 1),
|
||||
)
|
||||
DISK_SPACE_FACTORS = ((1, None, None), (1024, 1, None), (1024 * 1024, 1024, 1))
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class HostingPackageBase(TimeStampedModel):
|
||||
description = models.TextField(_('description'), blank=True)
|
||||
mailboxcount = models.PositiveIntegerField(_('mailbox count'))
|
||||
description = models.TextField(_("description"), blank=True)
|
||||
mailboxcount = models.PositiveIntegerField(_("mailbox count"))
|
||||
diskspace = models.PositiveIntegerField(
|
||||
_('disk space'), help_text=_('disk space for the hosting package'))
|
||||
_("disk space"), help_text=_("disk space for the hosting package")
|
||||
)
|
||||
diskspace_unit = models.PositiveSmallIntegerField(
|
||||
_('unit of disk space'), choices=DISK_SPACE_UNITS)
|
||||
_("unit of disk space"), choices=DISK_SPACE_UNITS
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -57,11 +43,11 @@ class HostingPackageBase(TimeStampedModel):
|
|||
|
||||
|
||||
class HostingPackageTemplate(HostingPackageBase):
|
||||
name = models.CharField(_('name'), max_length=128, unique=True)
|
||||
name = models.CharField(_("name"), max_length=128, unique=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Hosting package')
|
||||
verbose_name_plural = _('Hosting packages')
|
||||
verbose_name = _("Hosting package")
|
||||
verbose_name_plural = _("Hosting packages")
|
||||
|
||||
|
||||
class HostingOption(TimeStampedModel):
|
||||
|
@ -73,19 +59,21 @@ class HostingOption(TimeStampedModel):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class DiskSpaceOptionBase(models.Model):
|
||||
diskspace = models.PositiveIntegerField(_('disk space'))
|
||||
diskspace = models.PositiveIntegerField(_("disk space"))
|
||||
diskspace_unit = models.PositiveSmallIntegerField(
|
||||
_('unit of disk space'), choices=DISK_SPACE_UNITS)
|
||||
_("unit of disk space"), choices=DISK_SPACE_UNITS
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ['diskspace_unit', 'diskspace']
|
||||
verbose_name = _('Disk space option')
|
||||
verbose_name_plural = _('Disk space options')
|
||||
ordering = ["diskspace_unit", "diskspace"]
|
||||
verbose_name = _("Disk space option")
|
||||
verbose_name_plural = _("Disk space options")
|
||||
|
||||
def __str__(self):
|
||||
return _("Additional disk space {space} {unit}").format(
|
||||
space=self.diskspace, unit=self.get_diskspace_unit_display())
|
||||
space=self.diskspace, unit=self.get_diskspace_unit_display()
|
||||
)
|
||||
|
||||
|
||||
class DiskSpaceOption(DiskSpaceOptionBase, HostingOption):
|
||||
|
@ -94,31 +82,26 @@ class DiskSpaceOption(DiskSpaceOptionBase, HostingOption):
|
|||
existing hosting packages.
|
||||
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
unique_together = ['diskspace', 'diskspace_unit']
|
||||
unique_together = ["diskspace", "diskspace_unit"]
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class UserDatabaseOptionBase(models.Model):
|
||||
number = models.PositiveIntegerField(
|
||||
_('number of databases'), default=1)
|
||||
db_type = models.PositiveSmallIntegerField(
|
||||
_('database type'), choices=DB_TYPES)
|
||||
number = models.PositiveIntegerField(_("number of databases"), default=1)
|
||||
db_type = models.PositiveSmallIntegerField(_("database type"), choices=DB_TYPES)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ['db_type', 'number']
|
||||
verbose_name = _('Database option')
|
||||
verbose_name_plural = _('Database options')
|
||||
ordering = ["db_type", "number"]
|
||||
verbose_name = _("Database option")
|
||||
verbose_name_plural = _("Database options")
|
||||
|
||||
def __str__(self):
|
||||
return ungettext(
|
||||
'{type} database',
|
||||
'{count} {type} databases',
|
||||
self.number
|
||||
).format(
|
||||
type=self.get_db_type_display(), count=self.number
|
||||
)
|
||||
"{type} database", "{count} {type} databases", self.number
|
||||
).format(type=self.get_db_type_display(), count=self.number)
|
||||
|
||||
|
||||
class UserDatabaseOption(UserDatabaseOptionBase, HostingOption):
|
||||
|
@ -127,8 +110,9 @@ class UserDatabaseOption(UserDatabaseOptionBase, HostingOption):
|
|||
hosting packages.
|
||||
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
unique_together = ['number', 'db_type']
|
||||
unique_together = ["number", "db_type"]
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
|
@ -137,23 +121,19 @@ class MailboxOptionBase(models.Model):
|
|||
Base class for mailbox options.
|
||||
|
||||
"""
|
||||
number = models.PositiveIntegerField(
|
||||
_('number of mailboxes'), unique=True)
|
||||
|
||||
number = models.PositiveIntegerField(_("number of mailboxes"), unique=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ['number']
|
||||
verbose_name = _('Mailbox option')
|
||||
verbose_name_plural = _('Mailbox options')
|
||||
ordering = ["number"]
|
||||
verbose_name = _("Mailbox option")
|
||||
verbose_name_plural = _("Mailbox options")
|
||||
|
||||
def __str__(self):
|
||||
return ungettext(
|
||||
'{count} additional mailbox',
|
||||
'{count} additional mailboxes',
|
||||
self.number
|
||||
).format(
|
||||
count=self.number
|
||||
)
|
||||
"{count} additional mailbox", "{count} additional mailboxes", self.number
|
||||
).format(count=self.number)
|
||||
|
||||
|
||||
class MailboxOption(MailboxOptionBase, HostingOption):
|
||||
|
@ -188,10 +168,11 @@ class CustomerHostingPackageManager(models.Manager):
|
|||
|
||||
"""
|
||||
package = CustomerHostingPackage(
|
||||
customer=customer, template=template, name=name)
|
||||
customer=customer, template=template, name=name
|
||||
)
|
||||
package.description = template.description
|
||||
package.copy_template_attributes()
|
||||
if 'commit' in kwargs and kwargs['commit'] is True:
|
||||
if "commit" in kwargs and kwargs["commit"] is True:
|
||||
package.save(**kwargs)
|
||||
return package
|
||||
|
||||
|
@ -202,43 +183,49 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
This class defines customer specific hosting packages.
|
||||
|
||||
"""
|
||||
|
||||
customer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, verbose_name=_('customer'))
|
||||
settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE
|
||||
)
|
||||
template = models.ForeignKey(
|
||||
HostingPackageTemplate, verbose_name=_('hosting package template'),
|
||||
HostingPackageTemplate,
|
||||
verbose_name=_("hosting package template"),
|
||||
help_text=_(
|
||||
'The hosting package template that this hosting package is based'
|
||||
' on'
|
||||
))
|
||||
name = models.CharField(_('name'), max_length=128)
|
||||
"The hosting package template that this hosting package is based" " on"
|
||||
),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
name = models.CharField(_("name"), max_length=128)
|
||||
osuser = models.OneToOneField(
|
||||
OsUser, verbose_name=_('Operating system user'),
|
||||
blank=True, null=True)
|
||||
OsUser,
|
||||
verbose_name=_("Operating system user"),
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
objects = CustomerHostingPackageManager()
|
||||
|
||||
class Meta:
|
||||
unique_together = ['customer', 'name']
|
||||
verbose_name = _('customer hosting package')
|
||||
verbose_name_plural = _('customer hosting packages')
|
||||
unique_together = ["customer", "name"]
|
||||
verbose_name = _("customer hosting package")
|
||||
verbose_name_plural = _("customer hosting packages")
|
||||
|
||||
def __str__(self):
|
||||
return _("{name} for {customer}").format(
|
||||
name=self.name, customer=self.customer
|
||||
)
|
||||
return _("{name} for {customer}").format(name=self.name, customer=self.customer)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('hosting_package_details', kwargs={
|
||||
'user': self.customer.username,
|
||||
'pk': self.id,
|
||||
})
|
||||
return reverse(
|
||||
"hosting_package_details",
|
||||
kwargs={"user": self.customer.username, "pk": self.id},
|
||||
)
|
||||
|
||||
def copy_template_attributes(self):
|
||||
"""
|
||||
Copy the attributes of the hosting package's template to the package.
|
||||
|
||||
"""
|
||||
for attrname in ('diskspace', 'diskspace_unit', 'mailboxcount'):
|
||||
for attrname in ("diskspace", "diskspace_unit", "mailboxcount"):
|
||||
setattr(self, attrname, getattr(self.template, attrname))
|
||||
|
||||
def get_hostingoptions(self):
|
||||
|
@ -246,10 +233,11 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
for opt_type in [
|
||||
CustomerDiskSpaceOption,
|
||||
CustomerMailboxOption,
|
||||
CustomerUserDatabaseOption
|
||||
CustomerUserDatabaseOption,
|
||||
]:
|
||||
opts.extend(opt_type.objects.filter(hosting_package=self))
|
||||
return opts
|
||||
|
||||
hostingoptions = property(get_hostingoptions)
|
||||
|
||||
def get_disk_space(self, unit=None):
|
||||
|
@ -272,15 +260,16 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
diskspace += option.diskspace
|
||||
elif option.diskspace_unit > min_unit:
|
||||
diskspace += (
|
||||
DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] *
|
||||
option.diskspace)
|
||||
DISK_SPACE_FACTORS[option.diskspace_unit][min_unit]
|
||||
* option.diskspace
|
||||
)
|
||||
else:
|
||||
diskspace = (
|
||||
DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] *
|
||||
diskspace) + option.diskspace
|
||||
DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] * diskspace
|
||||
) + option.diskspace
|
||||
min_unit = option.diskspace_unit
|
||||
if unit is None:
|
||||
return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024**2
|
||||
return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024 ** 2
|
||||
if unit > min_unit:
|
||||
return DISK_SPACE_FACTORS[unit][min_unit] * diskspace
|
||||
return DISK_SPACE_FACTORS[min_unit][unit] * diskspace
|
||||
|
@ -297,21 +286,22 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
|
||||
"""
|
||||
if unit is None:
|
||||
return (DISK_SPACE_FACTORS[self.diskspace_unit][0] *
|
||||
self.diskspace * 1024**2)
|
||||
return (
|
||||
DISK_SPACE_FACTORS[self.diskspace_unit][0] * self.diskspace * 1024 ** 2
|
||||
)
|
||||
if unit > self.diskspace_unit:
|
||||
return (DISK_SPACE_FACTORS[unit][self.diskspace_unit] *
|
||||
self.diskspace)
|
||||
return DISK_SPACE_FACTORS[unit][self.diskspace_unit] * self.diskspace
|
||||
return DISK_SPACE_FACTORS[self.diskspace_unit][unit] * self.diskspace
|
||||
|
||||
def get_quota(self):
|
||||
soft = 1024 * self.get_disk_space(DISK_SPACE_UNITS.M)
|
||||
hard = soft * 105 / 100
|
||||
hard = soft * 105 // 100
|
||||
return (soft, hard)
|
||||
|
||||
def get_mailboxes(self):
|
||||
if self.osuser:
|
||||
return Mailbox.objects.filter(osuser=self.osuser).all()
|
||||
|
||||
mailboxes = property(get_mailboxes)
|
||||
|
||||
def get_used_mailbox_count(self):
|
||||
|
@ -322,6 +312,7 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
if self.osuser:
|
||||
return Mailbox.objects.filter(osuser=self.osuser).count()
|
||||
return 0
|
||||
|
||||
used_mailbox_count = property(get_used_mailbox_count)
|
||||
|
||||
def get_mailbox_count(self):
|
||||
|
@ -330,12 +321,11 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
of its mailbox options.
|
||||
|
||||
"""
|
||||
result = CustomerMailboxOption.objects.filter(
|
||||
hosting_package=self
|
||||
).aggregate(
|
||||
mailbox_sum=models.Sum('number')
|
||||
result = CustomerMailboxOption.objects.filter(hosting_package=self).aggregate(
|
||||
mailbox_sum=models.Sum("number")
|
||||
)
|
||||
return self.mailboxcount + (result['mailbox_sum'] or 0)
|
||||
return self.mailboxcount + (result["mailbox_sum"] or 0)
|
||||
|
||||
mailbox_count = property(get_mailbox_count)
|
||||
|
||||
def may_add_mailbox(self):
|
||||
|
@ -347,25 +337,23 @@ class CustomerHostingPackage(HostingPackageBase):
|
|||
options for this hosting package.
|
||||
|
||||
"""
|
||||
return CustomerUserDatabaseOption.objects.values(
|
||||
'db_type'
|
||||
).filter(hosting_package=self).annotate(
|
||||
number=models.Sum('number')
|
||||
).all()
|
||||
return (
|
||||
CustomerUserDatabaseOption.objects.values("db_type")
|
||||
.filter(hosting_package=self)
|
||||
.annotate(number=models.Sum("number"))
|
||||
.all()
|
||||
)
|
||||
|
||||
def get_databases_flat(self):
|
||||
if self.osuser:
|
||||
return UserDatabase.objects.filter(
|
||||
db_user__osuser=self.osuser).all()
|
||||
return UserDatabase.objects.filter(db_user__osuser=self.osuser).all()
|
||||
|
||||
databases = property(get_databases_flat)
|
||||
|
||||
def may_add_database(self):
|
||||
return (
|
||||
CustomerUserDatabaseOption.objects.filter(
|
||||
hosting_package=self).count()
|
||||
>
|
||||
UserDatabase.objects.filter(
|
||||
db_user__osuser=self.osuser).count()
|
||||
CustomerUserDatabaseOption.objects.filter(hosting_package=self).count()
|
||||
> UserDatabase.objects.filter(db_user__osuser=self.osuser).count()
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
|
@ -401,11 +389,16 @@ class CustomerHostingPackageDomain(TimeStampedModel):
|
|||
domain.
|
||||
|
||||
"""
|
||||
|
||||
hosting_package = models.ForeignKey(
|
||||
CustomerHostingPackage, verbose_name=_('hosting package'),
|
||||
related_name='domains')
|
||||
CustomerHostingPackage,
|
||||
verbose_name=_("hosting package"),
|
||||
related_name="domains",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
domain = models.OneToOneField(
|
||||
HostingDomain, verbose_name=_('hosting domain'))
|
||||
HostingDomain, verbose_name=_("hosting domain"), on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.domain.domain
|
||||
|
@ -423,56 +416,62 @@ class CustomerHostingPackageOption(TimeStampedModel):
|
|||
This class defines options for customer hosting packages.
|
||||
|
||||
"""
|
||||
|
||||
hosting_package = models.ForeignKey(
|
||||
CustomerHostingPackage, verbose_name=_('hosting package'))
|
||||
CustomerHostingPackage,
|
||||
verbose_name=_("hosting package"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('customer hosting option')
|
||||
verbose_name_plural = _('customer hosting options')
|
||||
verbose_name = _("customer hosting option")
|
||||
verbose_name_plural = _("customer hosting options")
|
||||
|
||||
|
||||
class CustomerDiskSpaceOption(DiskSpaceOptionBase,
|
||||
CustomerHostingPackageOption):
|
||||
class CustomerDiskSpaceOption(DiskSpaceOptionBase, CustomerHostingPackageOption):
|
||||
"""
|
||||
This is a class for customer hosting package options adding additional disk
|
||||
space to existing customer hosting package.
|
||||
|
||||
"""
|
||||
|
||||
template = models.ForeignKey(
|
||||
DiskSpaceOption,
|
||||
verbose_name=_('disk space option template'),
|
||||
verbose_name=_("disk space option template"),
|
||||
help_text=_(
|
||||
'The disk space option template that this disk space option is'
|
||||
' based on'
|
||||
))
|
||||
"The disk space option template that this disk space option is" " based on"
|
||||
),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
|
||||
class CustomerUserDatabaseOption(UserDatabaseOptionBase,
|
||||
CustomerHostingPackageOption):
|
||||
class CustomerUserDatabaseOption(UserDatabaseOptionBase, CustomerHostingPackageOption):
|
||||
"""
|
||||
This is a class for customer hosting package options adding user databases
|
||||
to existing customer hosting packages.
|
||||
|
||||
"""
|
||||
|
||||
template = models.ForeignKey(
|
||||
UserDatabaseOption,
|
||||
verbose_name=_('user database option template'),
|
||||
verbose_name=_("user database option template"),
|
||||
help_text=_(
|
||||
'The user database option template that this database option is'
|
||||
' based on'
|
||||
))
|
||||
"The user database option template that this database option is" " based on"
|
||||
),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
|
||||
class CustomerMailboxOption(MailboxOptionBase,
|
||||
CustomerHostingPackageOption):
|
||||
class CustomerMailboxOption(MailboxOptionBase, CustomerHostingPackageOption):
|
||||
"""
|
||||
This is a class for customer hosting package options adding additional
|
||||
mailboxes to existing customer hosting packages.
|
||||
|
||||
"""
|
||||
|
||||
template = models.ForeignKey(
|
||||
MailboxOption,
|
||||
verbose_name=_('mailbox option template'),
|
||||
help_text=_(
|
||||
'The mailbox option template that this mailbox option is based on'
|
||||
))
|
||||
verbose_name=_("mailbox option template"),
|
||||
help_text=_("The mailbox option template that this mailbox option is based on"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
|
|
@ -5,10 +5,7 @@ Test for models.
|
|||
|
||||
from django.test import TestCase
|
||||
|
||||
from hostingpackages.models import (
|
||||
DISK_SPACE_UNITS,
|
||||
CustomerHostingPackage,
|
||||
)
|
||||
from hostingpackages.models import DISK_SPACE_UNITS, CustomerHostingPackage
|
||||
|
||||
|
||||
class CustomerHostingPackageTest(TestCase):
|
||||
|
@ -16,7 +13,7 @@ class CustomerHostingPackageTest(TestCase):
|
|||
package = CustomerHostingPackage(
|
||||
diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G
|
||||
)
|
||||
self.assertEqual(package.get_disk_space(), 10 * 1024 * 1024**2)
|
||||
self.assertEqual(package.get_disk_space(), 10 * 1024 ** 3)
|
||||
|
||||
def test_get_disk_space_mib(self):
|
||||
package = CustomerHostingPackage(
|
||||
|
|
|
@ -4,7 +4,7 @@ This module defines the URL patterns for hosting package related views.
|
|||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import (
|
||||
AddHostingOption,
|
||||
|
@ -17,8 +17,7 @@ from .views import (
|
|||
)
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(r'^create$', CreateHostingPackage.as_view(),
|
||||
name='create_hosting_package'),
|
||||
url(r'^allpackages/',
|
||||
|
@ -35,4 +34,4 @@ urlpatterns = patterns(
|
|||
HostingOptionChoices.as_view(), name='hosting_option_choices'),
|
||||
url(r'^(?P<package>\d+)/add-option/(?P<type>\w+)/(?P<optionid>\d+)$',
|
||||
AddHostingOption.as_view(), name='add_hosting_option'),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -80,7 +80,8 @@ class CreateCustomerHostingPackage(CreateHostingPackage):
|
|||
get_user_model(), username=self.kwargs['user'])
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateCustomerHostingPackage, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
CreateCustomerHostingPackage, self).get_context_data(**kwargs)
|
||||
context['customer'] = self.get_customer_object()
|
||||
return context
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from celery import shared_task
|
|||
|
||||
|
||||
@shared_task
|
||||
def create_ldap_group(groupname, gid, descr):
|
||||
def create_ldap_group(groupname, gid, description):
|
||||
"""
|
||||
This task creates an :py:class:`LDAP group <ldapentities.models.LdapGroup>`
|
||||
if it does not exist yet.
|
||||
|
@ -19,9 +19,12 @@ def create_ldap_group(groupname, gid, descr):
|
|||
|
||||
:param str groupname: the group name
|
||||
:param int gid: the group id
|
||||
:param str descr: description text for the group
|
||||
:return: the distinguished name of the group
|
||||
:rtype: str
|
||||
:param str description: description text for the group
|
||||
:return: dictionary containing groupname, gid, description and
|
||||
:py:const:`group_dn` set to the distinguished name of the newly created
|
||||
or existing LDAP group
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
@ -45,8 +48,10 @@ def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
|
|||
is passed the password is not touched
|
||||
:raises celery.exceptions.Reject: if the specified primary group does not
|
||||
exist
|
||||
:return: the distinguished name of the user
|
||||
:rtype: str
|
||||
:return: dictionary containing username, uid, gid, gecos, homedir, shell,
|
||||
password and :py:const:`user_dn` set to the distinguished name of the
|
||||
newly created or existing LDAP user
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
@ -59,8 +64,10 @@ def set_ldap_user_password(username, password):
|
|||
|
||||
:param str username: the user name
|
||||
:param str password: teh clear text password
|
||||
:return: :py:const:`True` if the password has been set, :py:const:`False`
|
||||
if the user does not exist.
|
||||
:return: dictionary containing the username and a flag
|
||||
:py:const:`password_set` that is set to :py:const:`True` if the
|
||||
password has been set, :py:const:`False` if the user does not exist.
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
@ -76,8 +83,10 @@ def add_ldap_user_to_group(username, groupname):
|
|||
:param str groupname: the group name
|
||||
:raises celery.exceptions.Retry: if the user does not exist yet,
|
||||
:py:func:`create_ldap_user` should be called before
|
||||
:return: True if the user has been added to the group otherwise False
|
||||
:rtype: boolean
|
||||
:return: dictionary containing the username, groupname and a flag
|
||||
:py:const`added` that is as a :py:const:`True` if the user has been
|
||||
added to the group otherwise to :py:const:`False`
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
@ -89,20 +98,48 @@ def remove_ldap_user_from_group(username, groupname):
|
|||
|
||||
:param str username: the user name
|
||||
:param str groupname: the group name
|
||||
:return: True if the user has been removed, False otherwise
|
||||
:rtype: boolean
|
||||
:return: dictionary containing the input parameters and a flag
|
||||
:py:const:`removed` that is set to :py:const:`True` if the user has
|
||||
been removed, False otherwise
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_ldap_user(username):
|
||||
def delete_ldap_user(username, *args, **kwargs):
|
||||
"""
|
||||
This task deletes the given user.
|
||||
|
||||
:param str username: the user name
|
||||
:return: True if the user has been deleted, False otherwise
|
||||
:rtype: boolean
|
||||
:return: dictionary containing the username and a flag :py:const:`deleted`
|
||||
that is set to :py:const:`True` if the user has been deleted and is set
|
||||
to :py:const:`False` otherwise
|
||||
:rtype: dict
|
||||
|
||||
.. note::
|
||||
|
||||
This variant can only be used at the beginning of a Celery task chain
|
||||
or as a standalone task.
|
||||
|
||||
Use :py:func:`ldaptasks.tasks.delete_ldap_user_chained` at other
|
||||
positions in the task chain
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_ldap_user_chained(previous_result, *args, **kwargs):
|
||||
"""
|
||||
This task deletes the given user.
|
||||
|
||||
:param dict previous_result: a dictionary describing the result of the
|
||||
previous step in the Celery task chain. This dictionary must contain a
|
||||
:py:const:`username` key
|
||||
:return: a copy of the :py:obj:`previous_result` dictionary with a new
|
||||
:py:const:`deleted` key set to :py:const:`True` if the user has been
|
||||
deleted and set to :py:const:`False` otherwise
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
@ -113,8 +150,10 @@ def delete_ldap_group_if_empty(groupname):
|
|||
This task deletes the given group if it is empty.
|
||||
|
||||
:param str groupname: the group name
|
||||
:return: True if the user has been deleted, False otherwise
|
||||
:rtype: boolean
|
||||
:return: dictionary that contains the groupname and a flag
|
||||
:py:const:`deleted` that is set to :py:const:`True` if the group has
|
||||
been deleted and is set to :py:const:`False` otherwise
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
||||
|
@ -122,10 +161,12 @@ def delete_ldap_group_if_empty(groupname):
|
|||
@shared_task
|
||||
def delete_ldap_group(groupname):
|
||||
"""
|
||||
This taks deletes the given group.
|
||||
This task deletes the given group.
|
||||
|
||||
:param str groupname: the group name
|
||||
:return: True if the user has been deleted, False otherwise
|
||||
:rtype: boolean
|
||||
:return: dictionary that contains the groupname and a flag
|
||||
:py:const:`deleted` that is set to :py:const:`True` if the group has
|
||||
been deleted and is set to :py:const:`False` otherwise
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gnuviechadmin\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-02-01 19:04+0100\n"
|
||||
"POT-Creation-Date: 2016-01-29 11:04+0100\n"
|
||||
"PO-Revision-Date: 2015-02-01 19:04+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
|
@ -181,12 +181,12 @@ msgstr ""
|
|||
"Mailadresse des Benutzers %(user_display)s ist."
|
||||
|
||||
#: templates/account/login.html:4 templates/account/login.html.py:5
|
||||
#: templates/account/login.html:29 templates/base.html:82
|
||||
#: templates/account/login.html:30 templates/base.html.py:81
|
||||
#: templates/registration/login.html:4
|
||||
msgid "Sign In"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: templates/account/login.html:9
|
||||
#: templates/account/login.html:10
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Please sign in with one\n"
|
||||
|
@ -198,11 +198,11 @@ msgstr ""
|
|||
"href=\"%(signup_url)s\">registrieren Sie sich</a> für ein Konto auf "
|
||||
"%(site_name)s und melden Sie sich unten an:"
|
||||
|
||||
#: templates/account/login.html:16
|
||||
#: templates/account/login.html:17
|
||||
msgid "or"
|
||||
msgstr "oder"
|
||||
|
||||
#: templates/account/login.html:18
|
||||
#: templates/account/login.html:19
|
||||
#, python-format
|
||||
msgid ""
|
||||
"If you have not created an account yet, then please\n"
|
||||
|
@ -211,7 +211,7 @@ msgstr ""
|
|||
"Wenn Sie noch kein Konto haben, <a href=\"%(signup_url)s\">Registrieren Sie "
|
||||
"sich</a> bitte erst."
|
||||
|
||||
#: templates/account/login.html:28
|
||||
#: templates/account/login.html:29
|
||||
msgid "Forgot Password?"
|
||||
msgstr "Passwort vergessen?"
|
||||
|
||||
|
@ -342,12 +342,12 @@ msgstr "Ihr Passwort wurde geändert."
|
|||
msgid "Set Password"
|
||||
msgstr "Passwort setzen"
|
||||
|
||||
#: templates/account/signup.html:4 templates/socialaccount/signup.html:4
|
||||
#: templates/account/signup.html:4 templates/socialaccount/signup.html.py:4
|
||||
msgid "Signup"
|
||||
msgstr "Registrieren"
|
||||
|
||||
#: templates/account/signup.html:5 templates/account/signup.html.py:15
|
||||
#: templates/base.html:83 templates/socialaccount/signup.html:5
|
||||
#: templates/base.html:82 templates/socialaccount/signup.html.py:5
|
||||
#: templates/socialaccount/signup.html:15
|
||||
msgid "Sign Up"
|
||||
msgstr "Registrieren"
|
||||
|
@ -425,86 +425,86 @@ msgstr ""
|
|||
"<strong>Hinweis:</strong> Sie können Ihre <a href=\"%(email_url)s\">E-"
|
||||
"Mailadresse noch ändern</a>."
|
||||
|
||||
#: templates/base.html:44
|
||||
#: templates/base.html:43
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
|
||||
#: templates/base.html:50 templates/base.html.py:57
|
||||
#: templates/base.html:49 templates/base.html.py:56
|
||||
msgid "Hosting"
|
||||
msgstr "Hosting"
|
||||
|
||||
#: templates/base.html:52
|
||||
#: templates/base.html:51
|
||||
#: templates/hostingpackages/customerhostingpackage_list.html:5
|
||||
#: templates/hostingpackages/customerhostingpackage_list.html:13
|
||||
msgid "Your hosting packages"
|
||||
msgstr "Ihre Hostingpakete"
|
||||
|
||||
#: templates/base.html:53
|
||||
#: templates/base.html:52
|
||||
#: templates/hostingpackages/customerhostingpackage_admin_list.html:3
|
||||
#: templates/hostingpackages/customerhostingpackage_admin_list.html:4
|
||||
msgid "All hosting packages"
|
||||
msgstr "Alle Hostingpakete"
|
||||
|
||||
#: templates/base.html:60
|
||||
#: templates/base.html:59
|
||||
msgid "Links"
|
||||
msgstr "Links"
|
||||
|
||||
#: templates/base.html:62
|
||||
#: templates/base.html:61
|
||||
msgid "Web based mail system"
|
||||
msgstr "Webbasiertes E-Mailsystem"
|
||||
|
||||
#: templates/base.html:62
|
||||
#: templates/base.html:61
|
||||
msgid "Webmail"
|
||||
msgstr "Webmail"
|
||||
|
||||
#: templates/base.html:63
|
||||
#: templates/base.html:62
|
||||
msgid "phpMyAdmin - MySQL database administration tool"
|
||||
msgstr "phpMyAdmin - MySQL-Datenbankverwaltungswerkzeug"
|
||||
|
||||
#: templates/base.html:63
|
||||
#: templates/base.html:62
|
||||
msgid "phpMyAdmin"
|
||||
msgstr "phpMyAdmin"
|
||||
|
||||
#: templates/base.html:64
|
||||
#: templates/base.html:63
|
||||
msgid "phpPgAdmin - PostgreSQL database administration tool"
|
||||
msgstr "phpPgAdmin - PostgreSQL-Datenbankverwaltungswerkzeug"
|
||||
|
||||
#: templates/base.html:64
|
||||
#: templates/base.html:63
|
||||
msgid "phpPgAdmin"
|
||||
msgstr "phpPgAdmin"
|
||||
|
||||
#: templates/base.html:67
|
||||
#: templates/base.html:66
|
||||
msgid "Imprint"
|
||||
msgstr "Impressum"
|
||||
|
||||
#: templates/base.html:68 templates/contact_form/contact_form.html:4
|
||||
#: templates/base.html:67 templates/contact_form/contact_form.html.py:4
|
||||
#: templates/contact_form/contact_form.html:5
|
||||
#: templates/contact_form/contact_success.html:4
|
||||
#: templates/contact_form/contact_success.html:5
|
||||
msgid "Contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
#: templates/base.html:73
|
||||
#: templates/base.html:72
|
||||
msgid "My Account"
|
||||
msgstr "Mein Konto"
|
||||
|
||||
#: templates/base.html:75
|
||||
#: templates/base.html:74
|
||||
msgid "Admin site"
|
||||
msgstr "Adminsite"
|
||||
|
||||
#: templates/base.html:76
|
||||
#: templates/base.html:75
|
||||
msgid "Change Email"
|
||||
msgstr "E-Mail ändern"
|
||||
|
||||
#: templates/base.html:77
|
||||
#: templates/base.html:76
|
||||
msgid "Social Accounts"
|
||||
msgstr "Konten in sozialen Netzwerken"
|
||||
|
||||
#: templates/base.html:78
|
||||
#: templates/base.html:77
|
||||
msgid "Logout"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#: templates/base.html:89
|
||||
#: templates/base.html:88
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Signed in as <a href=\"%(profile_url)s\" class=\"navbar-link\" title=\"My "
|
||||
|
@ -513,7 +513,7 @@ msgstr ""
|
|||
"Angemeldet als <a href=\"%(profile_url)s\" class=\"navbar-link\" title="
|
||||
"\"Mein Profil\">%(user_display)s</a>"
|
||||
|
||||
#: templates/base.html:102
|
||||
#: templates/base.html:101
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ import os
|
|||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.local")
|
||||
os.environ.setdefault(
|
||||
"DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.utils.html import format_html
|
||||
from django.contrib import admin
|
||||
from django import forms
|
||||
from django.forms.util import flatatt
|
||||
from django.forms.utils import flatatt
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from .models import (
|
||||
|
@ -32,7 +32,7 @@ class ReadOnlyPasswordHashField(forms.Field):
|
|||
def bound_data(self, data, initial):
|
||||
return initial
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
def has_changed(self, initial, data):
|
||||
return False
|
||||
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ This module defines form classes for mailbox and mail address editing.
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import validate_email
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
|
@ -120,6 +120,7 @@ class MailAddressFieldMixin(forms.Form):
|
|||
label=_('Mailbox'),
|
||||
required=False,
|
||||
)
|
||||
# TODO: refactor as separate field class returning a list
|
||||
forwards = forms.CharField(
|
||||
label=_('Forwards'),
|
||||
required=False,
|
||||
|
@ -187,6 +188,7 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
|
|||
return localpart
|
||||
|
||||
def clean(self):
|
||||
super(AddMailAddressForm, self).clean()
|
||||
data = self.cleaned_data
|
||||
if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox:
|
||||
if not data['mailbox']:
|
||||
|
@ -194,7 +196,8 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
|
|||
elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
|
||||
if 'forwards' not in data or not data['forwards']:
|
||||
self.add_error('forwards', _('No forward addresses selected'))
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
# should not happen because of the field's validation
|
||||
raise forms.ValidationError(
|
||||
_('Illegal choice for target of the mail address'))
|
||||
|
||||
|
@ -213,7 +216,7 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
|
|||
if target_choice == MAILBOX_OR_FORWARDS.mailbox:
|
||||
mabox = self.instance.set_mailbox(data['mailbox'], commit=False)
|
||||
elif target_choice == MAILBOX_OR_FORWARDS.forwards:
|
||||
targets = [part.strip() for part in data['forwards'].split()]
|
||||
targets = [part.strip() for part in data['forwards'].split(',')]
|
||||
fwds = self.instance.set_forward_addresses(targets, commit=False)
|
||||
mailaddress = super(AddMailAddressForm, self).save(commit)
|
||||
if commit:
|
||||
|
@ -275,7 +278,8 @@ class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
|
|||
elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
|
||||
if 'forwards' not in data or not data['forwards']:
|
||||
self.add_error('forwards', _('No forward addresses selected'))
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
# should not happen because of the field's validation
|
||||
raise forms.ValidationError(
|
||||
_('Illegal choice for target of the mail address'))
|
||||
|
||||
|
@ -291,4 +295,4 @@ class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
|
|||
elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
|
||||
targets = [part.strip() for part in data['forwards'].split(',')]
|
||||
self.instance.set_forward_addresses(targets, commit)
|
||||
return self.instance
|
||||
return super(EditMailAddressForm, self).save(commit)
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: managemails\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"POT-Creation-Date: 2016-01-29 11:04+0100\n"
|
||||
"PO-Revision-Date: 2015-01-25 22:17+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
|
@ -23,7 +23,7 @@ msgstr ""
|
|||
msgid "Passwords don't match"
|
||||
msgstr "Passwörter stimmen nicht überein"
|
||||
|
||||
#: managemails/admin.py:21 managemails/tests/test_admin.py:37
|
||||
#: managemails/admin.py:21 managemails/tests/test_admin.py:40
|
||||
msgid "Hash"
|
||||
msgstr "Hash-Code"
|
||||
|
||||
|
@ -47,11 +47,11 @@ msgstr "Deaktivieren"
|
|||
msgid "Mailboxes and Mail Addresses"
|
||||
msgstr "Postfächer und E-Mailadressen"
|
||||
|
||||
#: managemails/forms.py:29 managemails/forms.py:120 managemails/models.py:103
|
||||
#: managemails/forms.py:29 managemails/forms.py:120 managemails/models.py:120
|
||||
msgid "Mailbox"
|
||||
msgstr "Postfach"
|
||||
|
||||
#: managemails/forms.py:30 managemails/forms.py:124
|
||||
#: managemails/forms.py:30 managemails/forms.py:125
|
||||
msgid "Forwards"
|
||||
msgstr "Weiterleitungen"
|
||||
|
||||
|
@ -67,84 +67,84 @@ msgstr "Passwort setzen"
|
|||
msgid "Mailbox or Forwards"
|
||||
msgstr "Postfach oder Weiterleitungen"
|
||||
|
||||
#: managemails/forms.py:128
|
||||
#: managemails/forms.py:129
|
||||
msgid "Please enter one or more email addresses separated by commas."
|
||||
msgstr ""
|
||||
"Bitte geben Sie eine oder mehrere durch Kommata getrennte E-Mailadressen ein."
|
||||
|
||||
#: managemails/forms.py:175
|
||||
#: managemails/forms.py:176
|
||||
msgid "Add mail address"
|
||||
msgstr "E-Mailadresse hinzufügen"
|
||||
|
||||
#: managemails/forms.py:185
|
||||
#: managemails/forms.py:186
|
||||
msgid "This mail address is already in use."
|
||||
msgstr "Diese E-Mailadresse wird bereits verwendet."
|
||||
|
||||
#: managemails/forms.py:193 managemails/forms.py:274
|
||||
#: managemails/forms.py:195 managemails/forms.py:277
|
||||
msgid "No mailbox selected"
|
||||
msgstr "Kein Postfach ausgewählt"
|
||||
|
||||
#: managemails/forms.py:196 managemails/forms.py:277
|
||||
#: managemails/forms.py:198 managemails/forms.py:280
|
||||
msgid "No forward addresses selected"
|
||||
msgstr "Keine Weiterleitungsadressen ausgewählt"
|
||||
|
||||
#: managemails/forms.py:199 managemails/forms.py:280
|
||||
#: managemails/forms.py:202 managemails/forms.py:284
|
||||
msgid "Illegal choice for target of the mail address"
|
||||
msgstr "Ungültige Auswahl für das Ziel der E-Mailadresse"
|
||||
|
||||
#: managemails/forms.py:267
|
||||
#: managemails/forms.py:270
|
||||
msgid "Change mail address targets"
|
||||
msgstr "E-Mailadressziele ändern"
|
||||
|
||||
#: managemails/models.py:104
|
||||
#: managemails/models.py:121
|
||||
msgid "Mailboxes"
|
||||
msgstr "Postfächer"
|
||||
|
||||
#: managemails/models.py:145
|
||||
#: managemails/models.py:164
|
||||
msgid "local part"
|
||||
msgstr "Lokaler Teil"
|
||||
|
||||
#: managemails/models.py:146
|
||||
#: managemails/models.py:165
|
||||
msgid "domain"
|
||||
msgstr "Domain"
|
||||
|
||||
#: managemails/models.py:151
|
||||
#: managemails/models.py:170
|
||||
msgid "Mail address"
|
||||
msgstr "E-Mailadresse"
|
||||
|
||||
#: managemails/models.py:152
|
||||
#: managemails/models.py:171
|
||||
msgid "Mail addresses"
|
||||
msgstr "E-Mailadressen"
|
||||
|
||||
#: managemails/models.py:229
|
||||
#: managemails/models.py:253
|
||||
msgid "mailaddress"
|
||||
msgstr "E-Mailadresse"
|
||||
|
||||
#: managemails/models.py:230
|
||||
#: managemails/models.py:254
|
||||
msgid "mailbox"
|
||||
msgstr "Postfach"
|
||||
|
||||
#: managemails/views.py:51
|
||||
#: managemails/views.py:52
|
||||
msgid "You are not allowed to add more mailboxes to this hosting package"
|
||||
msgstr "Sie können keine weiteren Postfächer zu diesem Hostingpaket hinzufügen"
|
||||
|
||||
#: managemails/views.py:70
|
||||
#: managemails/views.py:71
|
||||
#, python-brace-format
|
||||
msgid "Mailbox {mailbox} created successfully."
|
||||
msgstr "Postfach {mailbox} erfolgreich angelegt."
|
||||
|
||||
#: managemails/views.py:105
|
||||
#: managemails/views.py:106
|
||||
#, python-brace-format
|
||||
msgid "Successfully set new password for mailbox {mailbox}."
|
||||
msgstr ""
|
||||
"Für das Postfach {mailbox} wurde erfolgreich ein neues Passwort gesetzt."
|
||||
|
||||
#: managemails/views.py:144
|
||||
#: managemails/views.py:145
|
||||
#, python-brace-format
|
||||
msgid "Successfully added mail address {mailaddress}"
|
||||
msgstr "E-Mailadresse {mailaddress} erfolgreich hinzugefügt"
|
||||
|
||||
#: managemails/views.py:222
|
||||
#: managemails/views.py:223
|
||||
#, python-brace-format
|
||||
msgid "Successfully updated mail address {mailaddress} targets."
|
||||
msgstr "Ziele der E-Mailadresse {mailaddress} erfolgreich aktualisiert."
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('domains', '0001_initial'),
|
||||
('osusers', '0001_initial'),
|
||||
|
@ -17,9 +16,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MailAddress',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('active', models.BooleanField(default=True)),
|
||||
('localpart', models.CharField(max_length=128)),
|
||||
],
|
||||
|
@ -32,9 +37,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MailAddressForward',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('target', models.EmailField(max_length=254)),
|
||||
],
|
||||
options={
|
||||
|
@ -44,9 +55,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MailAddressMailbox',
|
||||
fields=[
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('mailaddress', models.OneToOneField(primary_key=True, serialize=False, to='managemails.MailAddress')),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('mailaddress', models.OneToOneField(
|
||||
primary_key=True, serialize=False,
|
||||
to='managemails.MailAddress', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
|
@ -55,13 +72,20 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Mailbox',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(
|
||||
default=django.utils.timezone.now, verbose_name='created',
|
||||
editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(
|
||||
default=django.utils.timezone.now, verbose_name='modified',
|
||||
editable=False)),
|
||||
('active', models.BooleanField(default=True)),
|
||||
('username', models.CharField(unique=True, max_length=128)),
|
||||
('password', models.CharField(max_length=255)),
|
||||
('osuser', models.ForeignKey(to='osusers.User')),
|
||||
('osuser', models.ForeignKey(
|
||||
to='osusers.User', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Mailbox',
|
||||
|
@ -72,31 +96,34 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='mailaddressmailbox',
|
||||
name='mailbox',
|
||||
field=models.ForeignKey(to='managemails.Mailbox'),
|
||||
field=models.ForeignKey(
|
||||
to='managemails.Mailbox', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='mailaddressmailbox',
|
||||
unique_together=set([('mailaddress', 'mailbox')]),
|
||||
unique_together={('mailaddress', 'mailbox')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailaddressforward',
|
||||
name='mailaddress',
|
||||
field=models.ForeignKey(to='managemails.MailAddress'),
|
||||
field=models.ForeignKey(
|
||||
to='managemails.MailAddress', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='mailaddressforward',
|
||||
unique_together=set([('mailaddress', 'target')]),
|
||||
unique_together={('mailaddress', 'target')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailaddress',
|
||||
name='domain',
|
||||
field=models.ForeignKey(to='domains.MailDomain'),
|
||||
field=models.ForeignKey(
|
||||
to='domains.MailDomain', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='mailaddress',
|
||||
unique_together=set([('localpart', 'domain')]),
|
||||
unique_together={('localpart', 'domain')},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('managemails', '0002_auto_20150117_1238'),
|
||||
]
|
||||
|
@ -14,13 +13,17 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='mailaddressmailbox',
|
||||
name='mailaddress',
|
||||
field=models.OneToOneField(primary_key=True, serialize=False, to='managemails.MailAddress', verbose_name='mailaddress'),
|
||||
field=models.OneToOneField(
|
||||
primary_key=True, serialize=False, to='managemails.MailAddress',
|
||||
verbose_name='mailaddress', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaddressmailbox',
|
||||
name='mailbox',
|
||||
field=models.ForeignKey(verbose_name='mailbox', to='managemails.Mailbox'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='mailbox', to='managemails.Mailbox',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('managemails', '0003_auto_20150124_2029'),
|
||||
]
|
||||
|
@ -14,7 +13,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='mailaddress',
|
||||
name='domain',
|
||||
field=models.ForeignKey(verbose_name='domain', to='domains.MailDomain'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='domain', to='domains.MailDomain',
|
||||
on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
|
|
@ -85,6 +85,23 @@ class MailboxManager(models.Manager):
|
|||
active=True, osuser=osuser,
|
||||
)
|
||||
|
||||
def create_mailbox(self, osuser, password=None, commit=True):
|
||||
"""
|
||||
Create a new mailbox for the given operating system user.
|
||||
|
||||
:param osuser: a :py:class:`osuser.models.OsUser` instance
|
||||
:param password: an optional password
|
||||
:param commit: whether the mailbox should be commited to the database
|
||||
:return: mailbox instance
|
||||
:rtype: :py:class:`managemails.models.Mailbox`
|
||||
|
||||
"""
|
||||
mailbox = self.create(
|
||||
osuser=osuser, username=self.get_next_mailbox_name(osuser))
|
||||
if password is not None:
|
||||
mailbox.set_password(password)
|
||||
return mailbox
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Mailbox(ActivateAbleMixin, TimeStampedModel):
|
||||
|
@ -92,7 +109,7 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel):
|
|||
This is the model class for a mailbox.
|
||||
|
||||
"""
|
||||
osuser = models.ForeignKey(OsUser)
|
||||
osuser = models.ForeignKey(OsUser, on_delete=models.CASCADE)
|
||||
username = models.CharField(max_length=128, unique=True)
|
||||
password = models.CharField(max_length=255)
|
||||
|
||||
|
@ -113,10 +130,12 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel):
|
|||
self.password = sha512_crypt.encrypt(password)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# TODO: refactor to use signals
|
||||
create_file_mailbox.delay(self.osuser.username, self.username).get()
|
||||
super(Mailbox, self).save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
# TODO: refactor to use signals
|
||||
delete_file_mailbox.delay(self.osuser.username, self.username).get()
|
||||
super(Mailbox, self).delete(*args, **kwargs)
|
||||
|
||||
|
@ -143,7 +162,8 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
|
|||
|
||||
"""
|
||||
localpart = models.CharField(_('local part'), max_length=128)
|
||||
domain = models.ForeignKey(MailDomain, verbose_name=_('domain'))
|
||||
domain = models.ForeignKey(
|
||||
MailDomain, verbose_name=_('domain'), on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
ordering = ['domain', 'localpart']
|
||||
|
@ -176,9 +196,12 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
|
|||
for mafwd in MailAddressForward.objects.filter(mailaddress=self):
|
||||
mafwd.delete()
|
||||
else:
|
||||
mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox)
|
||||
if commit:
|
||||
self.save()
|
||||
mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox)
|
||||
mabox.save()
|
||||
else:
|
||||
mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox)
|
||||
return mabox
|
||||
|
||||
def set_forward_addresses(self, addresses, commit=True):
|
||||
|
@ -200,17 +223,19 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
|
|||
forwards = MailAddressForward.objects.filter(
|
||||
mailaddress=self).all()
|
||||
for item in forwards:
|
||||
if not item.target in addresses:
|
||||
if item.target not in addresses:
|
||||
item.delete()
|
||||
else:
|
||||
retval.append(item)
|
||||
for target in addresses:
|
||||
if not target in [item.target for item in forwards]:
|
||||
if target not in [item.target for item in forwards]:
|
||||
mafwd = MailAddressForward(mailaddress=self, target=target)
|
||||
if commit:
|
||||
mafwd.save()
|
||||
retval.append(mafwd)
|
||||
else:
|
||||
if commit:
|
||||
self.save()
|
||||
for target in addresses:
|
||||
mafwd = MailAddressForward(mailaddress=self, target=target)
|
||||
if commit:
|
||||
|
@ -226,8 +251,10 @@ class MailAddressMailbox(TimeStampedModel, models.Model):
|
|||
|
||||
"""
|
||||
mailaddress = models.OneToOneField(
|
||||
MailAddress, verbose_name=_('mailaddress'), primary_key=True)
|
||||
mailbox = models.ForeignKey(Mailbox, verbose_name=_('mailbox'))
|
||||
MailAddress, verbose_name=_('mailaddress'), primary_key=True,
|
||||
on_delete=models.CASCADE)
|
||||
mailbox = models.ForeignKey(
|
||||
Mailbox, verbose_name=_('mailbox'), on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('mailaddress', 'mailbox')
|
||||
|
@ -241,7 +268,7 @@ class MailAddressForward(TimeStampedModel, models.Model):
|
|||
This is a model class to map mail addresses to forwarding addresses.
|
||||
|
||||
"""
|
||||
mailaddress = models.ForeignKey(MailAddress)
|
||||
mailaddress = models.ForeignKey(MailAddress, on_delete=models.CASCADE)
|
||||
target = models.EmailField(max_length=254)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.contrib.admin import AdminSite
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from mock import Mock
|
||||
from unittest.mock import Mock
|
||||
|
||||
from osusers.models import User
|
||||
|
||||
|
@ -21,9 +21,7 @@ from managemails.admin import (
|
|||
ReadOnlyPasswordHashField,
|
||||
ReadOnlyPasswordHashWidget,
|
||||
)
|
||||
from managemails.models import (
|
||||
Mailbox,
|
||||
)
|
||||
from managemails.models import Mailbox
|
||||
|
||||
Customer = get_user_model()
|
||||
|
||||
|
@ -31,14 +29,14 @@ Customer = get_user_model()
|
|||
class ReadOnlyPasswordHashWidgetTest(TestCase):
|
||||
def test_render(self):
|
||||
widget = ReadOnlyPasswordHashWidget()
|
||||
rendered = widget.render('password', 'secret', {'class': 'test'})
|
||||
rendered = widget.render("password", "secret", {"class": "test"})
|
||||
self.assertEqual(
|
||||
rendered,
|
||||
format_html(
|
||||
'<div class="test">{0}</div>',
|
||||
format_html('<strong>{0}</strong>: secret ',
|
||||
_('Hash'))
|
||||
))
|
||||
format_html("<strong>{0}</strong>: secret ", _("Hash")),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ReadOnlyPasswordHashFieldTest(TestCase):
|
||||
|
@ -48,24 +46,24 @@ class ReadOnlyPasswordHashFieldTest(TestCase):
|
|||
|
||||
def test_bound_data(self):
|
||||
field = ReadOnlyPasswordHashField()
|
||||
self.assertEqual(field.bound_data('new', 'old'), 'old')
|
||||
self.assertEqual(field.bound_data("new", "old"), "old")
|
||||
|
||||
def test__has_changed(self):
|
||||
field = ReadOnlyPasswordHashField()
|
||||
self.assertFalse(field._has_changed('new', 'old'))
|
||||
self.assertFalse(field.has_changed("new", "old"))
|
||||
|
||||
|
||||
class CustomerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
super(CustomerTestCase, self).setUp()
|
||||
self.customer = Customer.objects.create(username='test')
|
||||
self.customer = Customer.objects.create(username="test")
|
||||
|
||||
|
||||
class MailboxCreationFormTest(CustomerTestCase):
|
||||
def test_clean_password2_same(self):
|
||||
form = MailboxCreationForm()
|
||||
form.cleaned_data = {'password1': 'secret', 'password2': 'secret'}
|
||||
self.assertEqual(form.clean_password2(), 'secret')
|
||||
form.cleaned_data = {"password1": "secret", "password2": "secret"}
|
||||
self.assertEqual(form.clean_password2(), "secret")
|
||||
|
||||
def test_clean_password2_empty(self):
|
||||
form = MailboxCreationForm()
|
||||
|
@ -74,59 +72,47 @@ class MailboxCreationFormTest(CustomerTestCase):
|
|||
|
||||
def test_clean_password2_mismatch(self):
|
||||
form = MailboxCreationForm()
|
||||
form.cleaned_data = {'password1': 'secretx', 'password2': 'secrety'}
|
||||
form.cleaned_data = {"password1": "secretx", "password2": "secrety"}
|
||||
with self.assertRaises(forms.ValidationError) as cm:
|
||||
form.clean_password2()
|
||||
self.assertEqual(cm.exception.message, PASSWORD_MISMATCH_ERROR)
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_CACHE_BACKEND='memory',
|
||||
BROKER_BACKEND='memory'
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
def test_save_commit(self):
|
||||
user = User.objects.create_user(customer=self.customer)
|
||||
form = MailboxCreationForm(data={
|
||||
'osuser': user.uid,
|
||||
'password1': 'secret',
|
||||
'password2': 'secret',
|
||||
})
|
||||
form = MailboxCreationForm(
|
||||
data={"osuser": user.uid, "password1": "secret", "password2": "secret"}
|
||||
)
|
||||
mailbox = form.save()
|
||||
self.assertIsNotNone(mailbox)
|
||||
self.assertEqual(
|
||||
len(Mailbox.objects.filter(osuser=user)), 1)
|
||||
self.assertEqual(len(Mailbox.objects.filter(osuser=user)), 1)
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_CACHE_BACKEND='memory',
|
||||
BROKER_BACKEND='memory'
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
def test_save_no_commit(self):
|
||||
user = User.objects.create_user(customer=self.customer)
|
||||
form = MailboxCreationForm(data={
|
||||
'osuser': user.uid,
|
||||
'password1': 'secret',
|
||||
'password2': 'secret',
|
||||
})
|
||||
form = MailboxCreationForm(
|
||||
data={"osuser": user.uid, "password1": "secret", "password2": "secret"}
|
||||
)
|
||||
mailbox = form.save(commit=False)
|
||||
self.assertIsNotNone(mailbox)
|
||||
self.assertEqual(
|
||||
len(Mailbox.objects.filter(osuser=user)), 0)
|
||||
self.assertEqual(len(Mailbox.objects.filter(osuser=user)), 0)
|
||||
|
||||
|
||||
class MailboxChangeFormTest(CustomerTestCase):
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_CACHE_BACKEND='memory',
|
||||
BROKER_BACKEND='memory'
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
def test_clean_password(self):
|
||||
mailbox = Mailbox(
|
||||
username='test',
|
||||
osuser=User.objects.create_user(customer=self.customer))
|
||||
mailbox.set_password('test')
|
||||
username="test", osuser=User.objects.create_user(customer=self.customer)
|
||||
)
|
||||
mailbox.set_password("test")
|
||||
mailbox.save()
|
||||
form = MailboxChangeForm(instance=mailbox, data={'password': 'blub'})
|
||||
form = MailboxChangeForm(instance=mailbox, data={"password": "blub"})
|
||||
self.assertEqual(form.clean_password(), mailbox.password)
|
||||
|
||||
|
||||
|
@ -151,55 +137,43 @@ class MailBoxAdminTest(CustomerTestCase):
|
|||
self.mbadmin = MailboxAdmin(Mailbox, site)
|
||||
|
||||
def test_get_fieldsets_without_object(self):
|
||||
self.assertEqual(
|
||||
self.mbadmin.get_fieldsets(Mock()),
|
||||
self.mbadmin.add_fieldsets)
|
||||
self.assertEqual(self.mbadmin.get_fieldsets(Mock()), self.mbadmin.add_fieldsets)
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_CACHE_BACKEND='memory',
|
||||
BROKER_BACKEND='memory'
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
def test_get_fieldsets_with_object(self):
|
||||
mailbox = Mailbox(
|
||||
username='test',
|
||||
osuser=User.objects.create_user(customer=self.customer))
|
||||
mailbox.set_password('test')
|
||||
username="test", osuser=User.objects.create_user(customer=self.customer)
|
||||
)
|
||||
mailbox.set_password("test")
|
||||
mailbox.save()
|
||||
self.assertEqual(
|
||||
self.mbadmin.get_fieldsets(Mock(), mailbox),
|
||||
self.mbadmin.fieldsets)
|
||||
self.mbadmin.get_fieldsets(Mock(), mailbox), self.mbadmin.fieldsets
|
||||
)
|
||||
|
||||
def test_get_form_without_object(self):
|
||||
form = self.mbadmin.get_form(Mock)
|
||||
self.assertEqual(
|
||||
form.Meta.fields,
|
||||
['osuser', 'password1', 'password2']
|
||||
)
|
||||
self.assertEqual(form.Meta.fields, ["osuser", "password1", "password2"])
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_CACHE_BACKEND='memory',
|
||||
BROKER_BACKEND='memory'
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
def test_get_form_with_object(self):
|
||||
mailbox = Mailbox(
|
||||
username='test',
|
||||
osuser=User.objects.create_user(customer=self.customer))
|
||||
mailbox.set_password('test')
|
||||
username="test", osuser=User.objects.create_user(customer=self.customer)
|
||||
)
|
||||
mailbox.set_password("test")
|
||||
mailbox.save()
|
||||
form = self.mbadmin.get_form(Mock, mailbox)
|
||||
self.assertEqual(
|
||||
form.Meta.fields,
|
||||
['osuser', 'username', 'password', 'active']
|
||||
)
|
||||
self.assertEqual(form.Meta.fields, ["osuser", "username", "password", "active"])
|
||||
|
||||
def test_admin_for_mailbox(self):
|
||||
admin_url = reverse('admin:managemails_mailaddress_changelist')
|
||||
admin_url = reverse("admin:managemails_mailaddress_changelist")
|
||||
self.assertIsNotNone(admin_url)
|
||||
|
||||
|
||||
class MailAddressAdminTest(TestCase):
|
||||
def test_admin_for_mailaddress(self):
|
||||
admin_url = reverse('admin:managemails_mailaddress_changelist')
|
||||
admin_url = reverse("admin:managemails_mailaddress_changelist")
|
||||
self.assertIsNotNone(admin_url)
|
||||
|
|
|
@ -0,0 +1,599 @@
|
|||
"""
|
||||
This module provides tests for :py:mod:`managemails.forms`.
|
||||
|
||||
"""
|
||||
from unittest.mock import MagicMock, Mock, patch, ANY
|
||||
|
||||
from django.forms import ValidationError
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from managemails.forms import (
|
||||
AddMailAddressForm,
|
||||
ChangeMailboxPasswordForm,
|
||||
CreateMailboxForm,
|
||||
EditMailAddressForm,
|
||||
MAILBOX_OR_FORWARDS,
|
||||
MailAddressFieldMixin,
|
||||
multiple_email_validator,
|
||||
)
|
||||
|
||||
|
||||
class CreateMailboxFormTest(TestCase):
|
||||
def test_constructor_needs_hostingpackage(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
CreateMailboxForm(instance)
|
||||
|
||||
def test_constructor(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock()
|
||||
form = CreateMailboxForm(instance, hostingpackage=hostingpackage)
|
||||
self.assertTrue(hasattr(form, "hosting_package"))
|
||||
self.assertEqual(form.hosting_package, hostingpackage)
|
||||
self.assertTrue(hasattr(form, "helper"))
|
||||
self.assertEqual(
|
||||
form.helper.form_action, reverse("create_mailbox", kwargs={"package": 42})
|
||||
)
|
||||
self.assertIn("password1", form.fields)
|
||||
self.assertIn("password2", form.fields)
|
||||
self.assertEqual(form.helper.inputs[0].name, "submit")
|
||||
|
||||
@patch("managemails.forms.Mailbox.objects")
|
||||
def test_save(self, mailbox_objects):
|
||||
osuser = MagicMock()
|
||||
hostingpackage = Mock(id=42, osuser=osuser)
|
||||
instance = MagicMock()
|
||||
form = CreateMailboxForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
data={"password1": "secret", "password2": "secret"},
|
||||
)
|
||||
mailbox_objects.get_next_mailbox_name.return_value = "mailbox23"
|
||||
self.assertTrue(form.is_valid())
|
||||
form.save(commit=False)
|
||||
self.assertEqual(osuser, form.instance.osuser)
|
||||
self.assertEqual("mailbox23", form.instance.username)
|
||||
instance.set_password.assert_called_with("secret")
|
||||
|
||||
|
||||
class ChangeMailboxPasswordFormTest(TestCase):
|
||||
def test_constructor_needs_hostingpackage(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
ChangeMailboxPasswordForm(instance)
|
||||
|
||||
def test_constructor(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock(username="testuser")
|
||||
form = ChangeMailboxPasswordForm(
|
||||
instance=instance, hostingpackage=hostingpackage
|
||||
)
|
||||
self.assertTrue(hasattr(form, "hosting_package"))
|
||||
self.assertEqual(form.hosting_package, hostingpackage)
|
||||
self.assertTrue(hasattr(form, "helper"))
|
||||
self.assertEqual(
|
||||
form.helper.form_action,
|
||||
reverse(
|
||||
"change_mailbox_password", kwargs={"package": 42, "slug": "testuser"}
|
||||
),
|
||||
)
|
||||
self.assertIn("password1", form.fields)
|
||||
self.assertIn("password2", form.fields)
|
||||
self.assertEqual(form.helper.inputs[0].name, "submit")
|
||||
|
||||
def test_save(self):
|
||||
hostingpackage = Mock(id=42)
|
||||
instance = MagicMock(username="testuser")
|
||||
form = ChangeMailboxPasswordForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
data={"password1": "newsecret", "password2": "newsecret"},
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
form.save(commit=False)
|
||||
instance.set_password.assert_called_with("newsecret")
|
||||
|
||||
|
||||
class MultipleEmailValidatorTest(TestCase):
|
||||
def test_valid_single_address(self):
|
||||
self.assertEqual(
|
||||
"test@example.org", multiple_email_validator("test@example.org")
|
||||
)
|
||||
|
||||
def test_valid_multiple_addresses(self):
|
||||
self.assertEqual(
|
||||
"test1@example.org,test2@example.org",
|
||||
multiple_email_validator("test1@example.org,test2@example.org"),
|
||||
)
|
||||
|
||||
def test_empty(self):
|
||||
self.assertEqual("", multiple_email_validator(""))
|
||||
|
||||
def test_none(self):
|
||||
self.assertIsNone(multiple_email_validator(None))
|
||||
|
||||
def test_invalid_single_address(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
multiple_email_validator("no@ddress")
|
||||
|
||||
def test_invalid_multiple_addresses(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
multiple_email_validator("test1@example.org,no@ddress")
|
||||
|
||||
|
||||
class MailAddressFieldMixinTest(TestCase):
|
||||
def test_fields_defined(self):
|
||||
form = MailAddressFieldMixin()
|
||||
self.assertIn("mailbox_or_forwards", form.fields)
|
||||
self.assertIn("mailbox", form.fields)
|
||||
self.assertIn("forwards", form.fields)
|
||||
|
||||
|
||||
class AddMailAddressFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.patcher1 = patch("managemails.forms.Mailbox.objects")
|
||||
self.patcher2 = patch("managemails.forms.MailAddress.objects")
|
||||
self.mailbox_objects = self.patcher1.start()
|
||||
self.mailaddress_objects = self.patcher2.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher2.stop()
|
||||
self.patcher1.stop()
|
||||
|
||||
def test_constructor_needs_hostingpackage(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
AddMailAddressForm(instance=instance, maildomain=MagicMock())
|
||||
|
||||
def test_constructor_needs_maildomain(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
AddMailAddressForm(instance=instance, hostingpackage=MagicMock())
|
||||
|
||||
def test_constructor(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance, hostingpackage=hostingpackage, maildomain=maildomain
|
||||
)
|
||||
self.mailbox_objects.unused.assert_called_with(osuser=osuser)
|
||||
self.assertIn("mailbox_or_forwards", form.fields)
|
||||
self.assertIn("mailbox", form.fields)
|
||||
self.assertIn("forwards", form.fields)
|
||||
self.assertTrue(hasattr(form, "hosting_package"))
|
||||
self.assertEqual(form.hosting_package, hostingpackage)
|
||||
self.assertTrue(hasattr(form, "maildomain"))
|
||||
self.assertEqual(form.maildomain, maildomain)
|
||||
self.assertTrue(hasattr(form, "helper"))
|
||||
self.assertEqual(
|
||||
form.helper.form_action,
|
||||
reverse("add_mailaddress", kwargs={"package": 42, "domain": "example.org"}),
|
||||
)
|
||||
self.assertEqual(len(form.helper.layout), 2)
|
||||
self.assertEqual(form.helper.layout[1].name, "submit")
|
||||
|
||||
def test_clean_localpart_valid(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"forwards": "test2@example.org",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual("test", form.clean_localpart())
|
||||
|
||||
def test_clean_localpart_duplicate(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"forwards": "test2@example.org",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = True
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("localpart", form.errors)
|
||||
|
||||
def test_clean_no_mailbox_choice(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("mailbox", form.errors)
|
||||
|
||||
def test_clean_no_forward_address_choice(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("forwards", form.errors)
|
||||
|
||||
def test_save_with_forwards_no_commit(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"forwards": "test2@example.org,test3@example.org",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertTrue(form.is_valid())
|
||||
address1 = MagicMock(mailaddress="test2@example.org")
|
||||
address2 = MagicMock(mailaddress="test3@example.org")
|
||||
instance.set_forward_addresses.return_value = [address1, address2]
|
||||
form.save(commit=False)
|
||||
self.assertEqual(maildomain, instance.domain)
|
||||
instance.set_forward_addresses.assert_called_with(
|
||||
["test2@example.org", "test3@example.org"], commit=False
|
||||
)
|
||||
address1.save.assert_not_called()
|
||||
address2.save.assert_not_called()
|
||||
instance.save.assert_not_called()
|
||||
|
||||
def test_save_with_forwards_commit(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"forwards": "test2@example.org,test3@example.org",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertTrue(form.is_valid())
|
||||
address1 = MagicMock(mailaddress="test2@example.org")
|
||||
address2 = MagicMock(mailaddress="test3@example.org")
|
||||
instance.set_forward_addresses.return_value = [address1, address2]
|
||||
form.save(commit=True)
|
||||
self.assertEqual(maildomain, instance.domain)
|
||||
instance.set_forward_addresses.assert_called_with(
|
||||
["test2@example.org", "test3@example.org"], commit=False
|
||||
)
|
||||
address1.save.assert_called_with()
|
||||
address2.save.assert_called_with()
|
||||
instance.save.assert_called_with()
|
||||
|
||||
def test_save_with_mailbox_no_commit(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
"mailbox": "mailbox23",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertTrue(form.is_valid())
|
||||
mailbox = MagicMock(osuser=osuser, username="testuserp01")
|
||||
instance.set_mailbox.return_value = mailbox
|
||||
form.save(commit=False)
|
||||
self.assertEqual(maildomain, instance.domain)
|
||||
instance.set_mailbox.assert_called_with(ANY, commit=False)
|
||||
mailbox.save.assert_not_called()
|
||||
instance.save.assert_not_called()
|
||||
|
||||
def test_save_with_mailbox_commit(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
"mailbox": "mailbox23",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertTrue(form.is_valid())
|
||||
mailbox = MagicMock(osuser=osuser, username="testuserp01")
|
||||
instance.set_mailbox.return_value = mailbox
|
||||
form.save(commit=True)
|
||||
self.assertEqual(maildomain, instance.domain)
|
||||
instance.set_mailbox.assert_called_with(ANY, commit=False)
|
||||
instance.set_mailbox.return_value.save.assert_called_with()
|
||||
mailbox.save.assert_called_with()
|
||||
instance.save.assert_called_with()
|
||||
|
||||
def test_save_with_other_choice(self):
|
||||
instance = MagicMock()
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = AddMailAddressForm(
|
||||
instance=instance,
|
||||
hostingpackage=hostingpackage,
|
||||
maildomain=maildomain,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
"mailbox": "mailbox23",
|
||||
},
|
||||
)
|
||||
self.mailaddress_objects.filter(
|
||||
domain=maildomain, localpart="test"
|
||||
).exists.return_value = False
|
||||
self.assertTrue(form.is_valid())
|
||||
form.cleaned_data["mailbox_or_forwards"] = -1
|
||||
address1 = MagicMock(mailaddress="test2@example.org")
|
||||
address2 = MagicMock(mailaddress="test3@example.org")
|
||||
instance.set_forward_addresses.return_value = [address1, address2]
|
||||
mailbox = MagicMock(osuser=osuser, username="testuserp01")
|
||||
instance.set_mailbox.return_value = mailbox
|
||||
form.save(commit=True)
|
||||
instance.set_mailbox.assert_not_called()
|
||||
instance.set_forward_addresses.assert_not_called()
|
||||
address1.save.assert_not_called()
|
||||
address2.save.assert_not_called()
|
||||
mailbox.save.assert_not_called()
|
||||
instance.save.assert_called_with()
|
||||
|
||||
|
||||
class EditMailAddressFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.patcher1 = patch("managemails.forms.Mailbox.objects")
|
||||
self.patcher2 = patch("managemails.forms.MailAddress.objects")
|
||||
self.mailbox_objects = self.patcher1.start()
|
||||
self.mailaddress_objects = self.patcher2.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher2.stop()
|
||||
self.patcher1.stop()
|
||||
|
||||
def test_constructor_needs_hostingpackage(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
EditMailAddressForm(instance=instance, maildomain=MagicMock())
|
||||
|
||||
def test_constructor_needs_maildomain(self):
|
||||
instance = MagicMock()
|
||||
with self.assertRaises(KeyError):
|
||||
EditMailAddressForm(instance=instance, hostingpackage=MagicMock())
|
||||
|
||||
def test_constructor(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance, maildomain=maildomain, hostingpackage=hostingpackage
|
||||
)
|
||||
self.mailbox_objects.unused_or_own.assert_called_with(instance, osuser)
|
||||
self.assertIn("mailbox_or_forwards", form.fields)
|
||||
self.assertIn("mailbox", form.fields)
|
||||
self.assertIn("forwards", form.fields)
|
||||
self.assertTrue(hasattr(form, "hosting_package"))
|
||||
self.assertEqual(form.hosting_package, hostingpackage)
|
||||
self.assertTrue(hasattr(form, "maildomain"))
|
||||
self.assertEqual(form.maildomain, maildomain)
|
||||
self.assertTrue(hasattr(form, "helper"))
|
||||
self.assertEqual(
|
||||
form.helper.form_action,
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={"package": 42, "domain": "example.org", "pk": 23},
|
||||
),
|
||||
)
|
||||
self.assertEqual(len(form.helper.layout), 2)
|
||||
self.assertEqual(form.helper.layout[1].name, "submit")
|
||||
|
||||
def test_clean_no_mailbox_choice(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox},
|
||||
)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("mailbox", form.errors)
|
||||
|
||||
def test_clean_no_forward_address_choice(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards},
|
||||
)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("forwards", form.errors)
|
||||
|
||||
def test_save_with_forwards_no_commit(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"forwards": "test2@example.org,test3@example.org",
|
||||
},
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
address1 = MagicMock(mailaddress="test2@example.org")
|
||||
address2 = MagicMock(mailaddress="test3@example.org")
|
||||
instance.set_forward_addresses.return_value = [address1, address2]
|
||||
form.save(commit=False)
|
||||
instance.set_forward_addresses.assert_called_with(
|
||||
["test2@example.org", "test3@example.org"], False
|
||||
)
|
||||
address1.save.assert_not_called()
|
||||
address2.save.assert_not_called()
|
||||
instance.save.assert_not_called()
|
||||
|
||||
def test_save_with_forwards_commit(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"forwards": "test2@example.org,test3@example.org",
|
||||
},
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
address1 = MagicMock(mailaddress="test2@example.org")
|
||||
address2 = MagicMock(mailaddress="test3@example.org")
|
||||
instance.set_forward_addresses.return_value = [address1, address2]
|
||||
form.save(commit=True)
|
||||
instance.set_forward_addresses.assert_called_with(
|
||||
["test2@example.org", "test3@example.org"], True
|
||||
)
|
||||
instance.save.assert_called_with()
|
||||
|
||||
def test_save_with_mailbox_no_commit(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
"mailbox": "mailbox23",
|
||||
},
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
mailbox = MagicMock(osuser=osuser, username="testuserp01")
|
||||
instance.set_mailbox.return_value = mailbox
|
||||
form.save(commit=False)
|
||||
instance.set_mailbox.assert_called_with(ANY, False)
|
||||
mailbox.save.assert_not_called()
|
||||
instance.save.assert_not_called()
|
||||
|
||||
def test_save_with_mailbox_commit(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
"mailbox": "mailbox23",
|
||||
},
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
mailbox = MagicMock(osuser=osuser, username="testuserp01")
|
||||
instance.set_mailbox.return_value = mailbox
|
||||
self.mailbox_objects.unused_or_own.get.return_value = mailbox
|
||||
form.save(commit=True)
|
||||
instance.set_mailbox.assert_called_with(ANY, True)
|
||||
instance.save.assert_called_with()
|
||||
|
||||
def test_save_with_other_choice(self):
|
||||
instance = MagicMock(id=23)
|
||||
osuser = Mock(username="testuser")
|
||||
hostingpackage = MagicMock(id=42, osuser=osuser)
|
||||
maildomain = MagicMock(domain="example.org")
|
||||
form = EditMailAddressForm(
|
||||
instance=instance,
|
||||
maildomain=maildomain,
|
||||
hostingpackage=hostingpackage,
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
|
||||
"mailbox": "mailbox23",
|
||||
},
|
||||
)
|
||||
self.assertTrue(form.is_valid())
|
||||
form.cleaned_data["mailbox_or_forwards"] = -1
|
||||
form.save(commit=True)
|
||||
instance.set_mailbox.assert_not_called()
|
||||
instance.save.assert_called_with()
|
|
@ -1,4 +1,9 @@
|
|||
from django.test import TestCase
|
||||
"""
|
||||
This module contains tests for :py:mod:`managemails.models`
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
@ -7,39 +12,306 @@ from passlib.hash import sha512_crypt
|
|||
from domains.models import MailDomain
|
||||
from osusers.models import User
|
||||
|
||||
from managemails.models import (
|
||||
MailAddress,
|
||||
Mailbox,
|
||||
)
|
||||
from managemails.models import MailAddress, Mailbox
|
||||
|
||||
Customer = get_user_model()
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_CACHE_BACKEND='memory',
|
||||
BROKER_BACKEND='memory'
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class MailboxTest(TestCase):
|
||||
def setUp(self):
|
||||
super(MailboxTest, self).setUp()
|
||||
self.customer = Customer.objects.create_user('test')
|
||||
self.customer = Customer.objects.create_user("test")
|
||||
|
||||
def test_set_password(self):
|
||||
user = User.objects.create_user(self.customer)
|
||||
mb = Mailbox.objects.create(username='test', osuser=user)
|
||||
mb.set_password('test')
|
||||
self.assertTrue(sha512_crypt.verify('test', mb.password))
|
||||
mb = Mailbox.objects.create(username="test", osuser=user)
|
||||
mb.set_password("test")
|
||||
self.assertTrue(sha512_crypt.verify("test", mb.password))
|
||||
|
||||
def test___str__(self):
|
||||
user = User.objects.create_user(self.customer)
|
||||
mb = Mailbox.objects.create(username='test', osuser=user)
|
||||
mb.set_password('test')
|
||||
self.assertEqual(str(mb), 'test')
|
||||
mb = Mailbox.objects.create(username="test", osuser=user)
|
||||
mb.set_password("test")
|
||||
self.assertEqual(str(mb), "test")
|
||||
|
||||
@patch("managemails.models.create_file_mailbox")
|
||||
def test_save(self, create_file_mailbox_task):
|
||||
user = User.objects.create_user(self.customer)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
self.assertIsNotNone(mb.pk)
|
||||
create_file_mailbox_task.delay.assert_called_with(user.username, mb.username)
|
||||
|
||||
@patch("managemails.models.delete_file_mailbox")
|
||||
def test_delete(self, delete_file_mailbox_task):
|
||||
user = User.objects.create_user(self.customer)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
mb.delete()
|
||||
self.assertIsNone(mb.pk)
|
||||
delete_file_mailbox_task.delay.assert_called_with(user.username, mb.username)
|
||||
|
||||
def test_get_mailaddresses(self):
|
||||
user = User.objects.create_user(self.customer)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
address = MailAddress.objects.create(localpart="test", domain=md)
|
||||
address.set_mailbox(mb)
|
||||
mailaddresses = mb.get_mailaddresses()
|
||||
self.assertEqual(len(mailaddresses), 1)
|
||||
self.assertIn(address, mailaddresses)
|
||||
|
||||
|
||||
class MailAddressTest(TestCase):
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class MailAddressTest(TransactionTestCase):
|
||||
def test__str__(self):
|
||||
md = MailDomain.objects.create(domain='example.org')
|
||||
ma = MailAddress.objects.create(localpart='test', domain=md)
|
||||
self.assertEqual(str(ma), 'test@example.org')
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
self.assertEqual(str(ma), "test@example.org")
|
||||
|
||||
def test_set_mailbox_fresh(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb)
|
||||
self.assertIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def test_set_mailbox_reassing(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb)
|
||||
mb2 = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb2)
|
||||
self.assertIn(ma, mb2.get_mailaddresses())
|
||||
self.assertNotIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def test_set_mailbox_with_forwards(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_forward_addresses(["test2@example.org"])
|
||||
ma.set_mailbox(mb)
|
||||
self.assertEqual(ma.mailaddressforward_set.count(), 0)
|
||||
self.assertIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def test_set_mailbox_with_unsaved_address(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb)
|
||||
self.assertIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def test_set_mailbox_fresh_no_commit(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb, commit=False)
|
||||
self.assertNotIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def test_set_mailbox_with_unsaved_address_no_commit(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb, commit=False)
|
||||
self.assertNotIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def test_set_forward_addresses_fresh(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
ma.set_forward_addresses(["test2@example.org"])
|
||||
|
||||
def get_target(maf):
|
||||
return maf.target
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
ma.mailaddressforward_set.all(), ["test2@example.org"], get_target
|
||||
)
|
||||
|
||||
def test_set_forward_addresses_unsaved(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress(localpart="test", domain=md)
|
||||
ma.set_forward_addresses(["test2@example.org"])
|
||||
|
||||
def get_target(maf):
|
||||
return maf.target
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
ma.mailaddressforward_set.all(), ["test2@example.org"], get_target
|
||||
)
|
||||
|
||||
def test_set_forward_addresses_replace_forwards(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
ma.set_forward_addresses(["test2@example.org"])
|
||||
ma.set_forward_addresses(["test3@example.org"])
|
||||
|
||||
def get_target(maf):
|
||||
return maf.target
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
ma.mailaddressforward_set.all(), ["test3@example.org"], get_target
|
||||
)
|
||||
|
||||
def test_set_forward_addresses_add_forwards(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
ma.set_forward_addresses(["test2@example.org"])
|
||||
ma.set_forward_addresses(["test2@example.org", "test3@example.org"])
|
||||
|
||||
def get_target(maf):
|
||||
return maf.target
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
ma.mailaddressforward_set.all(),
|
||||
["test2@example.org", "test3@example.org"],
|
||||
get_target,
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
def test_set_forward_addresses_replace_mailbox(self):
|
||||
customer = Customer.objects.create_user("test")
|
||||
user = User.objects.create_user(customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb)
|
||||
ma.set_forward_addresses(["test2@example.org"])
|
||||
self.assertNotIn(ma, mb.get_mailaddresses())
|
||||
|
||||
def get_target(maf):
|
||||
return maf.target
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
ma.mailaddressforward_set.all(), ["test2@example.org"], get_target
|
||||
)
|
||||
|
||||
def test_set_forward_addresses_fresh_no_commit(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mafwds = ma.set_forward_addresses(["test2@example.org"], commit=False)
|
||||
self.assertEqual(ma.mailaddressforward_set.count(), 0)
|
||||
self.assertEqual(mafwds[0].target, "test2@example.org")
|
||||
|
||||
def test_set_forward_address_unsaved_no_commit(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress(localpart="test", domain=md)
|
||||
mafwds = ma.set_forward_addresses(["test2@example.org"], commit=False)
|
||||
self.assertEqual(ma.mailaddressforward_set.count(), 0)
|
||||
self.assertEqual(mafwds[0].target, "test2@example.org")
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class MailboxManagerTest(TransactionTestCase):
|
||||
def setUp(self):
|
||||
super(MailboxManagerTest, self).setUp()
|
||||
self.customer = Customer.objects.create_user("test")
|
||||
self.user = User.objects.create_user(self.customer)
|
||||
|
||||
def test_get_next_mailbox_name_fresh(self):
|
||||
mailboxname = Mailbox.objects.get_next_mailbox_name(self.user)
|
||||
self.assertEqual(mailboxname, "{}p01".format(self.user.username))
|
||||
|
||||
def test_get_next_mailbox_name_second(self):
|
||||
Mailbox.objects.create_mailbox(self.user)
|
||||
mailboxname = Mailbox.objects.get_next_mailbox_name(self.user)
|
||||
self.assertEqual(mailboxname, "{}p02".format(self.user.username))
|
||||
|
||||
def test_get_next_mailbox_name_gap_detection(self):
|
||||
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(3)]
|
||||
mailboxes[1].delete()
|
||||
mailboxname = Mailbox.objects.get_next_mailbox_name(self.user)
|
||||
self.assertEqual(mailboxname, "{}p02".format(self.user.username))
|
||||
|
||||
def test_unused_or_own_fresh(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
address = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mailboxes = Mailbox.objects.unused_or_own(address, self.user)
|
||||
self.assertQuerysetEqual(mailboxes, [])
|
||||
|
||||
def test_unused_or_own_unassigned(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
address = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
|
||||
assignable = Mailbox.objects.unused_or_own(address, self.user)
|
||||
self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes])
|
||||
|
||||
def test_unused_or_own_assigned(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
address = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
|
||||
address.set_mailbox(mailboxes[0])
|
||||
assignable = Mailbox.objects.unused_or_own(address, self.user)
|
||||
self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes])
|
||||
|
||||
def test_unused_or_own_assigned_other(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
address = MailAddress.objects.create(localpart="test", domain=md)
|
||||
address2 = MailAddress.objects.create(localpart="test2", domain=md)
|
||||
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
|
||||
address2.set_mailbox(mailboxes[0])
|
||||
assignable = Mailbox.objects.unused_or_own(address, self.user)
|
||||
self.assertQuerysetEqual(assignable, [repr(mailboxes[1])])
|
||||
|
||||
def test_unused_fresh(self):
|
||||
mailboxes = Mailbox.objects.unused(self.user)
|
||||
self.assertQuerysetEqual(mailboxes, [])
|
||||
|
||||
def test_unused_unassigned(self):
|
||||
mailbox = Mailbox.objects.create_mailbox(self.user)
|
||||
mailboxes = Mailbox.objects.unused(self.user)
|
||||
self.assertQuerysetEqual(mailboxes, [repr(mailbox)])
|
||||
|
||||
def test_unused_assigned(self):
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
address = MailAddress.objects.create(localpart="test", domain=md)
|
||||
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
|
||||
address.set_mailbox(mailboxes[0])
|
||||
assignable = Mailbox.objects.unused(self.user)
|
||||
self.assertQuerysetEqual(assignable, [repr(mailboxes[1])])
|
||||
|
||||
def test_create_mailbox_no_password(self):
|
||||
mailbox = Mailbox.objects.create_mailbox(self.user)
|
||||
self.assertEqual(mailbox.osuser, self.user)
|
||||
self.assertEqual(mailbox.username, "{}p01".format(self.user.username))
|
||||
self.assertEqual(mailbox.password, "")
|
||||
|
||||
def test_create_mailbox_with_password(self):
|
||||
mailbox = Mailbox.objects.create_mailbox(self.user, "test")
|
||||
self.assertEqual(mailbox.osuser, self.user)
|
||||
self.assertEqual(mailbox.username, "{}p01".format(self.user.username))
|
||||
self.assertTrue(sha512_crypt.verify("test", mailbox.password))
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class MailAddressMailboxTest(TestCase):
|
||||
def setUp(self):
|
||||
super(MailAddressMailboxTest, self).setUp()
|
||||
self.customer = Customer.objects.create_user("test")
|
||||
|
||||
def test___str__(self):
|
||||
user = User.objects.create_user(self.customer)
|
||||
md = MailDomain.objects.create(domain="example.org")
|
||||
ma = MailAddress(localpart="test", domain=md)
|
||||
mb = Mailbox.objects.create_mailbox(user)
|
||||
ma.set_mailbox(mb)
|
||||
self.assertEqual(str(ma.mailaddressmailbox), mb.username)
|
||||
|
|
|
@ -0,0 +1,787 @@
|
|||
"""
|
||||
Tests for :py:mod:`managemails.views`.
|
||||
|
||||
"""
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
|
||||
from domains.models import MailDomain
|
||||
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
|
||||
|
||||
from managemails.forms import MAILBOX_OR_FORWARDS
|
||||
from managemails.models import MailAddress, Mailbox
|
||||
from managemails.views import (
|
||||
AddMailAddress,
|
||||
ChangeMailboxPassword,
|
||||
CreateMailbox,
|
||||
EditMailAddress,
|
||||
)
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
TEST_USER = "test"
|
||||
TEST_PASSWORD = "secret"
|
||||
TEST_EMAIL = "test@example.org"
|
||||
TEST_NAME = "Example Tester".split()
|
||||
|
||||
|
||||
class HostingPackageAwareTestMixin(object):
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def _setup_hosting_package(self, customer, mailboxcount):
|
||||
template = HostingPackageTemplate.objects.create(
|
||||
name="testpackagetemplate",
|
||||
mailboxcount=mailboxcount,
|
||||
diskspace=1,
|
||||
diskspace_unit=0,
|
||||
)
|
||||
package = CustomerHostingPackage.objects.create_from_template(
|
||||
customer, template, "testpackage"
|
||||
)
|
||||
with patch("hostingpackages.models.settings") as hmsettings:
|
||||
hmsettings.OSUSER_DEFAULT_GROUPS = []
|
||||
package.save()
|
||||
return package
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class CreateMailboxTest(HostingPackageAwareTestMixin, TestCase):
|
||||
def test_get_anonymous(self):
|
||||
response = self.client.get(reverse("create_mailbox", kwargs={"package": 1}))
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_regular_user(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_other_regular_user(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
User.objects.create_user(
|
||||
"test2", email="test2@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username="test2", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_staff_user(self):
|
||||
customer = User.objects.create_user("customer")
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
User.objects.create_superuser(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_template(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk})
|
||||
)
|
||||
self.assertTemplateUsed(response, "managemails/mailbox_create.html")
|
||||
|
||||
def test_get_no_more_mailboxes(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 0)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertEqual(
|
||||
response.content,
|
||||
b"You are not allowed to add more mailboxes to this hosting" b" package",
|
||||
)
|
||||
|
||||
def test_get_context_data(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk})
|
||||
)
|
||||
self.assertIn("hostingpackage", response.context)
|
||||
self.assertEqual(response.context["hostingpackage"], package)
|
||||
self.assertIn("customer", response.context)
|
||||
self.assertEqual(response.context["customer"], customer)
|
||||
|
||||
def test_get_form_kwargs(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = CreateMailbox(request=MagicMock(), kwargs={"package": str(package.pk)})
|
||||
the_kwargs = view.get_form_kwargs()
|
||||
self.assertIn("hostingpackage", the_kwargs)
|
||||
self.assertEqual(the_kwargs["hostingpackage"], package)
|
||||
|
||||
def test_form_valid_redirect(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk}),
|
||||
data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD},
|
||||
)
|
||||
self.assertRedirects(response, package.get_absolute_url())
|
||||
|
||||
def test_form_valid_message(self):
|
||||
customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
package = self._setup_hosting_package(customer, 1)
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse("create_mailbox", kwargs={"package": package.pk}),
|
||||
follow=True,
|
||||
data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD},
|
||||
)
|
||||
messages = list(response.context["messages"])
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual("Mailbox usr01p01 created successfully.", str(messages[0]))
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class ChangeMailboxPasswordTest(HostingPackageAwareTestMixin, TestCase):
|
||||
def setUp(self):
|
||||
self.customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.package = self._setup_hosting_package(self.customer, 1)
|
||||
self.mailbox = Mailbox.objects.create_mailbox(
|
||||
self.package.osuser, TEST_PASSWORD
|
||||
)
|
||||
|
||||
def test_get_anonymous(self):
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_regular_user(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_other_regular_user(self):
|
||||
User.objects.create_user(
|
||||
"test2", email="test2@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="test2", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_staff_user(self):
|
||||
User.objects.create_superuser(
|
||||
"admin", email="admin@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="admin", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_template(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
)
|
||||
)
|
||||
self.assertTemplateUsed(response, "managemails/mailbox_setpassword.html")
|
||||
|
||||
def test_get_context_data(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
)
|
||||
)
|
||||
self.assertIn("hostingpackage", response.context)
|
||||
self.assertEqual(response.context["hostingpackage"], self.package)
|
||||
self.assertIn("customer", response.context)
|
||||
self.assertEqual(response.context["customer"], self.customer)
|
||||
|
||||
def test_get_form_kwargs(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = ChangeMailboxPassword(
|
||||
request=MagicMock(),
|
||||
kwargs={"package": str(self.package.pk), "slug": self.mailbox.username},
|
||||
)
|
||||
the_kwargs = view.get_form_kwargs()
|
||||
self.assertIn("hostingpackage", the_kwargs)
|
||||
self.assertEqual(the_kwargs["hostingpackage"], self.package)
|
||||
|
||||
def test_form_valid_redirect(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
),
|
||||
data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD},
|
||||
)
|
||||
self.assertRedirects(response, self.package.get_absolute_url())
|
||||
|
||||
def test_form_valid_message(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"change_mailbox_password",
|
||||
kwargs={"package": self.package.pk, "slug": self.mailbox.username},
|
||||
),
|
||||
follow=True,
|
||||
data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD},
|
||||
)
|
||||
messages = list(response.context["messages"])
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual(
|
||||
str(messages[0]), "Successfully set new password for mailbox usr01p01."
|
||||
)
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class AddMailAddressTest(HostingPackageAwareTestMixin, TestCase):
|
||||
def setUp(self):
|
||||
self.customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.package = self._setup_hosting_package(self.customer, 1)
|
||||
self.maildomain = MailDomain.objects.create(
|
||||
domain="example.org", customer=self.customer
|
||||
)
|
||||
|
||||
def test_get_anonymous(self):
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_regular_user(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_other_regular_user(self):
|
||||
User.objects.create_user(
|
||||
"test2", email="test2@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="test2", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_staff_user(self):
|
||||
User.objects.create_superuser(
|
||||
"admin", email="admin@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="admin", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_template(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
)
|
||||
)
|
||||
self.assertTemplateUsed(response, "managemails/mailaddress_create.html")
|
||||
|
||||
def test_get_context_data(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
)
|
||||
)
|
||||
self.assertIn("customer", response.context)
|
||||
self.assertEqual(response.context["customer"], self.customer)
|
||||
|
||||
def test_get_form_kwargs(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = AddMailAddress(
|
||||
request=MagicMock(),
|
||||
kwargs={"package": str(self.package.pk), "domain": self.maildomain.domain},
|
||||
)
|
||||
the_kwargs = view.get_form_kwargs()
|
||||
self.assertIn("hostingpackage", the_kwargs)
|
||||
self.assertEqual(the_kwargs["hostingpackage"], self.package)
|
||||
self.assertIn("maildomain", the_kwargs)
|
||||
self.assertEqual(the_kwargs["maildomain"], self.maildomain)
|
||||
|
||||
def test_form_valid_redirect(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
),
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"mailbox": "",
|
||||
"forwards": "test2@example.org",
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, self.package.get_absolute_url())
|
||||
|
||||
def test_form_valid_message(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"add_mailaddress",
|
||||
kwargs={"package": self.package.pk, "domain": self.maildomain.domain},
|
||||
),
|
||||
follow=True,
|
||||
data={
|
||||
"localpart": "test",
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"mailbox": "",
|
||||
"forwards": "test2@example.org",
|
||||
},
|
||||
)
|
||||
messages = list(response.context["messages"])
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual(
|
||||
str(messages[0]), "Successfully added mail address test@example.org"
|
||||
)
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class DeleteMailAddressTest(HostingPackageAwareTestMixin, TestCase):
|
||||
def setUp(self):
|
||||
self.customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.package = self._setup_hosting_package(self.customer, 1)
|
||||
self.maildomain = MailDomain.objects.create(
|
||||
domain="example.org", customer=self.customer
|
||||
)
|
||||
self.mailaddress = MailAddress.objects.create(
|
||||
localpart="test", domain=self.maildomain
|
||||
)
|
||||
self.mailaddress.set_forward_addresses(["test2@example.org"])
|
||||
|
||||
def test_get_anonymous(self):
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_regular_user(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_other_regular_user(self):
|
||||
User.objects.create_user(
|
||||
"test2", email="test2@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="test2", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_staff_user(self):
|
||||
User.objects.create_superuser(
|
||||
"admin", email="admin@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="admin", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_template(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertTemplateUsed(response, "managemails/mailaddress_confirm_delete.html")
|
||||
|
||||
def test_get_context_data(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertIn("customer", response.context)
|
||||
self.assertEqual(response.context["customer"], self.customer)
|
||||
self.assertIn("hostingpackage", response.context)
|
||||
self.assertEqual(response.context["hostingpackage"], self.package)
|
||||
self.assertIn("maildomain", response.context)
|
||||
self.assertEqual(response.context["maildomain"], self.maildomain)
|
||||
|
||||
def test_form_valid_redirect(self):
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"delete_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
),
|
||||
data={},
|
||||
)
|
||||
self.assertRedirects(response, self.package.get_absolute_url())
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory"
|
||||
)
|
||||
class EditMailAddressTest(HostingPackageAwareTestMixin, TestCase):
|
||||
def setUp(self):
|
||||
self.customer = User.objects.create_user(
|
||||
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD
|
||||
)
|
||||
self.package = self._setup_hosting_package(self.customer, 1)
|
||||
self.maildomain = MailDomain.objects.create(
|
||||
domain="example.org", customer=self.customer
|
||||
)
|
||||
|
||||
def _set_mailaddress_with_forward(self):
|
||||
self.mailaddress = MailAddress.objects.create(
|
||||
localpart="test", domain=self.maildomain
|
||||
)
|
||||
self.mailaddress.set_forward_addresses(["test2@example.org"])
|
||||
|
||||
def _set_mailaddress_with_mailbox(self):
|
||||
self.mailaddress = MailAddress.objects.create(
|
||||
localpart="test", domain=self.maildomain
|
||||
)
|
||||
self.mailbox = Mailbox.objects.create_mailbox(self.package.osuser)
|
||||
self.mailaddress.set_mailbox(self.mailbox)
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_regular_user(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_other_regular_user(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
User.objects.create_user(
|
||||
"test2", email="test2@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="test2", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_staff_user(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
User.objects.create_superuser(
|
||||
"admin", email="admin@example.org", password=TEST_PASSWORD
|
||||
)
|
||||
self.client.login(username="admin", password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_template(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertTemplateUsed(response, "managemails/mailaddress_edit.html")
|
||||
|
||||
def test_get_context_data(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.assertIn("customer", response.context)
|
||||
self.assertEqual(response.context["customer"], self.customer)
|
||||
|
||||
def test_get_form_kwargs(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = EditMailAddress(
|
||||
request=MagicMock(),
|
||||
kwargs={
|
||||
"package": str(self.package.pk),
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": str(self.mailaddress.pk),
|
||||
},
|
||||
)
|
||||
the_kwargs = view.get_form_kwargs()
|
||||
self.assertIn("hostingpackage", the_kwargs)
|
||||
self.assertEqual(the_kwargs["hostingpackage"], self.package)
|
||||
self.assertIn("maildomain", the_kwargs)
|
||||
self.assertEqual(the_kwargs["maildomain"], self.maildomain)
|
||||
|
||||
def test_get_initial_with_forwards(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = EditMailAddress(
|
||||
request=MagicMock(),
|
||||
kwargs={
|
||||
"package": str(self.package.pk),
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": str(self.mailaddress.pk),
|
||||
},
|
||||
)
|
||||
initial = view.get_initial()
|
||||
self.assertIn("mailbox_or_forwards", initial)
|
||||
self.assertEqual(initial["mailbox_or_forwards"], MAILBOX_OR_FORWARDS.forwards)
|
||||
self.assertIn("forwards", initial)
|
||||
self.assertEqual(initial["forwards"], "test2@example.org")
|
||||
self.assertNotIn("mailbox", initial)
|
||||
|
||||
def test_get_initial_with_mailbox(self):
|
||||
self._set_mailaddress_with_mailbox()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
view = EditMailAddress(
|
||||
request=MagicMock(),
|
||||
kwargs={
|
||||
"package": str(self.package.pk),
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": str(self.mailaddress.pk),
|
||||
},
|
||||
)
|
||||
initial = view.get_initial()
|
||||
self.assertIn("mailbox_or_forwards", initial)
|
||||
self.assertEqual(initial["mailbox_or_forwards"], MAILBOX_OR_FORWARDS.mailbox)
|
||||
self.assertIn("mailbox", initial)
|
||||
self.assertEqual(initial["mailbox"], self.mailbox)
|
||||
self.assertNotIn("forwards", initial)
|
||||
|
||||
def test_get_initial_with_unassigned_address(self):
|
||||
self.mailaddress = MailAddress.objects.create(
|
||||
localpart="test", domain=self.maildomain
|
||||
)
|
||||
view = EditMailAddress(
|
||||
request=MagicMock(),
|
||||
kwargs={
|
||||
"package": str(self.package.pk),
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": str(self.mailaddress.pk),
|
||||
},
|
||||
)
|
||||
initial = view.get_initial()
|
||||
self.assertNotIn("mailbox_or_forwards", initial)
|
||||
self.assertNotIn("forwards", initial)
|
||||
self.assertNotIn("mailbox", initial)
|
||||
|
||||
def test_form_valid_redirect(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
),
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"mailbox": "",
|
||||
"forwards": "test2@example.org,test3@example.org",
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, self.package.get_absolute_url())
|
||||
|
||||
def test_form_valid_message(self):
|
||||
self._set_mailaddress_with_forward()
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"edit_mailaddress",
|
||||
kwargs={
|
||||
"package": self.package.pk,
|
||||
"domain": self.maildomain.domain,
|
||||
"pk": self.mailaddress.pk,
|
||||
},
|
||||
),
|
||||
follow=True,
|
||||
data={
|
||||
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
|
||||
"mailbox": "",
|
||||
"forwards": "test2@example.org,test3@example.org",
|
||||
},
|
||||
)
|
||||
messages = list(response.context["messages"])
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual(
|
||||
str(messages[0]),
|
||||
"Successfully updated mail address test@example.org targets.",
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue