Compare commits
208 commits
Author | SHA1 | Date | |
---|---|---|---|
Jan Dittberner | 11c04df074 | ||
Jan Dittberner | 7c57b4cc94 | ||
Jan Dittberner | 78e06dd2b9 | ||
Jan Dittberner | 90795375e7 | ||
Jan Dittberner | c1b226e5a1 | ||
Jan Dittberner | 5e527ef10d | ||
Jan Dittberner | 3b6d50a62a | ||
Jan Dittberner | 4b74f5d04b | ||
Jan Dittberner | ec6a9a7cc1 | ||
Jan Dittberner | f21987158b | ||
Jan Dittberner | 2e7dca529a | ||
Jan Dittberner | 1d4f070867 | ||
Jan Dittberner | d1494af0a1 | ||
Jan Dittberner | 69638b6b6d | ||
Jan Dittberner | ee561a5127 | ||
Jan Dittberner | b69ecbfa2d | ||
Jan Dittberner | bf7b02d5b5 | ||
Jan Dittberner | 22945f72bf | ||
Jan Dittberner | d0fe915612 | ||
Jan Dittberner | cb62bd63e2 | ||
Jan Dittberner | affb49a971 | ||
Jan Dittberner | 8aadae1c83 | ||
Jan Dittberner | 30ffdf1751 | ||
Jan Dittberner | aed8e97dbc | ||
Jan Dittberner | 4577ec4896 | ||
Jan Dittberner | 8c7af9a246 | ||
Jan Dittberner | 175ffd19f4 | ||
Jan Dittberner | 27e9d27b2b | ||
Jan Dittberner | 3b04595c7a | ||
Jan Dittberner | c7fda0e993 | ||
Jan Dittberner | 376cfab88f | ||
Jan Dittberner | be328758e6 | ||
Jan Dittberner | d88745f46b | ||
Jan Dittberner | 866f6c8083 | ||
Jan Dittberner | 2d05580ed3 | ||
Jan Dittberner | 397e479925 | ||
Jan Dittberner | 5db97f03d1 | ||
Jan Dittberner | 0962891a9b | ||
Jan Dittberner | a136bcc52b | ||
Jan Dittberner | 806ee80a85 | ||
Jan Dittberner | 10628ee45f | ||
Jan Dittberner | be1ed6ecea | ||
Jan Dittberner | 9fbd608837 | ||
Jan Dittberner | 8e42cb9c18 | ||
Jan Dittberner | 5cf7ef7a23 | ||
Jan Dittberner | 0f91587c60 | ||
Jan Dittberner | a65b1574db | ||
Jan Dittberner | 9731d4e793 | ||
Jan Dittberner | f9ea88cd24 | ||
Jan Dittberner | 4b7e311c62 | ||
Jan Dittberner | 37f3ed2506 | ||
Jan Dittberner | e44bdd7be2 | ||
Jan Dittberner | 345b32f286 | ||
Jan Dittberner | d499b781d4 | ||
Jan Dittberner | 472e272305 | ||
Jan Dittberner | dd67ee91da | ||
Jan Dittberner | a5b65974fb | ||
Jan Dittberner | 35aae85c8d | ||
Jan Dittberner | 3452e2a8c2 | ||
Jan Dittberner | f89de16f6e | ||
Jan Dittberner | 38dae51a7a | ||
Jan Dittberner | d2f94c7bec | ||
Jan Dittberner | fc8f22432c | ||
Jan Dittberner | a8392ef91e | ||
Jan Dittberner | 610f8976fc | ||
Jan Dittberner | d6fc29a2b8 | ||
Jan Dittberner | 4af1a39ca4 | ||
Jan Dittberner | 0f18e59d67 | ||
Jan Dittberner | b3588b5e6c | ||
Jan Dittberner | 03250cef00 | ||
Jan Dittberner | 0c17f03489 | ||
Jan Dittberner | df3628499d | ||
Jan Dittberner | f220b865ff | ||
Jan Dittberner | a1084ec785 | ||
Jan Dittberner | 2328abe8db | ||
Jan Dittberner | b12a891fd7 | ||
Jan Dittberner | 000391a808 | ||
Jan Dittberner | 9e3b7ba133 | ||
Jan Dittberner | 73b70bf35f | ||
Jan Dittberner | 42b6f8d91d | ||
Jan Dittberner | 6a88ef6bd3 | ||
Jan Dittberner | b1da42e6b7 | ||
Jan Dittberner | 5b70494863 | ||
Jan Dittberner | d90c0e096c | ||
Jan Dittberner | 367a3197a1 | ||
Jan Dittberner | 5bd55bc418 | ||
Jan Dittberner | 47a1e1dc55 | ||
Jan Dittberner | fda129e81a | ||
Jan Dittberner | d10eaee382 | ||
Jan Dittberner | 0bf37d1bea | ||
Jan Dittberner | 54c1fbfed0 | ||
Jan Dittberner | 8675359586 | ||
Jan Dittberner | cfc7d46adc | ||
Jan Dittberner | 20d864d9d1 | ||
Jan Dittberner | e487dad026 | ||
Jan Dittberner | 977189c263 | ||
Jan Dittberner | e36a8baedf | ||
Jan Dittberner | 3d18392b67 | ||
Jan Dittberner | ddec6b4184 | ||
Jan Dittberner | 96a8e0e995 | ||
Jan Dittberner | 10a83d36f7 | ||
Jan Dittberner | 24009bff3e | ||
Jan Dittberner | c3e0506133 | ||
Jan Dittberner | 6f5c0a1b7c | ||
Jan Dittberner | 6a982f8db2 | ||
Jan Dittberner | bfcabe664f | ||
Jan Dittberner | 271421cdf8 | ||
Jan Dittberner | 71aad5e078 | ||
Jan Dittberner | 6cebd80c89 | ||
Jan Dittberner | adc57657dd | ||
Jan Dittberner | c676415c97 | ||
Jan Dittberner | 40c07745d2 | ||
Jan Dittberner | 8f4c6d5696 | ||
Jan Dittberner | e8bdf3fec1 | ||
Jan Dittberner | 4eb135eeb9 | ||
Jan Dittberner | 108f0e85bf | ||
Jan Dittberner | 36d082006b | ||
Jan Dittberner | 150c9111ca | ||
Jan Dittberner | 89011b155a | ||
Jan Dittberner | 5dc3549896 | ||
Jan Dittberner | 09cfc6a373 | ||
Jan Dittberner | 3c5d02776a | ||
Jan Dittberner | 6c606034b3 | ||
Jan Dittberner | 3d95a9f61a | ||
Jan Dittberner | 9d843c920a | ||
Jan Dittberner | cd696ceb1f | ||
Jan Dittberner | f3b76a09b7 | ||
Jan Dittberner | 07b0eb6981 | ||
Jan Dittberner | eda20937dc | ||
Jan Dittberner | e0449148a7 | ||
Jan Dittberner | 6147a90066 | ||
Jan Dittberner | f1f0e35ea1 | ||
Jan Dittberner | a03a00e61b | ||
Jan Dittberner | 2018520646 | ||
Jan Dittberner | 41fbb58def | ||
Jan Dittberner | 5b48a3f2db | ||
Jan Dittberner | 37b18a17af | ||
Jan Dittberner | e7006ac4a6 | ||
Jan Dittberner | 0a0524f1f0 | ||
Jan Dittberner | 1f700fc06a | ||
Jan Dittberner | e6c38b632b | ||
Jan Dittberner | e9fc8b7f89 | ||
Jan Dittberner | a8e28fd595 | ||
Jan Dittberner | ed0a93bb3d | ||
Jan Dittberner | de501cfdde | ||
Jan Dittberner | b8893e92d7 | ||
Jan Dittberner | 6a6009e7f2 | ||
Jan Dittberner | 085b126416 | ||
Jan Dittberner | 1b30b1a38c | ||
Jan Dittberner | c9a9fa11b2 | ||
Jan Dittberner | 1649e4592e | ||
Jan Dittberner | e96aac82fc | ||
Jan Dittberner | 4e54b6fcc5 | ||
Jan Dittberner | 28ff099df9 | ||
Jan Dittberner | 8616b2d6c9 | ||
Jan Dittberner | 4f39c0d2c4 | ||
Jan Dittberner | fb1f31a9bc | ||
Jan Dittberner | 30600ce107 | ||
Jan Dittberner | 6533205479 | ||
Jan Dittberner | f2c3f64a87 | ||
Jan Dittberner | 1cfd4327da | ||
Jan Dittberner | 03a7dc0320 | ||
Jan Dittberner | 78f54d0c92 | ||
Jan Dittberner | 28fc535f9e | ||
Jan Dittberner | b11055807f | ||
Jan Dittberner | be0531ec30 | ||
Jan Dittberner | 7bcb0d3100 | ||
Jan Dittberner | de0f3b8ca1 | ||
Jan Dittberner | 337947f50c | ||
Jan Dittberner | c058cc7b1d | ||
Jan Dittberner | 1df2534cf3 | ||
Jan Dittberner | 8e954a1a8c | ||
Jan Dittberner | 64b4302b87 | ||
Jan Dittberner | fbfd3cf41b | ||
Jan Dittberner | f5759f3194 | ||
Jan Dittberner | 7d7a8941c3 | ||
Jan Dittberner | 2de53757df | ||
Jan Dittberner | e51d202abd | ||
Jan Dittberner | c5d9673ac3 | ||
Jan Dittberner | 084dd5ba8d | ||
Jan Dittberner | 660ffa9de9 | ||
Jan Dittberner | d5bba7a22d | ||
Jan Dittberner | bcfea10e6f | ||
Jan Dittberner | 4f36c21d5b | ||
Jan Dittberner | 04871bb488 | ||
Jan Dittberner | 8ebb5cad6a | ||
Jan Dittberner | 68170f7576 | ||
Jan Dittberner | 3270b43578 | ||
Jan Dittberner | 5fe414133e | ||
Jan Dittberner | 5578647f33 | ||
Jan Dittberner | 6a0f88d7d4 | ||
Jan Dittberner | 33338af352 | ||
Jan Dittberner | b07ab0a14b | ||
Jan Dittberner | 3c6b779c44 | ||
Jan Dittberner | 18ae1e15f4 | ||
Jan Dittberner | 6f9b17dc49 | ||
Jan Dittberner | 11b1befa1b | ||
Jan Dittberner | 1aa51cf61e | ||
Jan Dittberner | c5962632d2 | ||
Jan Dittberner | 4c81502b8e | ||
Jan Dittberner | 25b5b82a06 | ||
Jan Dittberner | dd38edd498 | ||
Jan Dittberner | 13160b522d | ||
Jan Dittberner | a298548244 | ||
Jan Dittberner | 9cab636351 | ||
Jan Dittberner | 2af30d6148 | ||
Jan Dittberner | 78a11055f3 | ||
Jan Dittberner | f154a2efac |
18
.dockerignore
Normal file
18
.dockerignore
Normal file
|
@ -0,0 +1,18 @@
|
|||
**/*.pyc
|
||||
**/.*.swp
|
||||
**/.coverage
|
||||
**/__pycache__
|
||||
.dockerignore
|
||||
.env
|
||||
.envrc
|
||||
.git
|
||||
.gitignore
|
||||
.idea
|
||||
.isort.cfg
|
||||
.vagrant
|
||||
Dockerfile
|
||||
Vagrantfile
|
||||
docker-compose.yml
|
||||
docs
|
||||
media
|
||||
static
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -44,3 +44,17 @@ htmlcov/
|
|||
tags
|
||||
_build/
|
||||
*.mo
|
||||
.vagrant/
|
||||
gnuviechadmin/assets/
|
||||
coverage-report/
|
||||
.idea/
|
||||
|
||||
.env
|
||||
.envrc
|
||||
|
||||
/docker/django_media
|
||||
/docker/django_static
|
||||
!/docker/django_media/.empty
|
||||
!/docker/django_static/.empty
|
||||
/media/
|
||||
/static/
|
||||
|
|
7
.isort.cfg
Normal file
7
.isort.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[tool.isort]
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = True
|
||||
force_grid_wrap = 0
|
||||
use_parentheses = True
|
||||
ensure_newline_before_comments = True
|
||||
line_length = 88
|
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/>.
|
70
Dockerfile
Normal file
70
Dockerfile
Normal file
|
@ -0,0 +1,70 @@
|
|||
ARG DEBIAN_RELEASE=bookworm
|
||||
FROM debian:$DEBIAN_RELEASE AS builder
|
||||
|
||||
ARG GVAAPP=gva
|
||||
ARG POETRY_VERSION=1.7.1
|
||||
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
libpq-dev \
|
||||
python3-dev \
|
||||
python3-setuptools \
|
||||
python3-virtualenv \
|
||||
python3-wheel
|
||||
|
||||
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/root/.local POETRY_VERSION=$POETRY_VERSION python3 - \
|
||||
&& /root/.local/bin/poetry config virtualenvs.in-project true
|
||||
|
||||
WORKDIR /srv/$GVAAPP
|
||||
|
||||
COPY poetry.lock pyproject.toml /srv/$GVAAPP/
|
||||
|
||||
RUN /root/.local/bin/poetry install --only=main --no-root
|
||||
|
||||
FROM debian:$DEBIAN_RELEASE
|
||||
LABEL maintainer="Jan Dittberner <jan@dittberner.info>"
|
||||
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
dumb-init \
|
||||
gettext \
|
||||
postgresql-client \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/cache/apt/archives /var/lib/apt/lists/*
|
||||
|
||||
ARG GVAAPP=gva
|
||||
ARG GVAGID=2000
|
||||
ARG GVAUID=2000
|
||||
|
||||
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
|
||||
|
||||
COPY --chown=$GVAAPP:$GVAAPP --from=builder /srv/$GVAAPP/.venv /srv/$GVAAPP/.venv
|
||||
|
||||
WORKDIR /srv/$GVAAPP
|
||||
|
||||
VOLUME /srv/$GVAAPP/media /srv/$GVAAPP/static
|
||||
|
||||
VOLUME /srv/$GVAAPP/gnuviechadmin
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
COPY ${GVAAPP}.sh entrypoint.sh /srv/
|
||||
|
||||
ENTRYPOINT ["dumb-init", "/srv/entrypoint.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.
|
14
README.rst
14
README.rst
|
@ -1,10 +1,18 @@
|
|||
=============
|
||||
gnuviechadmin
|
||||
=============
|
||||
-------------
|
||||
|
||||
Customer center for gnuviech servers.
|
||||
This is the GNUViech Admin Customer Center for gnuviech servers.
|
||||
|
||||
GNUViech Admin is a suite of tools for server management used for hosting
|
||||
customer management at `Jan Dittberner IT-Consulting & -Solutions
|
||||
<http://www.gnuviech-server.de>`_.
|
||||
|
||||
Gnuviechadmin is based on Django_ and Celery_
|
||||
|
||||
.. _Django: https://djangoproject.com/
|
||||
.. _Celery: http://www.celeryproject.com/
|
||||
|
||||
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.
|
||||
|
|
113
docker-compose.yml
Normal file
113
docker-compose.yml
Normal file
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
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:bookworm
|
||||
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:
|
||||
- "django_media:/srv/gva/media"
|
||||
- "django_static:/srv/gva/static"
|
||||
- "./gnuviechadmin:/srv/gva/gnuviechadmin"
|
||||
web:
|
||||
image: gnuviech/gvaweb:buster
|
||||
build:
|
||||
context: ../gvaweb
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvaweb/.env
|
||||
volumes:
|
||||
- "../gvaweb/gvaweb:/srv/gvaweb/gvaweb"
|
||||
ldap:
|
||||
image: gnuviech/gvaldap:buster
|
||||
build:
|
||||
context: ../gvaldap
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvaldap/.env
|
||||
volumes:
|
||||
- "../gvaldap/gvaldap:/srv/gvaldap/gvaldap"
|
||||
file:
|
||||
image: gnuviech/gvafile:bookworm
|
||||
build:
|
||||
context: ../gvafile
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvafile/.env
|
||||
volumes:
|
||||
- "../gvafile/gvafile:/srv/gvafile/gvafile"
|
||||
pgsql:
|
||||
image: gnuviech/gvapgsql:buster
|
||||
build:
|
||||
context: ../gvapgsql
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvapgsql/.env
|
||||
volumes:
|
||||
- "../gvapgsql/gvapgsql:/srv/gvapgsql/gvapgsql"
|
||||
mysql:
|
||||
image: gnuviech/gvamysql:buster
|
||||
build:
|
||||
context: ../gvamysql
|
||||
args:
|
||||
GVAGID: 1000
|
||||
GVAUID: 1000
|
||||
depends_on:
|
||||
- mq
|
||||
- redis
|
||||
env_file: ../gvamysql/.env
|
||||
volumes:
|
||||
- "../gvamysql/gvamysql:/srv/gvamysql/gvamysql"
|
||||
volumes:
|
||||
django_media:
|
||||
django_static:
|
||||
pg_data:
|
||||
redis_data:
|
||||
mq_data:
|
|
@ -1,6 +1,77 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
* :release:`0.15.1 <2023-07-23>
|
||||
* :bug:`-` remove stale disk usage stats older than 30 minutes
|
||||
|
||||
* :release:`0.15.0 <2023-07-23>
|
||||
* :feature:`10` add disk usage details for mail and web
|
||||
|
||||
* :release:`0.14.4 <2023-07-22>`
|
||||
* :bug:`-` add customer to disk space detail view
|
||||
|
||||
* :release:`0.14.3 <2023-07-22>`
|
||||
* :bug:`-` fix missing permission check on disk space detail view
|
||||
|
||||
* :release:`0.14.2 <2023-07-22>`
|
||||
* :bug:`-` fix division by zero for hosting packages without disk space allocation
|
||||
|
||||
* :release:`0.14.1 <2023-07-22>`
|
||||
* :bug:`-` fix squashed migration for disk space statistics
|
||||
|
||||
* :release:`0.14.0 <2023-07-22>`
|
||||
* :feature:`-` add disk space statistics
|
||||
|
||||
* :release:`0.13.0 <2023-05-08>`
|
||||
* :feature:`-` add REST API to retrieve and set user information as admin
|
||||
* :feature:`-` add support model for offline account reset codes in new help
|
||||
app
|
||||
* :support:`-` remove unused PowerDNS support tables from domains app
|
||||
* :feature:`-` add impersonation support for superusers
|
||||
* :support:`-` remove django-braces dependency
|
||||
* :support:`-` remove Twitter support
|
||||
* :support:`-` update dependencies
|
||||
|
||||
* :release:`0.12.1 <2020-04-13>`
|
||||
* :bug:`7` fix handling of undefined mail domains in customer hosting package
|
||||
detail template
|
||||
|
||||
* :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
|
||||
|
||||
* :release:`0.11.2 <2015-02-06>`
|
||||
* :bug:`-` fix wrong variable name in
|
||||
managemails.models.MailAddress.set_forward_addresses and typo in
|
||||
managemails.forms.EditMailAddressForm
|
||||
|
||||
* :release:`0.11.1 <2015-02-01>`
|
||||
* :bug:`-` fix translation of contact form by using ugettext_lazy and adding
|
||||
contact_form to INSTALLED_APPS
|
||||
|
|
|
@ -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>`
|
||||
---------------------------------------------
|
||||
|
||||
|
|
143
docs/conf.py
143
docs/conf.py
|
@ -20,40 +20,49 @@ import django
|
|||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath(os.path.join('..', 'gnuviechadmin')))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join("..", "gnuviechadmin")))
|
||||
|
||||
os.environ['GVA_SITE_ADMINMAIL'] = 'admin@gva.example.org'
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "gnuviechadmin.settings"
|
||||
os.environ["GVA_SITE_ADMINMAIL"] = "admin@gva.example.org"
|
||||
|
||||
django.setup()
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# 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']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'gnuviechadmin'
|
||||
copyright = u'2014, 2015 Jan Dittberner'
|
||||
project = "gnuviechadmin"
|
||||
copyright = "2014-2023, 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
|
||||
|
@ -63,121 +72,121 @@ copyright = u'2014, 2015 Jan Dittberner'
|
|||
from gnuviechadmin import __version__ as release
|
||||
|
||||
# The short X.Y version.
|
||||
version = ".".join(release.split('.')[:2])
|
||||
version = ".".join(release.split(".")[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# 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
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'gnuviechadmindoc'
|
||||
htmlhelp_basename = "gnuviechadmindoc"
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
@ -185,10 +194,8 @@ htmlhelp_basename = 'gnuviechadmindoc'
|
|||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
@ -196,29 +203,34 @@ latex_elements = {
|
|||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'gnuviechadmin.tex', u'gnuviechadmin Documentation',
|
||||
u'Jan Dittberner', 'manual'),
|
||||
(
|
||||
"index",
|
||||
"gnuviechadmin.tex",
|
||||
"gnuviechadmin Documentation",
|
||||
"Jan Dittberner",
|
||||
"manual",
|
||||
),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
@ -226,12 +238,11 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'gnuviechadmin', u'gnuviechadmin Documentation',
|
||||
[u'Jan Dittberner'], 1)
|
||||
("index", "gnuviechadmin", "gnuviechadmin Documentation", ["Jan Dittberner"], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
@ -240,16 +251,22 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'gnuviechadmin', u'gnuviechadmin Documentation',
|
||||
u'Jan Dittberner', 'gnuviechadmin', 'Customer center for gnuviech servers.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
"index",
|
||||
"gnuviechadmin",
|
||||
"gnuviechadmin Documentation",
|
||||
"Jan Dittberner",
|
||||
"gnuviechadmin",
|
||||
"Customer center for gnuviech servers.",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
Deploy
|
||||
========
|
||||
======
|
||||
|
||||
This is where you describe how the project is deployed in production.
|
||||
The production deployment for gnuviechadmin is performed using saltstack and
|
||||
consists of the following steps:
|
||||
|
||||
* installation of native dependencies
|
||||
* setup of a virtualenv
|
||||
* installation of gnuviechadmin production dependencies inside the virtualenv
|
||||
* setup of uwsgi application for the web interface
|
||||
* setup of nginx with certificates and UWSGI support
|
||||
|
|
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 |
|
@ -1,19 +1,8 @@
|
|||
Development ideas and planned features
|
||||
======================================
|
||||
|
||||
* password reset for
|
||||
|
||||
- OS users
|
||||
- mailboxes
|
||||
|
||||
* add ssh key management for sftp users
|
||||
|
||||
* link to phpmyadmin and phppgadmin
|
||||
|
||||
* link to webmail
|
||||
|
||||
* list mail domains
|
||||
|
||||
* CRUD for mailboxes
|
||||
|
||||
* CRUD for mail addresses
|
||||
* add pure redirect websites
|
||||
* add management for rewrite rules
|
||||
* add accounts without SFTP (for pure mail hosting)
|
||||
* allow generation of Key and CSR, add upload of certificates for HTTPS sites
|
||||
* add XMPP management
|
||||
|
|
|
@ -3,34 +3,40 @@
|
|||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
.. include:: ../README.rst
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
gnuviechadmin is licensed under the terms of the MIT license:
|
||||
|
||||
.. include:: ../LICENSE.txt
|
||||
:literal:
|
||||
|
||||
=========================================
|
||||
Welcome to gnuviechadmin's documentation!
|
||||
=========================================
|
||||
|
||||
Contents:
|
||||
.. include:: ../README.rst
|
||||
|
||||
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
|
||||
==================
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
|
|
|
@ -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`
|
7
entrypoint.sh
Executable file
7
entrypoint.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
chown -Rc gva.gva /srv/gva/media /srv/gva/static
|
||||
|
||||
su -c /srv/gva.sh gva
|
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
frontend/.gitignore
vendored
Normal file
1
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules/
|
46
frontend/package-lock.json
generated
Normal file
46
frontend/package-lock.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
|
||||
"integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz",
|
||||
"integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.6"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-icons": {
|
||||
"version": "1.10.4",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.4.tgz",
|
||||
"integrity": "sha512-eI3HyIUmpGKRiRv15FCZccV+2sreGE2NnmH8mtxV/nPOzQVu0sPEj8HhF1MwjJ31IhjF0rgMvtYOX5VqIzcb/A=="
|
||||
}
|
||||
}
|
||||
}
|
6
frontend/package.json
Normal file
6
frontend/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.4"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -4,19 +4,17 @@ This module contains the form class for the contact_form app.
|
|||
"""
|
||||
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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.contrib.sites.models import RequestSite
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
from django import forms
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites.requests import RequestSite
|
||||
from django.core.mail import send_mail
|
||||
from django.template import RequestContext, loader
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
|
@ -24,40 +22,42 @@ class ContactForm(forms.Form):
|
|||
This is the contact form class.
|
||||
|
||||
"""
|
||||
name = forms.CharField(max_length=100, label=_('Your name'))
|
||||
email = forms.EmailField(max_length=200, label=_('Your email address'))
|
||||
body = forms.CharField(widget=forms.Textarea, label=_('Your message'))
|
||||
|
||||
name = forms.CharField(max_length=100, label=_("Your name"))
|
||||
email = forms.EmailField(max_length=200, label=_("Your email address"))
|
||||
body = forms.CharField(widget=forms.Textarea, label=_("Your message"))
|
||||
|
||||
subject_template_name = "contact_form/contact_form_subject.txt"
|
||||
template_name = 'contact_form/contact_form.txt'
|
||||
template_name = "contact_form/contact_form.txt"
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.request = kwargs.pop('request')
|
||||
self.request = kwargs.pop("request")
|
||||
super(ContactForm, self).__init__(**kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_action = reverse('contact_form')
|
||||
self.helper.add_input(Submit('submit', _('Send message')))
|
||||
self.helper.form_action = reverse("contact_form")
|
||||
self.helper.add_input(Submit("submit", _("Send message")))
|
||||
|
||||
def get_context(self):
|
||||
if not self.is_valid():
|
||||
raise ValueError(
|
||||
'Cannot generate context from invalid contact form')
|
||||
if Site._meta.installed:
|
||||
raise ValueError("Cannot generate context from invalid contact form")
|
||||
if apps.is_installed("django.contrib.sites"):
|
||||
site = Site.objects.get_current()
|
||||
else:
|
||||
site = RequestSite(self.request)
|
||||
return RequestContext(
|
||||
self.request, dict(self.cleaned_data, site=site))
|
||||
return RequestContext(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())
|
||||
return ''.join(subject.splitlines())
|
||||
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):
|
||||
"""
|
||||
|
@ -69,5 +69,5 @@ class ContactForm(forms.Form):
|
|||
from_email=self.from_email,
|
||||
recipient_list=self.recipient_list,
|
||||
subject=self.subject(),
|
||||
message=self.message()
|
||||
message=self.message(),
|
||||
)
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: contact_form\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-02-01 19:02+0100\n"
|
||||
"PO-Revision-Date: 2015-02-01 19:03+0100\n"
|
||||
"POT-Creation-Date: 2023-07-22 19:45+0200\n"
|
||||
"PO-Revision-Date: 2023-04-22 13:01+0200\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\n"
|
||||
|
@ -16,21 +16,32 @@ 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 3.2.2\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: forms.py:27
|
||||
#: contact_form/forms.py:26
|
||||
msgid "Your name"
|
||||
msgstr "Ihr Name"
|
||||
|
||||
#: forms.py:28
|
||||
#: contact_form/forms.py:27
|
||||
msgid "Your email address"
|
||||
msgstr "Ihre E-Mailadresse"
|
||||
msgstr "Ihre E-Mail-Adresse"
|
||||
|
||||
#: forms.py:29
|
||||
#: contact_form/forms.py:28
|
||||
msgid "Your message"
|
||||
msgstr "Ihre Nachricht"
|
||||
|
||||
#: forms.py:41
|
||||
#: contact_form/forms.py:40
|
||||
msgid "Send message"
|
||||
msgstr "Nachricht senden"
|
||||
|
||||
#: contact_form/templates/contact_form/contact_form.html:4
|
||||
#: contact_form/templates/contact_form/contact_form.html:5
|
||||
#: contact_form/templates/contact_form/contact_success.html:4
|
||||
#: contact_form/templates/contact_form/contact_success.html:5
|
||||
msgid "Contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
#: contact_form/templates/contact_form/contact_success.html:8
|
||||
msgid "Your message has been sent successfully."
|
||||
msgstr "Ihre Nachricht wurde erfolgreich übermittelt."
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "contact_form/base.html" %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% translate "Contact" %}{% endblock title %}
|
||||
{% block page_title %}{% translate "Contact" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
{% crispy form %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
let textFields = document.querySelectorAll("input[type=text]");
|
||||
|
||||
if (textFields[0].val() !== '') {
|
||||
document.getElementsByTagName("textarea")[0].focus();
|
||||
} else {
|
||||
textFields[0].focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock extra_js %}
|
|
@ -1,4 +1,4 @@
|
|||
User {{ name }} <{{ email }}> from IP address {{ request.META.REMOTE_ADDR }}
|
||||
User {{ name }} <{{ email }}> from IP address {{ remote_ip }}
|
||||
sent the following message via the contact form at
|
||||
{{ site }}{% url 'contact_form' %}:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "contact_form/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ block.super }} - {% translate "Contact" %}{% endblock title %}
|
||||
{% block page_title %}{% translate "Contact" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<p class="text-success">{% translate "Your message has been sent successfully." %}</p>
|
||||
{% endblock %}
|
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)
|
|
@ -2,18 +2,13 @@
|
|||
URL patterns for the contact_form views.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import (
|
||||
ContactFormView,
|
||||
ContactSuccessView,
|
||||
)
|
||||
from .views import ContactFormView, ContactSuccessView
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', ContactFormView.as_view(), name='contact_form'),
|
||||
url(r'^success/$', ContactSuccessView.as_view(), name='contact_success'),
|
||||
)
|
||||
urlpatterns = [
|
||||
re_path(r"^$", ContactFormView.as_view(), name="contact_form"),
|
||||
re_path(r"^success/$", ContactSuccessView.as_view(), name="contact_success"),
|
||||
]
|
||||
|
|
|
@ -2,14 +2,11 @@
|
|||
This module defines the views of the contact_form app.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.views.generic import (
|
||||
FormView,
|
||||
TemplateView,
|
||||
)
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import FormView, TemplateView
|
||||
|
||||
from .forms import ContactForm
|
||||
|
||||
|
@ -19,22 +16,22 @@ class ContactFormView(FormView):
|
|||
This is the contact form view.
|
||||
|
||||
"""
|
||||
|
||||
form_class = ContactForm
|
||||
template_name = 'contact_form/contact_form.html'
|
||||
success_url = reverse_lazy('contact_success')
|
||||
template_name = "contact_form/contact_form.html"
|
||||
success_url = reverse_lazy("contact_success")
|
||||
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super(ContactFormView, self).get_form_kwargs(**kwargs)
|
||||
kwargs['request'] = self.request
|
||||
kwargs["request"] = self.request
|
||||
return kwargs
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(ContactFormView, self).get_initial()
|
||||
currentuser = self.request.user
|
||||
if currentuser.is_authenticated():
|
||||
initial['name'] = (
|
||||
currentuser.get_full_name() or currentuser.username)
|
||||
initial['email'] = currentuser.email
|
||||
if currentuser.is_authenticated:
|
||||
initial["name"] = currentuser.get_full_name() or currentuser.username
|
||||
initial["email"] = currentuser.email
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
|
@ -47,4 +44,5 @@ class ContactSuccessView(TemplateView):
|
|||
This view is shown after successful contact form sending.
|
||||
|
||||
"""
|
||||
template_name = 'contact_form/contact_success.html'
|
||||
|
||||
template_name = "contact_form/contact_success.html"
|
||||
|
|
|
@ -7,18 +7,53 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gnuviechadmin dashboard\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-17 15:59+0100\n"
|
||||
"PO-Revision-Date: 2015-01-17 16:01+0100\n"
|
||||
"POT-Creation-Date: 2023-07-22 19:45+0200\n"
|
||||
"PO-Revision-Date: 2023-07-22 19:46+0200\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <de@li.org>\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-Generator: Poedit 3.2.2\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: dashboard/views.py:43
|
||||
msgid "You are not allowed to view this page."
|
||||
msgstr "Sie haben nicht die nötigen Berechtigungen um diese Seite zu sehen."
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:3
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:6
|
||||
#, python-format
|
||||
msgid "Dashboard for %(full_name)s"
|
||||
msgstr "Startseite für %(full_name)s"
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:10
|
||||
msgid "Hosting packages"
|
||||
msgstr "Hostingpakete"
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:17
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:18
|
||||
msgid "Setup date"
|
||||
msgstr "Einrichtungsdatum"
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:19
|
||||
msgid "Actions"
|
||||
msgstr "Aktionen"
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:26
|
||||
#, python-format
|
||||
msgid "Show details for %(packagename)s"
|
||||
msgstr "Details für %(packagename)s anzeigen"
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:38
|
||||
msgid "You have no hosting packages yet."
|
||||
msgstr "Sie haben noch keine Hostingpakete."
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:39
|
||||
msgid "This user has no hosting packages assigned yet."
|
||||
msgstr "Diesem Benutzer sind noch keine Hostingpakete zugewiesen."
|
||||
|
||||
#: dashboard/templates/dashboard/user_dashboard.html:43
|
||||
msgid "Add hosting package"
|
||||
msgstr "Hostingpaket anlegen"
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ block.super }} - {% blocktranslate with full_name=request.user.get_full_name trimmed %}
|
||||
Dashboard for {{ full_name }}
|
||||
{% endblocktranslate %}{% endblock title %}
|
||||
{% block page_title %}{% blocktranslate with full_name=request.user.get_full_name trimmed %}
|
||||
Dashboard for {{ full_name }}
|
||||
{% endblocktranslate %}{% endblock page_title %}
|
||||
{% block content %}
|
||||
<h2>{% translate "Hosting packages" %}</h2>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% if hosting_packages %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Name" %}</th>
|
||||
<th>{% translate "Setup date" %}</th>
|
||||
<th>{% translate "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for package in hosting_packages %}
|
||||
<tr>
|
||||
<td><a href="{{ package.get_absolute_url }}"
|
||||
title="{% blocktranslate with packagename=package.name trimmed %}
|
||||
Show details for {{ packagename }}
|
||||
{% endblocktranslate %}">{{ package.name }}</a>
|
||||
</td>
|
||||
<td>{{ package.created }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-info">
|
||||
{% if user == object %}{% translate "You have no hosting packages yet." %}{% else %}
|
||||
{% translate "This user has no hosting packages assigned yet." %}{% endif %}</p>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url "create_customer_hosting_package" user=request.user.username %}"
|
||||
class="btn btn-primary">{% translate "Add hosting package" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
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`.
|
||||
|
||||
"""
|
47
gnuviechadmin/dashboard/tests/test_views.py
Normal file
47
gnuviechadmin/dashboard/tests/test_views.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Tests for :py:mod:`dashboard.views`.
|
||||
|
||||
"""
|
||||
from django import http
|
||||
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 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_anonymous(self):
|
||||
User.objects.create(username=TEST_USER)
|
||||
response = self.client.get(reverse("customer_dashboard"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertRedirects(response, "/accounts/login/?next=/")
|
||||
|
||||
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"))
|
||||
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")
|
||||
)
|
||||
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"))
|
||||
self.assertIn("hosting_packages", response.context)
|
||||
self.assertEqual(len(response.context["hosting_packages"]), 0)
|
|
@ -1,16 +1,9 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.urls import path
|
||||
|
||||
from .views import (
|
||||
IndexView,
|
||||
UserDashboardView,
|
||||
)
|
||||
from .views import UserDashboardView
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', IndexView.as_view(), name='dashboard'),
|
||||
url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$',
|
||||
UserDashboardView.as_view(), name='customer_dashboard'),
|
||||
)
|
||||
urlpatterns = [
|
||||
path("", UserDashboardView.as_view(), name="customer_dashboard"),
|
||||
]
|
||||
|
|
|
@ -2,47 +2,26 @@
|
|||
This module defines the views for the gnuviechadmin customer dashboard.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.views.generic import (
|
||||
DetailView,
|
||||
TemplateView,
|
||||
)
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import DetailView, TemplateView
|
||||
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
|
||||
|
||||
from hostingpackages.models import CustomerHostingPackage
|
||||
|
||||
|
||||
class IndexView(TemplateView):
|
||||
"""
|
||||
This is the dashboard view.
|
||||
|
||||
"""
|
||||
template_name = 'dashboard/index.html'
|
||||
|
||||
|
||||
class UserDashboardView(StaffOrSelfLoginRequiredMixin, DetailView):
|
||||
class UserDashboardView(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
This is the user dashboard view.
|
||||
|
||||
"""
|
||||
model = get_user_model()
|
||||
context_object_name = 'dashboard_user'
|
||||
slug_field = 'username'
|
||||
template_name = 'dashboard/user_dashboard.html'
|
||||
|
||||
template_name = "dashboard/user_dashboard.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UserDashboardView, self).get_context_data(**kwargs)
|
||||
context['hosting_packages'] = CustomerHostingPackage.objects.filter(
|
||||
customer=self.object
|
||||
context["hosting_packages"] = CustomerHostingPackage.objects.filter(
|
||||
customer=self.request.user
|
||||
)
|
||||
return context
|
||||
|
||||
def get_customer_object(self):
|
||||
"""
|
||||
Returns the customer object.
|
||||
|
||||
"""
|
||||
return self.get_object()
|
||||
|
|
|
@ -2,4 +2,3 @@
|
|||
This app takes care of domains.
|
||||
|
||||
"""
|
||||
default_app_config = 'domains.apps.DomainAppConfig'
|
||||
|
|
|
@ -5,10 +5,7 @@ with the django admin site.
|
|||
"""
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import (
|
||||
MailDomain,
|
||||
HostingDomain,
|
||||
)
|
||||
from domains.models import HostingDomain, MailDomain
|
||||
|
||||
admin.site.register(MailDomain)
|
||||
admin.site.register(HostingDomain)
|
||||
|
|
|
@ -3,9 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
|
|||
:py:mod:`domains` app.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class DomainAppConfig(AppConfig):
|
||||
|
@ -13,5 +12,6 @@ class DomainAppConfig(AppConfig):
|
|||
AppConfig for the :py:mod:`domains` app.
|
||||
|
||||
"""
|
||||
name = 'domains'
|
||||
verbose_name = _('Domains')
|
||||
|
||||
name = "domains"
|
||||
verbose_name = _("Domains")
|
||||
|
|
|
@ -2,33 +2,30 @@
|
|||
This module defines form classes for domain editing.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import (
|
||||
Layout,
|
||||
Submit,
|
||||
)
|
||||
from crispy_forms.layout import Layout, Submit
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
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(
|
||||
_('host name too long'), code='too-long')
|
||||
raise forms.ValidationError(_("host name too long"), code="too-long")
|
||||
allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$")
|
||||
if not all(allowed.match(x) for x in value.split('.')):
|
||||
raise forms.ValidationError(_('invalid domain name'))
|
||||
if not all(allowed.match(x) for x in value.split(".")):
|
||||
raise forms.ValidationError(_("invalid domain name"))
|
||||
|
||||
|
||||
class CreateHostingDomainForm(forms.ModelForm):
|
||||
|
@ -36,31 +33,32 @@ class CreateHostingDomainForm(forms.ModelForm):
|
|||
This form is used to create new HostingDomain instances.
|
||||
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = HostingDomain
|
||||
fields = ['domain']
|
||||
fields = ["domain"]
|
||||
|
||||
def __init__(self, instance, *args, **kwargs):
|
||||
self.hosting_package = kwargs.pop('hostingpackage')
|
||||
self.hosting_package = kwargs.pop("hostingpackage")
|
||||
super(CreateHostingDomainForm, self).__init__(*args, **kwargs)
|
||||
self.fields['domain'].validators.append(relative_domain_validator)
|
||||
self.fields["domain"].validators.append(relative_domain_validator)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_action = reverse(
|
||||
'create_hosting_domain', kwargs={
|
||||
'package': self.hosting_package.id
|
||||
})
|
||||
"create_hosting_domain", kwargs={"package": self.hosting_package.id}
|
||||
)
|
||||
self.helper.layout = Layout(
|
||||
'domain',
|
||||
Submit('submit', _('Add Hosting Domain')),
|
||||
"domain",
|
||||
Submit("submit", _("Add Hosting Domain")),
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
self.cleaned_data = super(CreateHostingDomainForm, self).clean()
|
||||
self.cleaned_data['hosting_package'] = self.hosting_package
|
||||
self.cleaned_data["hosting_package"] = self.hosting_package
|
||||
|
||||
def save(self, commit=True):
|
||||
return HostingDomain.objects.create_for_hosting_package(
|
||||
commit=commit, **self.cleaned_data)
|
||||
commit=commit, **self.cleaned_data
|
||||
)
|
||||
|
||||
def save_m2m(self):
|
||||
pass
|
||||
|
|
|
@ -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: 2023-04-16 22:07+0200\n"
|
||||
"PO-Revision-Date: 2023-04-16 18:20+0200\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\n"
|
||||
|
@ -16,58 +16,60 @@ 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 3.2.2\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: domains/apps.py:17
|
||||
msgid "Domains"
|
||||
msgstr "Domains"
|
||||
|
||||
#: domains/forms.py:28
|
||||
#: domains/forms.py:25 domains/tests/test_forms.py:20
|
||||
msgid "host name too long"
|
||||
msgstr "zu langer Hostname"
|
||||
|
||||
#: domains/forms.py:31
|
||||
#: domains/forms.py:28 domains/tests/test_forms.py:24
|
||||
#: domains/tests/test_forms.py:28 domains/tests/test_forms.py:32
|
||||
#: domains/tests/test_forms.py:36
|
||||
msgid "invalid domain name"
|
||||
msgstr "ungültiger Domainname"
|
||||
|
||||
#: domains/forms.py:54
|
||||
#: domains/forms.py:51
|
||||
msgid "Add Hosting Domain"
|
||||
msgstr "Hostingdomain hinzufügen"
|
||||
|
||||
#: domains/models.py:21
|
||||
#: domains/models.py:19
|
||||
msgid "domain name"
|
||||
msgstr "Domainname"
|
||||
|
||||
#: domains/models.py:23
|
||||
#: domains/models.py:22
|
||||
msgid "customer"
|
||||
msgstr "Kunde"
|
||||
|
||||
#: domains/models.py:42
|
||||
#: domains/models.py:41
|
||||
msgid "Mail domain"
|
||||
msgstr "E-Maildomain"
|
||||
|
||||
#: domains/models.py:43
|
||||
#: domains/models.py:42
|
||||
msgid "Mail domains"
|
||||
msgstr "E-Maildomains"
|
||||
|
||||
#: domains/models.py:87
|
||||
#: domains/models.py:91
|
||||
msgid "mail domain"
|
||||
msgstr "E-Maildomain"
|
||||
|
||||
#: domains/models.py:88
|
||||
#: domains/models.py:94
|
||||
msgid "assigned mail domain for this domain"
|
||||
msgstr "zugeordnete E-Maildomain für diese Domain"
|
||||
|
||||
#: domains/models.py:94
|
||||
#: domains/models.py:101
|
||||
msgid "Hosting domain"
|
||||
msgstr "Hostingdomain"
|
||||
|
||||
#: domains/models.py:95
|
||||
#: domains/models.py:102
|
||||
msgid "Hosting domains"
|
||||
msgstr "Hostingdomains"
|
||||
|
||||
#: domains/views.py:58
|
||||
#: domains/views.py:51
|
||||
#, python-brace-format
|
||||
msgid "Successfully created domain {domainname}"
|
||||
msgstr "Domain {domainname} erfolgreich angelegt"
|
||||
|
|
|
@ -1,28 +1,46 @@
|
|||
# -*- 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 = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MailDomain',
|
||||
name="MailDomain",
|
||||
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)),
|
||||
(
|
||||
"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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Mail domain',
|
||||
'verbose_name_plural': 'Mail domains',
|
||||
"verbose_name": "Mail domain",
|
||||
"verbose_name_plural": "Mail domains",
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
|
|
|
@ -1,46 +1,97 @@
|
|||
# -*- 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),
|
||||
('domains', '0001_initial'),
|
||||
("domains", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HostingDomain',
|
||||
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',
|
||||
'verbose_name_plural': 'Hosting domains',
|
||||
"verbose_name": "Hosting domain",
|
||||
"verbose_name_plural": "Hosting domains",
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='maildomain',
|
||||
name='customer',
|
||||
field=models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
model_name="maildomain",
|
||||
name="customer",
|
||||
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'),
|
||||
model_name="maildomain",
|
||||
name="domain",
|
||||
field=models.CharField(
|
||||
unique=True, max_length=128, verbose_name="domain name"
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
285
gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py
Normal file
285
gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py
Normal file
|
@ -0,0 +1,285 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import django.utils.timezone
|
||||
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),
|
||||
("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")},
|
||||
),
|
||||
]
|
87
gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py
Normal file
87
gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
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"),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,74 @@
|
|||
# Generated by Django 3.2.18 on 2023-04-15 09:53
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('domains', '0004_auto_20151107_1708'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='dnscomment',
|
||||
index_together=None,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnscomment',
|
||||
name='customer',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnscomment',
|
||||
name='domain',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnscryptokey',
|
||||
name='domain',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnsdomain',
|
||||
name='customer',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnsdomainmetadata',
|
||||
name='domain',
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='dnsrecord',
|
||||
index_together=None,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnsrecord',
|
||||
name='domain',
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dnssupermaster',
|
||||
unique_together=None,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dnssupermaster',
|
||||
name='customer',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSTSIGKey',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSComment',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSCryptoKey',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSDomain',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSDomainMetadata',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSRecord',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DNSSupermaster',
|
||||
),
|
||||
]
|
|
@ -2,35 +2,33 @@
|
|||
This module contains models related to domain names.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.utils.translation import gettext as _
|
||||
from model_utils.models import TimeStampedModel
|
||||
|
||||
|
||||
@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)
|
||||
settings.AUTH_USER_MODEL,
|
||||
verbose_name=_("customer"),
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class MailDomain(DomainBase):
|
||||
"""
|
||||
This is the model for mail domains. Mail domains are used to configure the
|
||||
|
@ -38,9 +36,10 @@ class MailDomain(DomainBase):
|
|||
domains.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
verbose_name = _('Mail domain')
|
||||
verbose_name_plural = _('Mail domains')
|
||||
|
||||
class Meta(DomainBase.Meta):
|
||||
verbose_name = _("Mail domain")
|
||||
verbose_name_plural = _("Mail domains")
|
||||
|
||||
def __str__(self):
|
||||
return self.domain
|
||||
|
@ -51,6 +50,7 @@ class MailDomain(DomainBase):
|
|||
|
||||
"""
|
||||
return self.mailaddress_set.all()
|
||||
|
||||
mailaddresses = property(get_mailaddresses)
|
||||
|
||||
|
||||
|
@ -59,40 +59,47 @@ class HostingDomainManager(models.Manager):
|
|||
Default Manager for :py:class:`HostingDomain`.
|
||||
|
||||
"""
|
||||
|
||||
@transaction.atomic
|
||||
def create_for_hosting_package(
|
||||
self, hosting_package, domain, commit, **kwargs
|
||||
):
|
||||
def create_for_hosting_package(self, hosting_package, domain, commit, **kwargs):
|
||||
from hostingpackages.models import CustomerHostingPackageDomain
|
||||
|
||||
hostingdomain = self.create(
|
||||
customer=hosting_package.customer, domain=domain, **kwargs)
|
||||
customer=hosting_package.customer, domain=domain, **kwargs
|
||||
)
|
||||
hostingdomain.maildomain = MailDomain.objects.create(
|
||||
customer=hosting_package.customer, domain=domain)
|
||||
customer=hosting_package.customer, domain=domain
|
||||
)
|
||||
custdomain = CustomerHostingPackageDomain.objects.create(
|
||||
hosting_package=hosting_package, domain=hostingdomain)
|
||||
hosting_package=hosting_package, domain=hostingdomain
|
||||
)
|
||||
if commit:
|
||||
hostingdomain.save()
|
||||
custdomain.save()
|
||||
return hostingdomain
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class HostingDomain(DomainBase):
|
||||
"""
|
||||
This is the model for hosting domains. A hosting domain is linked to a
|
||||
customer hosting account.
|
||||
|
||||
"""
|
||||
|
||||
maildomain = models.OneToOneField(
|
||||
MailDomain, verbose_name=_('mail domain'), blank=True, null=True,
|
||||
help_text=_('assigned mail domain for this domain')
|
||||
MailDomain,
|
||||
verbose_name=_("mail domain"),
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("assigned mail domain for this domain"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
objects = HostingDomainManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Hosting domain')
|
||||
verbose_name_plural = _('Hosting domains')
|
||||
verbose_name = _("Hosting domain")
|
||||
verbose_name_plural = _("Hosting domains")
|
||||
|
||||
def __str__(self):
|
||||
return self.domain
|
||||
|
|
|
@ -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 gettext as _
|
||||
|
||||
from domains.forms import CreateHostingDomainForm, relative_domain_validator
|
||||
|
||||
|
||||
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,69 @@
|
|||
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 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")
|
||||
|
|
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]))
|
|
@ -2,15 +2,16 @@
|
|||
This module defines the URL patterns for domain related views.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import CreateHostingDomain
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^(?P<package>\d+)/create$', CreateHostingDomain.as_view(),
|
||||
name='create_hosting_domain'),
|
||||
)
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
r"^(?P<package>\d+)/create$",
|
||||
CreateHostingDomain.as_view(),
|
||||
name="create_hosting_domain",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -2,17 +2,13 @@
|
|||
This module defines views related to domains.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.views.generic.edit import CreateView
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib import messages
|
||||
|
||||
from braces.views import (
|
||||
LoginRequiredMixin,
|
||||
StaffuserRequiredMixin,
|
||||
)
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.edit import CreateView
|
||||
|
||||
from hostingpackages.models import CustomerHostingPackage
|
||||
|
||||
|
@ -20,42 +16,40 @@ from .forms import CreateHostingDomainForm
|
|||
from .models import HostingDomain
|
||||
|
||||
|
||||
class CreateHostingDomain(
|
||||
LoginRequiredMixin, StaffuserRequiredMixin, CreateView
|
||||
):
|
||||
class CreateHostingDomain(PermissionRequiredMixin, 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'
|
||||
permission_required = 'domains.add_hostingdomain'
|
||||
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.1'
|
||||
__version__ = "0.15.1"
|
||||
|
|
11
gnuviechadmin/gnuviechadmin/auth.py
Normal file
11
gnuviechadmin/gnuviechadmin/auth.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
|
||||
|
||||
class NoNewUsersAccountAdapter(DefaultAccountAdapter):
|
||||
"""
|
||||
Adapter to disable allauth new signups
|
||||
|
||||
"""
|
||||
|
||||
def is_open_for_signup(self, request):
|
||||
return False
|
|
@ -6,11 +6,15 @@ from celery import Celery
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
|
||||
'gnuviechadmin.settings.production')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings")
|
||||
|
||||
|
||||
app = Celery('gnuviechadmin')
|
||||
app = Celery("gnuviechadmin")
|
||||
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
|
||||
def get_installed_apps():
|
||||
return settings.INSTALLED_APPS
|
||||
|
||||
|
||||
app.config_from_object("django.conf:settings")
|
||||
app.autodiscover_tasks(get_installed_apps)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
This module provides context processor implementations for gnuviechadmin.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
|
@ -13,7 +13,6 @@ from gnuviechadmin import __version__ as gvaversion
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
def navigation(request):
|
||||
"""
|
||||
Add navigation items to the request context.
|
||||
|
@ -23,38 +22,42 @@ def navigation(request):
|
|||
:rtype: dict
|
||||
|
||||
"""
|
||||
if request.is_ajax():
|
||||
if request.headers.get("x-requested-with") == "XMLHttpRequest":
|
||||
return {}
|
||||
context = {
|
||||
'webmail_url': settings.GVA_LINK_WEBMAIL,
|
||||
'phpmyadmin_url': settings.GVA_LINK_PHPMYADMIN,
|
||||
'phppgadmin_url': settings.GVA_LINK_PHPPGADMIN,
|
||||
'active_item': 'dashboard',
|
||||
"webmail_url": settings.GVA_LINK_WEBMAIL,
|
||||
"phpmyadmin_url": settings.GVA_LINK_PHPMYADMIN,
|
||||
"phppgadmin_url": settings.GVA_LINK_PHPPGADMIN,
|
||||
"active_item": "dashboard",
|
||||
}
|
||||
if request.resolver_match:
|
||||
viewfunc = request.resolver_match.func
|
||||
viewmodule = viewfunc.__module__
|
||||
if viewmodule == 'contact_form.views':
|
||||
context['active_item'] = 'contact'
|
||||
if viewmodule == "contact_form.views":
|
||||
context["active_item"] = "contact"
|
||||
elif viewmodule in (
|
||||
'hostingpackages.views', 'osusers.views', 'userdbs.views',
|
||||
'managemails.views', 'websites.views', 'domains.views',
|
||||
"hostingpackages.views",
|
||||
"osusers.views",
|
||||
"userdbs.views",
|
||||
"managemails.views",
|
||||
"websites.views",
|
||||
"domains.views",
|
||||
):
|
||||
context['active_item'] = 'hostingpackage'
|
||||
elif viewmodule in (
|
||||
'allauth.account.views', 'allauth.socialaccount.views'
|
||||
context["active_item"] = "hostingpackage"
|
||||
elif viewmodule in ("allauth.account.views", "allauth.socialaccount.views"):
|
||||
context["active_item"] = "account"
|
||||
elif viewmodule == "django.contrib.flatpages.views" and request.path.endswith(
|
||||
"/impressum/"
|
||||
):
|
||||
context['active_item'] = 'account'
|
||||
elif (
|
||||
viewmodule == 'django.contrib.flatpages.views' and
|
||||
request.path.endswith('/impressum/')
|
||||
):
|
||||
context['active_item'] = 'imprint'
|
||||
else:
|
||||
context["active_item"] = "imprint"
|
||||
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',
|
||||
viewfunc.__name__, viewmodule, context['active_item'])
|
||||
"no special handling for view %s in module %s, fallback to "
|
||||
"default active menu item %s",
|
||||
viewfunc.__name__,
|
||||
viewmodule,
|
||||
context["active_item"],
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
|
@ -65,6 +68,6 @@ def version_info(request):
|
|||
|
||||
"""
|
||||
context = {
|
||||
'gnuviechadmin_version': gvaversion,
|
||||
"gnuviechadmin_version": gvaversion,
|
||||
}
|
||||
return context
|
||||
|
|
514
gnuviechadmin/gnuviechadmin/settings.py
Normal file
514
gnuviechadmin/gnuviechadmin/settings.py
Normal file
|
@ -0,0 +1,514 @@
|
|||
# -*- 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
|
||||
|
||||
GVA_ENVIRONMENT = get_env_variable("GVA_ENVIRONMENT", default="prod")
|
||||
|
||||
# ######### DEBUG CONFIGURATION
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = GVA_ENVIRONMENT == "local"
|
||||
# ######### 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),
|
||||
}
|
||||
}
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
# ######### 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-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",
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
"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",
|
||||
"crispy_bootstrap5",
|
||||
"impersonate",
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
)
|
||||
|
||||
ALLAUTH_APPS = (
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"allauth.socialaccount.providers.google",
|
||||
"allauth.socialaccount.providers.linkedin_oauth2",
|
||||
)
|
||||
|
||||
# Apps specific for this project go here.
|
||||
LOCAL_APPS = (
|
||||
"dashboard",
|
||||
"taskresults",
|
||||
"ldaptasks",
|
||||
"mysqltasks",
|
||||
"pgsqltasks",
|
||||
"fileservertasks",
|
||||
"webtasks",
|
||||
"domains",
|
||||
"osusers",
|
||||
"managemails",
|
||||
"userdbs",
|
||||
"hostingpackages",
|
||||
"websites",
|
||||
"help",
|
||||
"invoices",
|
||||
"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_ADAPTER = "gnuviechadmin.auth.NoNewUsersAccountAdapter"
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
SOCIALACCOUNT_AUTO_SIGNUP = False
|
||||
SOCIALACCOUNT_QUERY_EMAIL = True
|
||||
# ######### END ALLAUTH CONFIGURATION
|
||||
|
||||
|
||||
# ######### CRISPY FORMS CONFIGURATION
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
# ######### END CRISPY_FORMS CONFIGURATION
|
||||
|
||||
|
||||
# ######### REST FRAMEWORK CONFIGURATION
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||
"rest_framework.authentication.BasicAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
"rest_framework.authentication.TokenAuthentication",
|
||||
],
|
||||
"DEFAULT_RENDERER_CLASSES": [
|
||||
"rest_framework.renderers.JSONRenderer",
|
||||
],
|
||||
"DEFAULT_PERMISSION_CLASSES": [
|
||||
"rest_framework.permissions.IsAdminUser",
|
||||
],
|
||||
}
|
||||
# ######### END REST FRAMEWORK 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": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
},
|
||||
"logfile": {
|
||||
"level": "INFO",
|
||||
"class": "logging.FileHandler",
|
||||
"filename": get_env_variable("GVA_LOG_FILE", default="gva.log"),
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
"level": "WARNING",
|
||||
},
|
||||
"loggers": {
|
||||
"django.request": {
|
||||
"handlers": ["mail_admins"],
|
||||
"level": "ERROR",
|
||||
"propagate": True,
|
||||
},
|
||||
"django": {
|
||||
"handlers": ["logfile"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for app in LOCAL_APPS:
|
||||
LOGGING["loggers"][app] = {
|
||||
"handlers": ["logfile"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
}
|
||||
# ######### 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
|
||||
|
||||
# ######### 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"
|
||||
|
||||
|
||||
# ######### TOOLBAR CONFIGURATION
|
||||
# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa
|
||||
INSTALLED_APPS += ("debug_toolbar",)
|
||||
|
||||
MIDDLEWARE += [
|
||||
"impersonate.middleware.ImpersonateMiddleware",
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
]
|
||||
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
"SHOW_TOOLBAR_CALLBACK": "gnuviechadmin.settings.show_debug_toolbar"
|
||||
}
|
||||
# ######### END TOOLBAR CONFIGURATION
|
||||
|
||||
|
||||
if GVA_ENVIRONMENT == "local":
|
||||
# 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
|
||||
|
||||
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 LOCAL_APPS
|
||||
],
|
||||
)
|
||||
)
|
||||
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 LOCAL_APPS
|
||||
]
|
||||
)
|
||||
)
|
||||
LOGGING["loggers"]["django"] = {
|
||||
"handlers": ["console"],
|
||||
"level": "CRITICAL",
|
||||
"propagate": True,
|
||||
}
|
||||
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,381 +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',
|
||||
'allauth.socialaccount.providers.xing',
|
||||
)
|
||||
|
||||
# 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()
|
||||
|
||||
TEST_USER = "test"
|
||||
TEST_PASSWORD = "secret"
|
||||
|
||||
|
||||
class NavigationContextProcessorTest(TestCase):
|
||||
|
||||
EXPECTED_ITEMS = ("webmail_url", "phpmyadmin_url", "phppgadmin_url", "active_item")
|
||||
|
||||
def test_ajax_request(self):
|
||||
response = self.client.get("/accounts/login/", 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):
|
||||
user = User.objects.create(username=TEST_USER)
|
||||
user.set_password(TEST_PASSWORD)
|
||||
user.save()
|
||||
|
||||
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
|
||||
|
||||
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_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("/accounts/login/")
|
||||
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,34 +1,50 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf import settings
|
||||
|
||||
import debug_toolbar
|
||||
from django.conf.urls import include
|
||||
from django.contrib import admin
|
||||
from django.contrib.flatpages import views
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import path
|
||||
|
||||
from help import views as help_views
|
||||
from invoices import views as invoice_views
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'', include('dashboard.urls')),
|
||||
url(r'^accounts/', include('allauth.urls')),
|
||||
url(r'^database/', include('userdbs.urls')),
|
||||
url(r'^domains/', include('domains.urls')),
|
||||
url(r'^hosting/', include('hostingpackages.urls')),
|
||||
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'^contact/', include('contact_form.urls')),
|
||||
)
|
||||
urlpatterns += patterns(
|
||||
'django.contrib.flatpages.views',
|
||||
url(r'^impressum/$', 'flatpage', {'url': '/impressum/'}, name='imprint'),
|
||||
)
|
||||
urlpatterns = [
|
||||
path("", include("dashboard.urls")),
|
||||
path("api/users/", help_views.ListHelpUserAPIView.as_view()),
|
||||
path(
|
||||
"api/users/<int:pk>/",
|
||||
help_views.HelpUserAPIView.as_view(),
|
||||
name="helpuser-detail",
|
||||
),
|
||||
path("api/invoices/", invoice_views.ListInvoiceAPIView.as_view()),
|
||||
path(
|
||||
"api/invoices/<invoice_number>/",
|
||||
invoice_views.InvoiceAPIView.as_view(),
|
||||
name="invoice-detail",
|
||||
),
|
||||
path("admin/", admin.site.urls),
|
||||
path("impersonate/", include("impersonate.urls")),
|
||||
path("accounts/", include("allauth.urls")),
|
||||
path("database/", include("userdbs.urls")),
|
||||
path("domains/", include("domains.urls")),
|
||||
path("hosting/", include("hostingpackages.urls")),
|
||||
path("website/", include("websites.urls")),
|
||||
path("mail/", include("managemails.urls")),
|
||||
path("osuser/", include("osusers.urls")),
|
||||
path("contact/", include("contact_form.urls")),
|
||||
path("impressum/", views.flatpage, {"url": "/impressum/"}, name="imprint"),
|
||||
path("datenschutz/", views.flatpage, {"url": "/datenschutz/"}, name="privacy"),
|
||||
path("issues/", views.flatpage, {"url": "/issues/"}, name="support"),
|
||||
]
|
||||
|
||||
# Uncomment the next line to serve media files in dev.
|
||||
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
urlpatterns += patterns('',
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
)
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
urlpatterns += [
|
||||
path("__debug__/", include(debug_toolbar.urls)),
|
||||
]
|
||||
|
|
|
@ -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")
|
|
@ -3,11 +3,10 @@ This module defines form classes that can be extended by other gnuviechadmin
|
|||
apps' forms.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
|
||||
"""
|
||||
|
@ -21,11 +20,14 @@ class PasswordModelFormMixin(forms.Form):
|
|||
whether both fields contain the same string.
|
||||
|
||||
"""
|
||||
|
||||
password1 = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
label=_("Password"),
|
||||
widget=forms.PasswordInput,
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label=_('Password (again)'), widget=forms.PasswordInput,
|
||||
label=_("Password (again)"),
|
||||
widget=forms.PasswordInput,
|
||||
)
|
||||
|
||||
def clean_password2(self):
|
||||
|
@ -36,8 +38,8 @@ class PasswordModelFormMixin(forms.Form):
|
|||
:rtype: str or None
|
||||
|
||||
"""
|
||||
password1 = self.cleaned_data.get('password1')
|
||||
password2 = self.cleaned_data.get('password2')
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
if password1 and password2 and password1 != password2:
|
||||
raise forms.ValidationError(PASSWORD_MISMATCH_ERROR)
|
||||
return password2
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gvawebcore\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
|
||||
"PO-Revision-Date: 2015-01-25 11:49+0100\n"
|
||||
"POT-Creation-Date: 2023-04-16 22:07+0200\n"
|
||||
"PO-Revision-Date: 2023-04-16 18:21+0200\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language: de\n"
|
||||
|
@ -16,17 +16,17 @@ 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 3.2.2\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: gvawebcore/forms.py:12
|
||||
#: gvawebcore/forms.py:11
|
||||
msgid "Passwords don't match"
|
||||
msgstr "Passwörter stimmen nicht überein"
|
||||
|
||||
#: gvawebcore/forms.py:25
|
||||
msgid "Password"
|
||||
msgstr "Passwort: "
|
||||
msgstr "Passwort"
|
||||
|
||||
#: gvawebcore/forms.py:28
|
||||
#: gvawebcore/forms.py:29
|
||||
msgid "Password (again)"
|
||||
msgstr "Passwortwiederholung"
|
||||
|
|
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())
|
|
@ -2,9 +2,10 @@
|
|||
This module defines common view code to be used by multiple gnuviechadmin apps.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from hostingpackages.models import CustomerHostingPackage
|
||||
|
||||
|
||||
|
@ -14,7 +15,8 @@ class HostingPackageAndCustomerMixin(object):
|
|||
keyword argument 'package'.
|
||||
|
||||
"""
|
||||
hosting_package_kwarg = 'package'
|
||||
|
||||
hosting_package_kwarg = "package"
|
||||
"""Keyword argument used to find the hosting package in the URL."""
|
||||
|
||||
hostingpackage = None
|
||||
|
@ -22,8 +24,8 @@ class HostingPackageAndCustomerMixin(object):
|
|||
def get_hosting_package(self):
|
||||
if self.hostingpackage is None:
|
||||
self.hostingpackage = get_object_or_404(
|
||||
CustomerHostingPackage,
|
||||
pk=int(self.kwargs[self.hosting_package_kwarg]))
|
||||
CustomerHostingPackage, pk=int(self.kwargs[self.hosting_package_kwarg])
|
||||
)
|
||||
return self.hostingpackage
|
||||
|
||||
def get_customer_object(self):
|
||||
|
|
0
gnuviechadmin/help/__init__.py
Normal file
0
gnuviechadmin/help/__init__.py
Normal file
22
gnuviechadmin/help/admin.py
Normal file
22
gnuviechadmin/help/admin.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from help.models import HelpUser
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class HelpUserInline(admin.StackedInline):
|
||||
model = HelpUser
|
||||
can_delete = False
|
||||
readonly_fields = ("offline_account_code",)
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
inlines = (HelpUserInline,)
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserAdmin)
|
8
gnuviechadmin/help/apps.py
Normal file
8
gnuviechadmin/help/apps.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class HelpConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "help"
|
||||
verbose_name = _("User self help")
|
36
gnuviechadmin/help/locale/de/LC_MESSAGES/django.po
Normal file
36
gnuviechadmin/help/locale/de/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,36 @@
|
|||
# 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: help\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-04-16 22:07+0200\n"
|
||||
"PO-Revision-Date: 2023-04-16 18:21+0200\n"
|
||||
"Last-Translator: \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 3.2.2\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: help/apps.py:8
|
||||
msgid "User self help"
|
||||
msgstr "Selbsthilfe für Nutzer"
|
||||
|
||||
#: help/models.py:10
|
||||
msgid "Contact email address"
|
||||
msgstr "Kontakt-E-Mail-Adresse"
|
||||
|
||||
#: help/models.py:11
|
||||
msgid "Contact postal address"
|
||||
msgstr "Kontakt-Postanschrift"
|
||||
|
||||
#: help/models.py:13
|
||||
msgid "Offline account reset code"
|
||||
msgstr "Offline-Code für die Konto-Rücksetzung"
|
0
gnuviechadmin/help/management/__init__.py
Normal file
0
gnuviechadmin/help/management/__init__.py
Normal file
0
gnuviechadmin/help/management/commands/__init__.py
Normal file
0
gnuviechadmin/help/management/commands/__init__.py
Normal file
17
gnuviechadmin/help/management/commands/populate.py
Normal file
17
gnuviechadmin/help/management/commands/populate.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from help.models import HelpUser
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Populate help user information for existing users"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for user in User.objects.filter(helpuser=None):
|
||||
help_user = HelpUser.objects.create(user_id=user.id, email_address=user.email)
|
||||
help_user.generate_offline_account_code()
|
||||
help_user.save()
|
||||
self.stdout.write(f"created offline account code {help_user.offline_account_code} for {user}.")
|
29
gnuviechadmin/help/management/commands/reset_offline_code.py
Normal file
29
gnuviechadmin/help/management/commands/reset_offline_code.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.core.management import BaseCommand, CommandError
|
||||
|
||||
from help.models import HelpUser
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Reset offline account reset code for existing users"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("users", nargs='+', type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for name in options["users"]:
|
||||
try:
|
||||
user = User.objects.get(username=name)
|
||||
except User.DoesNotExist:
|
||||
raise CommandError(f'User {name} does not exist')
|
||||
|
||||
help_user = user.helpuser
|
||||
if help_user is None:
|
||||
help_user = HelpUser.objects.create(email_address=user.email)
|
||||
|
||||
help_user.generate_offline_account_code()
|
||||
help_user.save()
|
||||
|
||||
self.stdout.write(f"generated new offline account reset code {help_user.offline_account_code} for {name}")
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue