Compare commits

..

No commits in common. "main" and "0.12.0" have entirely different histories.
main ... 0.12.0

278 changed files with 8818 additions and 18867 deletions

View file

@ -1,18 +0,0 @@
**/*.pyc
**/.*.swp
**/.coverage
**/__pycache__
.dockerignore
.env
.envrc
.git
.gitignore
.idea
.isort.cfg
.vagrant
Dockerfile
Vagrantfile
docker-compose.yml
docs
media
static

2
.gitignore vendored
View file

@ -50,11 +50,9 @@ coverage-report/
.idea/ .idea/
.env .env
.envrc
/docker/django_media /docker/django_media
/docker/django_static /docker/django_static
!/docker/django_media/.empty !/docker/django_media/.empty
!/docker/django_static/.empty !/docker/django_static/.empty
/media/
/static/ /static/

View file

@ -1,7 +0,0 @@
[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

View file

@ -1,70 +1,56 @@
ARG DEBIAN_RELEASE=bookworm ARG DEBIAN_RELEASE=buster
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 FROM debian:$DEBIAN_RELEASE
LABEL maintainer="Jan Dittberner <jan@dittberner.info>" LABEL maintainer="Jan Dittberner <jan@dittberner.info>"
ENV LC_ALL=C.UTF-8 ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8 ENV LANG=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates \ build-essential \
dumb-init \ dumb-init \
gettext \ gettext \
postgresql-client \ git \
python3 \ python3-dev \
python3-pip \ python3-pip \
python3-wheel \ python3-setuptools \
python3-virtualenv \
python3-wheel \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/cache/apt/archives /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*.*
RUN python3 -m pip install --prefix=/usr/local pipenv
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
libpq-dev \
postgresql-client \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*.*
ARG GVAAPP=gva
ARG GVAGID=2000 ARG GVAGID=2000
ARG GVAUID=2000 ARG GVAUID=2000
RUN addgroup --gid $GVAGID $GVAAPP ; \ ARG GVAAPP=gva
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/media /srv/$GVAAPP/static
VOLUME /srv/$GVAAPP/gnuviechadmin WORKDIR /srv/$GVAAPP
COPY Pipfile Pipfile.lock /srv/$GVAAPP/
RUN addgroup --gid $GVAGID $GVAAPP ; \
adduser --home /home/$GVAAPP --shell /bin/bash --uid $GVAUID --gid $GVAGID --disabled-password --gecos "User for gnuviechadmin component $GVAAPP" $GVAAPP
USER $GVAAPP
RUN python3 -m virtualenv --python=python3 /home/$GVAAPP/$GVAAPP-venv ; \
/home/$GVAAPP/$GVAAPP-venv/bin/python3 -m pip install -U pip ; \
VIRTUAL_ENV=/home/$GVAAPP/$GVAAPP-venv pipenv install --deploy --ignore-pipfile --dev
VOLUME /srv/$GVAAPP
EXPOSE 8000 EXPOSE 8000
COPY ${GVAAPP}.sh entrypoint.sh /srv/ COPY gva.sh /srv/
ENTRYPOINT ["dumb-init", "/srv/entrypoint.sh"] ENTRYPOINT ["dumb-init", "/srv/gva.sh"]

33
Pipfile Normal file
View file

@ -0,0 +1,33 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[[source]]
url = "https://pypi.gnuviech-server.de/simple"
verify_ssl = true
name = "gnuviech"
[packages]
"psycopg2" = "*"
Django = "<3"
celery = "*"
django-allauth = "*"
django-braces = "*"
django-crispy-forms = "*"
django-model-utils = "*"
gvacommon = {version = "*",index = "gnuviech"}
passlib = "*"
redis = "*"
requests-oauthlib = "*"
[dev-packages]
coverage = "*"
django-debug-toolbar = "*"
sphinx = "*"
releases = "*"
sphinxcontrib-blockdiag = "*"
pylama = "*"
[requires]
python_version = "3.7"

617
Pipfile.lock generated Normal file
View file

@ -0,0 +1,617 @@
{
"_meta": {
"hash": {
"sha256": "1c0b7bdab385f10279c852fa7fe7ae2c022dc1c4495d0e55fd407aea947bc976"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
},
{
"name": "gnuviech",
"url": "https://pypi.gnuviech-server.de/simple",
"verify_ssl": true
}
]
},
"default": {
"amqp": {
"hashes": [
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8",
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"
],
"version": "==2.5.2"
},
"billiard": {
"hashes": [
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede",
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"
],
"version": "==3.6.3.0"
},
"celery": {
"hashes": [
"sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f",
"sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"
],
"index": "pypi",
"version": "==4.4.2"
},
"certifi": {
"hashes": [
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
],
"version": "==2020.4.5.1"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"defusedxml": {
"hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
],
"version": "==0.6.0"
},
"django": {
"hashes": [
"sha256:69897097095f336d5aeef45b4103dceae51c00afa6d3ae198a2a18e519791b7a",
"sha256:6ecd229e1815d4fc5240fc98f1cca78c41e7a8cd3e3f2eefadc4735031077916"
],
"index": "pypi",
"version": "==2.2.12"
},
"django-allauth": {
"hashes": [
"sha256:7ab91485b80d231da191d5c7999ba93170ef1bf14ab6487d886598a1ad03e1d8"
],
"index": "pypi",
"version": "==0.41.0"
},
"django-braces": {
"hashes": [
"sha256:83705b78948de00804bfacf40c315d001bb39630f35bbdd8588211c2d5b4d43f",
"sha256:a6d9b34cf3e4949635e54884097c30410d7964fc7bec7231445ea7079b8c5722"
],
"index": "pypi",
"version": "==1.14.0"
},
"django-crispy-forms": {
"hashes": [
"sha256:50032184708ce351e3c9f0008ac35d659d9d5973fa2db218066f2e0a76eb41d9",
"sha256:67e73ac863d3159500029fbbcdcb788f287a3fd357becebc1a0b51f73896dce3"
],
"index": "pypi",
"version": "==1.9.0"
},
"django-model-utils": {
"hashes": [
"sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c",
"sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"
],
"index": "pypi",
"version": "==4.0.0"
},
"gvacommon": {
"hashes": [
"sha256:adf1ebc824433196d112764c61d9ca869481d33f612818c2840069f57ab42c25"
],
"index": "gnuviech",
"version": "==0.5.0"
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
"version": "==2.9"
},
"importlib-metadata": {
"hashes": [
"sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
"sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
],
"markers": "python_version < '3.8'",
"version": "==1.6.0"
},
"kombu": {
"hashes": [
"sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76",
"sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"
],
"version": "==4.6.8"
},
"oauthlib": {
"hashes": [
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
],
"version": "==3.1.0"
},
"passlib": {
"hashes": [
"sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177",
"sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"
],
"index": "pypi",
"version": "==1.7.2"
},
"psycopg2": {
"hashes": [
"sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535",
"sha256:2327bf42c1744a434ed8ed0bbaa9168cac7ee5a22a9001f6fc85c33b8a4a14b7",
"sha256:27c633f2d5db0fc27b51f1b08f410715b59fa3802987aec91aeb8f562724e95c",
"sha256:2c0afb40cfb4d53487ee2ebe128649028c9a78d2476d14a67781e45dc287f080",
"sha256:2df2bf1b87305bd95eb3ac666ee1f00a9c83d10927b8144e8e39644218f4cf81",
"sha256:440a3ea2c955e89321a138eb7582aa1d22fe286c7d65e26a2c5411af0a88ae72",
"sha256:6a471d4d2a6f14c97a882e8d3124869bc623f3df6177eefe02994ea41fd45b52",
"sha256:6b306dae53ec7f4f67a10942cf8ac85de930ea90e9903e2df4001f69b7833f7e",
"sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf",
"sha256:ac5b23d0199c012ad91ed1bbb971b7666da651c6371529b1be8cbe2a7bf3c3a9",
"sha256:acf56d564e443e3dea152efe972b1434058244298a94348fc518d6dd6a9fb0bb",
"sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055",
"sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"
],
"index": "pypi",
"version": "==2.8.5"
},
"python3-openid": {
"hashes": [
"sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa",
"sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"
],
"version": "==3.1.0"
},
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
],
"version": "==2019.3"
},
"redis": {
"hashes": [
"sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f",
"sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833"
],
"index": "pypi",
"version": "==3.4.1"
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
"version": "==2.23.0"
},
"requests-oauthlib": {
"hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
],
"index": "pypi",
"version": "==1.3.0"
},
"six": {
"hashes": [
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.14.0"
},
"sqlparse": {
"hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.1"
},
"urllib3": {
"hashes": [
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
],
"version": "==1.25.8"
},
"vine": {
"hashes": [
"sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87",
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
],
"version": "==1.3.0"
},
"zipp": {
"hashes": [
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
"version": "==3.1.0"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"asgiref": {
"hashes": [
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
],
"version": "==3.2.7"
},
"babel": {
"hashes": [
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
],
"version": "==2.8.0"
},
"blockdiag": {
"hashes": [
"sha256:16a69dd9f3b44c9e0869999ce82aa968586698febc86ece9ca0c902dba772397",
"sha256:fa0b47cf25bfc4d546b7fc284c70c3bac875a066e744b4a6b1d9ba457e4ed077"
],
"version": "==2.0.1"
},
"certifi": {
"hashes": [
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
],
"version": "==2020.4.5.1"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"coverage": {
"hashes": [
"sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0",
"sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30",
"sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b",
"sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0",
"sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823",
"sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe",
"sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037",
"sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6",
"sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31",
"sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd",
"sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892",
"sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1",
"sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78",
"sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac",
"sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006",
"sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014",
"sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2",
"sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7",
"sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8",
"sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7",
"sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9",
"sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1",
"sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307",
"sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a",
"sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435",
"sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0",
"sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5",
"sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441",
"sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732",
"sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de",
"sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1"
],
"index": "pypi",
"version": "==5.0.4"
},
"django": {
"hashes": [
"sha256:69897097095f336d5aeef45b4103dceae51c00afa6d3ae198a2a18e519791b7a",
"sha256:6ecd229e1815d4fc5240fc98f1cca78c41e7a8cd3e3f2eefadc4735031077916"
],
"index": "pypi",
"version": "==2.2.12"
},
"django-debug-toolbar": {
"hashes": [
"sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943",
"sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c"
],
"index": "pypi",
"version": "==2.2"
},
"docutils": {
"hashes": [
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
],
"version": "==0.16"
},
"funcparserlib": {
"hashes": [
"sha256:b7992eac1a3eb97b3d91faa342bfda0729e990bd8a43774c1592c091e563c91d"
],
"version": "==0.3.6"
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
"version": "==2.9"
},
"imagesize": {
"hashes": [
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
],
"version": "==1.2.0"
},
"jinja2": {
"hashes": [
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
],
"version": "==2.11.1"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"packaging": {
"hashes": [
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
],
"version": "==20.3"
},
"pillow": {
"hashes": [
"sha256:04a10558320eba9137d6a78ca6fc8f4a5801f1b971152938851dc4629d903579",
"sha256:0f89ddc77cf421b8cd34ae852309501458942bf370831b4a9b406156b599a14e",
"sha256:251e5618125ec12ac800265d7048f5857a8f8f1979db9ea3e11382e159d17f68",
"sha256:291bad7097b06d648222b769bbfcd61e40d0abdfe10df686d20ede36eb8162b6",
"sha256:2f0b52a08d175f10c8ea36685115681a484c55d24d0933f9fd911e4111c04144",
"sha256:3713386d1e9e79cea1c5e6aaac042841d7eef838cc577a3ca153c8bedf570287",
"sha256:433bbc2469a2351bea53666d97bb1eb30f0d56461735be02ea6b27654569f80f",
"sha256:4510c6b33277970b1af83c987277f9a08ec2b02cc20ac0f9234e4026136bb137",
"sha256:50a10b048f4dd81c092adad99fa5f7ba941edaf2f9590510109ac2a15e706695",
"sha256:670e58d3643971f4afd79191abd21623761c2ebe61db1c2cb4797d817c4ba1a7",
"sha256:6c1924ed7dbc6ad0636907693bbbdd3fdae1d73072963e71f5644b864bb10b4d",
"sha256:721c04d3c77c38086f1f95d1cd8df87f2f9a505a780acf8575912b3206479da1",
"sha256:8d5799243050c2833c2662b824dfb16aa98e408d2092805edea4300a408490e7",
"sha256:90cd441a1638ae176eab4d8b6b94ab4ec24b212ed4c3fbee2a6e74672481d4f8",
"sha256:a5dc9f28c0239ec2742d4273bd85b2aa84655be2564db7ad1eb8f64b1efcdc4c",
"sha256:b2f3e8cc52ecd259b94ca880fea0d15f4ebc6da2cd3db515389bb878d800270f",
"sha256:b7453750cf911785009423789d2e4e5393aae9cbb8b3f471dab854b85a26cb89",
"sha256:b99b2607b6cd58396f363b448cbe71d3c35e28f03e442ab00806463439629c2c",
"sha256:cd47793f7bc9285a88c2b5551d3f16a2ddd005789614a34c5f4a598c2a162383",
"sha256:d6bf085f6f9ec6a1724c187083b37b58a8048f86036d42d21802ed5d1fae4853",
"sha256:da737ab273f4d60ae552f82ad83f7cbd0e173ca30ca20b160f708c92742ee212",
"sha256:eb84e7e5b07ff3725ab05977ac56d5eeb0c510795aeb48e8b691491be3c5745b"
],
"version": "==7.1.1"
},
"pycodestyle": {
"hashes": [
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
],
"version": "==2.5.0"
},
"pydocstyle": {
"hashes": [
"sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
"sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
],
"version": "==5.0.2"
},
"pyflakes": {
"hashes": [
"sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
"sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
],
"version": "==2.2.0"
},
"pygments": {
"hashes": [
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
],
"version": "==2.6.1"
},
"pylama": {
"hashes": [
"sha256:9bae53ef9c1a431371d6a8dca406816a60d547147b60a4934721898f553b7d8f",
"sha256:fd61c11872d6256b019ef1235be37b77c922ef37ac9797df6bd489996dddeb15"
],
"index": "pypi",
"version": "==7.7.1"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"version": "==2.4.7"
},
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
],
"version": "==2019.3"
},
"releases": {
"hashes": [
"sha256:555ae4c97a671a420281c1c782e9236be25157b449fdf20b4c4b293fe93db2f1",
"sha256:cb3435ba372a6807433800fbe473760cfa781171513f670f3c4b76983ac80f18"
],
"index": "pypi",
"version": "==1.6.3"
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
"version": "==2.23.0"
},
"semantic-version": {
"hashes": [
"sha256:2a4328680073e9b243667b201119772aefc5fc63ae32398d6afafff07c4f54c0",
"sha256:2d06ab7372034bcb8b54f2205370f4aa0643c133b7e6dbd129c5200b83ab394b"
],
"version": "==2.6.0"
},
"six": {
"hashes": [
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.14.0"
},
"snowballstemmer": {
"hashes": [
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
],
"version": "==2.0.0"
},
"sphinx": {
"hashes": [
"sha256:6a099e6faffdc3ceba99ca8c2d09982d43022245e409249375edf111caf79ed3",
"sha256:b63a0c879c4ff9a4dffcb05217fa55672ce07abdeb81e33c73303a563f8d8901"
],
"index": "pypi",
"version": "==3.0.0"
},
"sphinxcontrib-applehelp": {
"hashes": [
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
],
"version": "==1.0.2"
},
"sphinxcontrib-blockdiag": {
"hashes": [
"sha256:51ce7cff8d25dfd4c8a753d5aa5491e6dbf280004719c49e8001e583ecda7d91",
"sha256:91fd35b64f1f25db59d80b8a5196ed4ffadf57a81f63ee207e34d53ec36d8f97"
],
"index": "pypi",
"version": "==2.0.0"
},
"sphinxcontrib-devhelp": {
"hashes": [
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
],
"version": "==1.0.2"
},
"sphinxcontrib-htmlhelp": {
"hashes": [
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
],
"version": "==1.0.3"
},
"sphinxcontrib-jsmath": {
"hashes": [
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
],
"version": "==1.0.1"
},
"sphinxcontrib-qthelp": {
"hashes": [
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
],
"version": "==1.0.3"
},
"sphinxcontrib-serializinghtml": {
"hashes": [
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
],
"version": "==1.1.4"
},
"sqlparse": {
"hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.1"
},
"urllib3": {
"hashes": [
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
],
"version": "==1.25.8"
},
"webcolors": {
"hashes": [
"sha256:76f360636957d1c976db7466bc71dcb713bb95ac8911944dffc55c01cb516de6",
"sha256:b8cd5d865a25c51ff1218f0c90d0c0781fc64312a49b746b320cf50de1648f6e"
],
"version": "==1.11.1"
}
}
}

View file

@ -1,4 +1,3 @@
---
version: "3" version: "3"
services: services:
db: db:
@ -19,7 +18,7 @@ services:
volumes: volumes:
- "redis_data:/var/lib/redis" - "redis_data:/var/lib/redis"
gva: gva:
image: gnuviech/gva:bookworm image: gnuviech/gva:buster
build: build:
context: . context: .
args: args:
@ -37,9 +36,9 @@ services:
GVA_DOMAIN_NAME: localhost GVA_DOMAIN_NAME: localhost
GVA_SITE_NAME: localhost GVA_SITE_NAME: localhost
volumes: volumes:
- "django_media:/srv/gva/media" - "./docker/django_media:/srv/gva/media"
- "django_static:/srv/gva/static" - "./docker/django_static:/srv/gva/static"
- "./gnuviechadmin:/srv/gva/gnuviechadmin" - ".:/srv/gva"
web: web:
image: gnuviech/gvaweb:buster image: gnuviech/gvaweb:buster
build: build:
@ -52,7 +51,7 @@ services:
- redis - redis
env_file: ../gvaweb/.env env_file: ../gvaweb/.env
volumes: volumes:
- "../gvaweb/gvaweb:/srv/gvaweb/gvaweb" - "../gvaweb:/srv/gvaweb"
ldap: ldap:
image: gnuviech/gvaldap:buster image: gnuviech/gvaldap:buster
build: build:
@ -65,9 +64,9 @@ services:
- redis - redis
env_file: ../gvaldap/.env env_file: ../gvaldap/.env
volumes: volumes:
- "../gvaldap/gvaldap:/srv/gvaldap/gvaldap" - "../gvaldap:/srv/gvaldap"
file: file:
image: gnuviech/gvafile:bookworm image: gnuviech/gvafile:buster
build: build:
context: ../gvafile context: ../gvafile
args: args:
@ -78,7 +77,7 @@ services:
- redis - redis
env_file: ../gvafile/.env env_file: ../gvafile/.env
volumes: volumes:
- "../gvafile/gvafile:/srv/gvafile/gvafile" - "../gvafile:/srv/gvafile"
pgsql: pgsql:
image: gnuviech/gvapgsql:buster image: gnuviech/gvapgsql:buster
build: build:
@ -91,7 +90,7 @@ services:
- redis - redis
env_file: ../gvapgsql/.env env_file: ../gvapgsql/.env
volumes: volumes:
- "../gvapgsql/gvapgsql:/srv/gvapgsql/gvapgsql" - "../gvapgsql:/srv/gvapgsql"
mysql: mysql:
image: gnuviech/gvamysql:buster image: gnuviech/gvamysql:buster
build: build:
@ -104,7 +103,7 @@ services:
- redis - redis
env_file: ../gvamysql/.env env_file: ../gvamysql/.env
volumes: volumes:
- "../gvamysql/gvamysql:/srv/gvamysql/gvamysql" - "../gvamysql:/srv/gvamysql"
volumes: volumes:
django_media: django_media:
django_static: django_static:

View file

@ -1,41 +1,6 @@
Changelog 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>` * :release:`0.12.0 <2020-04-10>`
* :support:`-` add architecture diagramm for documentation * :support:`-` add architecture diagramm for documentation
* :support:`-` drop environment specific settings * :support:`-` drop environment specific settings

View file

@ -20,49 +20,46 @@ import django
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # 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["DJANGO_SETTINGS_MODULE"] = "gnuviechadmin.settings" os.environ['DJANGO_SETTINGS_MODULE'] = 'gnuviechadmin.settings'
os.environ["GVA_SITE_ADMINMAIL"] = "admin@gva.example.org" os.environ['GVA_SITE_ADMINMAIL'] = 'admin@gva.example.org'
django.setup() django.setup()
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # 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 # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [ extensions = [
"releases", 'releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx',
"sphinx.ext.autodoc", 'sphinxcontrib.blockdiag']
"celery.contrib.sphinx",
"sphinxcontrib.blockdiag",
]
# configuration for releases extension # configuration for releases extension
releases_issue_uri = "https://git.dittberner.info/gnuviech/gva/issues/%s" releases_issue_uri = 'https://git.dittberner.info/gnuviech/gva/issues/%s'
releases_release_uri = "https://git.dittberner.info/gnuviech/gva/src/tag/%s" releases_release_uri = 'https://git.dittberner.info/gnuviech/gva/src/tag/%s'
# configuration for blockdiag extension # configuration for blockdiag extension
blockdiag_fontpath = "/usr/share/fonts/truetype/dejavu/" blockdiag_fontpath = '/usr/share/fonts/truetype/dejavu/'
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ['_templates']
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = ".rst" source_suffix = '.rst'
# The encoding of source files. # The encoding of source files.
# source_encoding = 'utf-8-sig' #source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = "index" master_doc = 'index'
# General information about the project. # General information about the project.
project = "gnuviechadmin" project = u'gnuviechadmin'
copyright = "2014-2023, Jan Dittberner" copyright = u'2014-2020, Jan Dittberner'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@ -72,121 +69,121 @@ copyright = "2014-2023, Jan Dittberner"
from gnuviechadmin import __version__ as release from gnuviechadmin import __version__ as release
# The short X.Y version. # 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 # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
# language = None #language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
# today = '' #today = ''
# Else, today_fmt is used as the format for a strftime call. # 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 # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # 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. # 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. # 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 # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
# add_module_names = True #add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
# show_authors = False #show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # 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. # A list of ignored prefixes for module index sorting.
# modindex_common_prefix = [] #modindex_common_prefix = []
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = "alabaster" html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the
# documentation. # documentation.
# html_theme_options = {} #html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory. # 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 # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
# html_title = None #html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # 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 # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # 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 # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
# html_favicon = None #html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # 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, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # 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 # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
# html_use_smartypants = True #html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # 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 # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
# html_additional_pages = {} #html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
# html_domain_indices = True #html_domain_indices = True
# If false, no index is generated. # 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. # 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. # 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. # 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. # 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 # 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 # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # 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"). # 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. # Output file base name for HTML help builder.
htmlhelp_basename = "gnuviechadmindoc" htmlhelp_basename = 'gnuviechadmindoc'
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
@ -194,8 +191,10 @@ htmlhelp_basename = "gnuviechadmindoc"
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt', #'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#'preamble': '', #'preamble': '',
} }
@ -203,34 +202,29 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
( ('index', 'gnuviechadmin.tex', u'gnuviechadmin Documentation',
"index", u'Jan Dittberner', 'manual'),
"gnuviechadmin.tex",
"gnuviechadmin Documentation",
"Jan Dittberner",
"manual",
),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
# latex_logo = None #latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
# latex_use_parts = False #latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
# latex_show_pagerefs = False #latex_show_pagerefs = False
# If true, show URL addresses after external links. # 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. # Documents to append as an appendix to all manuals.
# latex_appendices = [] #latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
# latex_domain_indices = True #latex_domain_indices = True
# -- Options for manual page output -------------------------------------------- # -- Options for manual page output --------------------------------------------
@ -238,11 +232,12 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
("index", "gnuviechadmin", "gnuviechadmin Documentation", ["Jan Dittberner"], 1) ('index', 'gnuviechadmin', u'gnuviechadmin Documentation',
[u'Jan Dittberner'], 1)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
# man_show_urls = False #man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------ # -- Options for Texinfo output ------------------------------------------------
@ -251,22 +246,16 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
( ('index', 'gnuviechadmin', u'gnuviechadmin Documentation',
"index", u'Jan Dittberner', 'gnuviechadmin', 'Customer center for gnuviech servers.',
"gnuviechadmin", 'Miscellaneous'),
"gnuviechadmin Documentation",
"Jan Dittberner",
"gnuviechadmin",
"Customer center for gnuviech servers.",
"Miscellaneous",
),
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
# texinfo_appendices = [] #texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
# texinfo_domain_indices = True #texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote' #texinfo_show_urls = 'footnote'

View file

@ -1,7 +0,0 @@
#!/bin/sh
set -e
chown -Rc gva.gva /srv/gva/media /srv/gva/static
su -c /srv/gva.sh gva

1
frontend/.gitignore vendored
View file

@ -1 +0,0 @@
node_modules/

View file

@ -1,46 +0,0 @@
{
"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=="
}
}
}

View file

@ -1,6 +0,0 @@
{
"dependencies": {
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.4"
}
}

View file

@ -4,17 +4,19 @@ This module contains the form class for the contact_form app.
""" """
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django import forms from django import forms
from django.apps import apps
from django.conf import settings from django.conf import settings
from django.core.mail import send_mail
from django.template import RequestContext
from django.template import loader
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.contrib.sites.requests import RequestSite from django.contrib.sites.requests import RequestSite
from django.core.mail import send_mail
from django.template import RequestContext, loader from crispy_forms.helper import FormHelper
from django.urls import reverse from crispy_forms.layout import Submit
from django.utils.translation import gettext_lazy as _
class ContactForm(forms.Form): class ContactForm(forms.Form):
@ -22,42 +24,45 @@ class ContactForm(forms.Form):
This is the contact form class. This is the contact form class.
""" """
name = forms.CharField(max_length=100, label=_('Your name'))
name = forms.CharField(max_length=100, label=_("Your name")) email = forms.EmailField(max_length=200, label=_('Your email address'))
email = forms.EmailField(max_length=200, label=_("Your email address")) body = forms.CharField(widget=forms.Textarea, label=_('Your message'))
body = forms.CharField(widget=forms.Textarea, label=_("Your message"))
subject_template_name = "contact_form/contact_form_subject.txt" 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 from_email = settings.DEFAULT_FROM_EMAIL
recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS] recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS]
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.request = kwargs.pop("request") self.request = kwargs.pop('request')
super(ContactForm, self).__init__(**kwargs) super(ContactForm, self).__init__(**kwargs)
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_action = reverse("contact_form") self.helper.form_action = reverse('contact_form')
self.helper.add_input(Submit("submit", _("Send message"))) self.helper.add_input(Submit('submit', _('Send message')))
def get_context(self): def get_context(self):
if not self.is_valid(): if not self.is_valid():
raise ValueError("Cannot generate context from invalid contact form") raise ValueError(
if apps.is_installed("django.contrib.sites"): 'Cannot generate context from invalid contact form')
if Site._meta.installed:
site = Site.objects.get_current() site = Site.objects.get_current()
else: else:
site = RequestSite(self.request) 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): def message(self):
context = self.get_context() context = self.get_context()
template_context = context.flatten() template_context = context.flatten()
template_context.update({"remote_ip": context.request.META["REMOTE_ADDR"]}) template_context.update({
'remote_ip': context.request.META['REMOTE_ADDR']
})
return loader.render_to_string(self.template_name, template_context) return loader.render_to_string(self.template_name, template_context)
def subject(self): def subject(self):
context = self.get_context().flatten() context = self.get_context().flatten()
subject = loader.render_to_string(self.subject_template_name, context) subject = loader.render_to_string(self.subject_template_name, context)
return "".join(subject.splitlines()) return ''.join(subject.splitlines())
def save(self, fail_silently=False): def save(self, fail_silently=False):
""" """
@ -69,5 +74,5 @@ class ContactForm(forms.Form):
from_email=self.from_email, from_email=self.from_email,
recipient_list=self.recipient_list, recipient_list=self.recipient_list,
subject=self.subject(), subject=self.subject(),
message=self.message(), message=self.message()
) )

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: contact_form\n" "Project-Id-Version: contact_form\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-22 19:45+0200\n" "POT-Creation-Date: 2016-01-29 11:04+0100\n"
"PO-Revision-Date: 2023-04-22 13:01+0200\n" "PO-Revision-Date: 2015-02-01 19:03+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n" "Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n" "Language-Team: Jan Dittberner <jan@dittberner.info>\n"
"Language: de\n" "Language: de\n"
@ -16,32 +16,21 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 1.6.10\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
#: contact_form/forms.py:26 #: contact_form/forms.py:27
msgid "Your name" msgid "Your name"
msgstr "Ihr Name" msgstr "Ihr Name"
#: contact_form/forms.py:27
msgid "Your email address"
msgstr "Ihre E-Mail-Adresse"
#: contact_form/forms.py:28 #: contact_form/forms.py:28
msgid "Your email address"
msgstr "Ihre E-Mailadresse"
#: contact_form/forms.py:29
msgid "Your message" msgid "Your message"
msgstr "Ihre Nachricht" msgstr "Ihre Nachricht"
#: contact_form/forms.py:40 #: contact_form/forms.py:41
msgid "Send message" msgid "Send message"
msgstr "Nachricht senden" 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."

View file

@ -1,23 +0,0 @@
{% 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 %}

View file

@ -1,9 +0,0 @@
{% 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 %}

View file

@ -2,13 +2,17 @@
URL patterns for the contact_form views. URL patterns for the contact_form views.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.urls import re_path from django.conf.urls import url
from .views import (
ContactFormView,
ContactSuccessView,
)
from .views import ContactFormView, ContactSuccessView
urlpatterns = [ urlpatterns = [
re_path(r"^$", ContactFormView.as_view(), name="contact_form"), url(r'^$', ContactFormView.as_view(), name='contact_form'),
re_path(r"^success/$", ContactSuccessView.as_view(), name="contact_success"), url(r'^success/$', ContactSuccessView.as_view(), name='contact_success'),
] ]

View file

@ -2,11 +2,14 @@
This module defines the views of the contact_form app. This module defines the views of the contact_form app.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import FormView, TemplateView from django.views.generic import (
FormView,
TemplateView,
)
from .forms import ContactForm from .forms import ContactForm
@ -16,22 +19,22 @@ class ContactFormView(FormView):
This is the contact form view. This is the contact form view.
""" """
form_class = ContactForm form_class = ContactForm
template_name = "contact_form/contact_form.html" template_name = 'contact_form/contact_form.html'
success_url = reverse_lazy("contact_success") success_url = reverse_lazy('contact_success')
def get_form_kwargs(self, **kwargs): def get_form_kwargs(self, **kwargs):
kwargs = super(ContactFormView, self).get_form_kwargs(**kwargs) kwargs = super(ContactFormView, self).get_form_kwargs(**kwargs)
kwargs["request"] = self.request kwargs['request'] = self.request
return kwargs return kwargs
def get_initial(self): def get_initial(self):
initial = super(ContactFormView, self).get_initial() initial = super(ContactFormView, self).get_initial()
currentuser = self.request.user currentuser = self.request.user
if currentuser.is_authenticated: if currentuser.is_authenticated:
initial["name"] = currentuser.get_full_name() or currentuser.username initial['name'] = (
initial["email"] = currentuser.email currentuser.get_full_name() or currentuser.username)
initial['email'] = currentuser.email
return initial return initial
def form_valid(self, form): def form_valid(self, form):
@ -44,5 +47,4 @@ class ContactSuccessView(TemplateView):
This view is shown after successful contact form sending. This view is shown after successful contact form sending.
""" """
template_name = 'contact_form/contact_success.html'
template_name = "contact_form/contact_success.html"

View file

@ -7,53 +7,18 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnuviechadmin dashboard\n" "Project-Id-Version: gnuviechadmin dashboard\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-22 19:45+0200\n" "POT-Creation-Date: 2015-01-17 15:59+0100\n"
"PO-Revision-Date: 2023-07-22 19:46+0200\n" "PO-Revision-Date: 2015-01-17 16:01+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n" "Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n" "Language-Team: Jan Dittberner <de@li.org>\n"
"Language: de\n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 1.6.10\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
#: dashboard/templates/dashboard/user_dashboard.html:3 #: dashboard/views.py:43
#: dashboard/templates/dashboard/user_dashboard.html:6 msgid "You are not allowed to view this page."
#, python-format msgstr "Sie haben nicht die nötigen Berechtigungen um diese Seite zu sehen."
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"

View file

@ -1,47 +0,0 @@
{% 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 %}

View file

@ -2,7 +2,7 @@
Tests for :py:mod:`dashboard.views`. Tests for :py:mod:`dashboard.views`.
""" """
from django import http
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
@ -13,35 +13,55 @@ TEST_USER = "test"
TEST_PASSWORD = "secret" TEST_PASSWORD = "secret"
class IndexViewTest(TestCase):
def test_index_view(self):
response = self.client.get(reverse("dashboard"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "dashboard/index.html")
class UserDashboardViewTest(TestCase): class UserDashboardViewTest(TestCase):
def _create_test_user(self): def _create_test_user(self):
self.user = User.objects.create(username=TEST_USER) self.user = User.objects.create(username=TEST_USER)
self.user.set_password(TEST_PASSWORD) self.user.set_password(TEST_PASSWORD)
self.user.save() self.user.save()
def test_user_dashboard_view_no_user(self):
response = self.client.get(
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
)
self.assertEqual(response.status_code, 404)
def test_user_dashboard_view_anonymous(self): def test_user_dashboard_view_anonymous(self):
User.objects.create(username=TEST_USER) User.objects.create(username=TEST_USER)
response = self.client.get(reverse("customer_dashboard")) response = self.client.get(
self.assertEqual(response.status_code, 302) reverse("customer_dashboard", kwargs={"slug": TEST_USER})
self.assertRedirects(response, "/accounts/login/?next=/") )
self.assertEqual(response.status_code, 403)
def test_user_dashboard_view_logged_in_ok(self): def test_user_dashboard_view_logged_in_ok(self):
self._create_test_user() self._create_test_user()
self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD)) self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD))
response = self.client.get(reverse("customer_dashboard")) response = self.client.get(
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_user_dashboard_view_logged_in_template(self): def test_user_dashboard_view_logged_in_template(self):
self._create_test_user() self._create_test_user()
self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD)) self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD))
response = self.client.get( response = self.client.get(
reverse("customer_dashboard") reverse("customer_dashboard", kwargs={"slug": TEST_USER})
) )
self.assertTemplateUsed(response, "dashboard/user_dashboard.html") self.assertTemplateUsed(response, "dashboard/user_dashboard.html")
def test_user_dashboard_view_logged_in_context_fresh(self): def test_user_dashboard_view_logged_in_context_fresh(self):
self._create_test_user() self._create_test_user()
self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD)) self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD))
response = self.client.get(reverse("customer_dashboard")) response = self.client.get(
reverse("customer_dashboard", kwargs={"slug": TEST_USER})
)
self.assertIn("dashboard_user", response.context)
self.assertEqual(self.user, response.context["dashboard_user"])
self.assertIn("hosting_packages", response.context) self.assertIn("hosting_packages", response.context)
self.assertEqual(len(response.context["hosting_packages"]), 0) self.assertEqual(len(response.context["hosting_packages"]), 0)

View file

@ -1,9 +1,15 @@
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.urls import path from django.conf.urls import url
from .views import (
IndexView,
UserDashboardView,
)
from .views import UserDashboardView
urlpatterns = [ urlpatterns = [
path("", UserDashboardView.as_view(), name="customer_dashboard"), url(r'^$', IndexView.as_view(), name='dashboard'),
url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$',
UserDashboardView.as_view(), name='customer_dashboard'),
] ]

View file

@ -2,26 +2,47 @@
This module defines the views for the gnuviechadmin customer dashboard. 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 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 gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from hostingpackages.models import CustomerHostingPackage from hostingpackages.models import CustomerHostingPackage
class UserDashboardView(LoginRequiredMixin, TemplateView): class IndexView(TemplateView):
"""
This is the dashboard view.
"""
template_name = 'dashboard/index.html'
class UserDashboardView(StaffOrSelfLoginRequiredMixin, DetailView):
""" """
This is the user dashboard view. This is the user dashboard view.
""" """
model = get_user_model()
template_name = "dashboard/user_dashboard.html" context_object_name = 'dashboard_user'
slug_field = 'username'
template_name = 'dashboard/user_dashboard.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UserDashboardView, self).get_context_data(**kwargs) context = super(UserDashboardView, self).get_context_data(**kwargs)
context["hosting_packages"] = CustomerHostingPackage.objects.filter( context['hosting_packages'] = CustomerHostingPackage.objects.filter(
customer=self.request.user customer=self.object
) )
return context return context
def get_customer_object(self):
"""
Returns the customer object.
"""
return self.get_object()

View file

@ -2,3 +2,4 @@
This app takes care of domains. This app takes care of domains.
""" """
default_app_config = 'domains.apps.DomainAppConfig'

View file

@ -5,7 +5,24 @@ with the django admin site.
""" """
from django.contrib import admin from django.contrib import admin
from domains.models import HostingDomain, MailDomain from .models import (
DNSComment,
DNSCryptoKey,
DNSDomain,
DNSDomainMetadata,
DNSRecord,
DNSSupermaster,
DNSTSIGKey,
HostingDomain,
MailDomain,
)
admin.site.register(MailDomain) admin.site.register(MailDomain)
admin.site.register(HostingDomain) admin.site.register(HostingDomain)
admin.site.register(DNSComment)
admin.site.register(DNSCryptoKey)
admin.site.register(DNSDomain)
admin.site.register(DNSDomainMetadata)
admin.site.register(DNSRecord)
admin.site.register(DNSSupermaster)
admin.site.register(DNSTSIGKey)

View file

@ -3,8 +3,9 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`domains` app. :py:mod:`domains` app.
""" """
from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class DomainAppConfig(AppConfig): class DomainAppConfig(AppConfig):
@ -12,6 +13,5 @@ class DomainAppConfig(AppConfig):
AppConfig for the :py:mod:`domains` app. AppConfig for the :py:mod:`domains` app.
""" """
name = 'domains'
name = "domains" verbose_name = _('Domains')
verbose_name = _("Domains")

View file

@ -2,15 +2,19 @@
This module defines form classes for domain editing. This module defines form classes for domain editing.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
import re import re
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from django import forms from django import forms
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import ugettext as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from .models import HostingDomain from .models import HostingDomain
@ -22,10 +26,11 @@ def relative_domain_validator(value):
""" """
if len(value) > 254: 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}(?<!-)$") allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$")
if not all(allowed.match(x) for x in value.split(".")): if not all(allowed.match(x) for x in value.split('.')):
raise forms.ValidationError(_("invalid domain name")) raise forms.ValidationError(_('invalid domain name'))
class CreateHostingDomainForm(forms.ModelForm): class CreateHostingDomainForm(forms.ModelForm):
@ -33,32 +38,31 @@ class CreateHostingDomainForm(forms.ModelForm):
This form is used to create new HostingDomain instances. This form is used to create new HostingDomain instances.
""" """
class Meta: class Meta:
model = HostingDomain model = HostingDomain
fields = ["domain"] fields = ['domain']
def __init__(self, instance, *args, **kwargs): def __init__(self, instance, *args, **kwargs):
self.hosting_package = kwargs.pop("hostingpackage") self.hosting_package = kwargs.pop('hostingpackage')
super(CreateHostingDomainForm, self).__init__(*args, **kwargs) 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 = FormHelper()
self.helper.form_action = reverse( 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( self.helper.layout = Layout(
"domain", 'domain',
Submit("submit", _("Add Hosting Domain")), Submit('submit', _('Add Hosting Domain')),
) )
def clean(self): def clean(self):
self.cleaned_data = super(CreateHostingDomainForm, self).clean() 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): def save(self, commit=True):
return HostingDomain.objects.create_for_hosting_package( return HostingDomain.objects.create_for_hosting_package(
commit=commit, **self.cleaned_data commit=commit, **self.cleaned_data)
)
def save_m2m(self): def save_m2m(self):
pass pass

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnuviechadmin domains\n" "Project-Id-Version: gnuviechadmin domains\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-16 22:07+0200\n" "POT-Creation-Date: 2016-01-29 11:04+0100\n"
"PO-Revision-Date: 2023-04-16 18:20+0200\n" "PO-Revision-Date: 2015-11-08 12:02+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n" "Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n" "Language-Team: Jan Dittberner <jan@dittberner.info>\n"
"Language: de\n" "Language: de\n"
@ -16,60 +16,152 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 1.8.6\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
#: domains/apps.py:17 #: domains/apps.py:17
msgid "Domains" msgid "Domains"
msgstr "Domains" msgstr "Domains"
#: domains/forms.py:25 domains/tests/test_forms.py:20 #: domains/forms.py:30 domains/tests/test_forms.py:24
msgid "host name too long" msgid "host name too long"
msgstr "zu langer Hostname" msgstr "zu langer Hostname"
#: domains/forms.py:28 domains/tests/test_forms.py:24 #: domains/forms.py:33 domains/tests/test_forms.py:29
#: domains/tests/test_forms.py:28 domains/tests/test_forms.py:32 #: domains/tests/test_forms.py:34 domains/tests/test_forms.py:39
#: domains/tests/test_forms.py:36 #: domains/tests/test_forms.py:44
msgid "invalid domain name" msgid "invalid domain name"
msgstr "ungültiger Domainname" msgstr "ungültiger Domainname"
#: domains/forms.py:51 #: domains/forms.py:56
msgid "Add Hosting Domain" msgid "Add Hosting Domain"
msgstr "Hostingdomain hinzufügen" msgstr "Hostingdomain hinzufügen"
#: domains/models.py:17
msgid "Master"
msgstr "Master"
#: domains/models.py:18
msgid "Slave"
msgstr "Slave"
#: domains/models.py:19 #: domains/models.py:19
msgid "Native"
msgstr "Native"
#: domains/models.py:44
msgid "HMAC MD5"
msgstr "HMAC MD5"
#: domains/models.py:45
msgid "HMAC SHA1"
msgstr "HMAC SHA1"
#: domains/models.py:46
msgid "HMAC SHA224"
msgstr "HMAC SHA224"
#: domains/models.py:47
msgid "HMAC SHA256"
msgstr "HMAC SHA256"
#: domains/models.py:48
msgid "HMAC SHA384"
msgstr "HMAC SHA384"
#: domains/models.py:49
msgid "HMAC SHA512"
msgstr "HMAC SHA512"
#: domains/models.py:58
msgid "domain name" msgid "domain name"
msgstr "Domainname" msgstr "Domainname"
#: domains/models.py:22 #: domains/models.py:60 domains/models.py:258 domains/models.py:308
msgid "customer" msgid "customer"
msgstr "Kunde" msgstr "Kunde"
#: domains/models.py:41 #: domains/models.py:76
msgid "Mail domain" msgid "Mail domain"
msgstr "E-Maildomain" msgstr "E-Maildomain"
#: domains/models.py:42 #: domains/models.py:77
msgid "Mail domains" msgid "Mail domains"
msgstr "E-Maildomains" msgstr "E-Maildomains"
#: domains/models.py:91 #: domains/models.py:121
msgid "mail domain" msgid "mail domain"
msgstr "E-Maildomain" msgstr "E-Maildomain"
#: domains/models.py:94 #: domains/models.py:122
msgid "assigned mail domain for this domain" msgid "assigned mail domain for this domain"
msgstr "zugeordnete E-Maildomain für diese Domain" msgstr "zugeordnete E-Maildomain für diese Domain"
#: domains/models.py:101 #: domains/models.py:128
msgid "Hosting domain" msgid "Hosting domain"
msgstr "Hostingdomain" msgstr "Hostingdomain"
#: domains/models.py:102 #: domains/models.py:129
msgid "Hosting domains" msgid "Hosting domains"
msgstr "Hostingdomains" msgstr "Hostingdomains"
#: domains/views.py:51 #: domains/models.py:169
msgid "DNS domain"
msgstr "DNS-Domain"
#: domains/models.py:170
msgid "DNS domains"
msgstr "DNS-Domains"
#: domains/models.py:226
msgid "DNS record"
msgstr "DNS-Record"
#: domains/models.py:227
msgid "DNS records"
msgstr "DNS-Records"
#: domains/models.py:261
msgid "DNS supermaster"
msgstr "DNS-Supermaster"
#: domains/models.py:262
msgid "DNS supermasters"
msgstr "DNS-Supermasters"
#: domains/models.py:313
msgid "DNS comment"
msgstr "DNS-Kommentar"
#: domains/models.py:314
msgid "DNS comments"
msgstr "DNS-Kommentare"
#: domains/models.py:351
msgid "DNS domain metadata item"
msgstr "DNS-Domainmetadaten-Eintrag"
#: domains/models.py:352
msgid "DNS domain metadata items"
msgstr "DNS-Domainmetadaten-Einträge"
#: domains/models.py:385
msgid "DNS crypto key"
msgstr "DNS-Cryposchlüssel"
#: domains/models.py:386
msgid "DNS crypto keys"
msgstr "DNS-Cryptoschlüssel"
#: domains/models.py:420
msgid "DNS TSIG key"
msgstr "DNS-TSIG-Schlüssel"
#: domains/models.py:421
msgid "DNS TSIG keys"
msgstr "DNS-TSIG-Schlüssel"
#: domains/views.py:58
#, python-brace-format #, python-brace-format
msgid "Successfully created domain {domainname}" msgid "Successfully created domain {domainname}"
msgstr "Domain {domainname} erfolgreich angelegt" msgstr "Domain {domainname} erfolgreich angelegt"

View file

@ -1,46 +1,28 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone import django.utils.timezone
import model_utils.fields import model_utils.fields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = []
dependencies = [
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name="MailDomain", name='MailDomain',
fields=[ fields=[
( ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
"id", ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
models.AutoField( ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
verbose_name="ID", ('domain', models.CharField(unique=True, max_length=128)),
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={ options={
"verbose_name": "Mail domain", 'verbose_name': 'Mail domain',
"verbose_name_plural": "Mail domains", 'verbose_name_plural': 'Mail domains',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),

View file

@ -1,97 +1,68 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone import django.utils.timezone
import model_utils.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models import model_utils.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("domains", "0001_initial"), ('domains', '0001_initial'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name="HostingDomain", name='HostingDomain',
fields=[ fields=[
( ('id',
"id", models.AutoField(verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('created',
serialize=False, model_utils.fields.AutoCreatedField(
auto_created=True, default=django.utils.timezone.now, verbose_name='created',
primary_key=True, editable=False)),
), ('modified',
), model_utils.fields.AutoLastModifiedField(
( default=django.utils.timezone.now, verbose_name='modified',
"created", editable=False)),
model_utils.fields.AutoCreatedField( ('domain',
default=django.utils.timezone.now, models.CharField(
verbose_name="created", unique=True, max_length=128, verbose_name='domain name')),
editable=False, ('customer',
), models.ForeignKey(
), verbose_name='customer', blank=True,
( to=settings.AUTH_USER_MODEL, null=True,
"modified", on_delete=models.CASCADE)),
model_utils.fields.AutoLastModifiedField( ('maildomain',
default=django.utils.timezone.now, models.OneToOneField(
verbose_name="modified", null=True, to='domains.MailDomain', blank=True,
editable=False, help_text='assigned mail domain for this domain',
), verbose_name='mail domain',
), on_delete=models.CASCADE)),
(
"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={ options={
"verbose_name": "Hosting domain", 'verbose_name': 'Hosting domain',
"verbose_name_plural": "Hosting domains", 'verbose_name_plural': 'Hosting domains',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.AddField( migrations.AddField(
model_name="maildomain", model_name='maildomain',
name="customer", name='customer',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="customer", verbose_name='customer', blank=True,
blank=True, to=settings.AUTH_USER_MODEL, null=True,
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
null=True,
on_delete=models.CASCADE,
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name="maildomain", model_name='maildomain',
name="domain", name='domain',
field=models.CharField( field=models.CharField(
unique=True, max_length=128, verbose_name="domain name" unique=True, max_length=128, verbose_name='domain name'),
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -1,285 +1,199 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import django.utils.timezone from __future__ import unicode_literals
import model_utils.fields
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.utils.timezone
from django.conf import settings
import model_utils.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("domains", "0002_auto_20150124_1909"), ('domains', '0002_auto_20150124_1909'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name="DNSComment", name='DNSComment',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('name', models.CharField(max_length=255)),
serialize=False, ('commenttype',
auto_created=True, models.CharField(max_length=10, db_column='type')),
primary_key=True, ('modified_at', models.IntegerField()),
), ('comment', models.CharField(max_length=65535)),
), ('customer', models.ForeignKey(
("name", models.CharField(max_length=255)), verbose_name='customer',
("commenttype", models.CharField(max_length=10, db_column="type")), to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
("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( migrations.RunSQL(
"""ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name '''ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))""" CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
), ),
migrations.CreateModel( migrations.CreateModel(
name="DNSCryptoKey", name='DNSCryptoKey',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('flags', models.IntegerField()),
serialize=False, ('active', models.BooleanField(default=True)),
auto_created=True, ('content', models.TextField()),
primary_key=True,
),
),
("flags", models.IntegerField()),
("active", models.BooleanField(default=True)),
("content", models.TextField()),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name="DNSDomain", name='DNSDomain',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
( ('domain', models.CharField(
"created", unique=True, max_length=255, verbose_name='domain name')),
model_utils.fields.AutoCreatedField( ('master',
default=django.utils.timezone.now, models.CharField(max_length=128, null=True, blank=True)),
verbose_name="created", ('last_check', models.IntegerField(null=True)),
editable=False, ('domaintype', models.CharField(
), max_length=6, db_column='type',
), choices=[('MASTER', 'Master'),
( ('SLAVE', 'Slave'),
"modified", ('NATIVE', 'Native')])),
model_utils.fields.AutoLastModifiedField( ('notified_serial', models.IntegerField(null=True)),
default=django.utils.timezone.now, ('customer', models.ForeignKey(
verbose_name="modified", verbose_name='customer', blank=True,
editable=False, to=settings.AUTH_USER_MODEL, null=True,
), on_delete=models.CASCADE)),
),
(
"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={ options={
"verbose_name": "DNS domain", 'verbose_name': 'DNS domain',
"verbose_name_plural": "DNS domains", 'verbose_name_plural': 'DNS domains',
}, },
), ),
migrations.RunSQL( migrations.RunSQL(
"""ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name '''ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name
CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))""" CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))'''
), ),
migrations.CreateModel( migrations.CreateModel(
name="DNSDomainMetadata", name='DNSDomainMetadata',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('kind', models.CharField(max_length=32)),
serialize=False, ('content', models.TextField()),
auto_created=True, ('domain', models.ForeignKey(
primary_key=True, to='domains.DNSDomain', on_delete=models.CASCADE)),
),
),
("kind", models.CharField(max_length=32)),
("content", models.TextField()),
(
"domain",
models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE),
),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name="DNSRecord", name='DNSRecord',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('name', models.CharField(
serialize=False, db_index=True, max_length=255, null=True, blank=True)),
auto_created=True, ('recordtype', models.CharField(
primary_key=True, 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)),
"name", ('prio', models.IntegerField(null=True)),
models.CharField( ('change_date', models.IntegerField(null=True)),
db_index=True, max_length=255, null=True, blank=True ('disabled', models.BooleanField(default=False)),
), ('ordername', models.CharField(max_length=255)),
), ('auth', models.BooleanField(default=True)),
( ('domain', models.ForeignKey(
"recordtype", to='domains.DNSDomain', on_delete=models.CASCADE)),
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={ options={
"verbose_name": "DNS record", 'verbose_name': 'DNS record',
"verbose_name_plural": "DNS records", 'verbose_name_plural': 'DNS records',
}, },
), ),
migrations.RunSQL( migrations.RunSQL(
"""ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name '''ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))""" CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
), ),
migrations.RunSQL( migrations.RunSQL(
"""CREATE INDEX recordorder ON domains_dnsrecord (domain_id, '''CREATE INDEX recordorder ON domains_dnsrecord (domain_id,
ordername text_pattern_ops)""" ordername text_pattern_ops)'''
), ),
migrations.CreateModel( migrations.CreateModel(
name="DNSSupermaster", name='DNSSupermaster',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('ip', models.GenericIPAddressField()),
serialize=False, ('nameserver', models.CharField(max_length=255)),
auto_created=True, ('customer', models.ForeignKey(
primary_key=True, verbose_name='customer', to=settings.AUTH_USER_MODEL,
), on_delete=models.CASCADE)),
),
("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( migrations.CreateModel(
name="DNSTSIGKey", name='DNSTSIGKey',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False,
models.AutoField( auto_created=True, primary_key=True)),
verbose_name="ID", ('name', models.CharField(max_length=255)),
serialize=False, ('algorithm', models.CharField(max_length=50)),
auto_created=True, ('secret', models.CharField(max_length=255)),
primary_key=True,
),
),
("name", models.CharField(max_length=255)),
("algorithm", models.CharField(max_length=50)),
("secret", models.CharField(max_length=255)),
], ],
), ),
migrations.RunSQL( migrations.RunSQL(
"""ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name '''ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))""" CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
), ),
migrations.AlterField( migrations.AlterField(
model_name="hostingdomain", model_name='hostingdomain',
name="domain", name='domain',
field=models.CharField( field=models.CharField(
unique=True, max_length=255, verbose_name="domain name" unique=True, max_length=255, verbose_name='domain name'),
),
), ),
migrations.AlterField( migrations.AlterField(
model_name="maildomain", model_name='maildomain',
name="domain", name='domain',
field=models.CharField( field=models.CharField(
unique=True, max_length=255, verbose_name="domain name" unique=True, max_length=255, verbose_name='domain name'),
),
), ),
migrations.AddField( migrations.AddField(
model_name="dnscryptokey", model_name='dnscryptokey',
name="domain", name='domain',
field=models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE), field=models.ForeignKey(
to='domains.DNSDomain', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name="dnscomment", model_name='dnscomment',
name="domain", name='domain',
field=models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE), field=models.ForeignKey(
to='domains.DNSDomain', on_delete=models.CASCADE),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="dnssupermaster", name='dnssupermaster',
unique_together=set([("ip", "nameserver")]), unique_together=set([('ip', 'nameserver')]),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="dnstsigkey", name='dnstsigkey',
unique_together=set([("name", "algorithm")]), unique_together=set([('name', 'algorithm')]),
), ),
migrations.AlterIndexTogether( migrations.AlterIndexTogether(
name="dnsrecord", name='dnsrecord',
index_together=set([("name", "recordtype")]), index_together=set([('name', 'recordtype')]),
), ),
migrations.AlterIndexTogether( migrations.AlterIndexTogether(
name="dnscomment", name='dnscomment',
index_together={("name", "commenttype"), ("domain", "modified_at")}, index_together={('name', 'commenttype'), ('domain', 'modified_at')},
), ),
] ]

View file

@ -1,87 +1,44 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("domains", "0003_auto_20151105_2133"), ('domains', '0003_auto_20151105_2133'),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="dnscomment", name='dnscomment',
options={ options={'verbose_name': 'DNS comment', 'verbose_name_plural': 'DNS comments'},
"verbose_name": "DNS comment",
"verbose_name_plural": "DNS comments",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="dnscryptokey", name='dnscryptokey',
options={ options={'verbose_name': 'DNS crypto key', 'verbose_name_plural': 'DNS crypto keys'},
"verbose_name": "DNS crypto key",
"verbose_name_plural": "DNS crypto keys",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="dnsdomainmetadata", name='dnsdomainmetadata',
options={ options={'verbose_name': 'DNS domain metadata item', 'verbose_name_plural': 'DNS domain metadata items'},
"verbose_name": "DNS domain metadata item",
"verbose_name_plural": "DNS domain metadata items",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="dnssupermaster", name='dnssupermaster',
options={ options={'verbose_name': 'DNS supermaster', 'verbose_name_plural': 'DNS supermasters'},
"verbose_name": "DNS supermaster",
"verbose_name_plural": "DNS supermasters",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="dnstsigkey", name='dnstsigkey',
options={ options={'verbose_name': 'DNS TSIG key', 'verbose_name_plural': 'DNS TSIG keys'},
"verbose_name": "DNS TSIG key",
"verbose_name_plural": "DNS TSIG keys",
},
), ),
migrations.AlterField( migrations.AlterField(
model_name="dnsdomainmetadata", model_name='dnsdomainmetadata',
name="kind", name='kind',
field=models.CharField( 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')]),
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( migrations.AlterField(
model_name="dnstsigkey", model_name='dnstsigkey',
name="algorithm", name='algorithm',
field=models.CharField( 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')]),
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"),
],
),
), ),
] ]

View file

@ -1,74 +0,0 @@
# 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',
),
]

View file

@ -2,12 +2,52 @@
This module contains models related to domain names. This module contains models related to domain names.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.db import models, transaction from django.db import models, transaction
from django.utils.translation import gettext as _ from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from model_utils import Choices
DNS_DOMAIN_TYPES = Choices(
('MASTER', _('Master')),
('SLAVE', _('Slave')),
('NATIVE', _('Native')),
)
# see https://doc.powerdns.com/md/authoritative/domainmetadata/
DNS_DOMAIN_METADATA_KINDS = Choices(
'ALLOW-DNSUPDATE-FROM',
'ALSO-NOTIFY',
'AXFR-MASTER-TSIG',
'AXFR-SOURCE',
'FORWARD-DNSUPDATE',
'GSS-ACCEPTOR-PRINCIPAL',
'GSS-ALLOW-AXFR-PRINCIPAL',
'LUA-AXFR-SCRIPT',
'NSEC3NARROW',
'NSEC3PARAM',
'PRESIGNED',
'PUBLISH_CDNSKEY',
'PUBLISH_CDS',
'SOA-EDIT',
'SOA-EDIT-DNSUPDATE',
'TSIG-ALLOW-AXFR',
'TSIG-ALLOW-DNSUPDATE',
)
DNS_TSIG_KEY_ALGORITHMS = Choices(
('hmac-md5', _('HMAC MD5')),
('hmac-sha1', _('HMAC SHA1')),
('hmac-sha224', _('HMAC SHA224')),
('hmac-sha256', _('HMAC SHA256')),
('hmac-sha384', _('HMAC SHA384')),
('hmac-sha512', _('HMAC SHA512')),
)
class DomainBase(TimeStampedModel): class DomainBase(TimeStampedModel):
@ -15,20 +55,16 @@ class DomainBase(TimeStampedModel):
This is the base model for domains. This is the base model for domains.
""" """
domain = models.CharField(_('domain name'), max_length=255, unique=True)
domain = models.CharField(_("domain name"), max_length=255, unique=True)
customer = models.ForeignKey( customer = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True,
verbose_name=_("customer"), null=True, on_delete=models.CASCADE)
blank=True,
null=True,
on_delete=models.CASCADE,
)
class Meta: class Meta:
abstract = True abstract = True
@python_2_unicode_compatible
class MailDomain(DomainBase): class MailDomain(DomainBase):
""" """
This is the model for mail domains. Mail domains are used to configure the This is the model for mail domains. Mail domains are used to configure the
@ -36,10 +72,9 @@ class MailDomain(DomainBase):
domains. domains.
""" """
class Meta:
class Meta(DomainBase.Meta): verbose_name = _('Mail domain')
verbose_name = _("Mail domain") verbose_name_plural = _('Mail domains')
verbose_name_plural = _("Mail domains")
def __str__(self): def __str__(self):
return self.domain return self.domain
@ -50,7 +85,6 @@ class MailDomain(DomainBase):
""" """
return self.mailaddress_set.all() return self.mailaddress_set.all()
mailaddresses = property(get_mailaddresses) mailaddresses = property(get_mailaddresses)
@ -59,47 +93,339 @@ class HostingDomainManager(models.Manager):
Default Manager for :py:class:`HostingDomain`. Default Manager for :py:class:`HostingDomain`.
""" """
@transaction.atomic @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 from hostingpackages.models import CustomerHostingPackageDomain
hostingdomain = self.create( hostingdomain = self.create(
customer=hosting_package.customer, domain=domain, **kwargs customer=hosting_package.customer, domain=domain, **kwargs)
)
hostingdomain.maildomain = MailDomain.objects.create( hostingdomain.maildomain = MailDomain.objects.create(
customer=hosting_package.customer, domain=domain customer=hosting_package.customer, domain=domain)
)
custdomain = CustomerHostingPackageDomain.objects.create( custdomain = CustomerHostingPackageDomain.objects.create(
hosting_package=hosting_package, domain=hostingdomain hosting_package=hosting_package, domain=hostingdomain)
)
if commit: if commit:
hostingdomain.save() hostingdomain.save()
custdomain.save() custdomain.save()
return hostingdomain return hostingdomain
@python_2_unicode_compatible
class HostingDomain(DomainBase): class HostingDomain(DomainBase):
""" """
This is the model for hosting domains. A hosting domain is linked to a This is the model for hosting domains. A hosting domain is linked to a
customer hosting account. customer hosting account.
""" """
maildomain = models.OneToOneField( maildomain = models.OneToOneField(
MailDomain, MailDomain, verbose_name=_('mail domain'), blank=True, null=True,
verbose_name=_("mail domain"), help_text=_('assigned mail domain for this domain'),
blank=True,
null=True,
help_text=_("assigned mail domain for this domain"),
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
objects = HostingDomainManager() objects = HostingDomainManager()
class Meta: class Meta:
verbose_name = _("Hosting domain") verbose_name = _('Hosting domain')
verbose_name_plural = _("Hosting domains") verbose_name_plural = _('Hosting domains')
def __str__(self): def __str__(self):
return self.domain return self.domain
@python_2_unicode_compatible
class DNSDomain(DomainBase):
"""
This model represents a DNS zone. The model is similar to the domain table
in the PowerDNS schema specified in
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
.. code-block:: sql
CREATE TABLE domains (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
notified_serial INT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
CONSTRAINT c_lowercase_name CHECK (
((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE UNIQUE INDEX name_index ON domains(name);
"""
# name is represented by domain
master = models.CharField(max_length=128, blank=True, null=True)
last_check = models.IntegerField(null=True)
domaintype = models.CharField(
max_length=6, choices=DNS_DOMAIN_TYPES, db_column='type')
notified_serial = models.IntegerField(null=True)
# account is represented by customer_id
# check constraint is added via RunSQL in migration
class Meta:
verbose_name = _('DNS domain')
verbose_name_plural = _('DNS domains')
def __str__(self):
return self.domain
@python_2_unicode_compatible
class DNSRecord(models.Model):
"""
This model represents a DNS record. The model is similar to the record
table in the PowerDNS schema specified in
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
.. code-block:: sql
CREATE TABLE records (
id SERIAL PRIMARY KEY,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(65535) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
change_date INT DEFAULT NULL,
disabled BOOL DEFAULT 'f',
ordername VARCHAR(255),
auth BOOL DEFAULT 't',
CONSTRAINT domain_exists
FOREIGN KEY(domain_id) REFERENCES domains(id)
ON DELETE CASCADE,
CONSTRAINT c_lowercase_name CHECK (
((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (
domain_id, ordername text_pattern_ops);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
name = models.CharField(
max_length=255, blank=True, null=True, db_index=True)
recordtype = models.CharField(
max_length=10, blank=True, null=True, db_column='type')
content = models.CharField(max_length=65535, blank=True, null=True)
ttl = models.IntegerField(null=True)
prio = models.IntegerField(null=True)
change_date = models.IntegerField(null=True)
disabled = models.BooleanField(default=False)
ordername = models.CharField(max_length=255)
auth = models.BooleanField(default=True)
# check constraint and index recordorder are added via RunSQL in migration
class Meta:
verbose_name = _('DNS record')
verbose_name_plural = _('DNS records')
index_together = [
['name', 'recordtype']
]
def __str__(self):
return "{name} IN {type} {content}".format(
name=self.name, type=self.recordtype, content=self.content)
@python_2_unicode_compatible
class DNSSupermaster(models.Model):
"""
This model represents the supermasters table in the PowerDNS schema
specified in
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
.. code-block:: sql
CREATE TABLE supermasters (
ip INET NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY(ip, nameserver)
);
"""
ip = models.GenericIPAddressField()
nameserver = models.CharField(max_length=255)
# account is replaced by customer
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('customer'),
on_delete=models.CASCADE)
class Meta:
verbose_name = _('DNS supermaster')
verbose_name_plural = _('DNS supermasters')
unique_together = (
('ip', 'nameserver')
)
def __str__(self):
return "{ip} {nameserver}".format(
ip=self.ip, nameserver=self.nameserver)
@python_2_unicode_compatible
class DNSComment(models.Model):
"""
This model represents the comments table in the PowerDNS schema specified
in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The
comments table is used to store user comments related to individual DNS
records.
.. code-block:: sql
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) DEFAULT NULL,
comment VARCHAR(65535) NOT NULL,
CONSTRAINT domain_exists
FOREIGN KEY(domain_id) REFERENCES domains(id)
ON DELETE CASCADE,
CONSTRAINT c_lowercase_name CHECK (
((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
commenttype = models.CharField(max_length=10, db_column='type')
modified_at = models.IntegerField()
# account is replaced by customer
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('customer'),
on_delete=models.CASCADE)
comment = models.CharField(max_length=65535)
# check constraint is added via RunSQL in migration
class Meta:
verbose_name = _('DNS comment')
verbose_name_plural = _('DNS comments')
index_together = [
['name', 'commenttype'],
['domain', 'modified_at']
]
def __str__(self):
return "{name} IN {type}: {comment}".format(
name=self.name, type=self.commenttype, comment=self.comment)
@python_2_unicode_compatible
class DNSDomainMetadata(models.Model):
"""
This model represents the domainmetadata table in the PowerDNS schema
specified in
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
The domainmetadata table is used to store domain meta data as described in
https://doc.powerdns.com/md/authoritative/domainmetadata/.
.. code-block:: sql
CREATE TABLE domainmetadata (
id SERIAL PRIMARY KEY,
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
kind VARCHAR(32),
content TEXT
);
CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS)
content = models.TextField()
class Meta:
verbose_name = _('DNS domain metadata item')
verbose_name_plural = _('DNS domain metadata items')
def __str__(self):
return "{domain} {kind} {content}".format(
domain=self.domain.domain, kind=self.kind, content=self.content)
@python_2_unicode_compatible
class DNSCryptoKey(models.Model):
"""
This model represents the cryptokeys table in the PowerDNS schema
specified in
https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
.. code-block:: sql
CREATE TABLE cryptokeys (
id SERIAL PRIMARY KEY,
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
flags INT NOT NULL,
active BOOL,
content TEXT
);
CREATE INDEX domainidindex ON cryptokeys(domain_id);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
flags = models.IntegerField()
active = models.BooleanField(default=True)
content = models.TextField()
class Meta:
verbose_name = _('DNS crypto key')
verbose_name_plural = _('DNS crypto keys')
def __str__(self):
return "{domain} {content}".format(
domain=self.domain.domain, content=self.content)
@python_2_unicode_compatible
class DNSTSIGKey(models.Model):
"""
This model represents the tsigkeys table in the PowerDNS schema specified
in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
.. code-block:: sql
CREATE TABLE tsigkeys (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255),
CONSTRAINT c_lowercase_name CHECK (
((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
"""
name = models.CharField(max_length=255)
algorithm = models.CharField(
max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS)
secret = models.CharField(max_length=255)
# check constraint is added via RunSQL in migration
class Meta:
verbose_name = _('DNS TSIG key')
verbose_name_plural = _('DNS TSIG keys')
unique_together = [
['name', 'algorithm']
]
def __str__(self):
return "{name} {algorithm} XXXX".format(
name=self.name, algorithm=self.algorithm)

View file

@ -7,9 +7,9 @@ from unittest.mock import MagicMock, Mock, patch
from django.forms import ValidationError from django.forms import ValidationError
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import ugettext as _
from domains.forms import CreateHostingDomainForm, relative_domain_validator from domains.forms import relative_domain_validator, CreateHostingDomainForm
class RelativeDomainValidatorTest(TestCase): class RelativeDomainValidatorTest(TestCase):

View file

@ -7,10 +7,20 @@ from unittest.mock import patch
from django.test import TestCase from django.test import TestCase
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from domains.models import HostingDomain, MailDomain from domains.models import (
DNSComment,
DNSCryptoKey,
DNSDomain,
DNSDomainMetadata,
DNSRecord,
DNSSupermaster,
DNSTSIGKey,
HostingDomain,
MailDomain,
)
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
User = get_user_model() User = get_user_model()
TEST_USER = "test" TEST_USER = "test"
@ -67,3 +77,49 @@ class HostingDomainTest(TestCase):
def test___str__(self): def test___str__(self):
hostingdomain = HostingDomain(domain="test") hostingdomain = HostingDomain(domain="test")
self.assertEqual(str(hostingdomain), "test") self.assertEqual(str(hostingdomain), "test")
class DNSDomainTest(TestCase):
def test___str__(self):
dnsdomain = DNSDomain(domain="test")
self.assertEqual(str(dnsdomain), "test")
class DNSRecordTest(TestCase):
def test___str__(self):
dnsrecord = DNSRecord(name="localhost", recordtype="A", content="127.0.0.1")
self.assertEqual(str(dnsrecord), "localhost IN A 127.0.0.1")
class DNSSupermasterTest(TestCase):
def test___str__(self):
dnssupermaster = DNSSupermaster(ip="127.0.0.1", nameserver="dns.example.org")
self.assertEqual(str(dnssupermaster), "127.0.0.1 dns.example.org")
class DNSCommentTest(TestCase):
def test___str__(self):
dnscomment = DNSComment(name="localhost", commenttype="A", comment="good stuff")
self.assertEqual(str(dnscomment), "localhost IN A: good stuff")
class DNSDomainMetadataTest(TestCase):
def test___str__(self):
dnsdomain = DNSDomain(domain="test")
dnsdomainmetadata = DNSDomainMetadata(
domain=dnsdomain, kind="SOA-EDIT", content="INCEPTION"
)
self.assertEqual(str(dnsdomainmetadata), "test SOA-EDIT INCEPTION")
class DNSCryptoKeyTest(TestCase):
def test___str__(self):
dnsdomain = DNSDomain(domain="test")
dnscryptokey = DNSCryptoKey(domain=dnsdomain, content="testvalue")
self.assertEqual(str(dnscryptokey), "test testvalue")
class DNSTSIGKeyTest(TestCase):
def test___str__(self):
dnstsigkey = DNSTSIGKey(name="testkey", algorithm="hmac-md5", secret="dummykey")
self.assertEqual(str(dnstsigkey), "testkey hmac-md5 XXXX")

View file

@ -2,16 +2,14 @@
This module defines the URL patterns for domain related views. This module defines the URL patterns for domain related views.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.urls import re_path from django.conf.urls import url
from .views import CreateHostingDomain from .views import CreateHostingDomain
urlpatterns = [ urlpatterns = [
re_path( url(r'^(?P<package>\d+)/create$', CreateHostingDomain.as_view(),
r"^(?P<package>\d+)/create$", name='create_hosting_domain'),
CreateHostingDomain.as_view(),
name="create_hosting_domain",
),
] ]

View file

@ -2,21 +2,20 @@
This module defines views related to domains. This module defines views related to domains.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from braces.views import StaffuserRequiredMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext as _ from django.utils.translation import ugettext as _
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from hostingpackages.models import CustomerHostingPackage from hostingpackages.models import CustomerHostingPackage
from .forms import CreateHostingDomainForm from .forms import CreateHostingDomainForm
from .models import HostingDomain from .models import HostingDomain
class CreateHostingDomain(PermissionRequiredMixin, CreateView): class CreateHostingDomain(StaffuserRequiredMixin, CreateView):
""" """
This view is used for creating a new HostingDomain instance for an existing This view is used for creating a new HostingDomain instance for an existing
hosting package. hosting package.
@ -24,7 +23,6 @@ class CreateHostingDomain(PermissionRequiredMixin, CreateView):
model = HostingDomain model = HostingDomain
raise_exception = True raise_exception = True
permission_required = 'domains.add_hostingdomain'
template_name_suffix = "_create" template_name_suffix = "_create"
form_class = CreateHostingDomainForm form_class = CreateHostingDomainForm

View file

@ -1,4 +1,4 @@
# import celery_app to initialize it # import celery_app to initialize it
from gnuviechadmin.celery import app as celery_app # NOQA from gnuviechadmin.celery import app as celery_app # NOQA
__version__ = "0.15.1" __version__ = '0.12.0'

View file

@ -1,11 +0,0 @@
from allauth.account.adapter import DefaultAccountAdapter
class NoNewUsersAccountAdapter(DefaultAccountAdapter):
"""
Adapter to disable allauth new signups
"""
def is_open_for_signup(self, request):
return False

View file

@ -6,15 +6,15 @@ from celery import Celery
from django.conf import settings from django.conf import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings") os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'gnuviechadmin.settings.production')
app = Celery("gnuviechadmin") app = Celery('gnuviechadmin')
def get_installed_apps(): def get_installed_apps():
return settings.INSTALLED_APPS return settings.INSTALLED_APPS
app.config_from_object('django.conf:settings')
app.config_from_object("django.conf:settings")
app.autodiscover_tasks(get_installed_apps) app.autodiscover_tasks(get_installed_apps)

View file

@ -2,7 +2,7 @@
This module provides context processor implementations for gnuviechadmin. This module provides context processor implementations for gnuviechadmin.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
import logging import logging
@ -22,42 +22,38 @@ def navigation(request):
:rtype: dict :rtype: dict
""" """
if request.headers.get("x-requested-with") == "XMLHttpRequest": if request.is_ajax():
return {} return {}
context = { context = {
"webmail_url": settings.GVA_LINK_WEBMAIL, 'webmail_url': settings.GVA_LINK_WEBMAIL,
"phpmyadmin_url": settings.GVA_LINK_PHPMYADMIN, 'phpmyadmin_url': settings.GVA_LINK_PHPMYADMIN,
"phppgadmin_url": settings.GVA_LINK_PHPPGADMIN, 'phppgadmin_url': settings.GVA_LINK_PHPPGADMIN,
"active_item": "dashboard", 'active_item': 'dashboard',
} }
if request.resolver_match: if request.resolver_match:
viewfunc = request.resolver_match.func viewfunc = request.resolver_match.func
viewmodule = viewfunc.__module__ viewmodule = viewfunc.__module__
if viewmodule == "contact_form.views": if viewmodule == 'contact_form.views':
context["active_item"] = "contact" context['active_item'] = 'contact'
elif viewmodule in ( elif viewmodule in (
"hostingpackages.views", 'hostingpackages.views', 'osusers.views', 'userdbs.views',
"osusers.views", 'managemails.views', 'websites.views', 'domains.views',
"userdbs.views",
"managemails.views",
"websites.views",
"domains.views",
): ):
context["active_item"] = "hostingpackage" context['active_item'] = 'hostingpackage'
elif viewmodule in ("allauth.account.views", "allauth.socialaccount.views"): elif viewmodule in (
context["active_item"] = "account" 'allauth.account.views', 'allauth.socialaccount.views'
elif viewmodule == "django.contrib.flatpages.views" and request.path.endswith(
"/impressum/"
): ):
context["active_item"] = "imprint" context['active_item'] = 'account'
elif not viewmodule.startswith("django.contrib.admin"): elif (
viewmodule == 'django.contrib.flatpages.views' and
request.path.endswith('/impressum/')
):
context['active_item'] = 'imprint'
elif not viewmodule.startswith('django.contrib.admin'):
_LOGGER.debug( _LOGGER.debug(
"no special handling for view %s in module %s, fallback to " 'no special handling for view %s in module %s, fallback to '
"default active menu item %s", 'default active menu item %s',
viewfunc.__name__, viewfunc.__name__, viewmodule, context['active_item'])
viewmodule,
context["active_item"],
)
return context return context
@ -68,6 +64,6 @@ def version_info(request):
""" """
context = { context = {
"gnuviechadmin_version": gvaversion, 'gnuviechadmin_version': gvaversion,
} }
return context return context

View file

@ -6,12 +6,11 @@ Common settings and globals.
""" """
from os.path import abspath, basename, dirname, join, normpath from os.path import abspath, basename, dirname, join, normpath
from environs import Env
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
env = Env() from gvacommon.settings_utils import get_env_variable
env.read_env()
# ######### PATH CONFIGURATION # ######### PATH CONFIGURATION
# Absolute filesystem path to the Django project directory: # Absolute filesystem path to the Django project directory:
@ -20,18 +19,15 @@ DJANGO_ROOT = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder: # Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(DJANGO_ROOT) SITE_ROOT = dirname(DJANGO_ROOT)
ROOT_DIR = dirname(DJANGO_ROOT)
# Site name: # Site name:
SITE_NAME = basename(DJANGO_ROOT) SITE_NAME = basename(DJANGO_ROOT)
# ######### END PATH CONFIGURATION # ######### END PATH CONFIGURATION
GVA_ENVIRONMENT = env.str("GVA_ENVIRONMENT", default="prod")
# ######### DEBUG CONFIGURATION # ######### DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = GVA_ENVIRONMENT == "local" DEBUG = False
# ######### END DEBUG CONFIGURATION # ######### END DEBUG CONFIGURATION
@ -39,8 +35,8 @@ DEBUG = GVA_ENVIRONMENT == "local"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = ( ADMINS = (
( (
env.str("GVA_ADMIN_NAME", default="Admin"), get_env_variable("GVA_ADMIN_NAME", default="Admin"),
env.str("GVA_ADMIN_EMAIL", default="admin@example.org"), get_env_variable("GVA_ADMIN_EMAIL", default="admin@example.org"),
), ),
) )
@ -52,10 +48,15 @@ MANAGERS = ADMINS
# ######### DATABASE CONFIGURATION # ######### DATABASE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = { DATABASES = {
"default": env.dj_db_url("GVA_DATABASE_URL"), "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 # ######### END DATABASE CONFIGURATION
@ -68,12 +69,15 @@ LANGUAGE_CODE = "en-us"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id # See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1 SITE_ID = 1
SITES_DOMAIN_NAME = env.str("GVA_DOMAIN_NAME") SITES_DOMAIN_NAME = get_env_variable("GVA_DOMAIN_NAME")
SITES_SITE_NAME = env.str("GVA_SITE_NAME") SITES_SITE_NAME = get_env_variable("GVA_SITE_NAME")
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True 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 # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True USE_TZ = True
# ######### END GENERAL CONFIGURATION # ######### END GENERAL CONFIGURATION
@ -81,6 +85,7 @@ USE_TZ = True
LOCALE_PATHS = (normpath(join(SITE_ROOT, "gnuviechadmin", "locale")),) LOCALE_PATHS = (normpath(join(SITE_ROOT, "gnuviechadmin", "locale")),)
# ######### MEDIA CONFIGURATION # ######### MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = normpath(join(SITE_ROOT, "media")) MEDIA_ROOT = normpath(join(SITE_ROOT, "media"))
@ -107,7 +112,7 @@ STATICFILES_FINDERS = (
# ######### SECRET CONFIGURATION # ######### SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key should only be used for development and testing. # Note: This key should only be used for development and testing.
SECRET_KEY = env.str("GVA_SITE_SECRET") SECRET_KEY = get_env_variable("GVA_SITE_SECRET")
# ######### END SECRET CONFIGURATION # ######### END SECRET CONFIGURATION
@ -159,9 +164,9 @@ MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"allauth.account.middleware.AccountMiddleware",
"django.middleware.locale.LocaleMiddleware", "django.middleware.locale.LocaleMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
# uncomment next line to enable translation to browser locale
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
# ######### END MIDDLEWARE CONFIGURATION # ######### END MIDDLEWARE CONFIGURATION
@ -174,6 +179,7 @@ AUTHENTICATION_BACKENDS = (
"allauth.account.auth_backends.AuthenticationBackend", "allauth.account.auth_backends.AuthenticationBackend",
) )
# ######### URL CONFIGURATION # ######### URL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf # See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = "%s.urls" % SITE_NAME ROOT_URLCONF = "%s.urls" % SITE_NAME
@ -201,10 +207,6 @@ DJANGO_APPS = (
# Flatpages for about page # Flatpages for about page
"django.contrib.flatpages", "django.contrib.flatpages",
"crispy_forms", "crispy_forms",
"crispy_bootstrap5",
"impersonate",
"rest_framework",
"rest_framework.authtoken",
) )
ALLAUTH_APPS = ( ALLAUTH_APPS = (
@ -213,6 +215,7 @@ ALLAUTH_APPS = (
"allauth.socialaccount", "allauth.socialaccount",
"allauth.socialaccount.providers.google", "allauth.socialaccount.providers.google",
"allauth.socialaccount.providers.linkedin_oauth2", "allauth.socialaccount.providers.linkedin_oauth2",
"allauth.socialaccount.providers.twitter",
) )
# Apps specific for this project go here. # Apps specific for this project go here.
@ -230,8 +233,6 @@ LOCAL_APPS = (
"userdbs", "userdbs",
"hostingpackages", "hostingpackages",
"websites", "websites",
"help",
"invoices",
"contact_form", "contact_form",
) )
@ -249,38 +250,18 @@ MESSAGE_TAGS = {
# ######### ALLAUTH CONFIGURATION # ######### ALLAUTH CONFIGURATION
ACCOUNT_ADAPTER = "gnuviechadmin.auth.NoNewUsersAccountAdapter"
ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory" ACCOUNT_EMAIL_VERIFICATION = "mandatory"
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
SOCIALACCOUNT_AUTO_SIGNUP = False
SOCIALACCOUNT_QUERY_EMAIL = True SOCIALACCOUNT_QUERY_EMAIL = True
# ######### END ALLAUTH CONFIGURATION # ######### END ALLAUTH CONFIGURATION
# ######### CRISPY FORMS CONFIGURATION # ######### CRISPY FORMS CONFIGURATION
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" CRISPY_TEMPLATE_PACK = "bootstrap3"
CRISPY_TEMPLATE_PACK = "bootstrap5"
# ######### END CRISPY_FORMS CONFIGURATION # ######### 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 # ######### LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging # A sample logging configuration. The only tangible logging
@ -300,45 +281,20 @@ LOGGING = {
}, },
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
"handlers": { "handlers": {
"console": {
"class": "logging.StreamHandler",
},
"logfile": {
"level": "INFO",
"class": "logging.FileHandler",
"filename": env.str("GVA_LOG_FILE", default="gva.log"),
"formatter": "verbose",
},
"mail_admins": { "mail_admins": {
"level": "ERROR", "level": "ERROR",
"filters": ["require_debug_false"], "filters": ["require_debug_false"],
"class": "django.utils.log.AdminEmailHandler", "class": "django.utils.log.AdminEmailHandler",
}, }
},
"root": {
"handlers": ["console"],
"level": "WARNING",
}, },
"loggers": { "loggers": {
"django.request": { "django.request": {
"handlers": ["mail_admins"], "handlers": ["mail_admins"],
"level": "ERROR", "level": "ERROR",
"propagate": True, "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 # ######### END LOGGING CONFIGURATION
@ -349,7 +305,7 @@ WSGI_APPLICATION = "%s.wsgi.application" % SITE_NAME
# ######### CELERY CONFIGURATION # ######### CELERY CONFIGURATION
BROKER_URL = env.str( BROKER_URL = get_env_variable(
"GVA_BROKER_URL", default="amqp://gnuviechadmin:gnuviechadmin@mq/gnuviechadmin" "GVA_BROKER_URL", default="amqp://gnuviechadmin:gnuviechadmin@mq/gnuviechadmin"
) )
BROKER_TRANSPORT_OPTIONS = { BROKER_TRANSPORT_OPTIONS = {
@ -358,7 +314,7 @@ BROKER_TRANSPORT_OPTIONS = {
"interval_step": 0.2, "interval_step": 0.2,
"interval_max": 0.2, "interval_max": 0.2,
} }
CELERY_RESULT_BACKEND = env.str( CELERY_RESULT_BACKEND = get_env_variable(
"GVA_RESULTS_REDIS_URL", default="redis://:gnuviechadmin@redis:6379/0" "GVA_RESULTS_REDIS_URL", default="redis://:gnuviechadmin@redis:6379/0"
) )
CELERY_TASK_RESULT_EXPIRES = None CELERY_TASK_RESULT_EXPIRES = None
@ -372,50 +328,45 @@ CELERY_RESULT_SERIALIZER = "json"
# ######### CUSTOM APP CONFIGURATION # ######### CUSTOM APP CONFIGURATION
OSUSER_MINUID = env.int("GVA_MIN_OS_UID", default=10000) OSUSER_MINUID = get_env_variable("GVA_MIN_OS_UID", int, default=10000)
OSUSER_MINGID = env.int("GVA_MIN_OS_GID", default=10000) OSUSER_MINGID = get_env_variable("GVA_MIN_OS_GID", int, default=10000)
OSUSER_USERNAME_PREFIX = env.str("GVA_OSUSER_PREFIX", default="usr") OSUSER_USERNAME_PREFIX = get_env_variable("GVA_OSUSER_PREFIX", default="usr")
OSUSER_HOME_BASEPATH = env.str("GVA_OSUSER_HOME_BASEPATH", default="/home") OSUSER_HOME_BASEPATH = get_env_variable("GVA_OSUSER_HOME_BASEPATH", default="/home")
OSUSER_DEFAULT_SHELL = env.str("GVA_OSUSER_DEFAULT_SHELL", default="/usr/bin/rssh") OSUSER_DEFAULT_SHELL = get_env_variable(
"GVA_OSUSER_DEFAULT_SHELL", default="/usr/bin/rssh"
)
OSUSER_SFTP_GROUP = "sftponly" OSUSER_SFTP_GROUP = "sftponly"
OSUSER_SSH_GROUP = "sshusers" OSUSER_SSH_GROUP = "sshusers"
OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP] OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP]
OSUSER_UPLOAD_SERVER = env.str("GVA_OSUSER_UPLOADSERVER", default="file") OSUSER_UPLOAD_SERVER = get_env_variable("GVA_OSUSER_UPLOADSERVER", default="file")
GVA_LINK_WEBMAIL = env.str("GVA_WEBMAIL_URL", default="https://webmail.example.org/") GVA_LINK_WEBMAIL = get_env_variable(
GVA_LINK_PHPMYADMIN = env.str( "GVA_WEBMAIL_URL", default="https://webmail.example.org/"
)
GVA_LINK_PHPMYADMIN = get_env_variable(
"GVA_PHPMYADMIN_URL", default="https://phpmyadmin.example.org/" "GVA_PHPMYADMIN_URL", default="https://phpmyadmin.example.org/"
) )
GVA_LINK_PHPPGADMIN = env.str( GVA_LINK_PHPPGADMIN = get_env_variable(
"GVA_PHPPGADMIN_URL", default="https://phppgadmin.example.org/" "GVA_PHPPGADMIN_URL", default="https://phppgadmin.example.org/"
) )
# ######### END CUSTOM APP CONFIGURATION # ######### END CUSTOM APP CONFIGURATION
GVA_ENVIRONMENT = get_env_variable("GVA_ENVIRONMENT", default="prod")
# ######### STATIC FILE CONFIGURATION # ######### STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = env.str("GVA_STATIC_PATH", default=normpath(join(ROOT_DIR, "static"))) STATIC_ROOT = "/srv/gva/static/"
def show_debug_toolbar(request): def show_debug_toolbar(request):
return DEBUG and GVA_ENVIRONMENT == "local" 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": if GVA_ENVIRONMENT == "local":
# ######### DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
# ######### END DEBUG CONFIGURATION # ######### END DEBUG CONFIGURATION
@ -430,6 +381,12 @@ if GVA_ENVIRONMENT == "local":
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}} CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
# ######### END CACHE CONFIGURATION # ######### END CACHE CONFIGURATION
# ######### TOOLBAR CONFIGURATION
# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa
INSTALLED_APPS += ("debug_toolbar",)
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
LOGGING["handlers"].update( LOGGING["handlers"].update(
{ {
"console": { "console": {
@ -443,10 +400,32 @@ if GVA_ENVIRONMENT == "local":
dict( dict(
[ [
(key, {"handlers": ["console"], "level": "DEBUG", "propagate": True}) (key, {"handlers": ["console"], "level": "DEBUG", "propagate": True})
for key in LOCAL_APPS for key in [
], "dashboard",
"domains",
"fileservertasks",
"gvacommon",
"gvawebcore",
"hostingpackages",
"ldaptasks",
"managemails",
"mysqltasks",
"osusers",
"pgsqltasks",
"taskresults",
"userdbs",
"websites",
]
]
) )
) )
DEBUG_TOOLBAR_PATCH_SETTINGS = False
DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": "gnuviechadmin.settings.show_debug_toolbar"
}
# ######### END TOOLBAR CONFIGURATION
elif GVA_ENVIRONMENT == "test": elif GVA_ENVIRONMENT == "test":
ALLOWED_HOSTS = ["localhost"] ALLOWED_HOSTS = ["localhost"]
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
@ -463,15 +442,25 @@ elif GVA_ENVIRONMENT == "test":
dict( dict(
[ [
(key, {"handlers": ["console"], "level": "ERROR", "propagate": True}) (key, {"handlers": ["console"], "level": "ERROR", "propagate": True})
for key in LOCAL_APPS for key in [
"dashboard",
"domains",
"fileservertasks",
"gvacommon",
"gvawebcore",
"hostingpackages",
"ldaptasks",
"managemails",
"mysqltasks",
"osusers",
"pgsqltasks",
"taskresults",
"userdbs",
"websites",
]
] ]
) )
) )
LOGGING["loggers"]["django"] = {
"handlers": ["console"],
"level": "CRITICAL",
"propagate": True,
}
BROKER_URL = BROKER_URL + "_test" BROKER_URL = BROKER_URL + "_test"
CELERY_RESULT_PERSISTENT = False CELERY_RESULT_PERSISTENT = False
else: else:
@ -488,10 +477,12 @@ else:
EMAIL_SUBJECT_PREFIX = "[%s] " % SITE_NAME EMAIL_SUBJECT_PREFIX = "[%s] " % SITE_NAME
# See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email # See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
DEFAULT_FROM_EMAIL = env.str("GVA_SITE_ADMINMAIL", default="admin@example.org") DEFAULT_FROM_EMAIL = get_env_variable(
"GVA_SITE_ADMINMAIL", default="admin@example.org"
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email # See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = env.str("GVA_SITE_ADMINMAIL", default="admin@example.org") SERVER_EMAIL = get_env_variable("GVA_SITE_ADMINMAIL", default="admin@example.org")
# ######### END EMAIL CONFIGURATION # ######### END EMAIL CONFIGURATION
# ######### CACHE CONFIGURATION # ######### CACHE CONFIGURATION

View file

@ -18,16 +18,13 @@ from gnuviechadmin.context_processors import navigation
User = get_user_model() User = get_user_model()
TEST_USER = "test"
TEST_PASSWORD = "secret"
class NavigationContextProcessorTest(TestCase): class NavigationContextProcessorTest(TestCase):
EXPECTED_ITEMS = ("webmail_url", "phpmyadmin_url", "phppgadmin_url", "active_item") EXPECTED_ITEMS = ("webmail_url", "phpmyadmin_url", "phppgadmin_url", "active_item")
def test_ajax_request(self): def test_ajax_request(self):
response = self.client.get("/accounts/login/", HTTP_X_REQUESTED_WITH="XMLHttpRequest") response = self.client.get("/", HTTP_X_REQUESTED_WITH="XMLHttpRequest")
for item in self.EXPECTED_ITEMS: for item in self.EXPECTED_ITEMS:
self.assertNotIn(item, response.context) self.assertNotIn(item, response.context)
@ -37,12 +34,6 @@ class NavigationContextProcessorTest(TestCase):
self.assertEqual(context["phppgadmin_url"], settings.GVA_LINK_PHPPGADMIN) self.assertEqual(context["phppgadmin_url"], settings.GVA_LINK_PHPPGADMIN)
def test_index_page_context(self): 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("/") response = self.client.get("/")
for item in self.EXPECTED_ITEMS: for item in self.EXPECTED_ITEMS:
self.assertIn(item, response.context) self.assertIn(item, response.context)
@ -56,6 +47,15 @@ class NavigationContextProcessorTest(TestCase):
self._check_static_urls(response.context) self._check_static_urls(response.context)
self.assertEqual(response.context["active_item"], "contact") self.assertEqual(response.context["active_item"], "contact")
def test_hostingpackage_page_context(self):
User.objects.create_user("test", password="test")
self.client.login(username="test", password="test")
response = self.client.get(reverse("hosting_packages", kwargs={"user": "test"}))
for item in self.EXPECTED_ITEMS:
self.assertIn(item, response.context)
self._check_static_urls(response.context)
self.assertEqual(response.context["active_item"], "hostingpackage")
def _test_page_context_by_viewmodule(self, viewmodule, expecteditem): def _test_page_context_by_viewmodule(self, viewmodule, expecteditem):
request = HttpRequest() request = HttpRequest()
request.resolver_match = MagicMock() request.resolver_match = MagicMock()
@ -106,6 +106,6 @@ class NavigationContextProcessorTest(TestCase):
class VersionInfoContextProcessorTest(TestCase): class VersionInfoContextProcessorTest(TestCase):
def test_version_info_in_context(self): def test_version_info_in_context(self):
response = self.client.get("/accounts/login/") response = self.client.get("/")
self.assertIn("gnuviechadmin_version", response.context) self.assertIn("gnuviechadmin_version", response.context)
self.assertEqual(response.context["gnuviechadmin_version"], gvaversion) self.assertEqual(response.context["gnuviechadmin_version"], gvaversion)

View file

@ -1,50 +1,36 @@
from __future__ import absolute_import from __future__ import absolute_import
import debug_toolbar from django.conf.urls import include, url
from django.conf.urls import include from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.flatpages import views from django.contrib.flatpages import views
from django.contrib.staticfiles.urls import staticfiles_urlpatterns 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() admin.autodiscover()
urlpatterns = [ urlpatterns = [
path("", include("dashboard.urls")), url(r'', include('dashboard.urls')),
path("api/users/", help_views.ListHelpUserAPIView.as_view()), url(r'^accounts/', include('allauth.urls')),
path( url(r'^database/', include('userdbs.urls')),
"api/users/<int:pk>/", url(r'^domains/', include('domains.urls')),
help_views.HelpUserAPIView.as_view(), url(r'^hosting/', include('hostingpackages.urls')),
name="helpuser-detail", url(r'^website/', include('websites.urls')),
), url(r'^mail/', include('managemails.urls')),
path("api/invoices/", invoice_views.ListInvoiceAPIView.as_view()), url(r'^osuser/', include('osusers.urls')),
path( url(r'^admin/', admin.site.urls),
"api/invoices/<invoice_number>/", url(r'^contact/', include('contact_form.urls')),
invoice_views.InvoiceAPIView.as_view(), url(r'^impressum/$', views.flatpage, {
name="invoice-detail", 'url': '/impressum/'
), }, name='imprint'),
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. # Uncomment the next line to serve media files in dev.
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns() if settings.DEBUG: # pragma: no cover
urlpatterns += [ import debug_toolbar
path("__debug__/", include(debug_toolbar.urls)),
] urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
] + staticfiles_urlpatterns() + urlpatterns

View file

@ -3,10 +3,11 @@ This module defines form classes that can be extended by other gnuviechadmin
apps' forms. apps' forms.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
PASSWORD_MISMATCH_ERROR = _("Passwords don't match") PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
""" """
@ -20,14 +21,11 @@ class PasswordModelFormMixin(forms.Form):
whether both fields contain the same string. whether both fields contain the same string.
""" """
password1 = forms.CharField( password1 = forms.CharField(
label=_("Password"), label=_('Password'), widget=forms.PasswordInput,
widget=forms.PasswordInput,
) )
password2 = forms.CharField( password2 = forms.CharField(
label=_("Password (again)"), label=_('Password (again)'), widget=forms.PasswordInput,
widget=forms.PasswordInput,
) )
def clean_password2(self): def clean_password2(self):
@ -38,8 +36,8 @@ class PasswordModelFormMixin(forms.Form):
:rtype: str or None :rtype: str or None
""" """
password1 = self.cleaned_data.get("password1") password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get("password2") password2 = self.cleaned_data.get('password2')
if password1 and password2 and password1 != password2: if password1 and password2 and password1 != password2:
raise forms.ValidationError(PASSWORD_MISMATCH_ERROR) raise forms.ValidationError(PASSWORD_MISMATCH_ERROR)
return password2 return password2

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gvawebcore\n" "Project-Id-Version: gvawebcore\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-16 22:07+0200\n" "POT-Creation-Date: 2016-01-29 11:04+0100\n"
"PO-Revision-Date: 2023-04-16 18:21+0200\n" "PO-Revision-Date: 2015-01-25 11:49+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n" "Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n" "Language-Team: Jan Dittberner <jan@dittberner.info>\n"
"Language: de\n" "Language: de\n"
@ -16,17 +16,17 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 1.6.10\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
#: gvawebcore/forms.py:11 #: gvawebcore/forms.py:12
msgid "Passwords don't match" msgid "Passwords don't match"
msgstr "Passwörter stimmen nicht überein" msgstr "Passwörter stimmen nicht überein"
#: gvawebcore/forms.py:25 #: gvawebcore/forms.py:25
msgid "Password" msgid "Password"
msgstr "Passwort" msgstr "Passwort: "
#: gvawebcore/forms.py:29 #: gvawebcore/forms.py:28
msgid "Password (again)" msgid "Password (again)"
msgstr "Passwortwiederholung" msgstr "Passwortwiederholung"

View file

@ -2,10 +2,9 @@
This module defines common view code to be used by multiple gnuviechadmin apps. This module defines common view code to be used by multiple gnuviechadmin apps.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from hostingpackages.models import CustomerHostingPackage from hostingpackages.models import CustomerHostingPackage
@ -15,8 +14,7 @@ class HostingPackageAndCustomerMixin(object):
keyword argument 'package'. keyword argument 'package'.
""" """
hosting_package_kwarg = 'package'
hosting_package_kwarg = "package"
"""Keyword argument used to find the hosting package in the URL.""" """Keyword argument used to find the hosting package in the URL."""
hostingpackage = None hostingpackage = None
@ -24,8 +22,8 @@ class HostingPackageAndCustomerMixin(object):
def get_hosting_package(self): def get_hosting_package(self):
if self.hostingpackage is None: if self.hostingpackage is None:
self.hostingpackage = get_object_or_404( 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 return self.hostingpackage
def get_customer_object(self): def get_customer_object(self):

View file

@ -1,22 +0,0 @@
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)

View file

@ -1,8 +0,0 @@
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")

View file

@ -1,36 +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: 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"

View file

@ -1,17 +0,0 @@
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}.")

View file

@ -1,29 +0,0 @@
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}")

View file

@ -1,55 +0,0 @@
# Generated by Django 3.2.18 on 2023-04-16 09:32
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="HelpUser",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"email_address",
models.EmailField(
help_text="Contact email address", max_length=254
),
),
(
"postal_address",
models.TextField(blank=True, help_text="Contact postal address"),
),
(
"offline_account_code",
models.CharField(
default="",
help_text="Offline account reset code",
max_length=36,
),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View file

@ -1,17 +0,0 @@
import uuid
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
class HelpUser(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
email_address = models.EmailField(help_text=_("Contact email address"))
postal_address = models.TextField(help_text=_("Contact postal address"), blank=True)
offline_account_code = models.CharField(
help_text=_("Offline account reset code"), max_length=36, default=""
)
def generate_offline_account_code(self):
self.offline_account_code = str(uuid.uuid4())

View file

@ -1,22 +0,0 @@
"""
Serializers for the REST API
"""
from rest_framework import serializers
from help.models import HelpUser
class HelpUserSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.StringRelatedField()
class Meta:
model = HelpUser
fields = [
"url",
"user",
"email_address",
"postal_address",
"offline_account_code",
]
read_only_fields = ["user", "offline_account_code"]

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,26 +0,0 @@
from rest_framework import generics
from help.models import HelpUser
from help.serializers import HelpUserSerializer
class ListHelpUserAPIView(generics.ListAPIView):
"""
API endpoint that allows user help profile to be viewed or edited.
"""
queryset = (
HelpUser.objects.all().prefetch_related("user").order_by("user__username")
)
serializer_class = HelpUserSerializer
class HelpUserAPIView(generics.RetrieveUpdateAPIView):
"""
API endpoint that allows user help profile to be viewed or edited.
"""
queryset = HelpUser.objects.all()
serializer_class = HelpUserSerializer

View file

@ -2,3 +2,4 @@
This app takes care of hosting packages. This app takes care of hosting packages.
""" """
default_app_config = 'hostingpackages.apps.HostingPackagesAppConfig'

View file

@ -2,7 +2,7 @@
This module contains the admin site interface for hosting packages. This module contains the admin site interface for hosting packages.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
@ -12,7 +12,6 @@ from .models import (
CustomerHostingPackage, CustomerHostingPackage,
CustomerHostingPackageDomain, CustomerHostingPackageDomain,
CustomerMailboxOption, CustomerMailboxOption,
CustomerPackageDiskUsage,
CustomerUserDatabaseOption, CustomerUserDatabaseOption,
DiskSpaceOption, DiskSpaceOption,
HostingPackageTemplate, HostingPackageTemplate,
@ -26,10 +25,9 @@ class CustomerHostingPackageCreateForm(forms.ModelForm):
This is the form class for creating new customer hosting packages. This is the form class for creating new customer hosting packages.
""" """
class Meta: class Meta:
model = CustomerHostingPackage model = CustomerHostingPackage
fields = ["customer", "template", "name"] fields = ['customer', 'template', 'name']
def save(self, **kwargs): def save(self, **kwargs):
""" """
@ -41,11 +39,10 @@ class CustomerHostingPackageCreateForm(forms.ModelForm):
""" """
hostinpackages = CustomerHostingPackage.objects.create_from_template( hostinpackages = CustomerHostingPackage.objects.create_from_template(
customer=self.cleaned_data["customer"], customer=self.cleaned_data['customer'],
template=self.cleaned_data["template"], template=self.cleaned_data['template'],
name=self.cleaned_data["name"], name=self.cleaned_data['name'],
**kwargs **kwargs)
)
return hostinpackages return hostinpackages
def save_m2m(self): def save_m2m(self):
@ -58,7 +55,6 @@ class CustomerDiskSpaceOptionInline(admin.TabularInline):
space options. space options.
""" """
model = CustomerDiskSpaceOption model = CustomerDiskSpaceOption
extra = 0 extra = 0
@ -69,7 +65,6 @@ class CustomerMailboxOptionInline(admin.TabularInline):
mailbox options. mailbox options.
""" """
model = CustomerMailboxOption model = CustomerMailboxOption
extra = 0 extra = 0
@ -80,7 +75,6 @@ class CustomerUserDatabaseOptionInline(admin.TabularInline):
database options. database options.
""" """
model = CustomerUserDatabaseOption model = CustomerUserDatabaseOption
extra = 0 extra = 0
@ -91,41 +85,30 @@ class CustomerHostingPackageDomainInline(admin.TabularInline):
hosting packages. hosting packages.
""" """
model = CustomerHostingPackageDomain model = CustomerHostingPackageDomain
extra = 0 extra = 0
class CustomerPackageDiskUsageInline(admin.TabularInline):
model = CustomerPackageDiskUsage
ordering = ["-used_kb", "source", "item"]
fields = ["source", "item", "used_kb"]
readonly_fields = ["source", "item", "used_kb"]
extra = 0
can_delete = False
def has_add_permission(self, request, obj):
return False
class CustomerHostingPackageAdmin(admin.ModelAdmin): class CustomerHostingPackageAdmin(admin.ModelAdmin):
""" """
This class implements the admin interface for This class implements the admin interface for
:py:class:`CustomerHostingPackage`. :py:class:`CustomerHostingPackage`.
""" """
add_form = CustomerHostingPackageCreateForm add_form = CustomerHostingPackageCreateForm
add_fieldsets = ((None, {"fields": ("customer", "template", "name")}),) add_fieldsets = (
(None, {
'fields': ('customer', 'template', 'name')
}),
)
inlines = [ inlines = [
CustomerDiskSpaceOptionInline, CustomerDiskSpaceOptionInline,
CustomerMailboxOptionInline, CustomerMailboxOptionInline,
CustomerUserDatabaseOptionInline, CustomerUserDatabaseOptionInline,
CustomerHostingPackageDomainInline, CustomerHostingPackageDomainInline,
CustomerPackageDiskUsageInline,
] ]
list_display = ["name", "customer", "osuser"] list_display = ['name', 'customer', 'osuser']
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
""" """
@ -142,16 +125,13 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin):
""" """
defaults = {} defaults = {}
if obj is None: if obj is None:
defaults.update( defaults.update({
{ 'form': self.add_form,
"form": self.add_form, 'fields': admin.options.flatten_fieldsets(self.add_fieldsets),
"fields": admin.options.flatten_fieldsets(self.add_fieldsets), })
}
)
defaults.update(kwargs) defaults.update(kwargs)
return super(CustomerHostingPackageAdmin, self).get_form( return super(CustomerHostingPackageAdmin, self).get_form(
request, obj, **defaults request, obj, **defaults)
)
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
""" """
@ -167,7 +147,7 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin):
""" """
if obj: if obj:
return ["customer", "template"] return ['customer', 'template']
return [] return []

View file

@ -3,8 +3,9 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`hostingpackages` app. :py:mod:`hostingpackages` app.
""" """
from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class HostingPackagesAppConfig(AppConfig): class HostingPackagesAppConfig(AppConfig):
@ -12,6 +13,5 @@ class HostingPackagesAppConfig(AppConfig):
AppConfig for the :py:mod:`hostingpackages` app. AppConfig for the :py:mod:`hostingpackages` app.
""" """
name = 'hostingpackages'
name = "hostingpackages" verbose_name = _('Hosting Packages and Options')
verbose_name = _("Hosting Packages and Options")

View file

@ -2,13 +2,17 @@
This module contains the form classes related to hosting packages. This module contains the form classes related to hosting packages.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from django import forms from django import forms
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import ugettext as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from .models import ( from .models import (
CustomerDiskSpaceOption, CustomerDiskSpaceOption,
@ -24,24 +28,25 @@ class CreateCustomerHostingPackageForm(forms.ModelForm):
a preselected customer. a preselected customer.
""" """
class Meta: class Meta:
model = CustomerHostingPackage model = CustomerHostingPackage
fields = ["template", "name", "description"] fields = ['template', 'name', 'description']
def __init__(self, instance, *args, **kwargs): def __init__(self, instance, *args, **kwargs):
username = kwargs.pop("user") username = kwargs.pop('user')
super(CreateCustomerHostingPackageForm, self).__init__(*args, **kwargs) super(CreateCustomerHostingPackageForm, self).__init__(
self.fields["description"].widget.attrs["rows"] = 2 *args, **kwargs
)
self.fields['description'].widget.attrs['rows'] = 2
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_action = reverse( self.helper.form_action = reverse(
"create_customer_hosting_package", kwargs={"user": username} 'create_customer_hosting_package', kwargs={'user': username}
) )
self.helper.layout = Layout( self.helper.layout = Layout(
"template", 'template',
"name", 'name',
"description", 'description',
Submit("submit", _("Add Hosting Package")), Submit('submit', _('Add Hosting Package')),
) )
@ -50,44 +55,44 @@ class CreateHostingPackageForm(forms.ModelForm):
This form class is used for creating new customer hosting packages. This form class is used for creating new customer hosting packages.
""" """
class Meta: class Meta:
model = CustomerHostingPackage model = CustomerHostingPackage
fields = ["customer", "template", "name", "description"] fields = ['customer', 'template', 'name', 'description']
def __init__(self, instance, *args, **kwargs): def __init__(self, instance, *args, **kwargs):
super(CreateHostingPackageForm, self).__init__(*args, **kwargs) super(CreateHostingPackageForm, self).__init__(
self.fields["description"].widget.attrs["rows"] = 2 *args, **kwargs
)
self.fields['description'].widget.attrs['rows'] = 2
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_action = reverse("create_hosting_package") self.helper.form_action = reverse('create_hosting_package')
self.helper.layout = Layout( self.helper.layout = Layout(
"customer", 'customer',
"template", 'template',
"name", 'name',
"description", 'description',
Submit("submit", _("Add Hosting Package")), Submit('submit', _('Add Hosting Package')),
) )
class AddDiskspaceOptionForm(forms.ModelForm): class AddDiskspaceOptionForm(forms.ModelForm):
class Meta: class Meta:
model = CustomerDiskSpaceOption model = CustomerDiskSpaceOption
fields = ["diskspace", "diskspace_unit"] fields = ['diskspace', 'diskspace_unit']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.hostingpackage = kwargs.pop("hostingpackage") self.hostingpackage = kwargs.pop('hostingpackage')
self.option_template = kwargs.pop("option_template") self.option_template = kwargs.pop('option_template')
super(AddDiskspaceOptionForm, self).__init__(*args, **kwargs) super(AddDiskspaceOptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_action = reverse( self.helper.form_action = reverse(
"add_hosting_option", 'add_hosting_option',
kwargs={ kwargs={
"package": self.hostingpackage.id, 'package': self.hostingpackage.id,
"type": "diskspace", 'type': 'diskspace',
"optionid": self.option_template.id, 'optionid': self.option_template.id,
}, })
) self.helper.add_input(Submit('submit', _('Add disk space option')))
self.helper.add_input(Submit("submit", _("Add disk space option")))
def save(self, commit=True): def save(self, commit=True):
self.instance.hosting_package = self.hostingpackage self.instance.hosting_package = self.hostingpackage
@ -98,22 +103,21 @@ class AddDiskspaceOptionForm(forms.ModelForm):
class AddMailboxOptionForm(forms.ModelForm): class AddMailboxOptionForm(forms.ModelForm):
class Meta: class Meta:
model = CustomerMailboxOption model = CustomerMailboxOption
fields = ["number"] fields = ['number']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.hostingpackage = kwargs.pop("hostingpackage") self.hostingpackage = kwargs.pop('hostingpackage')
self.option_template = kwargs.pop("option_template") self.option_template = kwargs.pop('option_template')
super(AddMailboxOptionForm, self).__init__(*args, **kwargs) super(AddMailboxOptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_action = reverse( self.helper.form_action = reverse(
"add_hosting_option", 'add_hosting_option',
kwargs={ kwargs={
"package": self.hostingpackage.id, 'package': self.hostingpackage.id,
"type": "mailboxes", 'type': 'mailboxes',
"optionid": self.option_template.id, 'optionid': self.option_template.id,
}, })
) self.helper.add_input(Submit('submit', _('Add mailbox option')))
self.helper.add_input(Submit("submit", _("Add mailbox option")))
def save(self, commit=True): def save(self, commit=True):
self.instance.hosting_package = self.hostingpackage self.instance.hosting_package = self.hostingpackage
@ -124,22 +128,21 @@ class AddMailboxOptionForm(forms.ModelForm):
class AddUserDatabaseOptionForm(forms.ModelForm): class AddUserDatabaseOptionForm(forms.ModelForm):
class Meta: class Meta:
model = CustomerUserDatabaseOption model = CustomerUserDatabaseOption
fields = ["number"] fields = ['number']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.hostingpackage = kwargs.pop("hostingpackage") self.hostingpackage = kwargs.pop('hostingpackage')
self.option_template = kwargs.pop("option_template") self.option_template = kwargs.pop('option_template')
super(AddUserDatabaseOptionForm, self).__init__(*args, **kwargs) super(AddUserDatabaseOptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_action = reverse( self.helper.form_action = reverse(
"add_hosting_option", 'add_hosting_option',
kwargs={ kwargs={
"package": self.hostingpackage.id, 'package': self.hostingpackage.id,
"type": "databases", 'type': 'databases',
"optionid": self.option_template.id, 'optionid': self.option_template.id,
}, })
) self.helper.add_input(Submit('submit', _('Add database option')))
self.helper.add_input(Submit("submit", _("Add database option")))
def save(self, commit=True): def save(self, commit=True):
self.instance.hosting_package = self.hostingpackage self.instance.hosting_package = self.hostingpackage

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnuviechadmin hostingpackages\n" "Project-Id-Version: gnuviechadmin hostingpackages\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-23 10:23+0200\n" "POT-Creation-Date: 2016-01-29 11:04+0100\n"
"PO-Revision-Date: 2023-07-23 10:24+0200\n" "PO-Revision-Date: 2015-01-25 15:49+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n" "Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n" "Language-Team: Jan Dittberner <jan@dittberner.info>\n"
"Language: de\n" "Language: de\n"
@ -16,682 +16,221 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 1.6.10\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
#: hostingpackages/apps.py:17 #: hostingpackages/apps.py:17
msgid "Hosting Packages and Options" msgid "Hosting Packages and Options"
msgstr "Hostingpakete und -Optionen" msgstr "Hostingpakete und -Optionen"
#: hostingpackages/forms.py:44 hostingpackages/forms.py:68 #: hostingpackages/forms.py:49 hostingpackages/forms.py:74
msgid "Add Hosting Package" msgid "Add Hosting Package"
msgstr "Hostingpaket anlegen" msgstr "Hostingpaket anlegen"
#: hostingpackages/forms.py:90 #: hostingpackages/forms.py:95
msgid "Add disk space option" msgid "Add disk space option"
msgstr "Speicherplatzoption hinzufügen" msgstr "Speicherplatzoption hinzufügen"
#: hostingpackages/forms.py:116 #: hostingpackages/forms.py:120
msgid "Add mailbox option" msgid "Add mailbox option"
msgstr "Postfachoption hinzufügen" msgstr "Postfachoption hinzufügen"
#: hostingpackages/forms.py:142 #: hostingpackages/forms.py:145
msgid "Add database option" msgid "Add database option"
msgstr "Datenbankoption hinzufügen" msgstr "Datenbankoption hinzufügen"
#: hostingpackages/models.py:22 #: hostingpackages/models.py:31
msgid "MiB" msgid "MiB"
msgstr "MiB" msgstr "MiB"
#: hostingpackages/models.py:22 #: hostingpackages/models.py:32
msgid "GiB" msgid "GiB"
msgstr "GiB" msgstr "GiB"
#: hostingpackages/models.py:22 #: hostingpackages/models.py:33
msgid "TiB" msgid "TiB"
msgstr "TiB" msgstr "TiB"
#: hostingpackages/models.py:28 #: hostingpackages/models.py:45
msgid "description" msgid "description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: hostingpackages/models.py:29 #: hostingpackages/models.py:46
msgid "mailbox count" msgid "mailbox count"
msgstr "Anzahl Postfächer" msgstr "Anzahl Postfächer"
#: hostingpackages/models.py:31 hostingpackages/models.py:60 #: hostingpackages/models.py:48 hostingpackages/models.py:76
msgid "disk space" msgid "disk space"
msgstr "Speicherplatz" msgstr "Speicherplatz"
#: hostingpackages/models.py:31 #: hostingpackages/models.py:48
msgid "disk space for the hosting package" msgid "disk space for the hosting package"
msgstr "Speicherplatz für das Hostingpaket" msgstr "Speicherplatz für das Hostingpaket"
#: hostingpackages/models.py:34 hostingpackages/models.py:62 #: hostingpackages/models.py:50 hostingpackages/models.py:78
msgid "unit of disk space" msgid "unit of disk space"
msgstr "Maßeinheit für den Speicherplatz" msgstr "Maßeinheit für den Speicherplatz"
#: hostingpackages/models.py:45 hostingpackages/models.py:193 #: hostingpackages/models.py:60 hostingpackages/models.py:213
msgid "name" msgid "name"
msgstr "Name" msgstr "Name"
#: hostingpackages/models.py:48 #: hostingpackages/models.py:63
msgid "Hosting package" msgid "Hosting package"
msgstr "Hostingpaket" msgstr "Hostingpaket"
#: hostingpackages/models.py:49 #: hostingpackages/models.py:64
msgid "Hosting packages" msgid "Hosting packages"
msgstr "Hostingpakete" msgstr "Hostingpakete"
#: hostingpackages/models.py:68 #: hostingpackages/models.py:83
msgid "Disk space option" msgid "Disk space option"
msgstr "Speicherplatzoption" msgstr "Speicherplatzoption"
#: hostingpackages/models.py:69 #: hostingpackages/models.py:84
msgid "Disk space options" msgid "Disk space options"
msgstr "Speicherplatzoptionen" msgstr "Speicherplatzoptionen"
#: hostingpackages/models.py:72 #: hostingpackages/models.py:87
#, python-brace-format #, python-brace-format
msgid "Additional disk space {space} {unit}" msgid "Additional disk space {space} {unit}"
msgstr "Zusätzlicher Speicherplatz {space} {unit}" msgstr "Zusätzlicher Speicherplatz {space} {unit}"
#: hostingpackages/models.py:89 #: hostingpackages/models.py:104
msgid "number of databases" msgid "number of databases"
msgstr "Anzahl von Datenbanken" msgstr "Anzahl von Datenbanken"
#: hostingpackages/models.py:90 #: hostingpackages/models.py:106
msgid "database type" msgid "database type"
msgstr "Datenbanktyp" msgstr "Datenbanktyp"
#: hostingpackages/models.py:95 #: hostingpackages/models.py:111
msgid "Database option" msgid "Database option"
msgstr "Datenbankoption" msgstr "Datenbankoption"
#: hostingpackages/models.py:96 #: hostingpackages/models.py:112
msgid "Database options" msgid "Database options"
msgstr "Datenbankoptionen" msgstr "Datenbankoptionen"
#: hostingpackages/models.py:100 #: hostingpackages/models.py:116
#, python-brace-format #, python-brace-format
msgid "{type} database" msgid "{type} database"
msgid_plural "{count} {type} databases" msgid_plural "{count} {type} databases"
msgstr[0] "{type}-Datenbank" msgstr[0] "{type}-Datenbank"
msgstr[1] "{count} {type}-Datenbanken" msgstr[1] "{count} {type}-Datenbanken"
#: hostingpackages/models.py:121 #: hostingpackages/models.py:141
msgid "number of mailboxes" msgid "number of mailboxes"
msgstr "Anzahl von Postfächern" msgstr "Anzahl von Postfächern"
#: hostingpackages/models.py:126 #: hostingpackages/models.py:146
msgid "Mailbox option" msgid "Mailbox option"
msgstr "Postfachoption" msgstr "Postfachoption"
#: hostingpackages/models.py:127 #: hostingpackages/models.py:147
msgid "Mailbox options" msgid "Mailbox options"
msgstr "Postfachoptionen" msgstr "Postfachoptionen"
#: hostingpackages/models.py:131 #: hostingpackages/models.py:151
#, python-brace-format #, python-brace-format
msgid "{count} additional mailbox" msgid "{count} additional mailbox"
msgid_plural "{count} additional mailboxes" msgid_plural "{count} additional mailboxes"
msgstr[0] "{count} zusätzliches Postfach" msgstr[0] "{count} zusätzliches Postfach"
msgstr[1] "{count} zusätzliche Postfächer" msgstr[1] "{count} zusätzliche Postfächer"
#: hostingpackages/models.py:183 #: hostingpackages/models.py:206
msgid "customer" msgid "customer"
msgstr "Kunde" msgstr "Kunde"
#: hostingpackages/models.py:187 #: hostingpackages/models.py:208
msgid "hosting package template" msgid "hosting package template"
msgstr "Hostingpaketvorlage" msgstr "Hostingpaketvorlage"
#: hostingpackages/models.py:189 #: hostingpackages/models.py:210
msgid "The hosting package template that this hosting package is based on" msgid "The hosting package template that this hosting package is based on"
msgstr "Die Hostingpaketvorlage, auf der dieses Hostingpaket aufgebaut ist" msgstr "Die Hostingpaketvorlage, auf der dieses Hostingpaket aufgebaut ist"
#: hostingpackages/models.py:196 #: hostingpackages/models.py:215
msgid "Operating system user" msgid "Operating system user"
msgstr "Betriebssystemnutzer" msgstr "Betriebssystemnutzer"
#: hostingpackages/models.py:206 #: hostingpackages/models.py:222
msgid "customer hosting package" msgid "customer hosting package"
msgstr "Kundenhostingpaket" msgstr "Kundenhostingpaket"
#: hostingpackages/models.py:207 #: hostingpackages/models.py:223
msgid "customer hosting packages" msgid "customer hosting packages"
msgstr "Kundenhostingpakete" msgstr "Kundenhostingpakete"
#: hostingpackages/models.py:210 #: hostingpackages/models.py:226
#, python-brace-format #, python-brace-format
msgid "{name} for {customer}" msgid "{name} for {customer}"
msgstr "{name} für {customer}" msgstr "{name} für {customer}"
#: hostingpackages/models.py:421 hostingpackages/models.py:448 #: hostingpackages/models.py:404 hostingpackages/models.py:426
#: hostingpackages/models.py:513
msgid "hosting package" msgid "hosting package"
msgstr "Hostingpaket" msgstr "Hostingpaket"
#: hostingpackages/models.py:426 #: hostingpackages/models.py:407
msgid "hosting domain" msgid "hosting domain"
msgstr "Hostingdomain" msgstr "Hostingdomain"
#: hostingpackages/models.py:453 #: hostingpackages/models.py:429
msgid "customer hosting option" msgid "customer hosting option"
msgstr "kundenspezifische Hostingoption" msgstr "kundenspezifische Hostingoption"
#: hostingpackages/models.py:454 #: hostingpackages/models.py:430
msgid "customer hosting options" msgid "customer hosting options"
msgstr "kundenspezifische Hostingoptionen" msgstr "kundenspezifische Hostingoptionen"
#: hostingpackages/models.py:466 #: hostingpackages/models.py:442
msgid "disk space option template" msgid "disk space option template"
msgstr "Speicherplatzoptionsvorlage" msgstr "Speicherplatzoptionsvorlage"
#: hostingpackages/models.py:468 #: hostingpackages/models.py:444
msgid "The disk space option template that this disk space option is based on" msgid "The disk space option template that this disk space option is based on"
msgstr "" msgstr ""
"Die Speicherplatzoptionsvorlage auf der diese Speicherplatzoption aufgebaut " "Die Speicherplatzoptionsvorlage auf der diese Speicherplatzoption aufgebaut "
"ist" "ist"
#: hostingpackages/models.py:483 #: hostingpackages/models.py:458
msgid "user database option template" msgid "user database option template"
msgstr "Nutzerdatenbankoptionsvorlage" msgstr "Nutzerdatenbankoptionsvorlage"
#: hostingpackages/models.py:485 #: hostingpackages/models.py:460
msgid "The user database option template that this database option is based on" msgid "The user database option template that this database option is based on"
msgstr "" msgstr ""
"Die Nutzerdatenbankoptionsvorlage auf der diese Datenbankoption aufgebaut ist" "Die Nutzerdatenbankoptionsvorlage auf der diese Datenbankoption aufgebaut ist"
#: hostingpackages/models.py:500 #: hostingpackages/models.py:474
msgid "mailbox option template" msgid "mailbox option template"
msgstr "Postfachoptionsvorlage" msgstr "Postfachoptionsvorlage"
#: hostingpackages/models.py:501 #: hostingpackages/models.py:476
msgid "The mailbox option template that this mailbox option is based on" msgid "The mailbox option template that this mailbox option is based on"
msgstr "Die Postfachoptionsvorlage auf der diese Postfachoption aufgebaut ist" msgstr "Die Postfachoptionsvorlage auf der diese Postfachoption aufgebaut ist"
#: hostingpackages/models.py:514 #: hostingpackages/views.py:60 hostingpackages/views.py:94
msgid "The hosting package"
msgstr "Das Hostingpaket"
#: hostingpackages/models.py:518
msgid "data source"
msgstr "Datenquelle"
#: hostingpackages/models.py:520
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:202
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:42
msgid "Mailbox"
msgstr "Postfach"
#: hostingpackages/models.py:521
msgid "Website"
msgstr "Webauftritt"
#: hostingpackages/models.py:522
msgid "MariaDB database"
msgstr "MariaDB-Datenbank"
#: hostingpackages/models.py:523
msgid "PostgreSQL database"
msgstr "PostgreSQL-Datenbank"
#: hostingpackages/models.py:526
msgid "data item"
msgstr "Dateneinheit"
#: hostingpackages/models.py:528
msgid "space used in KiB"
msgstr "genutzter Platz in KiB"
#: hostingpackages/models.py:532
msgid "mail address"
msgstr "E-Mailadresse"
#: hostingpackages/models.py:533
msgid "Assigned mail address"
msgstr "Zugeordnete E-Mailadresse"
#: hostingpackages/models.py:539
msgid "website"
msgstr "Webauftritt"
#: hostingpackages/models.py:540
msgid "Assigned web site"
msgstr "Zugeordneter Webauftritt"
#: hostingpackages/templates/hostingpackages/add_hosting_option.html:4
#: hostingpackages/templates/hostingpackages/add_hosting_option.html:7
#, python-format
msgid "Add Option to Hosting Package %(package)s of Customer %(full_name)s"
msgstr ""
"Option zum Hostingpaket %(package)s des Kunden %(full_name)s hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:3
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:4
msgid "All hosting packages"
msgstr "Alle Hostingpakete"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:11
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:36
msgid "Name"
msgstr "Name"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:12
msgid "Customer"
msgstr "Kunde"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:13
msgid "OS User"
msgstr "OS-Nutzer"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:14
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:40
#: hostingpackages/views.py:184
msgid "Disk space"
msgstr "Speicherplatz"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:15
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:54
#: hostingpackages/views.py:191
msgid "Mailboxes"
msgstr "Postfächer"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:16
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:239
#: hostingpackages/views.py:198
msgid "Databases"
msgstr "Datenbanken"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:17
msgid "Setup date"
msgstr "Einrichtungsdatum"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:28
#, python-format
msgid ""
"The reserved disk space for your hosting package is %(diskspace)s bytes."
msgstr ""
"Der für Ihr Hostingpaket reservierte Speicherplatz beträgt %(diskspace)s "
"Bytes."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:34
#, python-format
msgid "used %(num)s of %(total)s"
msgstr "used %(num)s of %(total)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:48
msgid "No hosting packages have been setup yet."
msgstr "Es wurden noch keine Hostingpakete eingerichtet."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:51
msgid "Add hosting package"
msgstr "Hostingpaket anlegen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_create.html:3
#, python-format
msgid "Add hosting package for Customer %(full_name)s"
msgstr "Hostingpaket für Kunde %(full_name)s hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_create.html:6
#, python-format
msgid "Add Hosting Package for Customer %(full_name)s"
msgstr "Hosting Paket für Kunde %(full_name)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:6
#, python-format
msgid "Details for your Hosting Package %(package)s"
msgstr "Details zu Ihrem Hostingpaket %(package)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:10
#, python-format
msgid "Details for Hosting Package %(package)s of %(full_name)s"
msgstr "Details zum Hostingpaket %(package)s von %(full_name)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:16
#, python-format
msgid "Details of Hosting Package %(package)s"
msgstr "Details zum Hostingpaket %(package)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:25
msgid "Hosting Package Information"
msgstr "Informationen zum Hostingpaket"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:29
msgid "Edit Hosting Package Information"
msgstr "Informationen zum Hostingpaket ändern"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:38
msgid "Description"
msgstr "Beschreibung"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:43
#, python-format
msgid ""
"You use %(used_space)s of the reserved disk space of %(disk_space)s for your "
"hosting package"
msgstr ""
"Sie nutzen aktuell %(used_space)s des reservierten Speicherplatzes von "
"%(disk_space)s für Ihr Hostingpaket"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:45
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:28
#, python-format
msgid "%(used_space)s of %(disk_space)s (%(space_level_percent)s%%)"
msgstr "%(used_space)s von %(disk_space)s (%(space_level_percent)s%%)"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:47
msgid "Disk usage details"
msgstr "Details zur Speicherplatznutzung"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:47
msgid "Details"
msgstr "Details"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:49
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:32
#, python-format
msgid ""
"The package contributes %(package_space)s the difference comes from disk "
"space options"
msgstr ""
"Das Paket trägt %(package_space)s zur Gesamtgröße bei, der Unterschied "
"ergibt sich aus Speicherplatzoptionen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:56
#, python-format
msgid "%(num)s of %(total)s in use"
msgstr "%(num)s von %(total)s genutzt"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:59
#, python-format
msgid ""
"The package provides %(mailboxcount)s mailboxes the difference comes from "
"mailbox options."
msgstr ""
"Das Paket bietet %(mailboxcount)s Postfächer, der Unterschied ergibt sich "
"durch die Postfachoptionen."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:61
msgid "SFTP username"
msgstr "SFTP-Benutzername"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:62
msgid "SSH/SFTP username"
msgstr "SSH/SFTP-Benutzername"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:65
#, python-format
msgid "There is an SSH public key set for this user."
msgid_plural "There are %(counter)s SSH public keys set for this user."
msgstr[0] "Es wurde ein SSH-Schlüssel für diesen Nutzer hinterlegt."
msgstr[1] "Es wurden %(counter)s SSH-Schlüssel für diesen Nutzer hinterlegt."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:67
msgid "Upload server"
msgstr "Uploadserver"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:75
msgid "Hosting Package Options"
msgstr "Hostingpaketoptionen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:83
msgid "No options booked"
msgstr "Keine Optionen gebucht"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:89
msgid "Add another hosting option"
msgstr "Eine weitere Hostingoption hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:89
msgid "Add option"
msgstr "Option hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:96
msgid "Hosting Package Actions"
msgstr "Aktionen zum Hostingpaket"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:100
msgid "Edit Hosting Package Description"
msgstr "Beschreibung des Hostingpakets bearbeiten"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:100
msgid "Edit description"
msgstr "Beschreibung bearbeiten"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:103
msgid "Set SFTP password"
msgstr "SFTP-Passwort setzen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:104
msgid "Set SSH/SFTP password"
msgstr "SSH/SFTP-Passwort setzen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:107
msgid "Add an SSH public key that can be used as an alternative for password"
msgstr ""
"Einen SSH-Schlüssel, der als Alternative zum Passwort genutzt werden kann, "
"hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:109
msgid "Add SSH public key"
msgstr "SSH-Schlüssel hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:118
msgid "Domains"
msgstr "Domains"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:123
msgid "Domain name"
msgstr "Domainname"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:124
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:203
msgid "Mail addresses"
msgstr "E-Mailadressen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:125
msgid "Websites"
msgstr "Webauftritte"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:126
msgid "Domain actions"
msgstr "Domainaktionen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:127
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:206
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:249
msgid "Actions"
msgstr "Aktionen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:139
msgid "Edit mail address targets"
msgstr "E-Mailadressziele bearbeiten"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:141
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:143
msgid "Delete mail address"
msgstr "E-Mailadresse löschen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:148
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:163
msgid "None"
msgstr "Keine"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:156
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:158
msgid "Delete website"
msgstr "Webauftritt löschen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:169
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:171
msgid "Add mail address"
msgstr "E-Mailadresse hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:176
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:177
msgid "Add website"
msgstr "Webauftritt anlegen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:185
msgid "There are no domains assigned to this hosting package yet."
msgstr "Diesem Paket sind noch keine Domains zugeordnet."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:189
msgid "Add domain"
msgstr "Domain hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:197
msgid "E-Mail-Accounts"
msgstr "E-Mail-Konten"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:204
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:216
msgid "Active"
msgstr "Aktiv"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:205
msgid "Mailbox actions"
msgstr "Postfachaktionen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:217
msgid "inactive"
msgstr "inaktiv"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:220
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:221
msgid "Set mailbox password"
msgstr "Postfachpasswort setzen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:227
msgid "There are no mailboxes assigned to this hosting package yet."
msgstr "Diesem Hostingpaket sind noch keine Postfächer zugeordnet."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:232
msgid "Add mailbox"
msgstr "Postfach hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:244
msgid "Database name"
msgstr "Datenbankname"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:245
msgid "Database user"
msgstr "Datenbanknutzer"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:246
msgid "Database type"
msgstr "Datenbanktyp"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:247
msgid "Type"
msgstr "Typ"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:248
msgid "Database actions"
msgstr "Datenbankaktionen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:260
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:262
msgid "Set database user password"
msgstr "Datenbanknutzerpasswort setzen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:264
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:265
msgid "Delete database"
msgstr "Datenbank löschen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:272
msgid "There are no databases assigned to this hosting package yet."
msgstr "Diesem Hostingpaket sind noch keine Datenbanken zugeordnet."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:277
msgid "Add database"
msgstr "Datenbank hinzufügen"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:6
#, python-format
msgid "Disk usage of your Hosting Package %(package)s"
msgstr "Speicherplatznutzung Ihres Hostingpakets %(package)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:10
#, python-format
msgid "Disk usage of Hosting Package %(package)s of %(full_name)s"
msgstr "Speicherplatznutzung des Hostingpakets %(package)s von %(full_name)s"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:17
#, python-format
msgid ""
"Disk usage of Hosting Package <a href=\"%(package_url)s\">%(package)s</a>"
msgstr ""
"Speicherplatznutzung des Hostingpakets <a "
"href=\"%(package_url)s\">%(package)s</a>"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:23
#, python-format
msgid ""
"You use %(used_space)s of the reserved disk space of %(disk_space)s for your "
"hosting package."
msgstr ""
"Sie nutzen %(used_space)s des reservierten Speicherplatzes von "
"%(disk_space)s für Ihr Hostingpaket."
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:36
msgid "Breakdown by usage"
msgstr "Aufgliederung nach Nutzung"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:38
msgid "Mailbox usage"
msgstr "Postfachnutzung"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:43
msgid "Primary email address"
msgstr "Primäre E-Mailadresse"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:44
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:64
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:83
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:102
msgid "Used space"
msgstr "Genutzter Speicherplatz"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:59
msgid "Website usage"
msgstr "Nutzung für Webauftritte"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:63
msgid "Website / Directory"
msgstr "Webauftritt / Verzeichnis"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:78
msgid "MySQL/MariaDB database usage"
msgstr "MySQL/MariaDB-Datenbanknutzung"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:82
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:101
msgid "Database"
msgstr "Datenbank"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_disk_usage_details.html:97
msgid "PostgreSQL database usage"
msgstr "PostgreSQL-Datenbanknutzung"
#: hostingpackages/templates/hostingpackages/customerhostingpackage_option_choices.html:5
#: hostingpackages/templates/hostingpackages/customerhostingpackage_option_choices.html:8
#, python-format
msgid ""
"Choose new Option for Hosting Package %(package)s of Customer %(full_name)s"
msgstr ""
"Wählen Sie eine neue Option für das Hostingpaket %(package)s des Kunden "
"%(full_name)s"
#: hostingpackages/views.py:63 hostingpackages/views.py:97
#, python-brace-format #, python-brace-format
msgid "Started setup of new hosting package {name}." msgid "Started setup of new hosting package {name}."
msgstr "Einrichtung des Hostingpakets {name} wurde gestartet." msgstr "Einrichtung des Hostingpakets {name} wurde gestartet."
#: hostingpackages/views.py:272 #: hostingpackages/views.py:186
msgid "Disk space"
msgstr "Speicherplatz"
#: hostingpackages/views.py:189
msgid "Mailboxes"
msgstr "Postfächer"
#: hostingpackages/views.py:192
msgid "Databases"
msgstr "Datenbanken"
#: hostingpackages/views.py:262
#, python-brace-format #, python-brace-format
msgid "Successfully added option {option} to hosting package {package}." msgid "Successfully added option {option} to hosting package {package}."
msgstr "Option {option} erfolgreich zum Hostingpaket {package} hinzugefügt." msgstr "Option {option} erfolgreich zum Hostingpaket {package} hinzugefügt."
#~ msgid "Hosting options"
#~ msgstr "Hostingoptionen"

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone import django.utils.timezone
import model_utils.fields import model_utils.fields
from django.conf import settings from django.conf import settings
@ -12,469 +14,301 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name="CustomerHostingPackage", name='CustomerHostingPackage',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
( ('name', models.CharField(
"created", unique=True, max_length=128, verbose_name='name')),
model_utils.fields.AutoCreatedField( ('description', models.TextField(
default=django.utils.timezone.now, verbose_name='description', blank=True)),
verbose_name="created", ('mailboxcount', models.PositiveIntegerField(
editable=False, verbose_name='mailbox count')),
), ('diskspace', models.PositiveIntegerField(
), help_text='disk space for the hosting package',
( verbose_name='disk space')),
"modified", ('diskspace_unit', models.PositiveSmallIntegerField(
model_utils.fields.AutoLastModifiedField( verbose_name='unit of disk space',
default=django.utils.timezone.now, choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
verbose_name="modified", ('customer', models.ForeignKey(
editable=False, verbose_name='customer', to=settings.AUTH_USER_MODEL,
), on_delete=models.CASCADE)),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
(
"customer",
models.ForeignKey(
verbose_name="customer",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
], ],
options={ options={
"verbose_name": "customer hosting package", 'verbose_name': 'customer hosting package',
"verbose_name_plural": "customer hosting packages", 'verbose_name_plural': 'customer hosting packages',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerHostingPackageOption", name='CustomerHostingPackageOption',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
], ],
options={ options={
"verbose_name": "customer hosting option", 'verbose_name': 'customer hosting option',
"verbose_name_plural": "customer hosting options", 'verbose_name_plural': 'customer hosting options',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerDiskSpaceOption", name='CustomerDiskSpaceOption',
fields=[ fields=[
( ('customerhostingpackageoption_ptr', models.OneToOneField(
"customerhostingpackageoption_ptr", parent_link=True, auto_created=True, primary_key=True,
models.OneToOneField( serialize=False,
parent_link=True, to='hostingpackages.CustomerHostingPackageOption',
auto_created=True, on_delete=models.CASCADE)),
primary_key=True, ('diskspace', models.PositiveIntegerField(
serialize=False, verbose_name='disk space')),
to="hostingpackages.CustomerHostingPackageOption", ('diskspace_unit', models.PositiveSmallIntegerField(
on_delete=models.CASCADE, verbose_name='unit of disk space',
), choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
], ],
options={ options={
"ordering": ["diskspace_unit", "diskspace"], 'ordering': ['diskspace_unit', 'diskspace'],
"abstract": False, 'abstract': False,
"verbose_name": "Disk space option", 'verbose_name': 'Disk space option',
"verbose_name_plural": "Disk space options", 'verbose_name_plural': 'Disk space options',
}, },
bases=("hostingpackages.customerhostingpackageoption", models.Model), bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerMailboxOption", name='CustomerMailboxOption',
fields=[ fields=[
( ('customerhostingpackageoption_ptr', models.OneToOneField(
"customerhostingpackageoption_ptr", parent_link=True, auto_created=True, primary_key=True,
models.OneToOneField( serialize=False,
parent_link=True, to='hostingpackages.CustomerHostingPackageOption',
auto_created=True, on_delete=models.CASCADE)),
primary_key=True, ('number', models.PositiveIntegerField(
serialize=False, unique=True, verbose_name='number of mailboxes')),
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
], ],
options={ options={
"ordering": ["number"], 'ordering': ['number'],
"abstract": False, 'abstract': False,
"verbose_name": "Mailbox option", 'verbose_name': 'Mailbox option',
"verbose_name_plural": "Mailbox options", 'verbose_name_plural': 'Mailbox options',
}, },
bases=("hostingpackages.customerhostingpackageoption", models.Model), bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerUserDatabaseOption", name='CustomerUserDatabaseOption',
fields=[ fields=[
( ('customerhostingpackageoption_ptr', models.OneToOneField(
"customerhostingpackageoption_ptr", parent_link=True, auto_created=True, primary_key=True,
models.OneToOneField( serialize=False,
parent_link=True, to='hostingpackages.CustomerHostingPackageOption',
auto_created=True, on_delete=models.CASCADE)),
primary_key=True, ('number', models.PositiveIntegerField(
serialize=False, default=1, verbose_name='number of databases')),
to="hostingpackages.CustomerHostingPackageOption", ('db_type', models.PositiveSmallIntegerField(
on_delete=models.CASCADE, verbose_name='database type',
), choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
], ],
options={ options={
"ordering": ["db_type", "number"], 'ordering': ['db_type', 'number'],
"abstract": False, 'abstract': False,
"verbose_name": "Database option", 'verbose_name': 'Database option',
"verbose_name_plural": "Database options", 'verbose_name_plural': 'Database options',
}, },
bases=("hostingpackages.customerhostingpackageoption", models.Model), bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="HostingOption", name='HostingOption',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
], ],
options={ options={
"verbose_name": "Hosting option", 'verbose_name': 'Hosting option',
"verbose_name_plural": "Hosting options", 'verbose_name_plural': 'Hosting options',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="DiskSpaceOption", name='DiskSpaceOption',
fields=[ fields=[
( ('hostingoption_ptr', models.OneToOneField(
"hostingoption_ptr", parent_link=True, auto_created=True, primary_key=True,
models.OneToOneField( serialize=False, to='hostingpackages.HostingOption',
parent_link=True, on_delete=models.CASCADE)),
auto_created=True, ('diskspace', models.PositiveIntegerField(
primary_key=True, verbose_name='disk space')),
serialize=False, ('diskspace_unit', models.PositiveSmallIntegerField(
to="hostingpackages.HostingOption", verbose_name='unit of disk space',
on_delete=models.CASCADE, choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
], ],
options={ options={
"ordering": ["diskspace_unit", "diskspace"], 'ordering': ['diskspace_unit', 'diskspace'],
"abstract": False, 'abstract': False,
"verbose_name": "Disk space option", 'verbose_name': 'Disk space option',
"verbose_name_plural": "Disk space options", 'verbose_name_plural': 'Disk space options',
}, },
bases=("hostingpackages.hostingoption", models.Model), bases=('hostingpackages.hostingoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="HostingPackageTemplate", name='HostingPackageTemplate',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
( ('name', models.CharField(
"created", unique=True, max_length=128, verbose_name='name')),
model_utils.fields.AutoCreatedField( ('description', models.TextField(
default=django.utils.timezone.now, verbose_name='description', blank=True)),
verbose_name="created", ('mailboxcount', models.PositiveIntegerField(
editable=False, verbose_name='mailbox count')),
), ('diskspace', models.PositiveIntegerField(
), help_text='disk space for the hosting package',
( verbose_name='disk space')),
"modified", ('diskspace_unit', models.PositiveSmallIntegerField(
model_utils.fields.AutoLastModifiedField( verbose_name='unit of disk space',
default=django.utils.timezone.now, choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
verbose_name="modified",
editable=False,
),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
], ],
options={ options={
"verbose_name": "Hosting package", 'verbose_name': 'Hosting package',
"verbose_name_plural": "Hosting packages", 'verbose_name_plural': 'Hosting packages',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="MailboxOption", name='MailboxOption',
fields=[ fields=[
( ('hostingoption_ptr', models.OneToOneField(
"hostingoption_ptr", parent_link=True, auto_created=True, primary_key=True,
models.OneToOneField( serialize=False, to='hostingpackages.HostingOption',
parent_link=True, on_delete=models.CASCADE)),
auto_created=True, ('number', models.PositiveIntegerField(
primary_key=True, unique=True, verbose_name='number of mailboxes')),
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
], ],
options={ options={
"ordering": ["number"], 'ordering': ['number'],
"abstract": False, 'abstract': False,
"verbose_name": "Mailbox option", 'verbose_name': 'Mailbox option',
"verbose_name_plural": "Mailbox options", 'verbose_name_plural': 'Mailbox options',
}, },
bases=("hostingpackages.hostingoption", models.Model), bases=('hostingpackages.hostingoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="UserDatabaseOption", name='UserDatabaseOption',
fields=[ fields=[
( ('hostingoption_ptr', models.OneToOneField(
"hostingoption_ptr", parent_link=True, auto_created=True, primary_key=True,
models.OneToOneField( serialize=False, to='hostingpackages.HostingOption',
parent_link=True, on_delete=models.CASCADE)),
auto_created=True, ('number', models.PositiveIntegerField(
primary_key=True, default=1, verbose_name='number of databases')),
serialize=False, ('db_type', models.PositiveSmallIntegerField(
to="hostingpackages.HostingOption", verbose_name='database type',
on_delete=models.CASCADE, choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
),
),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
], ],
options={ options={
"ordering": ["db_type", "number"], 'ordering': ['db_type', 'number'],
"abstract": False, 'abstract': False,
"verbose_name": "Database option", 'verbose_name': 'Database option',
"verbose_name_plural": "Database options", 'verbose_name_plural': 'Database options',
}, },
bases=("hostingpackages.hostingoption", models.Model), bases=('hostingpackages.hostingoption', models.Model),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="userdatabaseoption", name='userdatabaseoption',
unique_together={("number", "db_type")}, unique_together={('number', 'db_type')},
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="diskspaceoption", name='diskspaceoption',
unique_together={("diskspace", "diskspace_unit")}, unique_together={('diskspace', 'diskspace_unit')},
), ),
migrations.AddField( migrations.AddField(
model_name="customeruserdatabaseoption", model_name='customeruserdatabaseoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="user database option template", verbose_name='user database option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The user database option template that this " help_text='The user database option template that this '
"hosting option is based on", 'hosting option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customeruserdatabaseoption", name='customeruserdatabaseoption',
unique_together={("number", "db_type")}, unique_together={('number', 'db_type')},
), ),
migrations.AddField( migrations.AddField(
model_name="customermailboxoption", model_name='customermailboxoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="mailbox option template", verbose_name='mailbox option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The mailbox option template that this hosting " help_text='The mailbox option template that this hosting '
"option is based on", 'option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name="customerhostingpackageoption", model_name='customerhostingpackageoption',
name="hosting_package", name='hosting_package',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="hosting package", verbose_name='hosting package',
to="hostingpackages.CustomerHostingPackage", to='hostingpackages.CustomerHostingPackage',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="hosting package template", verbose_name='hosting package template',
to="hostingpackages.HostingPackageTemplate", to='hostingpackages.HostingPackageTemplate',
help_text="The hosting package template that this hosting " help_text='The hosting package template that this hosting '
"package is based on.", 'package is based on.',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name="customerdiskspaceoption", model_name='customerdiskspaceoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="disk space option template", verbose_name='disk space option template',
to="hostingpackages.DiskSpaceOption", to='hostingpackages.DiskSpaceOption',
help_text="The disk space option template that this hosting " help_text='The disk space option template that this hosting '
"option is based on", 'option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customerdiskspaceoption", name='customerdiskspaceoption',
unique_together={("diskspace", "diskspace_unit")}, unique_together={('diskspace', 'diskspace_unit')},
), ),
] ]

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone import django.utils.timezone
import model_utils.fields import model_utils.fields
from django.conf import settings from django.conf import settings
@ -6,530 +8,361 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
replaces = [ replaces = [('hostingpackages', '0001_initial'),
("hostingpackages", "0001_initial"), ('hostingpackages', '0002_auto_20150118_1149'),
("hostingpackages", "0002_auto_20150118_1149"), ('hostingpackages', '0003_auto_20150118_1221'),
("hostingpackages", "0003_auto_20150118_1221"), ('hostingpackages', '0004_customerhostingpackage_osuser'),
("hostingpackages", "0004_customerhostingpackage_osuser"), ('hostingpackages', '0005_auto_20150118_1303')]
("hostingpackages", "0005_auto_20150118_1303"),
]
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("osusers", "0004_auto_20150104_1751"), ('osusers', '0004_auto_20150104_1751'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name="CustomerHostingPackage", name='CustomerHostingPackage',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
( ('name', models.CharField(
"created", unique=True, max_length=128, verbose_name='name')),
model_utils.fields.AutoCreatedField( ('description', models.TextField(
default=django.utils.timezone.now, verbose_name='description', blank=True)),
verbose_name="created", ('mailboxcount', models.PositiveIntegerField(
editable=False, verbose_name='mailbox count')),
), ('diskspace', models.PositiveIntegerField(
), help_text='disk space for the hosting package',
( verbose_name='disk space')),
"modified", ('diskspace_unit', models.PositiveSmallIntegerField(
model_utils.fields.AutoLastModifiedField( verbose_name='unit of disk space',
default=django.utils.timezone.now, choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
verbose_name="modified", ('customer', models.ForeignKey(
editable=False, verbose_name='customer',
), to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
(
"customer",
models.ForeignKey(
verbose_name="customer",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
], ],
options={ options={
"verbose_name": "customer hosting package", 'verbose_name': 'customer hosting package',
"verbose_name_plural": "customer hosting packages", 'verbose_name_plural': 'customer hosting packages',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerHostingPackageOption", name='CustomerHostingPackageOption',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
], ],
options={ options={
"verbose_name": "customer hosting option", 'verbose_name': 'customer hosting option',
"verbose_name_plural": "customer hosting options", 'verbose_name_plural': 'customer hosting options',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerDiskSpaceOption", name='CustomerDiskSpaceOption',
fields=[ fields=[
( ('customerhostingpackageoption_ptr',
"customerhostingpackageoption_ptr", models.OneToOneField(
models.OneToOneField( parent_link=True, auto_created=True, primary_key=True,
parent_link=True, serialize=False,
auto_created=True, to='hostingpackages.CustomerHostingPackageOption',
primary_key=True, on_delete=models.CASCADE)),
serialize=False, ('diskspace', models.PositiveIntegerField(
to="hostingpackages.CustomerHostingPackageOption", verbose_name='disk space')),
on_delete=models.CASCADE, ('diskspace_unit', models.PositiveSmallIntegerField(
), verbose_name='unit of disk space',
), choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
], ],
options={ options={
"ordering": ["diskspace_unit", "diskspace"], 'ordering': ['diskspace_unit', 'diskspace'],
"abstract": False, 'abstract': False,
"verbose_name": "Disk space option", 'verbose_name': 'Disk space option',
"verbose_name_plural": "Disk space options", 'verbose_name_plural': 'Disk space options',
}, },
bases=("hostingpackages.customerhostingpackageoption", models.Model), bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerMailboxOption", name='CustomerMailboxOption',
fields=[ fields=[
( ('customerhostingpackageoption_ptr',
"customerhostingpackageoption_ptr", models.OneToOneField(
models.OneToOneField( parent_link=True, auto_created=True, primary_key=True,
parent_link=True, serialize=False,
auto_created=True, to='hostingpackages.CustomerHostingPackageOption',
primary_key=True, on_delete=models.CASCADE)),
serialize=False, ('number', models.PositiveIntegerField(
to="hostingpackages.CustomerHostingPackageOption", unique=True, verbose_name='number of mailboxes')),
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
], ],
options={ options={
"ordering": ["number"], 'ordering': ['number'],
"abstract": False, 'abstract': False,
"verbose_name": "Mailbox option", 'verbose_name': 'Mailbox option',
"verbose_name_plural": "Mailbox options", 'verbose_name_plural': 'Mailbox options',
}, },
bases=("hostingpackages.customerhostingpackageoption", models.Model), bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="CustomerUserDatabaseOption", name='CustomerUserDatabaseOption',
fields=[ fields=[
( ('customerhostingpackageoption_ptr',
"customerhostingpackageoption_ptr", models.OneToOneField(
models.OneToOneField( parent_link=True, auto_created=True, primary_key=True,
parent_link=True, serialize=False,
auto_created=True, to='hostingpackages.CustomerHostingPackageOption',
primary_key=True, on_delete=models.CASCADE)),
serialize=False, ('number', models.PositiveIntegerField(
to="hostingpackages.CustomerHostingPackageOption", default=1, verbose_name='number of databases')),
on_delete=models.CASCADE, ('db_type', models.PositiveSmallIntegerField(
), verbose_name='database type',
), choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
], ],
options={ options={
"ordering": ["db_type", "number"], 'ordering': ['db_type', 'number'],
"abstract": False, 'abstract': False,
"verbose_name": "Database option", 'verbose_name': 'Database option',
"verbose_name_plural": "Database options", 'verbose_name_plural': 'Database options',
}, },
bases=("hostingpackages.customerhostingpackageoption", models.Model), bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="HostingOption", name='HostingOption',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
], ],
options={ options={
"verbose_name": "Hosting option", 'verbose_name': 'Hosting option',
"verbose_name_plural": "Hosting options", 'verbose_name_plural': 'Hosting options',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="DiskSpaceOption", name='DiskSpaceOption',
fields=[ fields=[
( ('hostingoption_ptr',
"hostingoption_ptr", models.OneToOneField(
models.OneToOneField( parent_link=True, auto_created=True, primary_key=True,
parent_link=True, serialize=False, to='hostingpackages.HostingOption',
auto_created=True, on_delete=models.CASCADE)),
primary_key=True, ('diskspace', models.PositiveIntegerField(
serialize=False, verbose_name='disk space')),
to="hostingpackages.HostingOption", ('diskspace_unit', models.PositiveSmallIntegerField(
on_delete=models.CASCADE, verbose_name='unit of disk space',
), choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
], ],
options={ options={
"ordering": ["diskspace_unit", "diskspace"], 'ordering': ['diskspace_unit', 'diskspace'],
"abstract": False, 'abstract': False,
"verbose_name": "Disk space option", 'verbose_name': 'Disk space option',
"verbose_name_plural": "Disk space options", 'verbose_name_plural': 'Disk space options',
}, },
bases=("hostingpackages.hostingoption", models.Model), bases=('hostingpackages.hostingoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="HostingPackageTemplate", name='HostingPackageTemplate',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
( ('name', models.CharField(
"created", unique=True, max_length=128, verbose_name='name')),
model_utils.fields.AutoCreatedField( ('description', models.TextField(
default=django.utils.timezone.now, verbose_name='description', blank=True)),
verbose_name="created", ('mailboxcount', models.PositiveIntegerField(
editable=False, verbose_name='mailbox count')),
), ('diskspace', models.PositiveIntegerField(
), help_text='disk space for the hosting package',
( verbose_name='disk space')),
"modified", ('diskspace_unit', models.PositiveSmallIntegerField(
model_utils.fields.AutoLastModifiedField( verbose_name='unit of disk space',
default=django.utils.timezone.now, choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
verbose_name="modified",
editable=False,
),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
], ],
options={ options={
"verbose_name": "Hosting package", 'verbose_name': 'Hosting package',
"verbose_name_plural": "Hosting packages", 'verbose_name_plural': 'Hosting packages',
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name="MailboxOption", name='MailboxOption',
fields=[ fields=[
( ('hostingoption_ptr',
"hostingoption_ptr", models.OneToOneField(
models.OneToOneField( parent_link=True, auto_created=True, primary_key=True,
parent_link=True, serialize=False, to='hostingpackages.HostingOption',
auto_created=True, on_delete=models.CASCADE)),
primary_key=True, ('number', models.PositiveIntegerField(
serialize=False, unique=True, verbose_name='number of mailboxes')),
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
], ],
options={ options={
"ordering": ["number"], 'ordering': ['number'],
"abstract": False, 'abstract': False,
"verbose_name": "Mailbox option", 'verbose_name': 'Mailbox option',
"verbose_name_plural": "Mailbox options", 'verbose_name_plural': 'Mailbox options',
}, },
bases=("hostingpackages.hostingoption", models.Model), bases=('hostingpackages.hostingoption', models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name="UserDatabaseOption", name='UserDatabaseOption',
fields=[ fields=[
( ('hostingoption_ptr',
"hostingoption_ptr", models.OneToOneField(
models.OneToOneField( parent_link=True, auto_created=True, primary_key=True,
parent_link=True, serialize=False, to='hostingpackages.HostingOption',
auto_created=True, on_delete=models.CASCADE)),
primary_key=True, ('number', models.PositiveIntegerField(
serialize=False, default=1, verbose_name='number of databases')),
to="hostingpackages.HostingOption", ('db_type',
on_delete=models.CASCADE, models.PositiveSmallIntegerField(
), verbose_name='database type',
), choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
], ],
options={ options={
"ordering": ["db_type", "number"], 'ordering': ['db_type', 'number'],
"abstract": False, 'abstract': False,
"verbose_name": "Database option", 'verbose_name': 'Database option',
"verbose_name_plural": "Database options", 'verbose_name_plural': 'Database options',
}, },
bases=("hostingpackages.hostingoption", models.Model), bases=('hostingpackages.hostingoption', models.Model),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="userdatabaseoption", name='userdatabaseoption',
unique_together={("number", "db_type")}, unique_together={('number', 'db_type')},
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="diskspaceoption", name='diskspaceoption',
unique_together={("diskspace", "diskspace_unit")}, unique_together={('diskspace', 'diskspace_unit')},
), ),
migrations.AddField( migrations.AddField(
model_name="customeruserdatabaseoption", model_name='customeruserdatabaseoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="user database option template", verbose_name='user database option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The user database option template that this " help_text='The user database option template that this '
"hosting option is based on", 'hosting option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customeruserdatabaseoption", name='customeruserdatabaseoption',
unique_together={("number", "db_type")}, unique_together={('number', 'db_type')},
), ),
migrations.AddField( migrations.AddField(
model_name="customermailboxoption", model_name='customermailboxoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="mailbox option template", verbose_name='mailbox option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The mailbox option template that this mailbox " help_text='The mailbox option template that this mailbox '
"option is based on", 'option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name="customerhostingpackageoption", model_name='customerhostingpackageoption',
name="hosting_package", name='hosting_package',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="hosting package", verbose_name='hosting package',
to="hostingpackages.CustomerHostingPackage", to='hostingpackages.CustomerHostingPackage',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="hosting package template", verbose_name='hosting package template',
to="hostingpackages.HostingPackageTemplate", to='hostingpackages.HostingPackageTemplate',
help_text="The hosting package template that this hosting " help_text='The hosting package template that this hosting '
"package is based on", 'package is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name="customerdiskspaceoption", model_name='customerdiskspaceoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="disk space option template", verbose_name='disk space option template',
to="hostingpackages.DiskSpaceOption", to='hostingpackages.DiskSpaceOption',
help_text="The disk space option template that this hosting " help_text='The disk space option template that this hosting '
"option is based on", 'option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customerdiskspaceoption", name='customerdiskspaceoption',
unique_together={("diskspace", "diskspace_unit")}, unique_together={('diskspace', 'diskspace_unit')},
), ),
migrations.AlterField( migrations.AlterField(
model_name="customerdiskspaceoption", model_name='customerdiskspaceoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="disk space option template", verbose_name='disk space option template',
to="hostingpackages.DiskSpaceOption", to='hostingpackages.DiskSpaceOption',
help_text="The disk space option template that this disk " help_text='The disk space option template that this disk '
"space option is based on", 'space option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name="customeruserdatabaseoption", model_name='customeruserdatabaseoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="user database option template", verbose_name='user database option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The user database option template that this " help_text='The user database option template that this '
"database option is based on", 'database option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="name", name='name',
field=models.CharField(max_length=128, verbose_name="name"), field=models.CharField(max_length=128, verbose_name='name'),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customerhostingpackage", name='customerhostingpackage',
unique_together={("customer", "name")}, unique_together={('customer', 'name')},
), ),
migrations.AddField( migrations.AddField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="osuser", name='osuser',
field=models.OneToOneField( field=models.OneToOneField(
null=True, null=True, blank=True, to='osusers.User',
blank=True, verbose_name='Operating system user', on_delete=models.CASCADE),
to="osusers.User",
verbose_name="Operating system user",
on_delete=models.CASCADE,
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -1,59 +1,57 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0001_initial"), ('hostingpackages', '0001_initial'),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name="customerdiskspaceoption", model_name='customerdiskspaceoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="disk space option template", verbose_name='disk space option template',
to="hostingpackages.DiskSpaceOption", to='hostingpackages.DiskSpaceOption',
help_text="The disk space option template that this disk " help_text='The disk space option template that this disk '
"space option is based on", 'space option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="hosting package template", verbose_name='hosting package template',
to="hostingpackages.HostingPackageTemplate", to='hostingpackages.HostingPackageTemplate',
help_text="The hosting package template that this hosting " help_text='The hosting package template that this hosting '
"package is based on", 'package is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name="customermailboxoption", model_name='customermailboxoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="mailbox option template", verbose_name='mailbox option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The mailbox option template that this mailbox " help_text='The mailbox option template that this mailbox '
"option is based on", 'option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name="customeruserdatabaseoption", model_name='customeruserdatabaseoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="user database option template", verbose_name='user database option template',
to="hostingpackages.UserDatabaseOption", to='hostingpackages.UserDatabaseOption',
help_text="The user database option template that this " help_text='The user database option template that this '
"database option is based on", 'database option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -1,15 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.db import migrations from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0001_squashed_0005_auto_20150118_1303"), ('hostingpackages', '0001_squashed_0005_auto_20150118_1303'),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="hostingoption", name='hostingoption',
options={}, options={},
), ),
] ]

View file

@ -1,21 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.db import migrations, models from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0002_auto_20150118_1149"), ('hostingpackages', '0002_auto_20150118_1149'),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="name", name='name',
field=models.CharField(max_length=128, verbose_name="name"), field=models.CharField(max_length=128, verbose_name='name'),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customerhostingpackage", name='customerhostingpackage',
unique_together=set([("customer", "name")]), unique_together=set([('customer', 'name')]),
), ),
] ]

View file

@ -1,23 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0002_auto_20150118_1319"), ('hostingpackages', '0002_auto_20150118_1319'),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name="customermailboxoption", model_name='customermailboxoption',
name="template", name='template',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="mailbox option template", verbose_name='mailbox option template',
to="hostingpackages.MailboxOption", to='hostingpackages.MailboxOption',
help_text="The mailbox option template that this mailbox " help_text='The mailbox option template that this mailbox '
"option is based on", 'option is based on',
on_delete=models.CASCADE, on_delete=models.CASCADE),
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -1,24 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("osusers", "0004_auto_20150104_1751"), ('osusers', '0004_auto_20150104_1751'),
("hostingpackages", "0003_auto_20150118_1221"), ('hostingpackages', '0003_auto_20150118_1221'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="osuser", name='osuser',
field=models.ForeignKey( field=models.ForeignKey(
verbose_name="Operating system user", verbose_name='Operating system user', blank=True,
blank=True, to='osusers.User', null=True, on_delete=models.CASCADE),
to="osusers.User",
null=True,
on_delete=models.CASCADE,
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone import django.utils.timezone
import model_utils.fields import model_utils.fields
from django.db import migrations, models from django.db import migrations, models
@ -6,59 +8,33 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("domains", "0002_auto_20150124_1909"), ('domains', '0002_auto_20150124_1909'),
("hostingpackages", "0003_auto_20150118_1407"), ('hostingpackages', '0003_auto_20150118_1407'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name="CustomerHostingPackageDomain", name='CustomerHostingPackageDomain',
fields=[ fields=[
( ('id', models.AutoField(
"id", verbose_name='ID', serialize=False, auto_created=True,
models.AutoField( primary_key=True)),
verbose_name="ID", ('created', model_utils.fields.AutoCreatedField(
serialize=False, default=django.utils.timezone.now, verbose_name='created',
auto_created=True, editable=False)),
primary_key=True, ('modified', model_utils.fields.AutoLastModifiedField(
), default=django.utils.timezone.now, verbose_name='modified',
), editable=False)),
( ('domain', models.OneToOneField(
"created", verbose_name='hosting domain', to='domains.HostingDomain',
model_utils.fields.AutoCreatedField( on_delete=models.CASCADE)),
default=django.utils.timezone.now, ('hosting_package', models.ForeignKey(
verbose_name="created", related_name='domains', verbose_name='hosting package',
editable=False, to='hostingpackages.CustomerHostingPackage',
), on_delete=models.CASCADE)),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"domain",
models.OneToOneField(
verbose_name="hosting domain",
to="domains.HostingDomain",
on_delete=models.CASCADE,
),
),
(
"hosting_package",
models.ForeignKey(
related_name="domains",
verbose_name="hosting package",
to="hostingpackages.CustomerHostingPackage",
on_delete=models.CASCADE,
),
),
], ],
options={ options={
"abstract": False, 'abstract': False,
}, },
bases=(models.Model,), bases=(models.Model,),
), ),

View file

@ -1,23 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0004_customerhostingpackage_osuser"), ('hostingpackages', '0004_customerhostingpackage_osuser'),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name="customerhostingpackage", model_name='customerhostingpackage',
name="osuser", name='osuser',
field=models.OneToOneField( field=models.OneToOneField(
null=True, null=True, blank=True, to='osusers.User',
blank=True, verbose_name='Operating system user', on_delete=models.CASCADE),
to="osusers.User",
verbose_name="Operating system user",
on_delete=models.CASCADE,
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -1,19 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.db import migrations from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0004_customerhostingpackagedomain"), ('hostingpackages', '0004_customerhostingpackagedomain'),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="diskspaceoption", name='diskspaceoption',
options={}, options={},
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customerdiskspaceoption", name='customerdiskspaceoption',
unique_together=set([]), unique_together=set([]),
), ),
] ]

View file

@ -1,19 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.db import migrations from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("hostingpackages", "0005_auto_20150125_1508"), ('hostingpackages', '0005_auto_20150125_1508'),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="userdatabaseoption", name='userdatabaseoption',
options={}, options={},
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="customeruserdatabaseoption", name='customeruserdatabaseoption',
unique_together=set([]), unique_together=set([]),
), ),
] ]

View file

@ -1,74 +0,0 @@
# Generated by Django 4.2.3 on 2023-07-22 17:31
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("hostingpackages", "0006_auto_20150125_1510"),
]
operations = [
migrations.CreateModel(
name="CustomerPackageDiskUsage",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"source",
models.CharField(
choices=[
("disk", "disk"),
("mysql", "mysql"),
("pgsql", "pgsql"),
],
verbose_name="data source",
),
),
("item", models.CharField(verbose_name="data item")),
(
"used_kb",
models.PositiveBigIntegerField(
default=0, verbose_name="space used in KiB"
),
),
(
"package",
models.ForeignKey(
help_text="The hosting package",
on_delete=django.db.models.deletion.CASCADE,
to="hostingpackages.customerhostingpackage",
verbose_name="hosting package",
),
),
],
options={
"unique_together": {("package", "source", "item")},
},
),
]

View file

@ -1,53 +0,0 @@
# Generated by Django 4.2.3 on 2023-07-23 07:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("managemails", "0004_auto_20150125_1825"),
("websites", "0001_initial"),
("hostingpackages", "0007_add_disk_usage_table"),
]
operations = [
migrations.AddField(
model_name="customerpackagediskusage",
name="email_address",
field=models.ForeignKey(
help_text="Assigned mail address",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="managemails.mailaddress",
verbose_name="mail address",
),
),
migrations.AddField(
model_name="customerpackagediskusage",
name="website",
field=models.ForeignKey(
help_text="Assigned web site",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="websites.website",
verbose_name="website",
),
),
migrations.RunSQL(
"DELETE FROM hostingpackages_customerpackagediskusage WHERE source='disk'"
),
migrations.AlterField(
model_name="customerpackagediskusage",
name="source",
field=models.CharField(
choices=[
("mail", "Mailbox"),
("web", "Website"),
("mysql", "MariaDB database"),
("pgsql", "PostgreSQL database"),
],
verbose_name="data source",
),
),
]

View file

@ -2,21 +2,21 @@
This module contains the hosting package models. This module contains the hosting package models.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.db import transaction
from django.db import models, transaction from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ngettext from django.utils.translation import ugettext_lazy as _, ungettext
from model_utils import Choices from model_utils import Choices
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from domains.models import HostingDomain from domains.models import HostingDomain
from managemails.models import Mailbox from managemails.models import Mailbox
from osusers.models import AdditionalGroup, Group from osusers.models import AdditionalGroup, Group, User as OsUser
from osusers.models import User as OsUser
from userdbs.models import DB_TYPES, UserDatabase from userdbs.models import DB_TYPES, UserDatabase
DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("TiB"))) DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("TiB")))
@ -24,6 +24,7 @@ DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("T
DISK_SPACE_FACTORS = ((1, None, None), (1024, 1, None), (1024 * 1024, 1024, 1)) DISK_SPACE_FACTORS = ((1, None, None), (1024, 1, None), (1024 * 1024, 1024, 1))
@python_2_unicode_compatible
class HostingPackageBase(TimeStampedModel): class HostingPackageBase(TimeStampedModel):
description = models.TextField(_("description"), blank=True) description = models.TextField(_("description"), blank=True)
mailboxcount = models.PositiveIntegerField(_("mailbox count")) mailboxcount = models.PositiveIntegerField(_("mailbox count"))
@ -56,6 +57,7 @@ class HostingOption(TimeStampedModel):
""" """
@python_2_unicode_compatible
class DiskSpaceOptionBase(models.Model): class DiskSpaceOptionBase(models.Model):
diskspace = models.PositiveIntegerField(_("disk space")) diskspace = models.PositiveIntegerField(_("disk space"))
diskspace_unit = models.PositiveSmallIntegerField( diskspace_unit = models.PositiveSmallIntegerField(
@ -85,6 +87,7 @@ class DiskSpaceOption(DiskSpaceOptionBase, HostingOption):
unique_together = ["diskspace", "diskspace_unit"] unique_together = ["diskspace", "diskspace_unit"]
@python_2_unicode_compatible
class UserDatabaseOptionBase(models.Model): class UserDatabaseOptionBase(models.Model):
number = models.PositiveIntegerField(_("number of databases"), default=1) number = models.PositiveIntegerField(_("number of databases"), default=1)
db_type = models.PositiveSmallIntegerField(_("database type"), choices=DB_TYPES) db_type = models.PositiveSmallIntegerField(_("database type"), choices=DB_TYPES)
@ -96,7 +99,7 @@ class UserDatabaseOptionBase(models.Model):
verbose_name_plural = _("Database options") verbose_name_plural = _("Database options")
def __str__(self): def __str__(self):
return ngettext( return ungettext(
"{type} database", "{count} {type} databases", self.number "{type} database", "{count} {type} databases", self.number
).format(type=self.get_db_type_display(), count=self.number) ).format(type=self.get_db_type_display(), count=self.number)
@ -112,6 +115,7 @@ class UserDatabaseOption(UserDatabaseOptionBase, HostingOption):
unique_together = ["number", "db_type"] unique_together = ["number", "db_type"]
@python_2_unicode_compatible
class MailboxOptionBase(models.Model): class MailboxOptionBase(models.Model):
""" """
Base class for mailbox options. Base class for mailbox options.
@ -127,7 +131,7 @@ class MailboxOptionBase(models.Model):
verbose_name_plural = _("Mailbox options") verbose_name_plural = _("Mailbox options")
def __str__(self): def __str__(self):
return ngettext( return ungettext(
"{count} additional mailbox", "{count} additional mailboxes", self.number "{count} additional mailbox", "{count} additional mailboxes", self.number
).format(count=self.number) ).format(count=self.number)
@ -173,6 +177,7 @@ class CustomerHostingPackageManager(models.Manager):
return package return package
@python_2_unicode_compatible
class CustomerHostingPackage(HostingPackageBase): class CustomerHostingPackage(HostingPackageBase):
""" """
This class defines customer specific hosting packages. This class defines customer specific hosting packages.
@ -264,38 +269,11 @@ class CustomerHostingPackage(HostingPackageBase):
) + option.diskspace ) + option.diskspace
min_unit = option.diskspace_unit min_unit = option.diskspace_unit
if unit is None: if unit is None:
return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024**2 return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024 ** 2
if unit > min_unit: if unit > min_unit:
return DISK_SPACE_FACTORS[unit][min_unit] * diskspace return DISK_SPACE_FACTORS[unit][min_unit] * diskspace
return DISK_SPACE_FACTORS[min_unit][unit] * diskspace return DISK_SPACE_FACTORS[min_unit][unit] * diskspace
disk_space = property(get_disk_space)
def get_used_disk_space_sum(self, unit=None):
"""
Get the used disk space of this hosting package from submitted disk space statistics.
:param unit: value from :py:data:`DISK_SPACE_UNITS` or :py:const:`None`
:return: disk space in unit or bytes (if parameter unit is :py:const:`None`)
:rtype: int
"""
sum = 0
for usage in self.customerpackagediskusage_set.all():
sum += usage.size_in_bytes
if unit is None:
return sum
return DISK_SPACE_FACTORS[0][unit] * sum
used_disk_space_sum = property(get_used_disk_space_sum)
def get_space_level(self):
if not self.diskspace:
return 100.0
return self.used_disk_space_sum / self.disk_space * 100.0
space_level = property(get_space_level)
def get_package_space(self, unit=None): def get_package_space(self, unit=None):
""" """
Get the total disk space reserved for this package without looking at Get the total disk space reserved for this package without looking at
@ -309,7 +287,7 @@ class CustomerHostingPackage(HostingPackageBase):
""" """
if unit is None: if unit is None:
return ( return (
DISK_SPACE_FACTORS[self.diskspace_unit][0] * self.diskspace * 1024**2 DISK_SPACE_FACTORS[self.diskspace_unit][0] * self.diskspace * 1024 ** 2
) )
if unit > self.diskspace_unit: if unit > self.diskspace_unit:
return DISK_SPACE_FACTORS[unit][self.diskspace_unit] * self.diskspace return DISK_SPACE_FACTORS[unit][self.diskspace_unit] * self.diskspace
@ -398,17 +376,13 @@ class CustomerHostingPackage(HostingPackageBase):
self.copy_template_attributes() self.copy_template_attributes()
self.osuser = OsUser.objects.create_user(self.customer) self.osuser = OsUser.objects.create_user(self.customer)
for group in settings.OSUSER_DEFAULT_GROUPS: for group in settings.OSUSER_DEFAULT_GROUPS:
try: AdditionalGroup.objects.create(
AdditionalGroup.objects.create( user=self.osuser, group=Group.objects.get(groupname=group)
user=self.osuser, group=Group.objects.get(groupname=group) )
)
except Group.DoesNotExist as e:
raise ImproperlyConfigured(
f"group {group} has not been defined"
) from e
return super(CustomerHostingPackage, self).save(*args, **kwargs) return super(CustomerHostingPackage, self).save(*args, **kwargs)
@python_2_unicode_compatible
class CustomerHostingPackageDomain(TimeStampedModel): class CustomerHostingPackageDomain(TimeStampedModel):
""" """
This class defines the relationship from a hosting package to a hosting This class defines the relationship from a hosting package to a hosting
@ -501,53 +475,3 @@ class CustomerMailboxOption(MailboxOptionBase, CustomerHostingPackageOption):
help_text=_("The mailbox option template that this mailbox option is based on"), help_text=_("The mailbox option template that this mailbox option is based on"),
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
class CustomerPackageDiskUsage(TimeStampedModel):
"""
This class represents disk usage statistics for a customer hosting package.
"""
package = models.ForeignKey(
CustomerHostingPackage,
verbose_name=_("hosting package"),
help_text=_("The hosting package"),
on_delete=models.CASCADE,
)
source = models.CharField(
verbose_name=_("data source"),
choices=(
("mail", _("Mailbox")),
("web", _("Website")),
("mysql", _("MariaDB database")),
("pgsql", _("PostgreSQL database")),
),
)
item = models.CharField(verbose_name=_("data item"))
used_kb = models.PositiveBigIntegerField(
verbose_name=_("space used in KiB"), default=0
)
email_address = models.ForeignKey(
"managemails.MailAddress",
verbose_name=_("mail address"),
help_text=_("Assigned mail address"),
on_delete=models.CASCADE,
null=True,
)
website = models.ForeignKey(
"websites.Website",
verbose_name=_("website"),
help_text=_("Assigned web site"),
on_delete=models.CASCADE,
null=True,
)
class Meta:
unique_together = ("package", "source", "item")
def __str__(self):
return "%s %s = %d KiB" % (self.source, self.item, self.used_kb)
@property
def size_in_bytes(self):
return self.used_kb * 1024

View file

@ -1,7 +0,0 @@
from rest_framework import serializers
from hostingpackages.models import CustomerPackageDiskUsage
class DiskUsageSerializer(serializers.Serializer):
user = serializers.CharField()

View file

@ -1,13 +0,0 @@
{% extends "hostingpackages/base.html" %}
{% load i18n crispy_forms_tags %}
{% block title %}{{ block.super }} -
{% blocktranslate with package=hostingpackage.name full_name=customer.get_full_name trimmed %}
Add Option to Hosting Package {{ package }} of Customer {{ full_name }}
{% endblocktranslate %}{% endblock title %}
{% block page_title %}{% blocktranslate with package=hostingpackage.name full_name=customer.get_full_name trimmed %}
Add Option to Hosting Package {{ package }} of Customer {{ full_name }}
{% endblocktranslate %}{% endblock page_title %}
{% block content %}
{% crispy form %}
{% endblock content %}

View file

@ -1,53 +0,0 @@
{% extends "hostingpackages/base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} - {% translate "All hosting packages" %}{% endblock title %}
{% block page_title %}{% translate "All hosting packages" %}{% endblock page_title %}
{% block content %}
{% if customerhostingpackage_list %}
<table class="table table-condensed">
<thead>
<tr>
<th>{% translate "Name" %}</th>
<th>{% translate "Customer" %}</th>
<th>{% translate "OS User" %}</th>
<th>{% translate "Disk space" %}</th>
<th>{% translate "Mailboxes" %}</th>
<th>{% translate "Databases" %}</th>
<th>{% translate "Setup date" %}</th>
</tr>
</thead>
<tbody>
{% for package in customerhostingpackage_list %}
<tr>
<td><a href="{{ package.get_absolute_url }}">{{ package.name }}</a></td>
<td>{{ package.customer }}</td>
<td>{{ package.osuser.username }}</td>
<td>
{% with diskspace=package.get_disk_space %}
<span title="{% blocktranslate trimmed %}
The reserved disk space for your hosting package is {{ diskspace }} bytes.
{% endblocktranslate %}">{{ diskspace|filesizeformat }}</span>
{% endwith %}
</td>
<td>
{% blocktranslate with num=package.used_mailbox_count total=package.mailbox_count trimmed %}
used {{ num }} of {{ total }}
{% endblocktranslate %}</td>
<td>{% for dbtype in package.get_databases %}
{{ dbtype.number }}
{% include "userdbs/snippets/db_type.html" with db_type=dbtype.db_type %}
{% if not forloop.last %} / {% endif %}
{% endfor %}</td>
<td>{{ package.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-info">{% translate "No hosting packages have been setup yet." %}</p>
{% endif %}
<p>
<a href="{% url 'create_hosting_package' %}" class="btn btn-primary">{% translate "Add hosting package" %}</a>
</p>
{% endblock content %}

View file

@ -1,12 +0,0 @@
{% extends "hostingpackages/base.html" %}
{% load i18n crispy_forms_tags %}
{% block title %}{{ block.super }} - {% blocktranslate with full_name=customer.get_full_name trimmed %}
Add hosting package for Customer {{ full_name }}
{% endblocktranslate %}{% endblock title %}
{% block page_title %}{% blocktranslate with full_name=customer.get_full_name trimmed %}
Add Hosting Package for Customer {{ full_name }}
{% endblocktranslate %}{% endblock page_title %}
{% block content %}
{% crispy form %}
{% endblock content %}

View file

@ -1,282 +0,0 @@
{% extends "hostingpackages/base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} - {% spaceless %}
{% if user == customer %}
{% blocktranslate with package=hostingpackage.name trimmed %}
Details for your Hosting Package {{ package }}
{% endblocktranslate %}
{% else %}
{% blocktranslate with package=hostingpackage.name full_name=customer.get_full_name trimmed %}
Details for Hosting Package {{ package }} of {{ full_name }}
{% endblocktranslate %}
{% endif %}
{% endspaceless %}{% endblock title %}
{% block page_title %}{% blocktranslate with package=hostingpackage.name trimmed %}
Details of Hosting Package {{ package }}
{% endblocktranslate %}{% endblock page_title %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
<div class="col">
<div class="card">
<div class="card-header">
{% translate "Hosting Package Information" %}
{% if user.is_staff %}
<div class="float-end">
<a class="panel-title" href="#"
title="{% translate "Edit Hosting Package Information" %}"><i
class="bi-gear"></i></a>
</div>
{% endif %}
</div>
<div class="card-body">
<dl class="dl-horizontal">
<dt>{% translate "Name" %}</dt>
<dd>{{ hostingpackage.name }}</dd>
<dt>{% translate "Description" %}</dt>
<dd>{{ hostingpackage.description|default:"-" }}</dd>
<dt>{% translate "Disk space" %}</dt>
{% with used_space=hostingpackage.get_used_disk_space_sum|filesizeformat disk_space=hostingpackage.get_disk_space|filesizeformat package_space=hostingpackage.get_package_space|filesizeformat space_level=hostingpackage.space_level %}
<dd>
<span title="{% blocktranslate trimmed %}
You use {{ used_space }} of the reserved disk space of {{ disk_space }} for your hosting package
{% endblocktranslate %}" class="text-{% if space_level > 90.0 %}danger{% elif space_level > 80.0 %}warning{% else %}success{% endif %}">{% blocktranslate with space_level_percent=space_level|floatformat:1 trimmed%}
{{ used_space }} of {{ disk_space }} ({{ space_level_percent }}%)
{% endblocktranslate %} <a title="{% translate "Disk usage details" %}" href="{% url "disk_usage_details" user=hostingpackage.customer.username package=hostingpackage.id %}">{% translate "Details" %}</a></span>
<i class="bi-info-circle"
title="{% blocktranslate trimmed %}
The package contributes {{ package_space }} the difference comes from disk space options
{% endblocktranslate %}"></i>
</dd>
{% endwith %}
<dt>{% translate "Mailboxes" %}</dt>
<dd>
{% blocktranslate with num=hostingpackage.used_mailbox_count total=hostingpackage.mailbox_count trimmed %}
{{ num }} of {{ total }} in use{% endblocktranslate %}
<i class="bi-info-circle"
title="{% blocktranslate with mailboxcount=hostingpackage.mailboxcount trimmed %}The package provides {{ mailboxcount }} mailboxes the difference comes from mailbox options.{% endblocktranslate %}"></i>
</dd>
<dt>{% if osuser.is_sftp_user %}{% translate "SFTP username" %}{% else %}
{% translate "SSH/SFTP username" %}{% endif %}</dt>
<dd>{{ osuser.username }}{% if sshkeys %}
<a href="{% url 'list_ssh_keys' package=hostingpackage.id %}" class="badge"
title="{% blocktranslate count counter=sshkeys|length trimmed %}There is an SSH public key set for this user.{% plural %}There are {{ counter }} SSH public keys set for this user.{% endblocktranslate %}"><i
class="bi-key"></i> {{ sshkeys|length }}</a>{% endif %}</dd>
<dt>{% translate "Upload server" %}</dt>
<dd>{{ uploadserver }}</dd>
</dl>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header">{% translate "Hosting Package Options" %}</div>
{% if hostingoptions %}
<ul class="list-group list-group-flush">
{% for opt in hostingoptions %}
<li class="list-group-item">{{ opt }}</li>
{% endfor %}
</ul>
{% else %}
<div class="card-body text-info">{% translate "No options booked" %}</div>
{% endif %}
{% if user.is_staff %}
<div class="card-footer"><a
class="btn btn-primary"
href="{% url 'hosting_option_choices' pk=hostingpackage.id %}"
title="{% translate "Add another hosting option" %}">{% translate "Add option" %}</a>
</div>
{% endif %}
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header">{% translate "Hosting Package Actions" %}</div>
<ul class="list-group list-group-flush">
<li class="list-group-item"><a
href="#"
title="{% translate "Edit Hosting Package Description" %}">{% translate "Edit description" %}</a>
</li>
<li class="list-group-item"><a href="{% url "set_osuser_password" slug=osuser.username %}">
{% if osuser.is_sftp_user %}{% translate "Set SFTP password" %}{% else %}
{% translate "Set SSH/SFTP password" %}{% endif %}</a></li>
<li class="list-group-item"><a
href="{% url "add_ssh_key" package=hostingpackage.id %}"
title="{% blocktranslate trimmed %}
Add an SSH public key that can be used as an alternative for password
{% endblocktranslate %}">{% translate "Add SSH public key" %}</a>
</li>
</ul>
</div>
</div>
</div>
<div class="row row-cols-1 mt-4">
<div class="col">
<div class="card">
<div class="card-header">{% translate "Domains" %}</div>
{% if domains %}
<table class="table table-condensed">
<thead>
<tr>
<th class="name-column">{% translate "Domain name" %}</th>
<th>{% translate "Mail addresses" %}</th>
<th>{% translate "Websites" %}</th>
<th title="{% translate "Domain actions" %}" class="actions-column"><span
class="visually-hidden">{% translate "Actions" %}</span></th>
</tr>
</thead>
<tbody>
{% for domain in domains %}
<tr>
<td>{{ domain.domain }}</td>
{% if domain.domain.maildomain.mailaddress_set.exists %}
<td>
{% with maildomain=domain.domain.maildomain %}
{% for mailaddress in maildomain.mailaddresses %}{% spaceless %}
<a href="{% url 'edit_mailaddress' package=hostingpackage.id domain=maildomain.domain pk=mailaddress.id %}"
title="{% translate "Edit mail address targets" %}">{{ mailaddress }}</a>
<a href="{% url 'delete_mailaddress' package=hostingpackage.id domain=maildomain.domain pk=mailaddress.id %}"
title="{% translate "Delete mail address" %}"><i
class="bi-trash"></i><span
class="visually-hidden"> {% translate "Delete mail address" %}</span></a>
{% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endwith %}
</td>
{% else %}
<td class="text-info">{% translate "None" %}</td>
{% endif %}
{% if domain.domain.website_set.exists %}
<td>
{% with domain=domain.domain %}
{% for website in domain.website_set.all %}{% spaceless %}
{{ website }}
<a href="{% url 'delete_website' package=hostingpackage.id domain=domain.domain pk=website.id %}"
titel="{% translate "Delete website" %}"><i
class="bi-trash"></i><span
class="visually-hidden"> {% translate "Delete website" %}</span></a>
{% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endwith %}
</td>
{% else %}
<td class="text-info">{% translate "None" %}</td>
{% endif %}
<td>
{% if domain.domain.maildomain %}
{% with maildomain=domain.domain.maildomain %}
<a href="{% url 'add_mailaddress' package=hostingpackage.id domain=maildomain.domain %}"
title="{% translate "Add mail address" %}"><i
class="bi-envelope-plus"></i><span
class="visually-hidden"> {% translate "Add mail address" %}</span></a>
{% endwith %}
{% endif %}
{% with hostingdomain=domain.domain %}
<a href="{% url 'add_website' package=hostingpackage.id domain=hostingdomain.domain %}"
title="{% translate "Add website" %}"><i class="bi-globe"></i><span
class="visually-hidden"> {% translate "Add website" %}</span></a>
{% endwith %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-info">{% translate "There are no domains assigned to this hosting package yet." %}</div>
{% endif %}
{% if user.is_staff %}
<div class="card-footer"><a href="{% url 'create_hosting_domain' package=hostingpackage.id %}"
class="btn btn-primary">{% translate "Add domain" %}</a></div>
{% endif %}
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-lg-2 mt-4">
<div class="col">
<div class="card">
<div class="card-header">{% translate "E-Mail-Accounts" %}</div>
{% if mailboxes %}
<table class="table table-condensed">
<thead>
<tr>
<th class="name-column">{% translate "Mailbox" %}</th>
<th>{% translate "Mail addresses" %}</th>
<th class="status-column">{% translate "Active" %}</th>
<th title="{% translate "Mailbox actions" %}" class="actions-column"><span
class="visually-hidden">{% translate "Actions" %}</span></th>
</tr>
</thead>
<tbody>
{% for mailbox in mailboxes %}
<tr>
<td>{{ mailbox.username }}</td>
<td>{{ mailbox.mailaddresses|join:", " }}</td>
<td>
<i class="{% if mailbox.active %}bi-check-circle{% else %}bi-dash-circle{% endif %}"></i><span
class="visually-hidden"> {% if mailbox.active %}{% translate "Active" %}{% else %}
{% translate "inactive" %}{% endif %}</span></td>
<td>
<a href="{% url 'change_mailbox_password' package=hostingpackage.id slug=mailbox.username %}"><i
class="bi-lock" title="{% translate "Set mailbox password" %}"></i><span
class="visually-hidden"> {% translate "Set mailbox password" %}</span></a>
</td>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-info">{% translate "There are no mailboxes assigned to this hosting package yet." %}</div>
{% endif %}
{% if hostingpackage.may_add_mailbox %}
<div class="card-footer"><a
href="{% url 'create_mailbox' package=hostingpackage.id %}"
class="btn btn-primary">{% translate "Add mailbox" %}</a>
</div>
{% endif %}
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header">{% translate "Databases" %}</div>
{% if databases %}
<table class="table table-condensed">
<thead>
<tr>
<th class="name-column">{% translate "Database name" %}</th>
<th class="name-column">{% translate "Database user" %}</th>
<th title="{% translate "Database type" %}"><span
class="visually-hidden">{% translate "Type" %}</span></th>
<th title="{% translate "Database actions" %}" class="actions-column"><span
class="visually-hidden">{% translate "Actions" %}</span></th>
</tr>
</thead>
<tbody>
{% for database in databases %}
<tr>
<td>{{ database.db_name }}</td>
<td>{{ database.db_user.name }}</td>
<td>{% include "userdbs/snippets/db_type.html" with db_type=database.db_user.db_type %}</td>
<td>
<a href="{% url 'change_dbuser_password' package=hostingpackage.id slug=database.db_user.name %}"
title="{% translate "Set database user password" %}"><i
class="bi-database-gear"></i><span
class="visually-hidden"> {% translate "Set database user password" %}</span></a>
<a href="{% url 'delete_userdatabase' package=hostingpackage.id slug=database.db_name %}"
title="{% translate "Delete database" %}"><i class="bi-database-dash"></i><span
class="visually-hidden">{% translate "Delete database" %}</span></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-info">{% translate "There are no databases assigned to this hosting package yet." %}</div>
{% endif %}
{% if hostingpackage.may_add_database %}
<div class="card-footer"><a
href="{% url 'add_userdatabase' package=hostingpackage.id %}"
class="btn btn-primary">{% translate "Add database" %}</a></div>
{% endif %}
</div>
</div>
</div>
{% endblock content %}

View file

@ -1,117 +0,0 @@
{% extends "hostingpackages/base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} - {% spaceless %}
{% if user == customer %}
{% blocktranslate with package=hostingpackage.name trimmed %}
Disk usage of your Hosting Package {{ package }}
{% endblocktranslate %}
{% else %}
{% blocktranslate with package=hostingpackage.name full_name=customer.get_full_name trimmed %}
Disk usage of Hosting Package {{ package }} of {{ full_name }}
{% endblocktranslate %}
{% endif %}
{% endspaceless %}{% endblock title %}
{% block page_title %}
{% blocktranslate with package=hostingpackage.name package_url=hostingpackage.get_absolute_url trimmed %}
Disk usage of Hosting Package <a href="{{ package_url }}">{{ package }}</a>
{% endblocktranslate %}{% endblock page_title %}
{% block content %}
{% with used_space=hostingpackage.get_used_disk_space_sum|filesizeformat disk_space=hostingpackage.get_disk_space|filesizeformat package_space=hostingpackage.get_package_space|filesizeformat space_level=hostingpackage.space_level %}
<p>{% blocktranslate trimmed %}
You use {{ used_space }} of the reserved disk space of {{ disk_space }} for your hosting package.
{% endblocktranslate %}</p>
<p class="lead"><span
class="text-{% if space_level > 90.0 %}danger{% elif space_level > 80.0 %}warning{% else %}success{% endif %}">
{% blocktranslate with space_level_percent=space_level|floatformat:1 trimmed %}
{{ used_space }} of {{ disk_space }} ({{ space_level_percent }}%)
{% endblocktranslate %}</span>
<i class="bi-info-circle"
title="{% blocktranslate trimmed %}
The package contributes {{ package_space }} the difference comes from disk space options
{% endblocktranslate %}"></i>
</p>
<h2>{% trans "Breakdown by usage" %}</h2>
{% if mail_usage %}
<h3>{% trans "Mailbox usage" %}</h3>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>{% translate "Mailbox" %}</th>
<th>{% translate "Primary email address" %}</th>
<th class="text-end">{% translate "Used space" %}</th>
</tr>
</thead>
<tbody>
{% for line in mail_usage %}
<tr>
<td>{{ line.item }}</td>
<td>{% if line.email_address %}{{ line.email_address }}{% else %}-{% endif %}</td>
<td class="text-end">{{ line.size_in_bytes|filesizeformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if web_usage %}
<h3>{% trans "Website usage" %}</h3>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>{% translate "Website / Directory" %}</th>
<th class="text-end">{% translate "Used space" %}</th>
</tr>
</thead>
<tbody>
{% for line in web_usage %}
<tr>
<td>{{ line.item }}</td>
<td class="text-end">{{ line.size_in_bytes|filesizeformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if mysql_usage %}
<h3>{% trans "MySQL/MariaDB database usage" %}</h3>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>{% translate "Database" %}</th>
<th class="text-end">{% translate "Used space" %}</th>
</tr>
</thead>
<tbody>
{% for line in mysql_usage %}
<tr>
<td>{{ line.item }}</td>
<td class="text-end">{{ line.size_in_bytes|filesizeformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if pgsql_usage %}
<h3>{% trans "PostgreSQL database usage" %}</h3>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>{% translate "Database" %}</th>
<th class="text-end">{% translate "Used space" %}</th>
</tr>
</thead>
<tbody>
{% for line in pgsql_usage %}
<tr>
<td>{{ line.item }}</td>
<td class="text-end">{{ line.size_in_bytes|filesizeformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endwith %}
{% endblock content %}

View file

@ -1,29 +0,0 @@
{% extends "hostingpackages/base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} -
{% blocktranslate with package=hostingpackage.name full_name=customer.get_full_name trimmed %}Choose new Option for
Hosting Package {{ package }} of Customer {{ full_name }}{% endblocktranslate %}{% endblock title %}
{% block page_title %}{% blocktranslate with package=hostingpackage.name full_name=customer.get_full_name trimmed %}
Choose new Option for Hosting Package {{ package }} of Customer {{ full_name }}
{% endblocktranslate %}{% endblock page_title %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
{% for label, items in hosting_options %}
<div class="col">
<div class="card">
<div class="card-header">{{ label }}</div>
<ul class="list-group list-group-flush">
{% for item, option_type in items %}
<li class="list-group-item"><a
href="{% url 'add_hosting_option' package=hostingpackage.id type=option_type optionid=item.id %}"><i
class="bi-plus-circle"></i> {{ item }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View file

@ -2,68 +2,27 @@
Test for models. Test for models.
""" """
from django.contrib.auth import get_user_model
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase, override_settings
from hostingpackages.models import ( from django.test import TestCase
DISK_SPACE_UNITS,
CustomerHostingPackage,
HostingPackageTemplate,
)
User = get_user_model() from hostingpackages.models import DISK_SPACE_UNITS, CustomerHostingPackage
class CustomerHostingPackageTest(TestCase): class CustomerHostingPackageTest(TestCase):
def setUp(self) -> None:
self.user = User.objects.create(username="test")
self.template = HostingPackageTemplate.objects.create(
mailboxcount=10,
diskspace=100,
diskspace_unit=DISK_SPACE_UNITS.M,
description="Test package 1 - Description",
name="Test package 1",
)
@override_settings(OSUSER_DEFAULT_GROUPS=[])
def test_get_disk_space_bytes(self): def test_get_disk_space_bytes(self):
self.template.diskspace = 10 package = CustomerHostingPackage(
self.template.diskspace_unit = DISK_SPACE_UNITS.G diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G
self.template.save()
package = CustomerHostingPackage.objects.create_from_template(
customer=self.user, template=self.template, name="customer",
) )
package.save()
self.assertEqual(package.get_disk_space(), 10 * 1024 ** 3) self.assertEqual(package.get_disk_space(), 10 * 1024 ** 3)
@override_settings(OSUSER_DEFAULT_GROUPS=[])
def test_get_disk_space_mib(self): def test_get_disk_space_mib(self):
self.template.diskspace = 10 package = CustomerHostingPackage(
self.template.diskspace_unit = DISK_SPACE_UNITS.G diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G
self.template.save()
package = CustomerHostingPackage.objects.create_from_template(
customer=self.user, template=self.template, name="customer",
) )
package.save()
self.assertEqual(package.get_disk_space(DISK_SPACE_UNITS.M), 10 * 1024) self.assertEqual(package.get_disk_space(DISK_SPACE_UNITS.M), 10 * 1024)
@override_settings(OSUSER_DEFAULT_GROUPS=[])
def test_get_quota(self): def test_get_quota(self):
self.template.diskspace = 256 package = CustomerHostingPackage(
self.template.diskspace_unit = DISK_SPACE_UNITS.M diskspace=256, diskspace_unit=DISK_SPACE_UNITS.M
self.template.save()
package = CustomerHostingPackage.objects.create_from_template(
customer=self.user, template=self.template, name="customer",
) )
package.save()
self.assertEqual(package.get_quota(), (262144, 275251)) self.assertEqual(package.get_quota(), (262144, 275251))
@override_settings(OSUSER_DEFAULT_GROUPS=["testgroup"])
def test_additional_group_not_defined(self):
with self.assertRaises(ImproperlyConfigured) as ctx:
package = CustomerHostingPackage.objects.create_from_template(
customer=self.user, template=self.template, name="Test customer package",
)
package.save()
self.assertIn("testgroup", str(ctx.exception))

View file

@ -2,9 +2,9 @@
This module defines the URL patterns for hosting package related views. This module defines the URL patterns for hosting package related views.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
from django.urls import path, re_path from django.conf.urls import url
from .views import ( from .views import (
AddHostingOption, AddHostingOption,
@ -12,46 +12,26 @@ from .views import (
CreateCustomerHostingPackage, CreateCustomerHostingPackage,
CreateHostingPackage, CreateHostingPackage,
CustomerHostingPackageDetails, CustomerHostingPackageDetails,
CustomerHostingPackageDiskUsageDetails, CustomerHostingPackageList,
HostingOptionChoices, HostingOptionChoices,
UploadCustomerPackageDiskUsage,
) )
urlpatterns = [ urlpatterns = [
re_path(r"^create$", CreateHostingPackage.as_view(), name="create_hosting_package"), url(r'^create$', CreateHostingPackage.as_view(),
re_path( name='create_hosting_package'),
r"^allpackages/", url(r'^allpackages/',
AllCustomerHostingPackageList.as_view(), AllCustomerHostingPackageList.as_view(), name='all_hosting_packages'),
name="all_hosting_packages", url(r'^(?P<user>[-\w0-9@.+_]+)/$',
), CustomerHostingPackageList.as_view(), name='hosting_packages'),
re_path( url(r'^(?P<user>[-\w0-9@.+_]+)/create$',
r"^(?P<user>[-\w0-9@.+_]+)/create$",
CreateCustomerHostingPackage.as_view(), CreateCustomerHostingPackage.as_view(),
name="create_customer_hosting_package", name='create_customer_hosting_package'),
), url(r'^(?P<user>[-\w0-9@.+_]+)/(?P<pk>\d+)/$',
re_path(
r"^(?P<user>[-\w0-9@.+_]+)/(?P<pk>\d+)/$",
CustomerHostingPackageDetails.as_view(), CustomerHostingPackageDetails.as_view(),
name="hosting_package_details", name='hosting_package_details'),
), url(r'^(?P<pk>\d+)/option-choices$',
re_path( HostingOptionChoices.as_view(), name='hosting_option_choices'),
r"^(?P<pk>\d+)/option-choices$", url(r'^(?P<package>\d+)/add-option/(?P<type>\w+)/(?P<optionid>\d+)$',
HostingOptionChoices.as_view(), AddHostingOption.as_view(), name='add_hosting_option'),
name="hosting_option_choices",
),
re_path(
r"^(?P<package>\d+)/add-option/(?P<type>\w+)/(?P<optionid>\d+)$",
AddHostingOption.as_view(),
name="add_hosting_option",
),
path(
"<str:user>/<int:package>/disk-usage/",
CustomerHostingPackageDiskUsageDetails.as_view(),
name="disk_usage_details",
),
path(
"upload-disk-usage/",
UploadCustomerPackageDiskUsage.as_view(),
name="upload_disk_usage",
),
] ]

View file

@ -2,31 +2,30 @@
This module defines views related to hosting packages. This module defines views related to hosting packages.
""" """
from __future__ import absolute_import from __future__ import absolute_import, unicode_literals
import http
import logging
from datetime import timedelta
from django.conf import settings from django.conf import settings
from django.http import Http404
from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import ugettext as _
from django.views.generic import (
DetailView,
ListView,
)
from django.views.generic.edit import (
CreateView,
FormView,
)
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone
from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, FormView
import rest_framework.request from braces.views import (
from rest_framework.permissions import BasePermission LoginRequiredMixin,
from rest_framework.response import Response StaffuserRequiredMixin,
from rest_framework.views import APIView )
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from managemails.models import Mailbox
from .forms import ( from .forms import (
AddDiskspaceOptionForm, AddDiskspaceOptionForm,
AddMailboxOptionForm, AddMailboxOptionForm,
@ -36,37 +35,32 @@ from .forms import (
) )
from .models import ( from .models import (
CustomerHostingPackage, CustomerHostingPackage,
CustomerPackageDiskUsage,
DiskSpaceOption, DiskSpaceOption,
MailboxOption, MailboxOption,
UserDatabaseOption, UserDatabaseOption,
) )
from .serializers import DiskUsageSerializer
logger = logging.getLogger(__name__)
class CreateHostingPackage(PermissionRequiredMixin, CreateView): class CreateHostingPackage(
LoginRequiredMixin, StaffuserRequiredMixin, CreateView
):
""" """
Create a hosting package. Create a hosting package.
""" """
model = CustomerHostingPackage model = CustomerHostingPackage
raise_exception = True raise_exception = True
permission_required = "domains.add_customerhostingpackage" template_name_suffix = '_create'
template_name_suffix = "_create"
form_class = CreateHostingPackageForm form_class = CreateHostingPackageForm
def form_valid(self, form): def form_valid(self, form):
hosting_package = form.save() hostingpackage = form.save()
messages.success( messages.success(
self.request, self.request,
_("Started setup of new hosting package {name}.").format( _('Started setup of new hosting package {name}.').format(
name=hosting_package.name name=hostingpackage.name)
),
) )
return redirect(hosting_package) return redirect(hostingpackage)
class CreateCustomerHostingPackage(CreateHostingPackage): class CreateCustomerHostingPackage(CreateHostingPackage):
@ -74,7 +68,6 @@ class CreateCustomerHostingPackage(CreateHostingPackage):
Create a hosting package for a selected customer. Create a hosting package for a selected customer.
""" """
form_class = CreateCustomerHostingPackageForm form_class = CreateCustomerHostingPackageForm
def get_form_kwargs(self): def get_form_kwargs(self):
@ -83,24 +76,25 @@ class CreateCustomerHostingPackage(CreateHostingPackage):
return kwargs return kwargs
def get_customer_object(self): def get_customer_object(self):
return get_object_or_404(get_user_model(), username=self.kwargs["user"]) return get_object_or_404(
get_user_model(), username=self.kwargs['user'])
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CreateCustomerHostingPackage, self).get_context_data(**kwargs) context = super(
context["customer"] = self.get_customer_object() CreateCustomerHostingPackage, self).get_context_data(**kwargs)
context['customer'] = self.get_customer_object()
return context return context
def form_valid(self, form): def form_valid(self, form):
hosting_package = form.save(commit=False) hostingpackage = form.save(commit=False)
hosting_package.customer = self.get_customer_object() hostingpackage.customer = self.get_customer_object()
hosting_package.save() hostingpackage.save()
messages.success( messages.success(
self.request, self.request,
_("Started setup of new hosting package {name}.").format( _('Started setup of new hosting package {name}.').format(
name=hosting_package.name name=hostingpackage.name)
),
) )
return redirect(hosting_package) return redirect(hostingpackage)
class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView): class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView):
@ -108,161 +102,156 @@ class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView):
This view is for showing details of a customer hosting package. This view is for showing details of a customer hosting package.
""" """
model = CustomerHostingPackage model = CustomerHostingPackage
context_object_name = "hostingpackage" context_object_name = 'hostingpackage'
customer = None customer = None
def get_customer_object(self): def get_customer_object(self):
if self.customer is None: if self.customer is None:
self.customer = get_object_or_404( self.customer = get_object_or_404(
get_user_model(), username=self.kwargs["user"] get_user_model(), username=self.kwargs['user'])
)
return self.customer return self.customer
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CustomerHostingPackageDetails, self).get_context_data(**kwargs) context = super(CustomerHostingPackageDetails, self).get_context_data(
context.update( **kwargs)
{ context.update({
"customer": self.get_customer_object(), 'customer': self.get_customer_object(),
"uploadserver": settings.OSUSER_UPLOAD_SERVER, 'uploadserver': settings.OSUSER_UPLOAD_SERVER,
"databases": context["hostingpackage"].databases, 'databases': context['hostingpackage'].databases,
"osuser": context["hostingpackage"].osuser, 'osuser': context['hostingpackage'].osuser,
"hostingoptions": context["hostingpackage"].get_hostingoptions(), 'hostingoptions':
"domains": context["hostingpackage"].domains.all(), context['hostingpackage'].get_hostingoptions(),
"mailboxes": context["hostingpackage"].mailboxes, 'domains': context['hostingpackage'].domains.all(),
} 'mailboxes': context['hostingpackage'].mailboxes,
) })
context["sshkeys"] = context["osuser"].sshpublickey_set.all() context['sshkeys'] = context['osuser'].sshpublickey_set.all()
return context return context
class StaffUserRequiredMixin(UserPassesTestMixin): class AllCustomerHostingPackageList(
""" LoginRequiredMixin, StaffuserRequiredMixin, ListView
Mixin to make views available to staff members only. ):
"""
def test_func(self):
return self.request.user.is_staff
class AllCustomerHostingPackageList(StaffUserRequiredMixin, ListView):
""" """
This view is used for showing a list of all hosting packages. This view is used for showing a list of all hosting packages.
""" """
model = CustomerHostingPackage model = CustomerHostingPackage
template_name_suffix = "_admin_list" template_name_suffix = '_admin_list'
class CustomerHostingPackageList(StaffOrSelfLoginRequiredMixin, ListView):
"""
This view is used for showing a list of a customer's hosting packages.
"""
model = CustomerHostingPackage
customer = None
def get_customer_object(self):
if self.customer is None:
self.customer = get_object_or_404(
get_user_model(), username=self.kwargs['user'])
return self.customer
def get_context_data(self, **kwargs):
context = super(CustomerHostingPackageList, self).get_context_data(
**kwargs)
context['customer'] = self.get_customer_object()
return context
def get_queryset(self): def get_queryset(self):
return ( return super(CustomerHostingPackageList, self).get_queryset().filter(
super() customer__username=self.kwargs['user'])
.get_queryset()
.select_related("osuser", "customer")
.only("name", "pk", "created", "customer__username", "osuser__username")
)
class HostingOptionChoices(StaffUserRequiredMixin, DetailView): class HostingOptionChoices(
LoginRequiredMixin, StaffuserRequiredMixin, DetailView
):
""" """
This view displays choices of hosting options for a customer hosting This view displays choices of hosting options for a customer hosting
package. package.
""" """
model = CustomerHostingPackage model = CustomerHostingPackage
context_object_name = "hostingpackage" context_object_name = 'hostingpackage'
template_name_suffix = "_option_choices" template_name_suffix = '_option_choices'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(HostingOptionChoices, self).get_context_data(**kwargs) context = super(HostingOptionChoices, self).get_context_data(
context.update( **kwargs)
{ context.update({
"customer": self.get_object().customer, 'customer': self.get_object().customer,
"hosting_options": ( 'hosting_options': (
( (_('Disk space'),
_("Disk space"), [(option, 'diskspace') for option in
[ DiskSpaceOption.objects.all()]),
(option, "diskspace") (_('Mailboxes'),
for option in DiskSpaceOption.objects.all() [(option, 'mailboxes') for option in
], MailboxOption.objects.all()]),
), (_('Databases'),
( [(option, 'databases') for option in
_("Mailboxes"), UserDatabaseOption.objects.all()]),
[ ),
(option, "mailboxes") })
for option in MailboxOption.objects.all()
],
),
(
_("Databases"),
[
(option, "databases")
for option in UserDatabaseOption.objects.all()
],
),
),
}
)
return context return context
class AddHostingOption(StaffUserRequiredMixin, FormView): class AddHostingOption(
template_name = "hostingpackages/add_hosting_option.html" LoginRequiredMixin, StaffuserRequiredMixin, FormView
):
template_name = 'hostingpackages/add_hosting_option.html'
def get_form_class(self): def get_form_class(self):
optiontype = self.kwargs["type"] optiontype = self.kwargs['type']
if optiontype == "diskspace": if optiontype == 'diskspace':
return AddDiskspaceOptionForm return AddDiskspaceOptionForm
elif optiontype == "mailboxes": elif optiontype == 'mailboxes':
return AddMailboxOptionForm return AddMailboxOptionForm
elif optiontype == "databases": elif optiontype == 'databases':
return AddUserDatabaseOptionForm return AddUserDatabaseOptionForm
raise Http404() raise Http404()
def get_hosting_package(self): 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_option_template(self): def get_option_template(self):
optiontype = self.kwargs["type"] optiontype = self.kwargs['type']
optionid = int(self.kwargs["optionid"]) optionid = int(self.kwargs['optionid'])
if optiontype == "diskspace": if optiontype == 'diskspace':
return get_object_or_404(DiskSpaceOption, pk=optionid) return get_object_or_404(DiskSpaceOption, pk=optionid)
elif optiontype == "mailboxes": elif optiontype == 'mailboxes':
return get_object_or_404(MailboxOption, pk=optionid) return get_object_or_404(MailboxOption, pk=optionid)
elif optiontype == "databases": elif optiontype == 'databases':
return get_object_or_404(UserDatabaseOption, pk=optionid) return get_object_or_404(UserDatabaseOption, pk=optionid)
raise Http404() raise Http404()
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(AddHostingOption, self).get_form_kwargs() kwargs = super(AddHostingOption, self).get_form_kwargs()
kwargs["hostingpackage"] = self.get_hosting_package() kwargs['hostingpackage'] = self.get_hosting_package()
kwargs["option_template"] = self.get_option_template() kwargs['option_template'] = self.get_option_template()
return kwargs return kwargs
def get_initial(self): def get_initial(self):
initial = super(AddHostingOption, self).get_initial() initial = super(AddHostingOption, self).get_initial()
template = self.get_option_template() template = self.get_option_template()
if type(template) == DiskSpaceOption: if type(template) == DiskSpaceOption:
initial.update( initial.update({
{ 'diskspace': template.diskspace,
"diskspace": template.diskspace, 'diskspace_unit': template.diskspace_unit,
"diskspace_unit": template.diskspace_unit, })
}
)
elif type(template) == MailboxOption: elif type(template) == MailboxOption:
initial["number"] = template.number initial['number'] = template.number
elif type(template) == UserDatabaseOption: elif type(template) == UserDatabaseOption:
initial["number"] = template.number initial['number'] = template.number
else: else:
raise Http404() raise Http404()
return initial return initial
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(AddHostingOption, self).get_context_data(**kwargs) context = super(AddHostingOption, self).get_context_data(**kwargs)
context["option_template"] = self.get_option_template() context['option_template'] = self.get_option_template()
return context return context
def form_valid(self, form): def form_valid(self, form):
@ -270,126 +259,8 @@ class AddHostingOption(StaffUserRequiredMixin, FormView):
hosting_package = self.get_hosting_package() hosting_package = self.get_hosting_package()
messages.success( messages.success(
self.request, self.request,
_( _("Successfully added option {option} to hosting package "
"Successfully added option {option} to hosting package " "{package}." "{package}.").format(
).format(option=option, package=hosting_package.name), option=option, package=hosting_package.name)
) )
return redirect(hosting_package) return redirect(hosting_package)
class HasDiskUsageUploadPermission(BasePermission):
def has_permission(self, request, view):
return (
request.user.has_perm("hostingpackages.add_customerpackagediskusage")
and request.method == "POST"
)
class UploadCustomerPackageDiskUsage(APIView):
permission_classes = [HasDiskUsageUploadPermission]
allowed_methods = ("POST",)
serializer = DiskUsageSerializer(many=True)
def post(self, request: rest_framework.request.Request, format=None):
if request.content_type != "application/json":
return Response("Unacceptable", status=http.HTTPStatus.BAD_REQUEST)
submitted_sources = set()
for row in request.data:
user = row["user"]
for key in row:
if key == "user":
continue
else:
submitted_sources.add(key)
for item, size in row[key].items():
try:
package = CustomerHostingPackage.objects.get(
osuser__username=user
)
(
metric,
created,
) = CustomerPackageDiskUsage.objects.get_or_create(
package=package,
source=key,
item=item,
)
metric.used_kb = size
if key == "mail":
try:
ma_mb = package.mailboxes.get(
username=item
).mailaddressmailbox_set.first()
if ma_mb:
metric.email_address_id = ma_mb.mailaddress_id
except Mailbox.DoesNotExist:
logger.warning("mail box %s does not exist", item)
metric.save()
except CustomerHostingPackage.DoesNotExist:
logger.warning(
"hosting package for user %s does not exist", user
)
if submitted_sources:
CustomerPackageDiskUsage.objects.filter(
source__in=submitted_sources,
modified__lt=timezone.now() - timedelta(minutes=30),
).delete()
logger.info("usage data submitted by %s", request.user)
return Response("Accepted", status=http.HTTPStatus.ACCEPTED)
class CustomerHostingPackageDiskUsageDetails(StaffOrSelfLoginRequiredMixin, DetailView):
template_name_suffix = "_disk_usage_details"
model = CustomerHostingPackage
pk_url_kwarg = "package"
context_object_name = "hostingpackage"
customer = None
def get_customer_object(self):
if self.customer is None:
self.customer = get_object_or_404(
get_user_model(), username=self.kwargs["user"]
)
return self.customer
def get_queryset(self, queryset=None):
return super().get_queryset().prefetch_related("customerpackagediskusage_set")
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
mail_usage, web_usage, mysql_usage, pgsql_usage = [], [], [], []
for usage in self.get_object().customerpackagediskusage_set.order_by(
"-used_kb"
):
if usage.used_kb <= 0:
continue
if usage.source == "mail":
mail_usage.append(usage)
elif usage.source == "web":
web_usage.append(usage)
elif usage.source == "mysql":
mysql_usage.append(usage)
elif usage.source == "pgsql":
pgsql_usage.append(usage)
context_data.update(
{
"customer": self.get_customer_object(),
"mail_usage": mail_usage,
"web_usage": web_usage,
"mysql_usage": mysql_usage,
"pgsql_usage": pgsql_usage,
}
)
return context_data

View file

@ -1,14 +0,0 @@
from django.contrib import admin
from invoices.models import Invoice
class InvoiceAdmin(admin.ModelAdmin):
list_display = ["invoice_number", "customer"]
readonly_fields = ["customer"]
def has_add_permission(self, request):
return False
admin.site.register(Invoice, InvoiceAdmin)

View file

@ -1,8 +0,0 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class InvoiceConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "invoices"
verbose_name = _("Invoices")

View file

@ -1,69 +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: gnuviechadmin invoice\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-22 19:45+0200\n"
"PO-Revision-Date: 2023-04-23 14:35+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"
#: invoices/apps.py:8
msgid "Invoices"
msgstr "Rechnungen"
#: invoices/models.py:26
msgid "customer"
msgstr "Kunde"
#: invoices/models.py:33
msgid "invoice number"
msgstr "Rechnungsnummer"
#: invoices/models.py:35
msgid "invoice date"
msgstr "Rechnungsdatum"
#: invoices/models.py:37
msgid "amount"
msgstr "Betrag"
#: invoices/models.py:40
msgid "currency"
msgstr "Währung"
#: invoices/models.py:42
msgid "due date"
msgstr "Fälligkeit"
#: invoices/models.py:44
msgid "payment date"
msgstr "Zahlungsdatum"
#: invoices/models.py:47
msgid "payment variant"
msgstr "Zahlungsart"
#: invoices/models.py:51
msgid "invoice"
msgstr "Rechnung"
#: invoices/models.py:52
msgid "invoices"
msgstr "Rechnungen"
#: invoices/models.py:56
#, python-brace-format
msgid "Invoice {0}"
msgstr "Rechnung {0}"

View file

@ -1,38 +0,0 @@
# Generated by Django 3.2.18 on 2023-04-23 10:37
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import invoices.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Invoice',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('invoice', models.FileField(upload_to=invoices.models.customer_invoice_path)),
('invoice_number', models.SlugField(max_length=10, unique=True, verbose_name='Invoice number')),
('invoice_date', models.DateField(verbose_name='Invoice date')),
('invoice_value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')),
('invoice_currency', models.PositiveSmallIntegerField(choices=[(1, 'EUR')], verbose_name='Currency')),
('due_date', models.DateField(verbose_name='Due date')),
('payment_date', models.DateField(blank=True, null=True, verbose_name='Payment date')),
('payment_variant', models.TextField(blank=True, null=True, verbose_name='Payment variant')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='customer')),
],
options={
'verbose_name': 'Invoice',
'verbose_name_plural': 'Invoices',
'ordering': ['-invoice_date', 'customer'],
},
),
]

View file

@ -1,59 +0,0 @@
# Generated by Django 3.2.18 on 2023-04-23 12:15
import django.core.validators
from django.db import migrations, models
import invoices.models
class Migration(migrations.Migration):
dependencies = [
('invoices', '0001_initial_invoice_model'),
]
operations = [
migrations.AlterModelOptions(
name='invoice',
options={'ordering': ['-invoice_date', 'customer'], 'verbose_name': 'invoice', 'verbose_name_plural': 'invoices'},
),
migrations.AlterField(
model_name='invoice',
name='due_date',
field=models.DateField(verbose_name='due date'),
),
migrations.AlterField(
model_name='invoice',
name='invoice',
field=models.FileField(upload_to=invoices.models.customer_invoice_path, validators=[invoices.models.validate_pdf, django.core.validators.FileExtensionValidator(allowed_extensions=['pdf'])]),
),
migrations.AlterField(
model_name='invoice',
name='invoice_currency',
field=models.PositiveSmallIntegerField(choices=[(1, 'EUR')], verbose_name='currency'),
),
migrations.AlterField(
model_name='invoice',
name='invoice_date',
field=models.DateField(verbose_name='invoice date'),
),
migrations.AlterField(
model_name='invoice',
name='invoice_number',
field=models.SlugField(max_length=10, unique=True, verbose_name='invoice number'),
),
migrations.AlterField(
model_name='invoice',
name='invoice_value',
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount'),
),
migrations.AlterField(
model_name='invoice',
name='payment_date',
field=models.DateField(blank=True, null=True, verbose_name='payment date'),
),
migrations.AlterField(
model_name='invoice',
name='payment_variant',
field=models.TextField(blank=True, null=True, verbose_name='payment variant'),
),
]

View file

@ -1,56 +0,0 @@
import magic
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
CURRENCIES = [(1, "EUR")]
def customer_invoice_path(instance, filename):
return "invoices/{0}/{1}.pdf".format(
instance.customer.username, instance.invoice_number
)
def validate_pdf(value):
valid_mime_types = ["application/pdf"]
file_mime_type = magic.from_buffer(value.read(1024), mime=True)
if file_mime_type not in valid_mime_types:
raise ValidationError("Unsupported file type.")
class Invoice(models.Model):
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE
)
invoice = models.FileField(
upload_to=customer_invoice_path,
validators=[validate_pdf, FileExtensionValidator(allowed_extensions=["pdf"])],
)
invoice_number = models.SlugField(
verbose_name=_("invoice number"), max_length=10, unique=True
)
invoice_date = models.DateField(verbose_name=_("invoice date"))
invoice_value = models.DecimalField(
verbose_name=_("amount"), decimal_places=2, max_digits=10
)
invoice_currency = models.PositiveSmallIntegerField(
verbose_name=_("currency"), choices=CURRENCIES
)
due_date = models.DateField(verbose_name=_("due date"))
payment_date = models.DateField(
verbose_name=_("payment date"), blank=True, null=True
)
payment_variant = models.TextField(
verbose_name=_("payment variant"), blank=True, null=True
)
class Meta:
verbose_name = _("invoice")
verbose_name_plural = _("invoices")
ordering = ["-invoice_date", "customer"]
def __str__(self):
return _("Invoice {0}").format(self.invoice_number)

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