Compare commits
	
		
			48 commits
		
	
	
		
			df3628499d
			...
			4577ec4896
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4577ec4896 | |||
| 8c7af9a246 | |||
| 175ffd19f4 | |||
| 27e9d27b2b | |||
| 3b04595c7a | |||
| c7fda0e993 | |||
| 376cfab88f | |||
| be328758e6 | |||
| d88745f46b | |||
| 866f6c8083 | |||
| 2d05580ed3 | |||
| 397e479925 | |||
| 5db97f03d1 | |||
| 0962891a9b | |||
| a136bcc52b | |||
| 806ee80a85 | |||
| 10628ee45f | |||
| be1ed6ecea | |||
| 9fbd608837 | |||
| 8e42cb9c18 | |||
| 5cf7ef7a23 | |||
| 0f91587c60 | |||
| a65b1574db | |||
| 9731d4e793 | |||
| f9ea88cd24 | |||
| 4b7e311c62 | |||
| 37f3ed2506 | |||
| e44bdd7be2 | |||
| 345b32f286 | |||
| d499b781d4 | |||
| 472e272305 | |||
| dd67ee91da | |||
| a5b65974fb | |||
| 35aae85c8d | |||
| 3452e2a8c2 | |||
| f89de16f6e | |||
| 38dae51a7a | |||
| d2f94c7bec | |||
| fc8f22432c | |||
| a8392ef91e | |||
| 610f8976fc | |||
| d6fc29a2b8 | |||
| 4af1a39ca4 | |||
| 0f18e59d67 | |||
| b3588b5e6c | |||
| 03250cef00 | |||
| 0c17f03489 | |||
| b12a891fd7 | 
					 270 changed files with 17933 additions and 8644 deletions
				
			
		
							
								
								
									
										18
									
								
								.dockerignore
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.dockerignore
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | **/*.pyc | ||||||
|  | **/.*.swp | ||||||
|  | **/.coverage | ||||||
|  | **/__pycache__ | ||||||
|  | .dockerignore | ||||||
|  | .env | ||||||
|  | .envrc | ||||||
|  | .git | ||||||
|  | .gitignore | ||||||
|  | .idea | ||||||
|  | .isort.cfg | ||||||
|  | .vagrant | ||||||
|  | Dockerfile | ||||||
|  | Vagrantfile | ||||||
|  | docker-compose.yml | ||||||
|  | docs | ||||||
|  | media | ||||||
|  | static | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -50,9 +50,11 @@ 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/ | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								.isort.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.isort.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | [tool.isort] | ||||||
|  | multi_line_output = 3 | ||||||
|  | include_trailing_comma = True | ||||||
|  | force_grid_wrap = 0 | ||||||
|  | use_parentheses = True | ||||||
|  | ensure_newline_before_comments = True | ||||||
|  | line_length = 88 | ||||||
							
								
								
									
										82
									
								
								Dockerfile
									
										
									
									
									
								
							
							
						
						
									
										82
									
								
								Dockerfile
									
										
									
									
									
								
							|  | @ -1,56 +1,70 @@ | ||||||
| ARG DEBIAN_RELEASE=buster | ARG DEBIAN_RELEASE=buster | ||||||
|  | FROM debian:$DEBIAN_RELEASE AS builder | ||||||
|  | 
 | ||||||
|  | ARG GVAAPP=gva | ||||||
|  | ARG POETRY_VERSION=1.3.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 | ||||||
|  | 
 | ||||||
| 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 \ | ||||||
|     && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ |     && apt-get install -y --no-install-recommends \ | ||||||
|     build-essential \ |        ca-certificates \ | ||||||
|     dumb-init \ |        dumb-init \ | ||||||
|     gettext \ |        gettext \ | ||||||
|     git \ |        postgresql-client \ | ||||||
|     python3-dev \ |        python3 \ | ||||||
|     python3-pip \ |        python3-pip \ | ||||||
|     python3-setuptools \ |        python3-wheel \ | ||||||
|     python3-virtualenv \ |  | ||||||
|     python3-wheel \ |  | ||||||
|     && apt-get clean \ |     && apt-get clean \ | ||||||
|     && rm -rf /var/lib/apt/lists/*.* |     && rm -rf /var/cache/apt/archives /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 | ||||||
| 
 | 
 | ||||||
| ARG GVAAPP=gva | 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 | ||||||
| 
 | 
 | ||||||
| VOLUME /srv/$GVAAPP/media /srv/$GVAAPP/static | COPY --chown=$GVAAPP:$GVAAPP --from=builder /srv/$GVAAPP/.venv /srv/$GVAAPP/.venv | ||||||
| 
 | 
 | ||||||
| WORKDIR /srv/$GVAAPP | WORKDIR /srv/$GVAAPP | ||||||
| 
 | 
 | ||||||
| COPY Pipfile Pipfile.lock /srv/$GVAAPP/ | VOLUME /srv/$GVAAPP/media /srv/$GVAAPP/static | ||||||
| 
 | 
 | ||||||
| RUN addgroup --gid $GVAGID $GVAAPP ; \ | VOLUME /srv/$GVAAPP/gnuviechadmin | ||||||
|     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 gva.sh /srv/ | COPY ${GVAAPP}.sh entrypoint.sh /srv/ | ||||||
| 
 | 
 | ||||||
| ENTRYPOINT ["dumb-init", "/srv/gva.sh"] | ENTRYPOINT ["dumb-init", "/srv/entrypoint.sh"] | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								Pipfile
									
										
									
									
									
								
							
							
						
						
									
										33
									
								
								Pipfile
									
										
									
									
									
								
							|  | @ -1,33 +0,0 @@ | ||||||
| [[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
									
									
									
								
							
							
						
						
									
										617
									
								
								Pipfile.lock
									
										
									
										generated
									
									
									
								
							|  | @ -1,617 +0,0 @@ | ||||||
| { |  | ||||||
|     "_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" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | --- | ||||||
| version: "3" | version: "3" | ||||||
| services: | services: | ||||||
|   db: |   db: | ||||||
|  | @ -36,9 +37,9 @@ services: | ||||||
|       GVA_DOMAIN_NAME: localhost |       GVA_DOMAIN_NAME: localhost | ||||||
|       GVA_SITE_NAME: localhost |       GVA_SITE_NAME: localhost | ||||||
|     volumes: |     volumes: | ||||||
|       - "./docker/django_media:/srv/gva/media" |       - "django_media:/srv/gva/media" | ||||||
|       - "./docker/django_static:/srv/gva/static" |       - "django_static:/srv/gva/static" | ||||||
|       - ".:/srv/gva" |       - "./gnuviechadmin:/srv/gva/gnuviechadmin" | ||||||
|   web: |   web: | ||||||
|     image: gnuviech/gvaweb:buster |     image: gnuviech/gvaweb:buster | ||||||
|     build: |     build: | ||||||
|  | @ -51,7 +52,7 @@ services: | ||||||
|       - redis |       - redis | ||||||
|     env_file: ../gvaweb/.env |     env_file: ../gvaweb/.env | ||||||
|     volumes: |     volumes: | ||||||
|       - "../gvaweb:/srv/gvaweb" |       - "../gvaweb/gvaweb:/srv/gvaweb/gvaweb" | ||||||
|   ldap: |   ldap: | ||||||
|     image: gnuviech/gvaldap:buster |     image: gnuviech/gvaldap:buster | ||||||
|     build: |     build: | ||||||
|  | @ -64,7 +65,7 @@ services: | ||||||
|       - redis |       - redis | ||||||
|     env_file: ../gvaldap/.env |     env_file: ../gvaldap/.env | ||||||
|     volumes: |     volumes: | ||||||
|       - "../gvaldap:/srv/gvaldap" |       - "../gvaldap/gvaldap:/srv/gvaldap/gvaldap" | ||||||
|   file: |   file: | ||||||
|     image: gnuviech/gvafile:buster |     image: gnuviech/gvafile:buster | ||||||
|     build: |     build: | ||||||
|  | @ -77,7 +78,7 @@ services: | ||||||
|       - redis |       - redis | ||||||
|     env_file: ../gvafile/.env |     env_file: ../gvafile/.env | ||||||
|     volumes: |     volumes: | ||||||
|       - "../gvafile:/srv/gvafile" |       - "../gvafile/gvafile:/srv/gvafile/gvafile" | ||||||
|   pgsql: |   pgsql: | ||||||
|     image: gnuviech/gvapgsql:buster |     image: gnuviech/gvapgsql:buster | ||||||
|     build: |     build: | ||||||
|  | @ -90,7 +91,7 @@ services: | ||||||
|       - redis |       - redis | ||||||
|     env_file: ../gvapgsql/.env |     env_file: ../gvapgsql/.env | ||||||
|     volumes: |     volumes: | ||||||
|       - "../gvapgsql:/srv/gvapgsql" |       - "../gvapgsql/gvapgsql:/srv/gvapgsql/gvapgsql" | ||||||
|   mysql: |   mysql: | ||||||
|     image: gnuviech/gvamysql:buster |     image: gnuviech/gvamysql:buster | ||||||
|     build: |     build: | ||||||
|  | @ -103,7 +104,7 @@ services: | ||||||
|       - redis |       - redis | ||||||
|     env_file: ../gvamysql/.env |     env_file: ../gvamysql/.env | ||||||
|     volumes: |     volumes: | ||||||
|       - "../gvamysql:/srv/gvamysql" |       - "../gvamysql/gvamysql:/srv/gvamysql/gvamysql" | ||||||
| volumes: | volumes: | ||||||
|   django_media: |   django_media: | ||||||
|   django_static: |   django_static: | ||||||
|  |  | ||||||
|  | @ -1,6 +1,16 @@ | ||||||
| Changelog | Changelog | ||||||
| ========= | ========= | ||||||
| 
 | 
 | ||||||
|  | * :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>` | * :release:`0.12.1 <2020-04-13>` | ||||||
| * :bug:`7` fix handling of undefined mail domains in customer hosting package | * :bug:`7` fix handling of undefined mail domains in customer hosting package | ||||||
|   detail template |   detail template | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								entrypoint.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								entrypoint.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | #!/bin/sh | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | chown -Rc gva.gva /srv/gva/media /srv/gva/static | ||||||
|  | 
 | ||||||
|  | su -c /srv/gva.sh gva | ||||||
							
								
								
									
										1
									
								
								frontend/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | node_modules/ | ||||||
							
								
								
									
										46
									
								
								frontend/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								frontend/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | { | ||||||
|  |   "name": "frontend", | ||||||
|  |   "lockfileVersion": 3, | ||||||
|  |   "requires": true, | ||||||
|  |   "packages": { | ||||||
|  |     "": { | ||||||
|  |       "dependencies": { | ||||||
|  |         "bootstrap": "^5.2.3", | ||||||
|  |         "bootstrap-icons": "^1.10.4" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@popperjs/core": { | ||||||
|  |       "version": "2.11.7", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", | ||||||
|  |       "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", | ||||||
|  |       "peer": true, | ||||||
|  |       "funding": { | ||||||
|  |         "type": "opencollective", | ||||||
|  |         "url": "https://opencollective.com/popperjs" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/bootstrap": { | ||||||
|  |       "version": "5.2.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", | ||||||
|  |       "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", | ||||||
|  |       "funding": [ | ||||||
|  |         { | ||||||
|  |           "type": "github", | ||||||
|  |           "url": "https://github.com/sponsors/twbs" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "type": "opencollective", | ||||||
|  |           "url": "https://opencollective.com/bootstrap" | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "@popperjs/core": "^2.11.6" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/bootstrap-icons": { | ||||||
|  |       "version": "1.10.4", | ||||||
|  |       "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.4.tgz", | ||||||
|  |       "integrity": "sha512-eI3HyIUmpGKRiRv15FCZccV+2sreGE2NnmH8mtxV/nPOzQVu0sPEj8HhF1MwjJ31IhjF0rgMvtYOX5VqIzcb/A==" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								frontend/package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/package.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | { | ||||||
|  |   "dependencies": { | ||||||
|  |     "bootstrap": "^5.2.3", | ||||||
|  |     "bootstrap-icons": "^1.10.4" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -4,19 +4,16 @@ 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 django import forms |  | ||||||
| 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.requests import RequestSite |  | ||||||
| 
 |  | ||||||
| from crispy_forms.helper import FormHelper | from crispy_forms.helper import FormHelper | ||||||
| from crispy_forms.layout import Submit | from crispy_forms.layout import Submit | ||||||
|  | from django import forms | ||||||
|  | from django.conf import settings | ||||||
|  | from django.contrib.sites.models import Site | ||||||
|  | from django.contrib.sites.requests import RequestSite | ||||||
|  | from django.core.mail import send_mail | ||||||
|  | from django.template import RequestContext, loader | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ContactForm(forms.Form): | class ContactForm(forms.Form): | ||||||
|  | @ -24,45 +21,42 @@ 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')) | 
 | ||||||
|     email = forms.EmailField(max_length=200, label=_('Your email address')) |     name = forms.CharField(max_length=100, label=_("Your name")) | ||||||
|     body = forms.CharField(widget=forms.Textarea, label=_('Your message')) |     email = forms.EmailField(max_length=200, label=_("Your email address")) | ||||||
|  |     body = forms.CharField(widget=forms.Textarea, label=_("Your message")) | ||||||
| 
 | 
 | ||||||
|     subject_template_name = "contact_form/contact_form_subject.txt" |     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( |             raise ValueError("Cannot generate context from invalid contact form") | ||||||
|                 'Cannot generate context from invalid contact form') |  | ||||||
|         if Site._meta.installed: |         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( |         return RequestContext(self.request, dict(self.cleaned_data, site=site)) | ||||||
|             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({ |         template_context.update({"remote_ip": context.request.META["REMOTE_ADDR"]}) | ||||||
|             '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): | ||||||
|         """ |         """ | ||||||
|  | @ -74,5 +68,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(), | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -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: 2016-01-29 11:04+0100\n" | "POT-Creation-Date: 2023-04-22 13:01+0200\n" | ||||||
| "PO-Revision-Date: 2015-02-01 19:03+0100\n" | "PO-Revision-Date: 2023-04-22 13:01+0200\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,21 +16,32 @@ 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 1.6.10\n" | "X-Generator: Poedit 3.2.2\n" | ||||||
| "X-Poedit-SourceCharset: UTF-8\n" | "X-Poedit-SourceCharset: UTF-8\n" | ||||||
| 
 | 
 | ||||||
| #: contact_form/forms.py:27 | #: contact_form/forms.py:25 | ||||||
| msgid "Your name" | msgid "Your name" | ||||||
| msgstr "Ihr Name" | msgstr "Ihr Name" | ||||||
| 
 | 
 | ||||||
| #: contact_form/forms.py:28 | #: contact_form/forms.py:26 | ||||||
| msgid "Your email address" | msgid "Your email address" | ||||||
| msgstr "Ihre E-Mailadresse" | msgstr "Ihre E-Mail-Adresse" | ||||||
| 
 | 
 | ||||||
| #: contact_form/forms.py:29 | #: contact_form/forms.py:27 | ||||||
| msgid "Your message" | msgid "Your message" | ||||||
| msgstr "Ihre Nachricht" | msgstr "Ihre Nachricht" | ||||||
| 
 | 
 | ||||||
| #: contact_form/forms.py:41 | #: contact_form/forms.py:39 | ||||||
| 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." | ||||||
|  |  | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | {% extends "contact_form/base.html" %} | ||||||
|  | {% load i18n crispy_forms_tags %} | ||||||
|  | 
 | ||||||
|  | {% block title %}{{ block.super }} - {% translate "Contact" %}{% endblock title %} | ||||||
|  | {% block page_title %}{% translate "Contact" %}{% endblock page_title %} | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  |     {% crispy form %} | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
|  | {% block extra_js %} | ||||||
|  |     <script type="text/javascript"> | ||||||
|  |         document.addEventListener("DOMContentLoaded", function () { | ||||||
|  |             let textFields = document.querySelectorAll("input[type=text]"); | ||||||
|  | 
 | ||||||
|  |             if (textFields[0].val() !== '') { | ||||||
|  |                 document.getElementsByTagName("textarea")[0].focus(); | ||||||
|  |             } else { | ||||||
|  |                 textFields[0].focus(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     </script> | ||||||
|  | {% endblock extra_js %} | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | {% extends "contact_form/base.html" %} | ||||||
|  | {% load i18n %} | ||||||
|  | 
 | ||||||
|  | {% block title %}{{ block.super }} - {% translate "Contact" %}{% endblock title %} | ||||||
|  | {% block page_title %}{% translate "Contact" %}{% endblock page_title %} | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <p class="text-success">{% translate "Your message has been sent successfully." %}</p> | ||||||
|  | {% endblock %} | ||||||
|  | @ -2,17 +2,13 @@ | ||||||
| URL patterns for the contact_form views. | URL patterns for the contact_form views. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import url | from django.urls import re_path | ||||||
| 
 |  | ||||||
| from .views import ( |  | ||||||
|     ContactFormView, |  | ||||||
|     ContactSuccessView, |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
|  | from .views import ContactFormView, ContactSuccessView | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     url(r'^$', ContactFormView.as_view(), name='contact_form'), |     re_path(r"^$", ContactFormView.as_view(), name="contact_form"), | ||||||
|     url(r'^success/$', ContactSuccessView.as_view(), name='contact_success'), |     re_path(r"^success/$", ContactSuccessView.as_view(), name="contact_success"), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -2,14 +2,11 @@ | ||||||
| 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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| 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 ( | from django.views.generic import FormView, TemplateView | ||||||
|     FormView, |  | ||||||
|     TemplateView, |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| from .forms import ContactForm | from .forms import ContactForm | ||||||
| 
 | 
 | ||||||
|  | @ -19,22 +16,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'] = ( |             initial["name"] = currentuser.get_full_name() or currentuser.username | ||||||
|                 currentuser.get_full_name() or currentuser.username) |             initial["email"] = currentuser.email | ||||||
|             initial['email'] = currentuser.email |  | ||||||
|         return initial |         return initial | ||||||
| 
 | 
 | ||||||
|     def form_valid(self, form): |     def form_valid(self, form): | ||||||
|  | @ -47,4 +44,5 @@ 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" | ||||||
|  |  | ||||||
|  | @ -7,18 +7,92 @@ 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: 2015-01-17 15:59+0100\n" | "POT-Creation-Date: 2023-04-16 22:07+0200\n" | ||||||
| "PO-Revision-Date: 2015-01-17 16:01+0100\n" | "PO-Revision-Date: 2023-04-16 18:31+0200\n" | ||||||
| "Last-Translator: Jan Dittberner <jan@dittberner.info>\n" | "Last-Translator: Jan Dittberner <jan@dittberner.info>\n" | ||||||
| "Language-Team: Jan Dittberner <de@li.org>\n" | "Language-Team: Jan Dittberner <jan@dittberner.info>\n" | ||||||
| "Language: de\n" | "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 1.6.10\n" | "X-Generator: Poedit 3.2.2\n" | ||||||
| "X-Poedit-SourceCharset: UTF-8\n" | "X-Poedit-SourceCharset: UTF-8\n" | ||||||
| 
 | 
 | ||||||
| #: dashboard/views.py:43 | #: dashboard/templates/dashboard/index.html:3 | ||||||
| msgid "You are not allowed to view this page." | msgid "Welcome" | ||||||
| msgstr "Sie haben nicht die nötigen Berechtigungen um diese Seite zu sehen." | msgstr "Willkommen" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/index.html:4 | ||||||
|  | msgid "Welcome to our customer self service" | ||||||
|  | msgstr "Willkommen in unserem Selbstservice-System" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/index.html:7 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "Hello %(full_name)s,<br/> You can visit your <a " | ||||||
|  | "href=\"%(dashboard_url)s\">Dashboard</a> to view and modify your hosting " | ||||||
|  | "options." | ||||||
|  | msgstr "" | ||||||
|  | "Hallo %(full_name)s,<br /> Sie können Ihre <a " | ||||||
|  | "href=\"%(dashboard_url)s\">Startseite</a> besuchen, um Ihre " | ||||||
|  | "Hostingeinstellungen anzusehen und zu bearbeiten." | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:3 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:6 | ||||||
|  | #, python-format | ||||||
|  | msgid "Dashboard for %(full_name)s" | ||||||
|  | msgstr "Startseite für %(full_name)s" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:10 | ||||||
|  | msgid "Hosting packages" | ||||||
|  | msgstr "Hostingpakete" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:17 | ||||||
|  | msgid "Name" | ||||||
|  | msgstr "Name" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:18 | ||||||
|  | msgid "Disk space" | ||||||
|  | msgstr "Speicherplatz" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:19 | ||||||
|  | msgid "Mailboxes" | ||||||
|  | msgstr "Postfächer" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:20 | ||||||
|  | msgid "Databases" | ||||||
|  | msgstr "Datenbanken" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:21 | ||||||
|  | msgid "Actions" | ||||||
|  | msgstr "Aktionen" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:28 | ||||||
|  | #, python-format | ||||||
|  | msgid "Show details for %(packagename)s" | ||||||
|  | msgstr "Details für %(packagename)s anzeigen" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:34 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "The reserved disk space for your hosting package is %(diskspace)s bytes." | ||||||
|  | msgstr "" | ||||||
|  | "Der für Ihr Hostingpaket reservierte Speicherplatz sind %(diskspace)s Bytes." | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:40 | ||||||
|  | #, python-format | ||||||
|  | msgid "used %(num)s of %(total)s" | ||||||
|  | msgstr "%(num)s von %(total)s genutzt" | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:55 | ||||||
|  | msgid "You have no hosting packages yet." | ||||||
|  | msgstr "Sie haben noch keine Hostingpakete." | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:56 | ||||||
|  | msgid "This user has no hosting packages assigned yet." | ||||||
|  | msgstr "Diesem Benutzer sind noch keine Hostingpakete zugewiesen." | ||||||
|  | 
 | ||||||
|  | #: dashboard/templates/dashboard/user_dashboard.html:60 | ||||||
|  | msgid "Add hosting package" | ||||||
|  | msgstr "Hostingpaket anlegen" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | {% extends "base.html" %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% block title %}{{ block.super }} - {% blocktranslate with full_name=request.user.get_full_name trimmed %} | ||||||
|  |     Dashboard for {{ full_name }} | ||||||
|  | {% endblocktranslate %}{% endblock title %} | ||||||
|  | {% block page_title %}{% blocktranslate with full_name=request.user.get_full_name trimmed %} | ||||||
|  |     Dashboard for {{ full_name }} | ||||||
|  | {% endblocktranslate %}{% endblock page_title %} | ||||||
|  | {% block content %} | ||||||
|  |     <h2>{% translate "Hosting packages" %}</h2> | ||||||
|  |     <div class="row"> | ||||||
|  |         <div class="col-12"> | ||||||
|  |             {% if hosting_packages %} | ||||||
|  |                 <table class="table"> | ||||||
|  |                     <thead> | ||||||
|  |                     <tr> | ||||||
|  |                         <th>{% translate "Name" %}</th> | ||||||
|  |                         <th>{% translate "Setup date" %}</th> | ||||||
|  |                         <th>{% translate "Actions" %}</th> | ||||||
|  |                     </tr> | ||||||
|  |                     </thead> | ||||||
|  |                     <tbody> | ||||||
|  |                     {% for package in hosting_packages %} | ||||||
|  |                         <tr> | ||||||
|  |                             <td><a href="{{ package.get_absolute_url }}" | ||||||
|  |                                    title="{% blocktranslate with packagename=package.name trimmed %} | ||||||
|  |                                            Show details for {{ packagename }} | ||||||
|  | {% endblocktranslate %}">{{ package.name }}</a> | ||||||
|  |                             </td> | ||||||
|  |                             <td>{{ package.created }}</td> | ||||||
|  |                             <td></td> | ||||||
|  |                         </tr> | ||||||
|  |                     {% endfor %} | ||||||
|  |                     </tbody> | ||||||
|  |                 </table> | ||||||
|  |             {% else %} | ||||||
|  |                 <p class="text-info"> | ||||||
|  |                     {% if user == object %}{% translate "You have no hosting packages yet." %}{% else %} | ||||||
|  |                         {% translate "This user has no hosting packages assigned yet." %}{% endif %}</p> | ||||||
|  |             {% endif %} | ||||||
|  |             {% if user.is_staff %} | ||||||
|  |                 <a href="{% url "create_customer_hosting_package" user=request.user.username %}" | ||||||
|  |                    class="btn btn-primary">{% translate "Add hosting package" %}</a> | ||||||
|  |             {% endif %} | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | {% endblock content %} | ||||||
|  | @ -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,55 +13,35 @@ 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( |         response = self.client.get(reverse("customer_dashboard")) | ||||||
|             reverse("customer_dashboard", kwargs={"slug": TEST_USER}) |         self.assertEqual(response.status_code, 302) | ||||||
|         ) |         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( |         response = self.client.get(reverse("customer_dashboard")) | ||||||
|             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", kwargs={"slug": TEST_USER}) |             reverse("customer_dashboard") | ||||||
|         ) |         ) | ||||||
|         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( |         response = self.client.get(reverse("customer_dashboard")) | ||||||
|             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) | ||||||
|  |  | ||||||
|  | @ -1,15 +1,9 @@ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import url | from django.urls import path | ||||||
| 
 |  | ||||||
| from .views import ( |  | ||||||
|     IndexView, |  | ||||||
|     UserDashboardView, |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
|  | from .views import UserDashboardView | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     url(r'^$', IndexView.as_view(), name='dashboard'), |     path("", UserDashboardView.as_view(), name="customer_dashboard"), | ||||||
|     url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$', |  | ||||||
|         UserDashboardView.as_view(), name='customer_dashboard'), |  | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -2,47 +2,26 @@ | ||||||
| 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 IndexView(TemplateView): | class UserDashboardView(LoginRequiredMixin, 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() | 
 | ||||||
|     context_object_name = 'dashboard_user' |     template_name = "dashboard/user_dashboard.html" | ||||||
|     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.object |             customer=self.request.user | ||||||
|         ) |         ) | ||||||
|         return context |         return context | ||||||
| 
 |  | ||||||
|     def get_customer_object(self): |  | ||||||
|         """ |  | ||||||
|         Returns the customer object. |  | ||||||
| 
 |  | ||||||
|         """ |  | ||||||
|         return self.get_object() |  | ||||||
|  |  | ||||||
|  | @ -2,4 +2,3 @@ | ||||||
| This app takes care of domains. | This app takes care of domains. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| default_app_config = 'domains.apps.DomainAppConfig' |  | ||||||
|  |  | ||||||
|  | @ -5,24 +5,7 @@ with the django admin site. | ||||||
| """ | """ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| 
 | 
 | ||||||
| from .models import ( | from domains.models import HostingDomain, MailDomain | ||||||
|     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) |  | ||||||
|  |  | ||||||
|  | @ -3,9 +3,8 @@ 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 ugettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DomainAppConfig(AppConfig): | class DomainAppConfig(AppConfig): | ||||||
|  | @ -13,5 +12,6 @@ class DomainAppConfig(AppConfig): | ||||||
|     AppConfig for the :py:mod:`domains` app. |     AppConfig for the :py:mod:`domains` app. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     name = 'domains' | 
 | ||||||
|     verbose_name = _('Domains') |     name = "domains" | ||||||
|  |     verbose_name = _("Domains") | ||||||
|  |  | ||||||
|  | @ -2,19 +2,15 @@ | ||||||
| This module defines form classes for domain editing. | This module defines form classes for domain editing. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| 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 ugettext as _ | from django.utils.translation import gettext as _ | ||||||
| 
 |  | ||||||
| from crispy_forms.helper import FormHelper |  | ||||||
| from crispy_forms.layout import ( |  | ||||||
|     Layout, |  | ||||||
|     Submit, |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| from .models import HostingDomain | from .models import HostingDomain | ||||||
| 
 | 
 | ||||||
|  | @ -26,11 +22,10 @@ def relative_domain_validator(value): | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     if len(value) > 254: |     if len(value) > 254: | ||||||
|         raise forms.ValidationError( |         raise forms.ValidationError(_("host name too long"), code="too-long") | ||||||
|             _('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): | ||||||
|  | @ -38,31 +33,32 @@ 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={ |             "create_hosting_domain", kwargs={"package": self.hosting_package.id} | ||||||
|                 '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 | ||||||
|  |  | ||||||
|  | @ -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: 2016-01-29 11:04+0100\n" | "POT-Creation-Date: 2023-04-16 22:07+0200\n" | ||||||
| "PO-Revision-Date: 2015-11-08 12:02+0100\n" | "PO-Revision-Date: 2023-04-16 18:20+0200\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,152 +16,60 @@ 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 1.8.6\n" | "X-Generator: Poedit 3.2.2\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:30 domains/tests/test_forms.py:24 | #: domains/forms.py:25 domains/tests/test_forms.py:20 | ||||||
| msgid "host name too long" | msgid "host name too long" | ||||||
| msgstr "zu langer Hostname" | msgstr "zu langer Hostname" | ||||||
| 
 | 
 | ||||||
| #: domains/forms.py:33 domains/tests/test_forms.py:29 | #: domains/forms.py:28 domains/tests/test_forms.py:24 | ||||||
| #: domains/tests/test_forms.py:34 domains/tests/test_forms.py:39 | #: domains/tests/test_forms.py:28 domains/tests/test_forms.py:32 | ||||||
| #: domains/tests/test_forms.py:44 | #: domains/tests/test_forms.py:36 | ||||||
| msgid "invalid domain name" | msgid "invalid domain name" | ||||||
| msgstr "ungültiger Domainname" | msgstr "ungültiger Domainname" | ||||||
| 
 | 
 | ||||||
| #: domains/forms.py:56 | #: domains/forms.py:51 | ||||||
| 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:60 domains/models.py:258 domains/models.py:308 | #: domains/models.py:22 | ||||||
| msgid "customer" | msgid "customer" | ||||||
| msgstr "Kunde" | msgstr "Kunde" | ||||||
| 
 | 
 | ||||||
| #: domains/models.py:76 | #: domains/models.py:41 | ||||||
| msgid "Mail domain" | msgid "Mail domain" | ||||||
| msgstr "E-Maildomain" | msgstr "E-Maildomain" | ||||||
| 
 | 
 | ||||||
| #: domains/models.py:77 | #: domains/models.py:42 | ||||||
| msgid "Mail domains" | msgid "Mail domains" | ||||||
| msgstr "E-Maildomains" | msgstr "E-Maildomains" | ||||||
| 
 | 
 | ||||||
| #: domains/models.py:121 | #: domains/models.py:91 | ||||||
| msgid "mail domain" | msgid "mail domain" | ||||||
| msgstr "E-Maildomain" | msgstr "E-Maildomain" | ||||||
| 
 | 
 | ||||||
| #: domains/models.py:122 | #: domains/models.py:94 | ||||||
| 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:128 | #: domains/models.py:101 | ||||||
| msgid "Hosting domain" | msgid "Hosting domain" | ||||||
| msgstr "Hostingdomain" | msgstr "Hostingdomain" | ||||||
| 
 | 
 | ||||||
| #: domains/models.py:129 | #: domains/models.py:102 | ||||||
| msgid "Hosting domains" | msgid "Hosting domains" | ||||||
| msgstr "Hostingdomains" | msgstr "Hostingdomains" | ||||||
| 
 | 
 | ||||||
| #: domains/models.py:169 | #: domains/views.py:51 | ||||||
| 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" | ||||||
|  |  | ||||||
|  | @ -1,28 +1,46 @@ | ||||||
| # -*- 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)), |                 ( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), |                     "id", | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), |                     models.AutoField( | ||||||
|                 ('domain', models.CharField(unique=True, max_length=128)), |                         verbose_name="ID", | ||||||
|  |                         serialize=False, | ||||||
|  |                         auto_created=True, | ||||||
|  |                         primary_key=True, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "created", | ||||||
|  |                     model_utils.fields.AutoCreatedField( | ||||||
|  |                         default=django.utils.timezone.now, | ||||||
|  |                         verbose_name="created", | ||||||
|  |                         editable=False, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "modified", | ||||||
|  |                     model_utils.fields.AutoLastModifiedField( | ||||||
|  |                         default=django.utils.timezone.now, | ||||||
|  |                         verbose_name="modified", | ||||||
|  |                         editable=False, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("domain", models.CharField(unique=True, max_length=128)), | ||||||
|             ], |             ], | ||||||
|             options={ |             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,), | ||||||
|         ), |         ), | ||||||
|  |  | ||||||
|  | @ -1,68 +1,97 @@ | ||||||
| # -*- 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 | ||||||
| from django.conf import settings |  | ||||||
| import model_utils.fields | import model_utils.fields | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import migrations, models | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 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', |                 ( | ||||||
|                  models.AutoField(verbose_name='ID', serialize=False, |                     "id", | ||||||
|                                   auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', |                         verbose_name="ID", | ||||||
|                  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", | ||||||
|                 ('domain', |                     model_utils.fields.AutoCreatedField( | ||||||
|                  models.CharField( |                         default=django.utils.timezone.now, | ||||||
|                      unique=True, max_length=128, verbose_name='domain name')), |                         verbose_name="created", | ||||||
|                 ('customer', |                         editable=False, | ||||||
|                  models.ForeignKey( |                     ), | ||||||
|                      verbose_name='customer', blank=True, |                 ), | ||||||
|                      to=settings.AUTH_USER_MODEL, null=True, |                 ( | ||||||
|                      on_delete=models.CASCADE)), |                     "modified", | ||||||
|                 ('maildomain', |                     model_utils.fields.AutoLastModifiedField( | ||||||
|                  models.OneToOneField( |                         default=django.utils.timezone.now, | ||||||
|                      null=True, to='domains.MailDomain', blank=True, |                         verbose_name="modified", | ||||||
|                      help_text='assigned mail domain for this domain', |                         editable=False, | ||||||
|                      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', blank=True, |                 verbose_name="customer", | ||||||
|                 to=settings.AUTH_USER_MODEL, null=True, |                 blank=True, | ||||||
|                 on_delete=models.CASCADE), |                 to=settings.AUTH_USER_MODEL, | ||||||
|  |                 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, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,199 +1,285 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from __future__ import unicode_literals |  | ||||||
| 
 |  | ||||||
| from django.db import migrations, models |  | ||||||
| import django.utils.timezone | import django.utils.timezone | ||||||
| from django.conf import settings |  | ||||||
| import model_utils.fields | import model_utils.fields | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import migrations, models | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('name', models.CharField(max_length=255)), |                         verbose_name="ID", | ||||||
|                 ('commenttype', |                         serialize=False, | ||||||
|                  models.CharField(max_length=10, db_column='type')), |                         auto_created=True, | ||||||
|                 ('modified_at', models.IntegerField()), |                         primary_key=True, | ||||||
|                 ('comment', models.CharField(max_length=65535)), |                     ), | ||||||
|                 ('customer', models.ForeignKey( |                 ), | ||||||
|                     verbose_name='customer', |                 ("name", models.CharField(max_length=255)), | ||||||
|                     to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), |                 ("commenttype", models.CharField(max_length=10, db_column="type")), | ||||||
|  |                 ("modified_at", models.IntegerField()), | ||||||
|  |                 ("comment", models.CharField(max_length=65535)), | ||||||
|  |                 ( | ||||||
|  |                     "customer", | ||||||
|  |                     models.ForeignKey( | ||||||
|  |                         verbose_name="customer", | ||||||
|  |                         to=settings.AUTH_USER_MODEL, | ||||||
|  |                         on_delete=models.CASCADE, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|         ), |         ), | ||||||
|         migrations.RunSQL( |         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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('flags', models.IntegerField()), |                         verbose_name="ID", | ||||||
|                 ('active', models.BooleanField(default=True)), |                         serialize=False, | ||||||
|                 ('content', models.TextField()), |                         auto_created=True, | ||||||
|  |                         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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     default=django.utils.timezone.now, verbose_name='modified', |                     ), | ||||||
|                     editable=False)), |                 ), | ||||||
|                 ('domain', models.CharField( |                 ( | ||||||
|                     unique=True, max_length=255, verbose_name='domain name')), |                     "created", | ||||||
|                 ('master', |                     model_utils.fields.AutoCreatedField( | ||||||
|                  models.CharField(max_length=128, null=True, blank=True)), |                         default=django.utils.timezone.now, | ||||||
|                 ('last_check', models.IntegerField(null=True)), |                         verbose_name="created", | ||||||
|                 ('domaintype', models.CharField( |                         editable=False, | ||||||
|                     max_length=6, db_column='type', |                     ), | ||||||
|                     choices=[('MASTER', 'Master'), |                 ), | ||||||
|                              ('SLAVE', 'Slave'), |                 ( | ||||||
|                              ('NATIVE', 'Native')])), |                     "modified", | ||||||
|                 ('notified_serial', models.IntegerField(null=True)), |                     model_utils.fields.AutoLastModifiedField( | ||||||
|                 ('customer', models.ForeignKey( |                         default=django.utils.timezone.now, | ||||||
|                     verbose_name='customer', blank=True, |                         verbose_name="modified", | ||||||
|                     to=settings.AUTH_USER_MODEL, null=True, |                         editable=False, | ||||||
|                     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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('kind', models.CharField(max_length=32)), |                         verbose_name="ID", | ||||||
|                 ('content', models.TextField()), |                         serialize=False, | ||||||
|                 ('domain', models.ForeignKey( |                         auto_created=True, | ||||||
|                     to='domains.DNSDomain', on_delete=models.CASCADE)), |                         primary_key=True, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('name', models.CharField( |                         verbose_name="ID", | ||||||
|                     db_index=True, max_length=255, null=True, blank=True)), |                         serialize=False, | ||||||
|                 ('recordtype', models.CharField( |                         auto_created=True, | ||||||
|                     max_length=10, null=True, db_column='type', blank=True)), |                         primary_key=True, | ||||||
|                 ('content', models.CharField( |                     ), | ||||||
|                     max_length=65535, null=True, blank=True)), |                 ), | ||||||
|                 ('ttl', models.IntegerField(null=True)), |                 ( | ||||||
|                 ('prio', models.IntegerField(null=True)), |                     "name", | ||||||
|                 ('change_date', models.IntegerField(null=True)), |                     models.CharField( | ||||||
|                 ('disabled', models.BooleanField(default=False)), |                         db_index=True, max_length=255, null=True, blank=True | ||||||
|                 ('ordername', models.CharField(max_length=255)), |                     ), | ||||||
|                 ('auth', models.BooleanField(default=True)), |                 ), | ||||||
|                 ('domain', models.ForeignKey( |                 ( | ||||||
|                     to='domains.DNSDomain', on_delete=models.CASCADE)), |                     "recordtype", | ||||||
|  |                     models.CharField( | ||||||
|  |                         max_length=10, null=True, db_column="type", blank=True | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("content", models.CharField(max_length=65535, null=True, blank=True)), | ||||||
|  |                 ("ttl", models.IntegerField(null=True)), | ||||||
|  |                 ("prio", models.IntegerField(null=True)), | ||||||
|  |                 ("change_date", models.IntegerField(null=True)), | ||||||
|  |                 ("disabled", models.BooleanField(default=False)), | ||||||
|  |                 ("ordername", models.CharField(max_length=255)), | ||||||
|  |                 ("auth", models.BooleanField(default=True)), | ||||||
|  |                 ( | ||||||
|  |                     "domain", | ||||||
|  |                     models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE), | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|             options={ |             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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('ip', models.GenericIPAddressField()), |                         verbose_name="ID", | ||||||
|                 ('nameserver', models.CharField(max_length=255)), |                         serialize=False, | ||||||
|                 ('customer', models.ForeignKey( |                         auto_created=True, | ||||||
|                     verbose_name='customer', to=settings.AUTH_USER_MODEL, |                         primary_key=True, | ||||||
|                     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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, |                     "id", | ||||||
|                     auto_created=True, primary_key=True)), |                     models.AutoField( | ||||||
|                 ('name', models.CharField(max_length=255)), |                         verbose_name="ID", | ||||||
|                 ('algorithm', models.CharField(max_length=50)), |                         serialize=False, | ||||||
|                 ('secret', models.CharField(max_length=255)), |                         auto_created=True, | ||||||
|  |                         primary_key=True, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("name", models.CharField(max_length=255)), | ||||||
|  |                 ("algorithm", models.CharField(max_length=50)), | ||||||
|  |                 ("secret", models.CharField(max_length=255)), | ||||||
|             ], |             ], | ||||||
|         ), |         ), | ||||||
|         migrations.RunSQL( |         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( |             field=models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE), | ||||||
|                 to='domains.DNSDomain', on_delete=models.CASCADE), |  | ||||||
|         ), |         ), | ||||||
|         migrations.AddField( |         migrations.AddField( | ||||||
|             model_name='dnscomment', |             model_name="dnscomment", | ||||||
|             name='domain', |             name="domain", | ||||||
|             field=models.ForeignKey( |             field=models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE), | ||||||
|                 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")}, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,44 +1,87 @@ | ||||||
| # -*- 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={'verbose_name': 'DNS comment', 'verbose_name_plural': 'DNS comments'}, |             options={ | ||||||
|  |                 "verbose_name": "DNS comment", | ||||||
|  |                 "verbose_name_plural": "DNS comments", | ||||||
|  |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.AlterModelOptions( |         migrations.AlterModelOptions( | ||||||
|             name='dnscryptokey', |             name="dnscryptokey", | ||||||
|             options={'verbose_name': 'DNS crypto key', 'verbose_name_plural': 'DNS crypto keys'}, |             options={ | ||||||
|  |                 "verbose_name": "DNS crypto key", | ||||||
|  |                 "verbose_name_plural": "DNS crypto keys", | ||||||
|  |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.AlterModelOptions( |         migrations.AlterModelOptions( | ||||||
|             name='dnsdomainmetadata', |             name="dnsdomainmetadata", | ||||||
|             options={'verbose_name': 'DNS domain metadata item', 'verbose_name_plural': 'DNS domain metadata items'}, |             options={ | ||||||
|  |                 "verbose_name": "DNS domain metadata item", | ||||||
|  |                 "verbose_name_plural": "DNS domain metadata items", | ||||||
|  |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.AlterModelOptions( |         migrations.AlterModelOptions( | ||||||
|             name='dnssupermaster', |             name="dnssupermaster", | ||||||
|             options={'verbose_name': 'DNS supermaster', 'verbose_name_plural': 'DNS supermasters'}, |             options={ | ||||||
|  |                 "verbose_name": "DNS supermaster", | ||||||
|  |                 "verbose_name_plural": "DNS supermasters", | ||||||
|  |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.AlterModelOptions( |         migrations.AlterModelOptions( | ||||||
|             name='dnstsigkey', |             name="dnstsigkey", | ||||||
|             options={'verbose_name': 'DNS TSIG key', 'verbose_name_plural': 'DNS TSIG keys'}, |             options={ | ||||||
|  |                 "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(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')]), |             field=models.CharField( | ||||||
|  |                 max_length=32, | ||||||
|  |                 choices=[ | ||||||
|  |                     ("ALLOW-DNSUPDATE-FROM", "ALLOW-DNSUPDATE-FROM"), | ||||||
|  |                     ("ALSO-NOTIFY", "ALSO-NOTIFY"), | ||||||
|  |                     ("AXFR-MASTER-TSIG", "AXFR-MASTER-TSIG"), | ||||||
|  |                     ("AXFR-SOURCE", "AXFR-SOURCE"), | ||||||
|  |                     ("FORWARD-DNSUPDATE", "FORWARD-DNSUPDATE"), | ||||||
|  |                     ("GSS-ACCEPTOR-PRINCIPAL", "GSS-ACCEPTOR-PRINCIPAL"), | ||||||
|  |                     ("GSS-ALLOW-AXFR-PRINCIPAL", "GSS-ALLOW-AXFR-PRINCIPAL"), | ||||||
|  |                     ("LUA-AXFR-SCRIPT", "LUA-AXFR-SCRIPT"), | ||||||
|  |                     ("NSEC3NARROW", "NSEC3NARROW"), | ||||||
|  |                     ("NSEC3PARAM", "NSEC3PARAM"), | ||||||
|  |                     ("PRESIGNED", "PRESIGNED"), | ||||||
|  |                     ("PUBLISH_CDNSKEY", "PUBLISH_CDNSKEY"), | ||||||
|  |                     ("PUBLISH_CDS", "PUBLISH_CDS"), | ||||||
|  |                     ("SOA-EDIT", "SOA-EDIT"), | ||||||
|  |                     ("SOA-EDIT-DNSUPDATE", "SOA-EDIT-DNSUPDATE"), | ||||||
|  |                     ("TSIG-ALLOW-AXFR", "TSIG-ALLOW-AXFR"), | ||||||
|  |                     ("TSIG-ALLOW-DNSUPDATE", "TSIG-ALLOW-DNSUPDATE"), | ||||||
|  |                 ], | ||||||
|  |             ), | ||||||
|         ), |         ), | ||||||
|         migrations.AlterField( |         migrations.AlterField( | ||||||
|             model_name='dnstsigkey', |             model_name="dnstsigkey", | ||||||
|             name='algorithm', |             name="algorithm", | ||||||
|             field=models.CharField(max_length=50, choices=[('hmac-md5', 'HMAC MD5'), ('hmac-sha1', 'HMAC SHA1'), ('hmac-sha224', 'HMAC SHA224'), ('hmac-sha256', 'HMAC SHA256'), ('hmac-sha384', 'HMAC SHA384'), ('hmac-sha512', 'HMAC SHA512')]), |             field=models.CharField( | ||||||
|  |                 max_length=50, | ||||||
|  |                 choices=[ | ||||||
|  |                     ("hmac-md5", "HMAC MD5"), | ||||||
|  |                     ("hmac-sha1", "HMAC SHA1"), | ||||||
|  |                     ("hmac-sha224", "HMAC SHA224"), | ||||||
|  |                     ("hmac-sha256", "HMAC SHA256"), | ||||||
|  |                     ("hmac-sha384", "HMAC SHA384"), | ||||||
|  |                     ("hmac-sha512", "HMAC SHA512"), | ||||||
|  |                 ], | ||||||
|  |             ), | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | # Generated by Django 3.2.18 on 2023-04-15 09:53 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('domains', '0004_auto_20151107_1708'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterIndexTogether( | ||||||
|  |             name='dnscomment', | ||||||
|  |             index_together=None, | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnscomment', | ||||||
|  |             name='customer', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnscomment', | ||||||
|  |             name='domain', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnscryptokey', | ||||||
|  |             name='domain', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnsdomain', | ||||||
|  |             name='customer', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnsdomainmetadata', | ||||||
|  |             name='domain', | ||||||
|  |         ), | ||||||
|  |         migrations.AlterIndexTogether( | ||||||
|  |             name='dnsrecord', | ||||||
|  |             index_together=None, | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnsrecord', | ||||||
|  |             name='domain', | ||||||
|  |         ), | ||||||
|  |         migrations.AlterUniqueTogether( | ||||||
|  |             name='dnssupermaster', | ||||||
|  |             unique_together=None, | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='dnssupermaster', | ||||||
|  |             name='customer', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSTSIGKey', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSComment', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSCryptoKey', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSDomain', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSDomainMetadata', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSRecord', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='DNSSupermaster', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -2,52 +2,12 @@ | ||||||
| This module contains models related to domain names. | This module contains models related to domain names. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.db import models, transaction |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.utils.encoding import python_2_unicode_compatible | from django.db import models, transaction | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import gettext 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): | ||||||
|  | @ -55,16 +15,20 @@ 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, verbose_name=_('customer'), blank=True, |         settings.AUTH_USER_MODEL, | ||||||
|         null=True, on_delete=models.CASCADE) |         verbose_name=_("customer"), | ||||||
|  |         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 | ||||||
|  | @ -72,9 +36,10 @@ class MailDomain(DomainBase): | ||||||
|     domains. |     domains. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     class Meta: | 
 | ||||||
|         verbose_name = _('Mail domain') |     class Meta(DomainBase.Meta): | ||||||
|         verbose_name_plural = _('Mail domains') |         verbose_name = _("Mail domain") | ||||||
|  |         verbose_name_plural = _("Mail domains") | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.domain |         return self.domain | ||||||
|  | @ -85,6 +50,7 @@ class MailDomain(DomainBase): | ||||||
| 
 | 
 | ||||||
|         """ |         """ | ||||||
|         return self.mailaddress_set.all() |         return self.mailaddress_set.all() | ||||||
|  | 
 | ||||||
|     mailaddresses = property(get_mailaddresses) |     mailaddresses = property(get_mailaddresses) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -93,339 +59,47 @@ 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( |     def create_for_hosting_package(self, hosting_package, domain, commit, **kwargs): | ||||||
|         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, verbose_name=_('mail domain'), blank=True, null=True, |         MailDomain, | ||||||
|         help_text=_('assigned mail domain for this domain'), |         verbose_name=_("mail 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) |  | ||||||
|  |  | ||||||
|  | @ -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 ugettext as _ | from django.utils.translation import gettext as _ | ||||||
| 
 | 
 | ||||||
| from domains.forms import relative_domain_validator, CreateHostingDomainForm | from domains.forms import CreateHostingDomainForm, relative_domain_validator | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RelativeDomainValidatorTest(TestCase): | class RelativeDomainValidatorTest(TestCase): | ||||||
|  |  | ||||||
|  | @ -7,19 +7,9 @@ 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 ( | from domains.models import HostingDomain, MailDomain | ||||||
|     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() | ||||||
| 
 | 
 | ||||||
|  | @ -77,49 +67,3 @@ 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") |  | ||||||
|  |  | ||||||
|  | @ -2,14 +2,16 @@ | ||||||
| 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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import url | from django.urls import re_path | ||||||
| 
 | 
 | ||||||
| from .views import CreateHostingDomain | from .views import CreateHostingDomain | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     url(r'^(?P<package>\d+)/create$', CreateHostingDomain.as_view(), |     re_path( | ||||||
|         name='create_hosting_domain'), |         r"^(?P<package>\d+)/create$", | ||||||
|  |         CreateHostingDomain.as_view(), | ||||||
|  |         name="create_hosting_domain", | ||||||
|  |     ), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -2,20 +2,21 @@ | ||||||
| This module defines views related to domains. | This module defines views related to domains. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| 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 ugettext as _ | from django.utils.translation import gettext 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(StaffuserRequiredMixin, CreateView): | class CreateHostingDomain(PermissionRequiredMixin, 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. | ||||||
|  | @ -23,6 +24,7 @@ class CreateHostingDomain(StaffuserRequiredMixin, 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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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.12.1' | __version__ = "0.13.0" | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								gnuviechadmin/gnuviechadmin/auth.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								gnuviechadmin/gnuviechadmin/auth.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | from allauth.account.adapter import DefaultAccountAdapter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NoNewUsersAccountAdapter(DefaultAccountAdapter): | ||||||
|  |     """ | ||||||
|  |     Adapter to disable allauth new signups | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def is_open_for_signup(self, request): | ||||||
|  |         return False | ||||||
|  | @ -6,15 +6,15 @@ from celery import Celery | ||||||
| 
 | 
 | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| 
 | 
 | ||||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings") | ||||||
|                       '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) | ||||||
|  |  | ||||||
|  | @ -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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
|  | @ -22,38 +22,42 @@ def navigation(request): | ||||||
|     :rtype: dict |     :rtype: dict | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     if request.is_ajax(): |     if request.headers.get("x-requested-with") == "XMLHttpRequest": | ||||||
|         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', 'osusers.views', 'userdbs.views', |             "hostingpackages.views", | ||||||
|             'managemails.views', 'websites.views', 'domains.views', |             "osusers.views", | ||||||
|  |             "userdbs.views", | ||||||
|  |             "managemails.views", | ||||||
|  |             "websites.views", | ||||||
|  |             "domains.views", | ||||||
|         ): |         ): | ||||||
|             context['active_item'] = 'hostingpackage' |             context["active_item"] = "hostingpackage" | ||||||
|         elif viewmodule in ( |         elif viewmodule in ("allauth.account.views", "allauth.socialaccount.views"): | ||||||
|             'allauth.account.views', 'allauth.socialaccount.views' |             context["active_item"] = "account" | ||||||
|  |         elif viewmodule == "django.contrib.flatpages.views" and request.path.endswith( | ||||||
|  |             "/impressum/" | ||||||
|         ): |         ): | ||||||
|             context['active_item'] = 'account' |             context["active_item"] = "imprint" | ||||||
|         elif ( |         elif not viewmodule.startswith("django.contrib.admin"): | ||||||
|             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__, viewmodule, context['active_item']) |                 viewfunc.__name__, | ||||||
|  |                 viewmodule, | ||||||
|  |                 context["active_item"], | ||||||
|  |             ) | ||||||
|     return context |     return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -64,6 +68,6 @@ def version_info(request): | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     context = { |     context = { | ||||||
|         'gnuviechadmin_version': gvaversion, |         "gnuviechadmin_version": gvaversion, | ||||||
|     } |     } | ||||||
|     return context |     return context | ||||||
|  |  | ||||||
|  | @ -8,10 +8,8 @@ Common settings and globals. | ||||||
| from os.path import abspath, basename, dirname, join, normpath | from os.path import abspath, basename, dirname, join, normpath | ||||||
| 
 | 
 | ||||||
| from django.contrib.messages import constants as messages | from django.contrib.messages import constants as messages | ||||||
| 
 |  | ||||||
| from gvacommon.settings_utils import get_env_variable | from gvacommon.settings_utils import get_env_variable | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| # ######### PATH CONFIGURATION | # ######### PATH CONFIGURATION | ||||||
| # Absolute filesystem path to the Django project directory: | # Absolute filesystem path to the Django project directory: | ||||||
| DJANGO_ROOT = dirname(dirname(abspath(__file__))) | DJANGO_ROOT = dirname(dirname(abspath(__file__))) | ||||||
|  | @ -24,10 +22,11 @@ SITE_NAME = basename(DJANGO_ROOT) | ||||||
| 
 | 
 | ||||||
| # ######### END PATH CONFIGURATION | # ######### END PATH CONFIGURATION | ||||||
| 
 | 
 | ||||||
|  | GVA_ENVIRONMENT = get_env_variable("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 = False | DEBUG = GVA_ENVIRONMENT == "local" | ||||||
| # ######### END DEBUG CONFIGURATION | # ######### END DEBUG CONFIGURATION | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -57,6 +56,8 @@ DATABASES = { | ||||||
|         "PORT": get_env_variable("GVA_PGSQL_PORT", int, default=5432), |         "PORT": get_env_variable("GVA_PGSQL_PORT", int, default=5432), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | DEFAULT_AUTO_FIELD = "django.db.models.AutoField" | ||||||
| # ######### END DATABASE CONFIGURATION | # ######### END DATABASE CONFIGURATION | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -85,7 +86,6 @@ 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")) | ||||||
|  | @ -179,7 +179,6 @@ 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 | ||||||
|  | @ -207,6 +206,10 @@ 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 = ( | ||||||
|  | @ -215,7 +218,6 @@ 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. | ||||||
|  | @ -233,6 +235,8 @@ LOCAL_APPS = ( | ||||||
|     "userdbs", |     "userdbs", | ||||||
|     "hostingpackages", |     "hostingpackages", | ||||||
|     "websites", |     "websites", | ||||||
|  |     "help", | ||||||
|  |     "invoices", | ||||||
|     "contact_form", |     "contact_form", | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -250,18 +254,38 @@ 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_TEMPLATE_PACK = "bootstrap3" | CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" | ||||||
|  | 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 | ||||||
|  | @ -281,20 +305,45 @@ 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": get_env_variable("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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -351,8 +400,6 @@ GVA_LINK_PHPPGADMIN = get_env_variable( | ||||||
| ) | ) | ||||||
| # ######### 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 = "/srv/gva/static/" | STATIC_ROOT = "/srv/gva/static/" | ||||||
|  | @ -362,11 +409,22 @@ def show_debug_toolbar(request): | ||||||
|     return DEBUG and GVA_ENVIRONMENT == "local" |     return DEBUG and GVA_ENVIRONMENT == "local" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if GVA_ENVIRONMENT == "local": | # ######### TOOLBAR CONFIGURATION | ||||||
|     # ######### DEBUG CONFIGURATION | # See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa | ||||||
|     # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug | INSTALLED_APPS += ("debug_toolbar",) | ||||||
|     DEBUG = True |  | ||||||
| 
 | 
 | ||||||
|  | MIDDLEWARE += [ | ||||||
|  |     "impersonate.middleware.ImpersonateMiddleware", | ||||||
|  |     "debug_toolbar.middleware.DebugToolbarMiddleware", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | DEBUG_TOOLBAR_CONFIG = { | ||||||
|  |     "SHOW_TOOLBAR_CALLBACK": "gnuviechadmin.settings.show_debug_toolbar" | ||||||
|  | } | ||||||
|  | # ######### END TOOLBAR CONFIGURATION | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if GVA_ENVIRONMENT == "local": | ||||||
|     # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug |     # 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 | ||||||
|  | @ -381,12 +439,6 @@ 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": { | ||||||
|  | @ -400,32 +452,10 @@ if GVA_ENVIRONMENT == "local": | ||||||
|         dict( |         dict( | ||||||
|             [ |             [ | ||||||
|                 (key, {"handlers": ["console"], "level": "DEBUG", "propagate": True}) |                 (key, {"handlers": ["console"], "level": "DEBUG", "propagate": True}) | ||||||
|                 for key in [ |                 for key in LOCAL_APPS | ||||||
|                     "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",) | ||||||
|  | @ -442,25 +472,15 @@ elif GVA_ENVIRONMENT == "test": | ||||||
|         dict( |         dict( | ||||||
|             [ |             [ | ||||||
|                 (key, {"handlers": ["console"], "level": "ERROR", "propagate": True}) |                 (key, {"handlers": ["console"], "level": "ERROR", "propagate": True}) | ||||||
|                 for key in [ |                 for key in LOCAL_APPS | ||||||
|                     "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: | ||||||
|  |  | ||||||
|  | @ -18,13 +18,16 @@ 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("/", HTTP_X_REQUESTED_WITH="XMLHttpRequest") |         response = self.client.get("/accounts/login/", 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) | ||||||
| 
 | 
 | ||||||
|  | @ -34,6 +37,12 @@ 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) | ||||||
|  | @ -47,15 +56,6 @@ 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("/") |         response = self.client.get("/accounts/login/") | ||||||
|         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) | ||||||
|  |  | ||||||
|  | @ -1,36 +1,49 @@ | ||||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import include, url | import debug_toolbar | ||||||
| from django.conf import settings | from django.conf.urls import include | ||||||
| 
 |  | ||||||
| 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 = [ | ||||||
|     url(r'', include('dashboard.urls')), |     path("", include("dashboard.urls")), | ||||||
|     url(r'^accounts/', include('allauth.urls')), |     path("api/users/", help_views.ListHelpUserAPIView.as_view()), | ||||||
|     url(r'^database/', include('userdbs.urls')), |     path( | ||||||
|     url(r'^domains/', include('domains.urls')), |         "api/users/<int:pk>/", | ||||||
|     url(r'^hosting/', include('hostingpackages.urls')), |         help_views.HelpUserAPIView.as_view(), | ||||||
|     url(r'^website/', include('websites.urls')), |         name="helpuser-detail", | ||||||
|     url(r'^mail/', include('managemails.urls')), |     ), | ||||||
|     url(r'^osuser/', include('osusers.urls')), |     path("api/invoices/", invoice_views.ListInvoiceAPIView.as_view()), | ||||||
|     url(r'^admin/', admin.site.urls), |     path( | ||||||
|     url(r'^contact/', include('contact_form.urls')), |         "api/invoices/<invoice_number>/", | ||||||
|     url(r'^impressum/$', views.flatpage, { |         invoice_views.InvoiceAPIView.as_view(), | ||||||
|         'url': '/impressum/' |         name="invoice-detail", | ||||||
|     }, 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"), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # 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) | ||||||
| 
 | 
 | ||||||
| if settings.DEBUG:  # pragma: no cover | urlpatterns += staticfiles_urlpatterns() | ||||||
|     import debug_toolbar | urlpatterns += [ | ||||||
| 
 |     path("__debug__/", include(debug_toolbar.urls)), | ||||||
|     urlpatterns = [ | ] | ||||||
|         url(r'^__debug__/', include(debug_toolbar.urls)), |  | ||||||
|     ] + staticfiles_urlpatterns() + urlpatterns |  | ||||||
|  |  | ||||||
|  | @ -3,11 +3,10 @@ This module defines form classes that can be extended by other gnuviechadmin | ||||||
| apps' forms. | apps' forms. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django import forms | from django import forms | ||||||
| from django.utils.translation import ugettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| PASSWORD_MISMATCH_ERROR = _("Passwords don't match") | PASSWORD_MISMATCH_ERROR = _("Passwords don't match") | ||||||
| """ | """ | ||||||
|  | @ -21,11 +20,14 @@ 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'), widget=forms.PasswordInput, |         label=_("Password"), | ||||||
|  |         widget=forms.PasswordInput, | ||||||
|     ) |     ) | ||||||
|     password2 = forms.CharField( |     password2 = forms.CharField( | ||||||
|         label=_('Password (again)'), widget=forms.PasswordInput, |         label=_("Password (again)"), | ||||||
|  |         widget=forms.PasswordInput, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     def clean_password2(self): |     def clean_password2(self): | ||||||
|  | @ -36,8 +38,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 | ||||||
|  |  | ||||||
|  | @ -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: 2016-01-29 11:04+0100\n" | "POT-Creation-Date: 2023-04-16 22:07+0200\n" | ||||||
| "PO-Revision-Date: 2015-01-25 11:49+0100\n" | "PO-Revision-Date: 2023-04-16 18:21+0200\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 1.6.10\n" | "X-Generator: Poedit 3.2.2\n" | ||||||
| "X-Poedit-SourceCharset: UTF-8\n" | "X-Poedit-SourceCharset: UTF-8\n" | ||||||
| 
 | 
 | ||||||
| #: gvawebcore/forms.py:12 | #: gvawebcore/forms.py:11 | ||||||
| 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:28 | #: gvawebcore/forms.py:29 | ||||||
| msgid "Password (again)" | msgid "Password (again)" | ||||||
| msgstr "Passwortwiederholung" | msgstr "Passwortwiederholung" | ||||||
|  |  | ||||||
|  | @ -2,9 +2,10 @@ | ||||||
| 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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -14,7 +15,8 @@ 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 | ||||||
|  | @ -22,8 +24,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, |                 CustomerHostingPackage, pk=int(self.kwargs[self.hosting_package_kwarg]) | ||||||
|                 pk=int(self.kwargs[self.hosting_package_kwarg])) |             ) | ||||||
|         return self.hostingpackage |         return self.hostingpackage | ||||||
| 
 | 
 | ||||||
|     def get_customer_object(self): |     def get_customer_object(self): | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								gnuviechadmin/help/admin.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								gnuviechadmin/help/admin.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | from django.contrib import admin | ||||||
|  | from django.contrib.auth import get_user_model | ||||||
|  | from django.contrib.auth.admin import UserAdmin as BaseUserAdmin | ||||||
|  | from django.utils.translation import ugettext_lazy as _ | ||||||
|  | 
 | ||||||
|  | from help.models import HelpUser | ||||||
|  | 
 | ||||||
|  | User = get_user_model() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class HelpUserInline(admin.StackedInline): | ||||||
|  |     model = HelpUser | ||||||
|  |     can_delete = False | ||||||
|  |     readonly_fields = ("offline_account_code",) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UserAdmin(BaseUserAdmin): | ||||||
|  |     inlines = (HelpUserInline,) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | admin.site.unregister(User) | ||||||
|  | admin.site.register(User, UserAdmin) | ||||||
							
								
								
									
										8
									
								
								gnuviechadmin/help/apps.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								gnuviechadmin/help/apps.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class HelpConfig(AppConfig): | ||||||
|  |     default_auto_field = "django.db.models.BigAutoField" | ||||||
|  |     name = "help" | ||||||
|  |     verbose_name = _("User self help") | ||||||
							
								
								
									
										36
									
								
								gnuviechadmin/help/locale/de/LC_MESSAGES/django.po
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								gnuviechadmin/help/locale/de/LC_MESSAGES/django.po
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | # SOME DESCRIPTIVE TITLE. | ||||||
|  | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | ||||||
|  | # This file is distributed under the same license as the PACKAGE package. | ||||||
|  | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | ||||||
|  | # | ||||||
|  | msgid "" | ||||||
|  | msgstr "" | ||||||
|  | "Project-Id-Version: help\n" | ||||||
|  | "Report-Msgid-Bugs-To: \n" | ||||||
|  | "POT-Creation-Date: 2023-04-16 22:07+0200\n" | ||||||
|  | "PO-Revision-Date: 2023-04-16 18:21+0200\n" | ||||||
|  | "Last-Translator: \n" | ||||||
|  | "Language-Team: Jan Dittberner <jan@dittberner.info>\n" | ||||||
|  | "Language: de\n" | ||||||
|  | "MIME-Version: 1.0\n" | ||||||
|  | "Content-Type: text/plain; charset=UTF-8\n" | ||||||
|  | "Content-Transfer-Encoding: 8bit\n" | ||||||
|  | "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||||||
|  | "X-Generator: Poedit 3.2.2\n" | ||||||
|  | "X-Poedit-SourceCharset: UTF-8\n" | ||||||
|  | 
 | ||||||
|  | #: help/apps.py:8 | ||||||
|  | msgid "User self help" | ||||||
|  | msgstr "Selbsthilfe für Nutzer" | ||||||
|  | 
 | ||||||
|  | #: help/models.py:10 | ||||||
|  | msgid "Contact email address" | ||||||
|  | msgstr "Kontakt-E-Mail-Adresse" | ||||||
|  | 
 | ||||||
|  | #: help/models.py:11 | ||||||
|  | msgid "Contact postal address" | ||||||
|  | msgstr "Kontakt-Postanschrift" | ||||||
|  | 
 | ||||||
|  | #: help/models.py:13 | ||||||
|  | msgid "Offline account reset code" | ||||||
|  | msgstr "Offline-Code für die Konto-Rücksetzung" | ||||||
							
								
								
									
										0
									
								
								gnuviechadmin/help/management/commands/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/help/management/commands/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								gnuviechadmin/help/management/commands/populate.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								gnuviechadmin/help/management/commands/populate.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | from django.contrib.auth import get_user_model | ||||||
|  | from django.core.management import BaseCommand | ||||||
|  | 
 | ||||||
|  | from help.models import HelpUser | ||||||
|  | 
 | ||||||
|  | User = get_user_model() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Command(BaseCommand): | ||||||
|  |     help = "Populate help user information for existing users" | ||||||
|  | 
 | ||||||
|  |     def handle(self, *args, **options): | ||||||
|  |         for user in User.objects.filter(helpuser=None): | ||||||
|  |             help_user = HelpUser.objects.create(user_id=user.id, email_address=user.email) | ||||||
|  |             help_user.generate_offline_account_code() | ||||||
|  |             help_user.save() | ||||||
|  |             self.stdout.write(f"created offline account code {help_user.offline_account_code} for {user}.") | ||||||
							
								
								
									
										29
									
								
								gnuviechadmin/help/management/commands/reset_offline_code.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								gnuviechadmin/help/management/commands/reset_offline_code.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | from django.contrib.auth import get_user_model | ||||||
|  | from django.core.management import BaseCommand, CommandError | ||||||
|  | 
 | ||||||
|  | from help.models import HelpUser | ||||||
|  | 
 | ||||||
|  | User = get_user_model() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Command(BaseCommand): | ||||||
|  |     help = "Reset offline account reset code for existing users" | ||||||
|  | 
 | ||||||
|  |     def add_arguments(self, parser): | ||||||
|  |         parser.add_argument("users", nargs='+', type=str) | ||||||
|  | 
 | ||||||
|  |     def handle(self, *args, **options): | ||||||
|  |         for name in options["users"]: | ||||||
|  |             try: | ||||||
|  |                 user = User.objects.get(username=name) | ||||||
|  |             except User.DoesNotExist: | ||||||
|  |                 raise CommandError(f'User {name} does not exist') | ||||||
|  | 
 | ||||||
|  |             help_user = user.helpuser | ||||||
|  |             if help_user is None: | ||||||
|  |                 help_user = HelpUser.objects.create(email_address=user.email) | ||||||
|  | 
 | ||||||
|  |             help_user.generate_offline_account_code() | ||||||
|  |             help_user.save() | ||||||
|  | 
 | ||||||
|  |             self.stdout.write(f"generated new offline account reset code {help_user.offline_account_code} for {name}") | ||||||
|  | @ -0,0 +1,55 @@ | ||||||
|  | # 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, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										0
									
								
								gnuviechadmin/help/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/help/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								gnuviechadmin/help/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								gnuviechadmin/help/models.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | import uuid | ||||||
|  | 
 | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import models | ||||||
|  | from django.utils.translation import ugettext_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()) | ||||||
							
								
								
									
										22
									
								
								gnuviechadmin/help/serializers.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								gnuviechadmin/help/serializers.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | """ | ||||||
|  | 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"] | ||||||
							
								
								
									
										3
									
								
								gnuviechadmin/help/tests.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								gnuviechadmin/help/tests.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | from django.test import TestCase | ||||||
|  | 
 | ||||||
|  | # Create your tests here. | ||||||
							
								
								
									
										26
									
								
								gnuviechadmin/help/views.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								gnuviechadmin/help/views.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | 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 | ||||||
|  | @ -2,4 +2,3 @@ | ||||||
| This app takes care of hosting packages. | This app takes care of hosting packages. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| default_app_config = 'hostingpackages.apps.HostingPackagesAppConfig' |  | ||||||
|  |  | ||||||
|  | @ -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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django import forms | from django import forms | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
|  | @ -25,9 +25,10 @@ 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): | ||||||
|         """ |         """ | ||||||
|  | @ -39,10 +40,11 @@ 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): | ||||||
|  | @ -55,6 +57,7 @@ class CustomerDiskSpaceOptionInline(admin.TabularInline): | ||||||
|     space options. |     space options. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|  | 
 | ||||||
|     model = CustomerDiskSpaceOption |     model = CustomerDiskSpaceOption | ||||||
|     extra = 0 |     extra = 0 | ||||||
| 
 | 
 | ||||||
|  | @ -65,6 +68,7 @@ class CustomerMailboxOptionInline(admin.TabularInline): | ||||||
|     mailbox options. |     mailbox options. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|  | 
 | ||||||
|     model = CustomerMailboxOption |     model = CustomerMailboxOption | ||||||
|     extra = 0 |     extra = 0 | ||||||
| 
 | 
 | ||||||
|  | @ -75,6 +79,7 @@ class CustomerUserDatabaseOptionInline(admin.TabularInline): | ||||||
|     database options. |     database options. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|  | 
 | ||||||
|     model = CustomerUserDatabaseOption |     model = CustomerUserDatabaseOption | ||||||
|     extra = 0 |     extra = 0 | ||||||
| 
 | 
 | ||||||
|  | @ -85,6 +90,7 @@ class CustomerHostingPackageDomainInline(admin.TabularInline): | ||||||
|     hosting packages. |     hosting packages. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|  | 
 | ||||||
|     model = CustomerHostingPackageDomain |     model = CustomerHostingPackageDomain | ||||||
|     extra = 0 |     extra = 0 | ||||||
| 
 | 
 | ||||||
|  | @ -95,12 +101,9 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin): | ||||||
|     :py:class:`CustomerHostingPackage`. |     :py:class:`CustomerHostingPackage`. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|  | 
 | ||||||
|     add_form = CustomerHostingPackageCreateForm |     add_form = CustomerHostingPackageCreateForm | ||||||
|     add_fieldsets = ( |     add_fieldsets = ((None, {"fields": ("customer", "template", "name")}),) | ||||||
|         (None, { |  | ||||||
|             'fields': ('customer', 'template', 'name') |  | ||||||
|         }), |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     inlines = [ |     inlines = [ | ||||||
|         CustomerDiskSpaceOptionInline, |         CustomerDiskSpaceOptionInline, | ||||||
|  | @ -108,7 +111,7 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin): | ||||||
|         CustomerUserDatabaseOptionInline, |         CustomerUserDatabaseOptionInline, | ||||||
|         CustomerHostingPackageDomainInline, |         CustomerHostingPackageDomainInline, | ||||||
|     ] |     ] | ||||||
|     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): | ||||||
|         """ |         """ | ||||||
|  | @ -125,13 +128,16 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin): | ||||||
|         """ |         """ | ||||||
|         defaults = {} |         defaults = {} | ||||||
|         if obj is None: |         if obj is None: | ||||||
|             defaults.update({ |             defaults.update( | ||||||
|                 'form': self.add_form, |                 { | ||||||
|                 'fields': admin.options.flatten_fieldsets(self.add_fieldsets), |                     "form": self.add_form, | ||||||
|             }) |                     "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): | ||||||
|         """ |         """ | ||||||
|  | @ -147,7 +153,7 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin): | ||||||
| 
 | 
 | ||||||
|         """ |         """ | ||||||
|         if obj: |         if obj: | ||||||
|             return ['customer', 'template'] |             return ["customer", "template"] | ||||||
|         return [] |         return [] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,9 +3,8 @@ 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 ugettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class HostingPackagesAppConfig(AppConfig): | class HostingPackagesAppConfig(AppConfig): | ||||||
|  | @ -13,5 +12,6 @@ class HostingPackagesAppConfig(AppConfig): | ||||||
|     AppConfig for the :py:mod:`hostingpackages` app. |     AppConfig for the :py:mod:`hostingpackages` app. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     name = 'hostingpackages' | 
 | ||||||
|     verbose_name = _('Hosting Packages and Options') |     name = "hostingpackages" | ||||||
|  |     verbose_name = _("Hosting Packages and Options") | ||||||
|  |  | ||||||
|  | @ -2,17 +2,13 @@ | ||||||
| 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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 |  | ||||||
| from django import forms |  | ||||||
| from django.urls import reverse |  | ||||||
| from django.utils.translation import ugettext as _ |  | ||||||
| 
 | 
 | ||||||
| from crispy_forms.helper import FormHelper | from crispy_forms.helper import FormHelper | ||||||
| from crispy_forms.layout import ( | from crispy_forms.layout import Layout, Submit | ||||||
|     Layout, | from django import forms | ||||||
|     Submit, | from django.urls import reverse | ||||||
| ) | from django.utils.translation import gettext as _ | ||||||
| 
 | 
 | ||||||
| from .models import ( | from .models import ( | ||||||
|     CustomerDiskSpaceOption, |     CustomerDiskSpaceOption, | ||||||
|  | @ -28,25 +24,24 @@ 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__( |         super(CreateCustomerHostingPackageForm, self).__init__(*args, **kwargs) | ||||||
|             *args, **kwargs |         self.fields["description"].widget.attrs["rows"] = 2 | ||||||
|         ) |  | ||||||
|         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")), | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -55,44 +50,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__( |         super(CreateHostingPackageForm, self).__init__(*args, **kwargs) | ||||||
|             *args, **kwargs |         self.fields["description"].widget.attrs["rows"] = 2 | ||||||
|         ) |  | ||||||
|         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 | ||||||
|  | @ -103,21 +98,22 @@ 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 | ||||||
|  | @ -128,21 +124,22 @@ 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 | ||||||
|  |  | ||||||
|  | @ -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: 2016-01-29 11:04+0100\n" | "POT-Creation-Date: 2023-04-22 13:14+0200\n" | ||||||
| "PO-Revision-Date: 2015-01-25 15:49+0100\n" | "PO-Revision-Date: 2023-04-22 13:15+0200\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,221 +16,561 @@ 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 1.6.10\n" | "X-Generator: Poedit 3.2.2\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:49 hostingpackages/forms.py:74 | #: hostingpackages/forms.py:44 hostingpackages/forms.py:68 | ||||||
| msgid "Add Hosting Package" | msgid "Add Hosting Package" | ||||||
| msgstr "Hostingpaket anlegen" | msgstr "Hostingpaket anlegen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/forms.py:95 | #: hostingpackages/forms.py:90 | ||||||
| msgid "Add disk space option" | msgid "Add disk space option" | ||||||
| msgstr "Speicherplatzoption hinzufügen" | msgstr "Speicherplatzoption hinzufügen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/forms.py:120 | #: hostingpackages/forms.py:116 | ||||||
| msgid "Add mailbox option" | msgid "Add mailbox option" | ||||||
| msgstr "Postfachoption hinzufügen" | msgstr "Postfachoption hinzufügen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/forms.py:145 | #: hostingpackages/forms.py:142 | ||||||
| msgid "Add database option" | msgid "Add database option" | ||||||
| msgstr "Datenbankoption hinzufügen" | msgstr "Datenbankoption hinzufügen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:31 | #: hostingpackages/models.py:21 | ||||||
| msgid "MiB" | msgid "MiB" | ||||||
| msgstr "MiB" | msgstr "MiB" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:32 | #: hostingpackages/models.py:21 | ||||||
| msgid "GiB" | msgid "GiB" | ||||||
| msgstr "GiB" | msgstr "GiB" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:33 | #: hostingpackages/models.py:21 | ||||||
| msgid "TiB" | msgid "TiB" | ||||||
| msgstr "TiB" | msgstr "TiB" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:45 | #: hostingpackages/models.py:27 | ||||||
| msgid "description" | msgid "description" | ||||||
| msgstr "Beschreibung" | msgstr "Beschreibung" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:46 | #: hostingpackages/models.py:28 | ||||||
| msgid "mailbox count" | msgid "mailbox count" | ||||||
| msgstr "Anzahl Postfächer" | msgstr "Anzahl Postfächer" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:48 hostingpackages/models.py:76 | #: hostingpackages/models.py:30 hostingpackages/models.py:59 | ||||||
| msgid "disk space" | msgid "disk space" | ||||||
| msgstr "Speicherplatz" | msgstr "Speicherplatz" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:48 | #: hostingpackages/models.py:30 | ||||||
| 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:50 hostingpackages/models.py:78 | #: hostingpackages/models.py:33 hostingpackages/models.py:61 | ||||||
| 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:60 hostingpackages/models.py:213 | #: hostingpackages/models.py:44 hostingpackages/models.py:192 | ||||||
| msgid "name" | msgid "name" | ||||||
| msgstr "Name" | msgstr "Name" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:63 | #: hostingpackages/models.py:47 | ||||||
| msgid "Hosting package" | msgid "Hosting package" | ||||||
| msgstr "Hostingpaket" | msgstr "Hostingpaket" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:64 | #: hostingpackages/models.py:48 | ||||||
| msgid "Hosting packages" | msgid "Hosting packages" | ||||||
| msgstr "Hostingpakete" | msgstr "Hostingpakete" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:83 | #: hostingpackages/models.py:67 | ||||||
| msgid "Disk space option" | msgid "Disk space option" | ||||||
| msgstr "Speicherplatzoption" | msgstr "Speicherplatzoption" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:84 | #: hostingpackages/models.py:68 | ||||||
| msgid "Disk space options" | msgid "Disk space options" | ||||||
| msgstr "Speicherplatzoptionen" | msgstr "Speicherplatzoptionen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:87 | #: hostingpackages/models.py:71 | ||||||
| #, 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:104 | #: hostingpackages/models.py:88 | ||||||
| msgid "number of databases" | msgid "number of databases" | ||||||
| msgstr "Anzahl von Datenbanken" | msgstr "Anzahl von Datenbanken" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:106 | #: hostingpackages/models.py:89 | ||||||
| msgid "database type" | msgid "database type" | ||||||
| msgstr "Datenbanktyp" | msgstr "Datenbanktyp" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:111 | #: hostingpackages/models.py:94 | ||||||
| msgid "Database option" | msgid "Database option" | ||||||
| msgstr "Datenbankoption" | msgstr "Datenbankoption" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:112 | #: hostingpackages/models.py:95 | ||||||
| msgid "Database options" | msgid "Database options" | ||||||
| msgstr "Datenbankoptionen" | msgstr "Datenbankoptionen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:116 | #: hostingpackages/models.py:99 | ||||||
| #, 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:141 | #: hostingpackages/models.py:120 | ||||||
| msgid "number of mailboxes" | msgid "number of mailboxes" | ||||||
| msgstr "Anzahl von Postfächern" | msgstr "Anzahl von Postfächern" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:146 | #: hostingpackages/models.py:125 | ||||||
| msgid "Mailbox option" | msgid "Mailbox option" | ||||||
| msgstr "Postfachoption" | msgstr "Postfachoption" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:147 | #: hostingpackages/models.py:126 | ||||||
| msgid "Mailbox options" | msgid "Mailbox options" | ||||||
| msgstr "Postfachoptionen" | msgstr "Postfachoptionen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:151 | #: hostingpackages/models.py:130 | ||||||
| #, 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:206 | #: hostingpackages/models.py:182 | ||||||
| msgid "customer" | msgid "customer" | ||||||
| msgstr "Kunde" | msgstr "Kunde" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:208 | #: hostingpackages/models.py:186 | ||||||
| msgid "hosting package template" | msgid "hosting package template" | ||||||
| msgstr "Hostingpaketvorlage" | msgstr "Hostingpaketvorlage" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:210 | #: hostingpackages/models.py:188 | ||||||
| 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:215 | #: hostingpackages/models.py:195 | ||||||
| msgid "Operating system user" | msgid "Operating system user" | ||||||
| msgstr "Betriebssystemnutzer" | msgstr "Betriebssystemnutzer" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:222 | #: hostingpackages/models.py:205 | ||||||
| msgid "customer hosting package" | msgid "customer hosting package" | ||||||
| msgstr "Kundenhostingpaket" | msgstr "Kundenhostingpaket" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:223 | #: hostingpackages/models.py:206 | ||||||
| msgid "customer hosting packages" | msgid "customer hosting packages" | ||||||
| msgstr "Kundenhostingpakete" | msgstr "Kundenhostingpakete" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:226 | #: hostingpackages/models.py:209 | ||||||
| #, 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:404 hostingpackages/models.py:426 | #: hostingpackages/models.py:388 hostingpackages/models.py:415 | ||||||
| msgid "hosting package" | msgid "hosting package" | ||||||
| msgstr "Hostingpaket" | msgstr "Hostingpaket" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:407 | #: hostingpackages/models.py:393 | ||||||
| msgid "hosting domain" | msgid "hosting domain" | ||||||
| msgstr "Hostingdomain" | msgstr "Hostingdomain" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:429 | #: hostingpackages/models.py:420 | ||||||
| msgid "customer hosting option" | msgid "customer hosting option" | ||||||
| msgstr "kundenspezifische Hostingoption" | msgstr "kundenspezifische Hostingoption" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:430 | #: hostingpackages/models.py:421 | ||||||
| msgid "customer hosting options" | msgid "customer hosting options" | ||||||
| msgstr "kundenspezifische Hostingoptionen" | msgstr "kundenspezifische Hostingoptionen" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:442 | #: hostingpackages/models.py:433 | ||||||
| msgid "disk space option template" | msgid "disk space option template" | ||||||
| msgstr "Speicherplatzoptionsvorlage" | msgstr "Speicherplatzoptionsvorlage" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:444 | #: hostingpackages/models.py:435 | ||||||
| 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:458 | #: hostingpackages/models.py:450 | ||||||
| msgid "user database option template" | msgid "user database option template" | ||||||
| msgstr "Nutzerdatenbankoptionsvorlage" | msgstr "Nutzerdatenbankoptionsvorlage" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:460 | #: hostingpackages/models.py:452 | ||||||
| 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:474 | #: hostingpackages/models.py:467 | ||||||
| msgid "mailbox option template" | msgid "mailbox option template" | ||||||
| msgstr "Postfachoptionsvorlage" | msgstr "Postfachoptionsvorlage" | ||||||
| 
 | 
 | ||||||
| #: hostingpackages/models.py:476 | #: hostingpackages/models.py:468 | ||||||
| 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/views.py:60 hostingpackages/views.py:94 | #: 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 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:26 | ||||||
|  | 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_list.html:27 | ||||||
|  | msgid "Setup date" | ||||||
|  | msgstr "Einrichtungsdatum" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:31 | ||||||
|  | msgid "No hosting packages have been setup yet." | ||||||
|  | msgstr "Es wurden noch keine Hostingpakete eingerichtet." | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_admin_list.html:34 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:46 | ||||||
|  | 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:40 | ||||||
|  | #: hostingpackages/views.py:199 | ||||||
|  | msgid "Disk space" | ||||||
|  | msgstr "Speicherplatz" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:43 | ||||||
|  | #, 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_detail.html:47 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "The package contributes %(humanbytes)s (%(packagespace)s bytes) the " | ||||||
|  | "difference comes from disk space options" | ||||||
|  | msgstr "" | ||||||
|  | "Das Paket trägt %(humanbytes)s (%(packagespace)s Bytes) zur Gesamtgröße bei, " | ||||||
|  | "der Unterschied ergibt sich aus Speicherplatzoptionen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:52 | ||||||
|  | #: hostingpackages/views.py:206 | ||||||
|  | msgid "Mailboxes" | ||||||
|  | msgstr "Postfächer" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:54 | ||||||
|  | #, python-format | ||||||
|  | msgid "%(num)s of %(total)s in use" | ||||||
|  | msgstr "%(num)s von %(total)s genutzt" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:57 | ||||||
|  | #, 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:59 | ||||||
|  | msgid "SFTP username" | ||||||
|  | msgstr "SFTP-Benutzername" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:60 | ||||||
|  | msgid "SSH/SFTP username" | ||||||
|  | msgstr "SSH/SFTP-Benutzername" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:63 | ||||||
|  | #, 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:65 | ||||||
|  | msgid "Upload server" | ||||||
|  | msgstr "Uploadserver" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:73 | ||||||
|  | msgid "Hosting Package Options" | ||||||
|  | msgstr "Hostingpaketoptionen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:81 | ||||||
|  | msgid "No options booked" | ||||||
|  | msgstr "Keine Optionen gebucht" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:87 | ||||||
|  | msgid "Add another hosting option" | ||||||
|  | msgstr "Eine weitere Hostingoption hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:87 | ||||||
|  | msgid "Add option" | ||||||
|  | msgstr "Option hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:94 | ||||||
|  | msgid "Hosting Package Actions" | ||||||
|  | msgstr "Aktionen zum Hostingpaket" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:98 | ||||||
|  | msgid "Edit Hosting Package Description" | ||||||
|  | msgstr "Beschreibung des Hostingpakets bearbeiten" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:98 | ||||||
|  | msgid "Edit description" | ||||||
|  | msgstr "Beschreibung bearbeiten" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:101 | ||||||
|  | msgid "Set SFTP password" | ||||||
|  | msgstr "SFTP-Passwort setzen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:102 | ||||||
|  | msgid "Set SSH/SFTP password" | ||||||
|  | msgstr "SSH/SFTP-Passwort setzen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:105 | ||||||
|  | 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:107 | ||||||
|  | msgid "Add SSH public key" | ||||||
|  | msgstr "SSH-Schlüssel hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:116 | ||||||
|  | msgid "Domains" | ||||||
|  | msgstr "Domains" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:121 | ||||||
|  | msgid "Domain name" | ||||||
|  | msgstr "Domainname" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:122 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:201 | ||||||
|  | msgid "Mail addresses" | ||||||
|  | msgstr "E-Mailadressen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:123 | ||||||
|  | msgid "Websites" | ||||||
|  | msgstr "Webauftritte" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:124 | ||||||
|  | msgid "Domain actions" | ||||||
|  | msgstr "Domainaktionen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:125 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:204 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:247 | ||||||
|  | msgid "Actions" | ||||||
|  | msgstr "Aktionen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:137 | ||||||
|  | msgid "Edit mail address targets" | ||||||
|  | msgstr "E-Mailadressziele bearbeiten" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:139 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:141 | ||||||
|  | msgid "Delete mail address" | ||||||
|  | msgstr "E-Mailadresse löschen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:146 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:161 | ||||||
|  | msgid "None" | ||||||
|  | msgstr "Keine" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:154 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:156 | ||||||
|  | msgid "Delete website" | ||||||
|  | msgstr "Webauftritt löschen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:167 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:169 | ||||||
|  | msgid "Add mail address" | ||||||
|  | msgstr "E-Mailadresse hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:174 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:175 | ||||||
|  | msgid "Add website" | ||||||
|  | msgstr "Webauftritt anlegen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:183 | ||||||
|  | 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:187 | ||||||
|  | msgid "Add domain" | ||||||
|  | msgstr "Domain hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:195 | ||||||
|  | msgid "E-Mail-Accounts" | ||||||
|  | msgstr "E-Mail-Konten" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:200 | ||||||
|  | msgid "Mailbox" | ||||||
|  | msgstr "Postfach" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:202 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:214 | ||||||
|  | msgid "Active" | ||||||
|  | msgstr "Aktiv" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:203 | ||||||
|  | msgid "Mailbox actions" | ||||||
|  | msgstr "Postfachaktionen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:215 | ||||||
|  | msgid "inactive" | ||||||
|  | msgstr "inaktiv" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:218 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:219 | ||||||
|  | msgid "Set mailbox password" | ||||||
|  | msgstr "Postfachpasswort setzen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:225 | ||||||
|  | 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:230 | ||||||
|  | msgid "Add mailbox" | ||||||
|  | msgstr "Postfach hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:237 | ||||||
|  | #: hostingpackages/views.py:213 | ||||||
|  | msgid "Databases" | ||||||
|  | msgstr "Datenbanken" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:242 | ||||||
|  | msgid "Database name" | ||||||
|  | msgstr "Datenbankname" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:243 | ||||||
|  | msgid "Database user" | ||||||
|  | msgstr "Datenbanknutzer" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:244 | ||||||
|  | msgid "Database type" | ||||||
|  | msgstr "Datenbanktyp" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:245 | ||||||
|  | msgid "Type" | ||||||
|  | msgstr "Typ" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:246 | ||||||
|  | msgid "Database actions" | ||||||
|  | msgstr "Datenbankaktionen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:258 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:260 | ||||||
|  | msgid "Set database user password" | ||||||
|  | msgstr "Datenbanknutzerpasswort setzen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:262 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:263 | ||||||
|  | msgid "Delete database" | ||||||
|  | msgstr "Datenbank löschen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_detail.html:270 | ||||||
|  | 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:275 | ||||||
|  | msgid "Add database" | ||||||
|  | msgstr "Datenbank hinzufügen" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:5 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:14 | ||||||
|  | msgid "Your hosting packages" | ||||||
|  | msgstr "Ihre Hostingpakete" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:7 | ||||||
|  | #, python-format | ||||||
|  | msgid "Hosting Packages of %(customer)s" | ||||||
|  | msgstr "Hostingpakete des Kunden %(customer)s" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:16 | ||||||
|  | #, python-format | ||||||
|  | msgid "Hosting Packages <small>of %(customer)s</small>" | ||||||
|  | msgstr "Hostingpakete <small>des Kunden %(customer)s</small>" | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:41 | ||||||
|  | msgid "You have no hosting packages setup yet." | ||||||
|  | msgstr "Es wurden noch keine Hostingpakete für Sie eingerichtet." | ||||||
|  | 
 | ||||||
|  | #: hostingpackages/templates/hostingpackages/customerhostingpackage_list.html:42 | ||||||
|  | msgid "There are no hosting packages setup for this customer yet." | ||||||
|  | msgstr "Es wurden noch keine Hostingpakete für diesen Kunden eingerichtet." | ||||||
|  | 
 | ||||||
|  | #: 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:49 hostingpackages/views.py:83 | ||||||
| #, 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:186 | #: hostingpackages/views.py:287 | ||||||
| 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" |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| # -*- 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 | ||||||
|  | @ -14,301 +12,469 @@ class Migration(migrations.Migration): | ||||||
| 
 | 
 | ||||||
|     operations = [ |     operations = [ | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='CustomerHostingPackage', |             name="CustomerHostingPackage", | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('id', models.AutoField( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     default=django.utils.timezone.now, verbose_name='modified', |                     ), | ||||||
|                     editable=False)), |                 ), | ||||||
|                 ('name', models.CharField( |                 ( | ||||||
|                     unique=True, max_length=128, verbose_name='name')), |                     "created", | ||||||
|                 ('description', models.TextField( |                     model_utils.fields.AutoCreatedField( | ||||||
|                     verbose_name='description', blank=True)), |                         default=django.utils.timezone.now, | ||||||
|                 ('mailboxcount', models.PositiveIntegerField( |                         verbose_name="created", | ||||||
|                     verbose_name='mailbox count')), |                         editable=False, | ||||||
|                 ('diskspace', models.PositiveIntegerField( |                     ), | ||||||
|                     help_text='disk space for the hosting package', |                 ), | ||||||
|                     verbose_name='disk space')), |                 ( | ||||||
|                 ('diskspace_unit', models.PositiveSmallIntegerField( |                     "modified", | ||||||
|                     verbose_name='unit of disk space', |                     model_utils.fields.AutoLastModifiedField( | ||||||
|                     choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), |                         default=django.utils.timezone.now, | ||||||
|                 ('customer', models.ForeignKey( |                         verbose_name="modified", | ||||||
|                     verbose_name='customer', to=settings.AUTH_USER_MODEL, |                         editable=False, | ||||||
|                     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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     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( |                 ( | ||||||
|                     parent_link=True, auto_created=True, primary_key=True, |                     "customerhostingpackageoption_ptr", | ||||||
|                     serialize=False, |                     models.OneToOneField( | ||||||
|                     to='hostingpackages.CustomerHostingPackageOption', |                         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.CustomerHostingPackageOption", | ||||||
|                     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=( |             bases=("hostingpackages.customerhostingpackageoption", models.Model), | ||||||
|                 'hostingpackages.customerhostingpackageoption', models.Model), |  | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='CustomerMailboxOption', |             name="CustomerMailboxOption", | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('customerhostingpackageoption_ptr', models.OneToOneField( |                 ( | ||||||
|                     parent_link=True, auto_created=True, primary_key=True, |                     "customerhostingpackageoption_ptr", | ||||||
|                     serialize=False, |                     models.OneToOneField( | ||||||
|                     to='hostingpackages.CustomerHostingPackageOption', |                         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.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=( |             bases=("hostingpackages.customerhostingpackageoption", models.Model), | ||||||
|                 'hostingpackages.customerhostingpackageoption', models.Model), |  | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='CustomerUserDatabaseOption', |             name="CustomerUserDatabaseOption", | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('customerhostingpackageoption_ptr', models.OneToOneField( |                 ( | ||||||
|                     parent_link=True, auto_created=True, primary_key=True, |                     "customerhostingpackageoption_ptr", | ||||||
|                     serialize=False, |                     models.OneToOneField( | ||||||
|                     to='hostingpackages.CustomerHostingPackageOption', |                         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.CustomerHostingPackageOption", | ||||||
|                     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=( |             bases=("hostingpackages.customerhostingpackageoption", models.Model), | ||||||
|                 'hostingpackages.customerhostingpackageoption', models.Model), |  | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='HostingOption', |             name="HostingOption", | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('id', models.AutoField( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     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( |                 ( | ||||||
|                     parent_link=True, auto_created=True, primary_key=True, |                     "hostingoption_ptr", | ||||||
|                     serialize=False, to='hostingpackages.HostingOption', |                     models.OneToOneField( | ||||||
|                     on_delete=models.CASCADE)), |                         parent_link=True, | ||||||
|                 ('diskspace', models.PositiveIntegerField( |                         auto_created=True, | ||||||
|                     verbose_name='disk space')), |                         primary_key=True, | ||||||
|                 ('diskspace_unit', models.PositiveSmallIntegerField( |                         serialize=False, | ||||||
|                     verbose_name='unit of disk space', |                         to="hostingpackages.HostingOption", | ||||||
|                     choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), |                         on_delete=models.CASCADE, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("diskspace", models.PositiveIntegerField(verbose_name="disk space")), | ||||||
|  |                 ( | ||||||
|  |                     "diskspace_unit", | ||||||
|  |                     models.PositiveSmallIntegerField( | ||||||
|  |                         verbose_name="unit of disk space", | ||||||
|  |                         choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|             options={ |             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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     default=django.utils.timezone.now, verbose_name='modified', |                     ), | ||||||
|                     editable=False)), |                 ), | ||||||
|                 ('name', models.CharField( |                 ( | ||||||
|                     unique=True, max_length=128, verbose_name='name')), |                     "created", | ||||||
|                 ('description', models.TextField( |                     model_utils.fields.AutoCreatedField( | ||||||
|                     verbose_name='description', blank=True)), |                         default=django.utils.timezone.now, | ||||||
|                 ('mailboxcount', models.PositiveIntegerField( |                         verbose_name="created", | ||||||
|                     verbose_name='mailbox count')), |                         editable=False, | ||||||
|                 ('diskspace', models.PositiveIntegerField( |                     ), | ||||||
|                     help_text='disk space for the hosting package', |                 ), | ||||||
|                     verbose_name='disk space')), |                 ( | ||||||
|                 ('diskspace_unit', models.PositiveSmallIntegerField( |                     "modified", | ||||||
|                     verbose_name='unit of disk space', |                     model_utils.fields.AutoLastModifiedField( | ||||||
|                     choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), |                         default=django.utils.timezone.now, | ||||||
|  |                         verbose_name="modified", | ||||||
|  |                         editable=False, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "name", | ||||||
|  |                     models.CharField(unique=True, max_length=128, verbose_name="name"), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "description", | ||||||
|  |                     models.TextField(verbose_name="description", blank=True), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "mailboxcount", | ||||||
|  |                     models.PositiveIntegerField(verbose_name="mailbox count"), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "diskspace", | ||||||
|  |                     models.PositiveIntegerField( | ||||||
|  |                         help_text="disk space for the hosting package", | ||||||
|  |                         verbose_name="disk space", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "diskspace_unit", | ||||||
|  |                     models.PositiveSmallIntegerField( | ||||||
|  |                         verbose_name="unit of disk space", | ||||||
|  |                         choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|             options={ |             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( |                 ( | ||||||
|                     parent_link=True, auto_created=True, primary_key=True, |                     "hostingoption_ptr", | ||||||
|                     serialize=False, to='hostingpackages.HostingOption', |                     models.OneToOneField( | ||||||
|                     on_delete=models.CASCADE)), |                         parent_link=True, | ||||||
|                 ('number', models.PositiveIntegerField( |                         auto_created=True, | ||||||
|                     unique=True, verbose_name='number of mailboxes')), |                         primary_key=True, | ||||||
|  |                         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( |                 ( | ||||||
|                     parent_link=True, auto_created=True, primary_key=True, |                     "hostingoption_ptr", | ||||||
|                     serialize=False, to='hostingpackages.HostingOption', |                     models.OneToOneField( | ||||||
|                     on_delete=models.CASCADE)), |                         parent_link=True, | ||||||
|                 ('number', models.PositiveIntegerField( |                         auto_created=True, | ||||||
|                     default=1, verbose_name='number of databases')), |                         primary_key=True, | ||||||
|                 ('db_type', models.PositiveSmallIntegerField( |                         serialize=False, | ||||||
|                     verbose_name='database type', |                         to="hostingpackages.HostingOption", | ||||||
|                     choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), |                         on_delete=models.CASCADE, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "number", | ||||||
|  |                     models.PositiveIntegerField( | ||||||
|  |                         default=1, verbose_name="number of databases" | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "db_type", | ||||||
|  |                     models.PositiveSmallIntegerField( | ||||||
|  |                         verbose_name="database type", | ||||||
|  |                         choices=[(0, "PostgreSQL"), (1, "MySQL")], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|             options={ |             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")}, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| # -*- 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 | ||||||
|  | @ -8,361 +6,530 @@ from django.db import migrations, models | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Migration(migrations.Migration): | class Migration(migrations.Migration): | ||||||
|     replaces = [('hostingpackages', '0001_initial'), |     replaces = [ | ||||||
|                 ('hostingpackages', '0002_auto_20150118_1149'), |         ("hostingpackages", "0001_initial"), | ||||||
|                 ('hostingpackages', '0003_auto_20150118_1221'), |         ("hostingpackages", "0002_auto_20150118_1149"), | ||||||
|                 ('hostingpackages', '0004_customerhostingpackage_osuser'), |         ("hostingpackages", "0003_auto_20150118_1221"), | ||||||
|                 ('hostingpackages', '0005_auto_20150118_1303')] |         ("hostingpackages", "0004_customerhostingpackage_osuser"), | ||||||
|  |         ("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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     default=django.utils.timezone.now, verbose_name='modified', |                     ), | ||||||
|                     editable=False)), |                 ), | ||||||
|                 ('name', models.CharField( |                 ( | ||||||
|                     unique=True, max_length=128, verbose_name='name')), |                     "created", | ||||||
|                 ('description', models.TextField( |                     model_utils.fields.AutoCreatedField( | ||||||
|                     verbose_name='description', blank=True)), |                         default=django.utils.timezone.now, | ||||||
|                 ('mailboxcount', models.PositiveIntegerField( |                         verbose_name="created", | ||||||
|                     verbose_name='mailbox count')), |                         editable=False, | ||||||
|                 ('diskspace', models.PositiveIntegerField( |                     ), | ||||||
|                     help_text='disk space for the hosting package', |                 ), | ||||||
|                     verbose_name='disk space')), |                 ( | ||||||
|                 ('diskspace_unit', models.PositiveSmallIntegerField( |                     "modified", | ||||||
|                     verbose_name='unit of disk space', |                     model_utils.fields.AutoLastModifiedField( | ||||||
|                     choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), |                         default=django.utils.timezone.now, | ||||||
|                 ('customer', models.ForeignKey( |                         verbose_name="modified", | ||||||
|                     verbose_name='customer', |                         editable=False, | ||||||
|                     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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     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=( |             bases=("hostingpackages.customerhostingpackageoption", models.Model), | ||||||
|                 '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=( |             bases=("hostingpackages.customerhostingpackageoption", models.Model), | ||||||
|                 '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=( |             bases=("hostingpackages.customerhostingpackageoption", models.Model), | ||||||
|                 'hostingpackages.customerhostingpackageoption', models.Model), |  | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='HostingOption', |             name="HostingOption", | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('id', models.AutoField( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     default=django.utils.timezone.now, verbose_name='modified', |                     ), | ||||||
|                     editable=False)), |                 ), | ||||||
|                 ('name', models.CharField( |                 ( | ||||||
|                     unique=True, max_length=128, verbose_name='name')), |                     "created", | ||||||
|                 ('description', models.TextField( |                     model_utils.fields.AutoCreatedField( | ||||||
|                     verbose_name='description', blank=True)), |                         default=django.utils.timezone.now, | ||||||
|                 ('mailboxcount', models.PositiveIntegerField( |                         verbose_name="created", | ||||||
|                     verbose_name='mailbox count')), |                         editable=False, | ||||||
|                 ('diskspace', models.PositiveIntegerField( |                     ), | ||||||
|                     help_text='disk space for the hosting package', |                 ), | ||||||
|                     verbose_name='disk space')), |                 ( | ||||||
|                 ('diskspace_unit', models.PositiveSmallIntegerField( |                     "modified", | ||||||
|                     verbose_name='unit of disk space', |                     model_utils.fields.AutoLastModifiedField( | ||||||
|                     choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), |                         default=django.utils.timezone.now, | ||||||
|  |                         verbose_name="modified", | ||||||
|  |                         editable=False, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "name", | ||||||
|  |                     models.CharField(unique=True, max_length=128, verbose_name="name"), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "description", | ||||||
|  |                     models.TextField(verbose_name="description", blank=True), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "mailboxcount", | ||||||
|  |                     models.PositiveIntegerField(verbose_name="mailbox count"), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "diskspace", | ||||||
|  |                     models.PositiveIntegerField( | ||||||
|  |                         help_text="disk space for the hosting package", | ||||||
|  |                         verbose_name="disk space", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "diskspace_unit", | ||||||
|  |                     models.PositiveSmallIntegerField( | ||||||
|  |                         verbose_name="unit of disk space", | ||||||
|  |                         choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|             options={ |             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', |                         to="hostingpackages.HostingOption", | ||||||
|                  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.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, blank=True, to='osusers.User', |                 null=True, | ||||||
|                 verbose_name='Operating system user', on_delete=models.CASCADE), |                 blank=True, | ||||||
|  |                 to="osusers.User", | ||||||
|  |                 verbose_name="Operating system user", | ||||||
|  |                 on_delete=models.CASCADE, | ||||||
|  |             ), | ||||||
|             preserve_default=True, |             preserve_default=True, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,57 +1,59 @@ | ||||||
| # -*- 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, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,18 +1,15 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from __future__ import unicode_literals | from django.db import migrations | ||||||
| 
 |  | ||||||
| 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={}, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,24 +1,21 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from __future__ import unicode_literals | from django.db import migrations, models | ||||||
| 
 |  | ||||||
| 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")]), | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,24 +1,23 @@ | ||||||
| # -*- 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, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,22 +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 = [ | ||||||
|         ('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', blank=True, |                 verbose_name="Operating system user", | ||||||
|                 to='osusers.User', null=True, on_delete=models.CASCADE), |                 blank=True, | ||||||
|  |                 to="osusers.User", | ||||||
|  |                 null=True, | ||||||
|  |                 on_delete=models.CASCADE, | ||||||
|  |             ), | ||||||
|             preserve_default=True, |             preserve_default=True, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| # -*- 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 | ||||||
|  | @ -8,33 +6,59 @@ 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( |                 ( | ||||||
|                     verbose_name='ID', serialize=False, auto_created=True, |                     "id", | ||||||
|                     primary_key=True)), |                     models.AutoField( | ||||||
|                 ('created', model_utils.fields.AutoCreatedField( |                         verbose_name="ID", | ||||||
|                     default=django.utils.timezone.now, verbose_name='created', |                         serialize=False, | ||||||
|                     editable=False)), |                         auto_created=True, | ||||||
|                 ('modified', model_utils.fields.AutoLastModifiedField( |                         primary_key=True, | ||||||
|                     default=django.utils.timezone.now, verbose_name='modified', |                     ), | ||||||
|                     editable=False)), |                 ), | ||||||
|                 ('domain', models.OneToOneField( |                 ( | ||||||
|                     verbose_name='hosting domain', to='domains.HostingDomain', |                     "created", | ||||||
|                     on_delete=models.CASCADE)), |                     model_utils.fields.AutoCreatedField( | ||||||
|                 ('hosting_package', models.ForeignKey( |                         default=django.utils.timezone.now, | ||||||
|                     related_name='domains', verbose_name='hosting package', |                         verbose_name="created", | ||||||
|                     to='hostingpackages.CustomerHostingPackage', |                         editable=False, | ||||||
|                     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,), | ||||||
|         ), |         ), | ||||||
|  |  | ||||||
|  | @ -1,21 +1,23 @@ | ||||||
| # -*- 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, blank=True, to='osusers.User', |                 null=True, | ||||||
|                 verbose_name='Operating system user', on_delete=models.CASCADE), |                 blank=True, | ||||||
|  |                 to="osusers.User", | ||||||
|  |                 verbose_name="Operating system user", | ||||||
|  |                 on_delete=models.CASCADE, | ||||||
|  |             ), | ||||||
|             preserve_default=True, |             preserve_default=True, | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,22 +1,19 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from __future__ import unicode_literals | from django.db import migrations | ||||||
| 
 |  | ||||||
| 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([]), | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -1,22 +1,19 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from __future__ import unicode_literals | from django.db import migrations | ||||||
| 
 |  | ||||||
| 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([]), | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  | @ -2,21 +2,21 @@ | ||||||
| This module contains the hosting package models. | This module contains the hosting package models. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.db import transaction | from django.core.exceptions import ImproperlyConfigured | ||||||
| from django.db import models | from django.db import models, transaction | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.encoding import python_2_unicode_compatible | from django.utils.translation import gettext_lazy as _ | ||||||
| from django.utils.translation import ugettext_lazy as _, ungettext | from django.utils.translation import ngettext | ||||||
| 
 |  | ||||||
| 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, User as OsUser | from osusers.models import AdditionalGroup, Group | ||||||
|  | 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,7 +24,6 @@ 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")) | ||||||
|  | @ -57,7 +56,6 @@ 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( | ||||||
|  | @ -87,7 +85,6 @@ 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) | ||||||
|  | @ -99,7 +96,7 @@ class UserDatabaseOptionBase(models.Model): | ||||||
|         verbose_name_plural = _("Database options") |         verbose_name_plural = _("Database options") | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return ungettext( |         return ngettext( | ||||||
|             "{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) | ||||||
| 
 | 
 | ||||||
|  | @ -115,7 +112,6 @@ 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. | ||||||
|  | @ -131,7 +127,7 @@ class MailboxOptionBase(models.Model): | ||||||
|         verbose_name_plural = _("Mailbox options") |         verbose_name_plural = _("Mailbox options") | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return ungettext( |         return ngettext( | ||||||
|             "{count} additional mailbox", "{count} additional mailboxes", self.number |             "{count} additional mailbox", "{count} additional mailboxes", self.number | ||||||
|         ).format(count=self.number) |         ).format(count=self.number) | ||||||
| 
 | 
 | ||||||
|  | @ -177,7 +173,6 @@ 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. | ||||||
|  | @ -269,7 +264,7 @@ 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 | ||||||
|  | @ -287,7 +282,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 | ||||||
|  | @ -376,13 +371,17 @@ 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: | ||||||
|                 AdditionalGroup.objects.create( |                 try: | ||||||
|                     user=self.osuser, group=Group.objects.get(groupname=group) |                     AdditionalGroup.objects.create( | ||||||
|                 ) |                         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 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | {% 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 %} | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | {% 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 %} | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | {% 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 %} | ||||||
|  | @ -0,0 +1,280 @@ | ||||||
|  | {% 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 diskspace=hostingpackage.get_disk_space packagespace=hostingpackage.get_package_space %} | ||||||
|  |                             <dd> | ||||||
|  |                                 <span title="{% blocktranslate trimmed %} | ||||||
|  |                                 The reserved disk space for your hosting package is {{ diskspace }} bytes | ||||||
|  | {% endblocktranslate %}">{{ diskspace|filesizeformat }}</span> | ||||||
|  |                                 <i class="bi-info-circle" | ||||||
|  |                                    title="{% blocktranslate with humanbytes=packagespace|filesizeformat trimmed %} | ||||||
|  |                                       The package contributes {{ humanbytes }} ({{ packagespace }} bytes) 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 %} | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | {% 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 %} | ||||||
|  | @ -2,10 +2,17 @@ | ||||||
| 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 django.test import TestCase | from hostingpackages.models import ( | ||||||
|  |     DISK_SPACE_UNITS, | ||||||
|  |     CustomerHostingPackage, | ||||||
|  |     HostingPackageTemplate, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| from hostingpackages.models import DISK_SPACE_UNITS, CustomerHostingPackage | User = get_user_model() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CustomerHostingPackageTest(TestCase): | class CustomerHostingPackageTest(TestCase): | ||||||
|  | @ -13,7 +20,7 @@ class CustomerHostingPackageTest(TestCase): | ||||||
|         package = CustomerHostingPackage( |         package = CustomerHostingPackage( | ||||||
|             diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G |             diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(package.get_disk_space(), 10 * 1024 ** 3) |         self.assertEqual(package.get_disk_space(), 10 * 1024**3) | ||||||
| 
 | 
 | ||||||
|     def test_get_disk_space_mib(self): |     def test_get_disk_space_mib(self): | ||||||
|         package = CustomerHostingPackage( |         package = CustomerHostingPackage( | ||||||
|  | @ -26,3 +33,20 @@ class CustomerHostingPackageTest(TestCase): | ||||||
|             diskspace=256, diskspace_unit=DISK_SPACE_UNITS.M |             diskspace=256, diskspace_unit=DISK_SPACE_UNITS.M | ||||||
|         ) |         ) | ||||||
|         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): | ||||||
|  |         user = User.objects.create(username="test") | ||||||
|  |         template = HostingPackageTemplate.objects.create( | ||||||
|  |             description="Test package 1 - Description", | ||||||
|  |             mailboxcount=10, | ||||||
|  |             diskspace=100, | ||||||
|  |             diskspace_unit=DISK_SPACE_UNITS.M, | ||||||
|  |             name="Test package 1", | ||||||
|  |         ) | ||||||
|  |         with self.assertRaises(ImproperlyConfigured) as ctx: | ||||||
|  |             package = CustomerHostingPackage.objects.create_from_template( | ||||||
|  |                 customer=user, template=template, name="Test customer package" | ||||||
|  |             ) | ||||||
|  |             package.save() | ||||||
|  |         self.assertIn("testgroup", str(ctx.exception)) | ||||||
|  |  | ||||||
|  | @ -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, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import url | from django.urls import re_path | ||||||
| 
 | 
 | ||||||
| from .views import ( | from .views import ( | ||||||
|     AddHostingOption, |     AddHostingOption, | ||||||
|  | @ -12,26 +12,34 @@ from .views import ( | ||||||
|     CreateCustomerHostingPackage, |     CreateCustomerHostingPackage, | ||||||
|     CreateHostingPackage, |     CreateHostingPackage, | ||||||
|     CustomerHostingPackageDetails, |     CustomerHostingPackageDetails, | ||||||
|     CustomerHostingPackageList, |  | ||||||
|     HostingOptionChoices, |     HostingOptionChoices, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     url(r'^create$', CreateHostingPackage.as_view(), |     re_path(r"^create$", CreateHostingPackage.as_view(), name="create_hosting_package"), | ||||||
|         name='create_hosting_package'), |     re_path( | ||||||
|     url(r'^allpackages/', |         r"^allpackages/", | ||||||
|         AllCustomerHostingPackageList.as_view(), name='all_hosting_packages'), |         AllCustomerHostingPackageList.as_view(), | ||||||
|     url(r'^(?P<user>[-\w0-9@.+_]+)/$', |         name="all_hosting_packages", | ||||||
|         CustomerHostingPackageList.as_view(), name='hosting_packages'), |     ), | ||||||
|     url(r'^(?P<user>[-\w0-9@.+_]+)/create$', |     re_path( | ||||||
|  |         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$', |     ), | ||||||
|         HostingOptionChoices.as_view(), name='hosting_option_choices'), |     re_path( | ||||||
|     url(r'^(?P<package>\d+)/add-option/(?P<type>\w+)/(?P<optionid>\d+)$', |         r"^(?P<pk>\d+)/option-choices$", | ||||||
|         AddHostingOption.as_view(), name='add_hosting_option'), |         HostingOptionChoices.as_view(), | ||||||
|  |         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", | ||||||
|  |     ), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -2,28 +2,17 @@ | ||||||
| This module defines views related to hosting packages. | This module defines views related to hosting packages. | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import | ||||||
| 
 | 
 | ||||||
| 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 braces.views import ( | from django.http import Http404 | ||||||
|     LoginRequiredMixin, | from django.shortcuts import get_object_or_404, redirect | ||||||
|     StaffuserRequiredMixin, | from django.utils.translation import gettext as _ | ||||||
| ) | from django.views.generic import DetailView, ListView | ||||||
| 
 | from django.views.generic.edit import CreateView, FormView | ||||||
| from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin | from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin | ||||||
| 
 | 
 | ||||||
| from .forms import ( | from .forms import ( | ||||||
|  | @ -41,26 +30,27 @@ from .models import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CreateHostingPackage( | class CreateHostingPackage(PermissionRequiredMixin, CreateView): | ||||||
|     LoginRequiredMixin, StaffuserRequiredMixin, CreateView |  | ||||||
| ): |  | ||||||
|     """ |     """ | ||||||
|     Create a hosting package. |     Create a hosting package. | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|  | 
 | ||||||
|     model = CustomerHostingPackage |     model = CustomerHostingPackage | ||||||
|     raise_exception = True |     raise_exception = True | ||||||
|     template_name_suffix = '_create' |     permission_required = "domains.add_customerhostingpackage" | ||||||
|  |     template_name_suffix = "_create" | ||||||
|     form_class = CreateHostingPackageForm |     form_class = CreateHostingPackageForm | ||||||
| 
 | 
 | ||||||
|     def form_valid(self, form): |     def form_valid(self, form): | ||||||
|         hostingpackage = form.save() |         hosting_package = 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=hostingpackage.name) |                 name=hosting_package.name | ||||||
|  |             ), | ||||||
|         ) |         ) | ||||||
|         return redirect(hostingpackage) |         return redirect(hosting_package) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CreateCustomerHostingPackage(CreateHostingPackage): | class CreateCustomerHostingPackage(CreateHostingPackage): | ||||||
|  | @ -68,6 +58,7 @@ 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): | ||||||
|  | @ -76,25 +67,24 @@ class CreateCustomerHostingPackage(CreateHostingPackage): | ||||||
|         return kwargs |         return kwargs | ||||||
| 
 | 
 | ||||||
|     def get_customer_object(self): |     def get_customer_object(self): | ||||||
|         return get_object_or_404( |         return get_object_or_404(get_user_model(), username=self.kwargs["user"]) | ||||||
|             get_user_model(), username=self.kwargs['user']) |  | ||||||
| 
 | 
 | ||||||
|     def get_context_data(self, **kwargs): |     def get_context_data(self, **kwargs): | ||||||
|         context = super( |         context = super(CreateCustomerHostingPackage, self).get_context_data(**kwargs) | ||||||
|             CreateCustomerHostingPackage, self).get_context_data(**kwargs) |         context["customer"] = self.get_customer_object() | ||||||
|         context['customer'] = self.get_customer_object() |  | ||||||
|         return context |         return context | ||||||
| 
 | 
 | ||||||
|     def form_valid(self, form): |     def form_valid(self, form): | ||||||
|         hostingpackage = form.save(commit=False) |         hosting_package = form.save(commit=False) | ||||||
|         hostingpackage.customer = self.get_customer_object() |         hosting_package.customer = self.get_customer_object() | ||||||
|         hostingpackage.save() |         hosting_package.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=hostingpackage.name) |                 name=hosting_package.name | ||||||
|  |             ), | ||||||
|         ) |         ) | ||||||
|         return redirect(hostingpackage) |         return redirect(hosting_package) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView): | class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView): | ||||||
|  | @ -102,156 +92,161 @@ 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( |         context = super(CustomerHostingPackageDetails, self).get_context_data(**kwargs) | ||||||
|             **kwargs) |         context.update( | ||||||
|         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': |                 "hostingoptions": context["hostingpackage"].get_hostingoptions(), | ||||||
|                 context['hostingpackage'].get_hostingoptions(), |                 "domains": context["hostingpackage"].domains.all(), | ||||||
|             'domains': context['hostingpackage'].domains.all(), |                 "mailboxes": context["hostingpackage"].mailboxes, | ||||||
|             'mailboxes': context['hostingpackage'].mailboxes, |             } | ||||||
|         }) |         ) | ||||||
|         context['sshkeys'] = context['osuser'].sshpublickey_set.all() |         context["sshkeys"] = context["osuser"].sshpublickey_set.all() | ||||||
|         return context |         return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AllCustomerHostingPackageList( | class StaffUserRequiredMixin(UserPassesTestMixin): | ||||||
|     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 super(CustomerHostingPackageList, self).get_queryset().filter( |         return ( | ||||||
|             customer__username=self.kwargs['user']) |             super() | ||||||
|  |             .get_queryset() | ||||||
|  |             .select_related("osuser", "customer") | ||||||
|  |             .only("name", "pk", "created", "customer__username", "osuser__username") | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class HostingOptionChoices( | class HostingOptionChoices(StaffUserRequiredMixin, DetailView): | ||||||
|     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( |         context = super(HostingOptionChoices, self).get_context_data(**kwargs) | ||||||
|             **kwargs) |         context.update( | ||||||
|         context.update({ |             { | ||||||
|             'customer': self.get_object().customer, |                 "customer": self.get_object().customer, | ||||||
|             'hosting_options': ( |                 "hosting_options": ( | ||||||
|                 (_('Disk space'), |                     ( | ||||||
|                  [(option, 'diskspace') for option in |                         _("Disk space"), | ||||||
|                   DiskSpaceOption.objects.all()]), |                         [ | ||||||
|                 (_('Mailboxes'), |                             (option, "diskspace") | ||||||
|                  [(option, 'mailboxes') for option in |                             for option in DiskSpaceOption.objects.all() | ||||||
|                   MailboxOption.objects.all()]), |                         ], | ||||||
|                 (_('Databases'), |                     ), | ||||||
|                  [(option, 'databases') for option in |                     ( | ||||||
|                   UserDatabaseOption.objects.all()]), |                         _("Mailboxes"), | ||||||
|             ), |                         [ | ||||||
|         }) |                             (option, "mailboxes") | ||||||
|  |                             for option in MailboxOption.objects.all() | ||||||
|  |                         ], | ||||||
|  |                     ), | ||||||
|  |                     ( | ||||||
|  |                         _("Databases"), | ||||||
|  |                         [ | ||||||
|  |                             (option, "databases") | ||||||
|  |                             for option in UserDatabaseOption.objects.all() | ||||||
|  |                         ], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|         return context |         return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AddHostingOption( | class AddHostingOption(StaffUserRequiredMixin, FormView): | ||||||
|     LoginRequiredMixin, StaffuserRequiredMixin, FormView |     template_name = "hostingpackages/add_hosting_option.html" | ||||||
| ): |  | ||||||
|     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( |         return get_object_or_404(CustomerHostingPackage, pk=int(self.kwargs["package"])) | ||||||
|             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_unit': template.diskspace_unit, |                     "diskspace": template.diskspace, | ||||||
|             }) |                     "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): | ||||||
|  | @ -259,8 +254,8 @@ class AddHostingOption( | ||||||
|         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 " |             _( | ||||||
|               "{package}.").format( |                 "Successfully added option {option} to hosting package " "{package}." | ||||||
|                   option=option, package=hosting_package.name) |             ).format(option=option, package=hosting_package.name), | ||||||
|         ) |         ) | ||||||
|         return redirect(hosting_package) |         return redirect(hosting_package) | ||||||
|  |  | ||||||
							
								
								
									
										0
									
								
								gnuviechadmin/invoices/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/invoices/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										14
									
								
								gnuviechadmin/invoices/admin.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								gnuviechadmin/invoices/admin.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | 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) | ||||||
							
								
								
									
										8
									
								
								gnuviechadmin/invoices/apps.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								gnuviechadmin/invoices/apps.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | from django.utils.translation import ugettext_lazy as _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class InvoiceConfig(AppConfig): | ||||||
|  |     default_auto_field = "django.db.models.BigAutoField" | ||||||
|  |     name = "invoices" | ||||||
|  |     verbose_name = _("Invoices") | ||||||
							
								
								
									
										69
									
								
								gnuviechadmin/invoices/locale/de/LC_MESSAGES/django.po
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								gnuviechadmin/invoices/locale/de/LC_MESSAGES/django.po
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | # 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-04-23 14:35+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" | ||||||
|  | 
 | ||||||
|  | #: invoice/apps.py:8 | ||||||
|  | msgid "Invoices" | ||||||
|  | msgstr "Rechnungen" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:26 | ||||||
|  | msgid "customer" | ||||||
|  | msgstr "Kunde" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:33 | ||||||
|  | msgid "invoice number" | ||||||
|  | msgstr "Rechnungsnummer" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:35 | ||||||
|  | msgid "invoice date" | ||||||
|  | msgstr "Rechnungsdatum" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:37 | ||||||
|  | msgid "amount" | ||||||
|  | msgstr "Betrag" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:40 | ||||||
|  | msgid "currency" | ||||||
|  | msgstr "Währung" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:42 | ||||||
|  | msgid "due date" | ||||||
|  | msgstr "Fälligkeit" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:44 | ||||||
|  | msgid "payment date" | ||||||
|  | msgstr "Zahlungsdatum" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:47 | ||||||
|  | msgid "payment variant" | ||||||
|  | msgstr "Zahlungsart" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:51 | ||||||
|  | msgid "invoice" | ||||||
|  | msgstr "Rechnung" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:52 | ||||||
|  | msgid "invoices" | ||||||
|  | msgstr "Rechnungen" | ||||||
|  | 
 | ||||||
|  | #: invoice/models.py:56 | ||||||
|  | #, python-brace-format | ||||||
|  | msgid "Invoice {0}" | ||||||
|  | msgstr "Rechnung {0}" | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | # 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'], | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -0,0 +1,59 @@ | ||||||
|  | # 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'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										0
									
								
								gnuviechadmin/invoices/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/invoices/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										56
									
								
								gnuviechadmin/invoices/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								gnuviechadmin/invoices/models.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | 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 ugettext_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) | ||||||
							
								
								
									
										33
									
								
								gnuviechadmin/invoices/serializers.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								gnuviechadmin/invoices/serializers.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | from django.contrib.auth import get_user_model | ||||||
|  | from rest_framework import serializers | ||||||
|  | 
 | ||||||
|  | from invoices.models import Invoice | ||||||
|  | 
 | ||||||
|  | User = get_user_model() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class InvoiceSerializer(serializers.ModelSerializer): | ||||||
|  |     customer = serializers.SlugRelatedField( | ||||||
|  |         queryset=User.objects.all(), slug_field="username" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         model = Invoice | ||||||
|  |         fields = [ | ||||||
|  |             "url", | ||||||
|  |             "customer", | ||||||
|  |             "invoice", | ||||||
|  |             "invoice_number", | ||||||
|  |             "invoice_date", | ||||||
|  |             "invoice_value", | ||||||
|  |             "invoice_currency", | ||||||
|  |             "due_date", | ||||||
|  |             "payment_date", | ||||||
|  |             "payment_variant", | ||||||
|  |         ] | ||||||
|  |         extra_kwargs = { | ||||||
|  |             "url": { | ||||||
|  |                 "lookup_field": "invoice_number", | ||||||
|  |                 "lookup_url_kwarg": "invoice_number", | ||||||
|  |             }, | ||||||
|  |         } | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue