From 11b1befa1bab8badc69516dc7a71aac82880bcd9 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 4 Oct 2015 12:08:15 +0200 Subject: [PATCH 001/111] initial Vagrantfile --- Vagrantfile | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Vagrantfile diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..2db2557 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,71 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure(2) do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://atlas.hashicorp.com/search. + config.vm.box = "debian/jessie64" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # # Customize the amount of memory on the VM: + vb.memory = "1024" + end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies + # such as FTP and Heroku are also available. See the documentation at + # https://docs.vagrantup.com/v2/push/atlas.html for more information. + # config.push.define "atlas" do |push| + # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" + # end + + # Enable provisioning with a shell script. Additional provisioners such as + # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + # sudo apt-get update + # sudo apt-get install -y apache2 + # SHELL +end From 6f9b17dc49f282c8df5a58467c5c33641133d413 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 4 Oct 2015 14:20:44 +0200 Subject: [PATCH 002/111] setup salt provisioning for vagrant --- .gitignore | 1 + Vagrantfile | 28 ++++++++++------------------ salt/bootstrap.sh | 26 ++++++++++++++++++++++++++ salt/grains | 0 salt/minion | 11 +++++++++++ salt/pillar/top.sls | 0 salt/roots/base/init.sls | 6 ++++++ salt/roots/top.sls | 11 +++++++++++ salt/roots/vim/init.sls | 2 ++ 9 files changed, 67 insertions(+), 18 deletions(-) create mode 100755 salt/bootstrap.sh create mode 100644 salt/grains create mode 100644 salt/minion create mode 100644 salt/pillar/top.sls create mode 100644 salt/roots/base/init.sls create mode 100644 salt/roots/top.sls create mode 100644 salt/roots/vim/init.sls diff --git a/.gitignore b/.gitignore index ca72d2e..46f0db6 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ htmlcov/ tags _build/ *.mo +.vagrant/ diff --git a/Vagrantfile b/Vagrantfile index 2db2557..10854f0 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -22,7 +22,7 @@ Vagrant.configure(2) do |config| # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. - # config.vm.network "forwarded_port", guest: 80, host: 8080 + config.vm.network "forwarded_port", guest: 443, host: 8443 # Create a private network, which allows host-only access to the machine # using a specific IP. @@ -38,6 +38,8 @@ Vagrant.configure(2) do |config| # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. # config.vm.synced_folder "../data", "/vagrant_data" + config.vm.synced_folder "salt/roots/", "/srv/salt/" + config.vm.synced_folder "salt/pillar/", "/srv/pillar/" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. @@ -50,22 +52,12 @@ Vagrant.configure(2) do |config| # # Customize the amount of memory on the VM: vb.memory = "1024" end - # - # View the documentation for the provider you are using for more - # information on available options. - # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies - # such as FTP and Heroku are also available. See the documentation at - # https://docs.vagrantup.com/v2/push/atlas.html for more information. - # config.push.define "atlas" do |push| - # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" - # end - - # Enable provisioning with a shell script. Additional provisioners such as - # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the - # documentation for more information about their specific syntax and use. - # config.vm.provision "shell", inline: <<-SHELL - # sudo apt-get update - # sudo apt-get install -y apache2 - # SHELL + config.vm.provision :salt do |salt| + salt.bootstrap_script = "salt/bootstrap.sh" + salt.minion_id = "gvadev" + salt.masterless = true + salt.run_highstate = true + salt.colorize = true + end end diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh new file mode 100755 index 0000000..8f3f04f --- /dev/null +++ b/salt/bootstrap.sh @@ -0,0 +1,26 @@ +#!/bin/sh - + +# We just download the bootstrap script by default and execute that. +if [ -x /usr/bin/fetch ]; then + /usr/bin/fetch -o - https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@" +elif [ -x /usr/bin/curl ]; then + /usr/bin/curl -L https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@" +else + python \ + -c 'import urllib; print urllib.urlopen("https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh").read()' \ + | sh -s -- "$@" +fi + +cat >/etc/salt/minion < Date: Sun, 4 Oct 2015 19:00:46 +0200 Subject: [PATCH 003/111] setup vagrant box roles and nginx package --- Vagrantfile | 1 + salt/bootstrap.sh | 8 ++++ salt/roots/base/nginx.conf | 56 +++++++++++++++++++++++ salt/roots/base/nginx.sls | 21 +++++++++ salt/roots/gnuviechadmin/database.sls | 0 salt/roots/gnuviechadmin/init.sls | 0 salt/roots/gnuviechadmin/queues.sls | 0 salt/roots/gnuviechadmin/webinterface.sls | 0 salt/roots/webserver/init.sls | 2 + 9 files changed, 88 insertions(+) create mode 100644 salt/roots/base/nginx.conf create mode 100644 salt/roots/base/nginx.sls create mode 100644 salt/roots/gnuviechadmin/database.sls create mode 100644 salt/roots/gnuviechadmin/init.sls create mode 100644 salt/roots/gnuviechadmin/queues.sls create mode 100644 salt/roots/gnuviechadmin/webinterface.sls create mode 100644 salt/roots/webserver/init.sls diff --git a/Vagrantfile b/Vagrantfile index 10854f0..1fc6ff5 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -58,6 +58,7 @@ Vagrant.configure(2) do |config| salt.minion_id = "gvadev" salt.masterless = true salt.run_highstate = true + salt.verbose = true salt.colorize = true end end diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index 8f3f04f..98f077e 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -24,3 +24,11 @@ pillar_roots: log_file: file:///dev/log EOF + +cat >/etc/salt/grains < Date: Sun, 4 Oct 2015 19:32:42 +0200 Subject: [PATCH 004/111] setup default nginx ssl/security configuration for vagrant --- salt/roots/base/nginx.conf | 7 ---- salt/roots/base/nginx.sls | 17 ++++++++ salt/roots/webserver/init.sls | 48 +++++++++++++++++++++++ salt/roots/webserver/nginx-logformat.conf | 4 ++ salt/roots/webserver/nginx-security.conf | 19 +++++++++ salt/roots/webserver/nginx-ssl.conf | 15 +++++++ 6 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 salt/roots/webserver/nginx-logformat.conf create mode 100644 salt/roots/webserver/nginx-security.conf create mode 100644 salt/roots/webserver/nginx-ssl.conf diff --git a/salt/roots/base/nginx.conf b/salt/roots/base/nginx.conf index 7e6a853..dfeb36d 100644 --- a/salt/roots/base/nginx.conf +++ b/salt/roots/base/nginx.conf @@ -26,13 +26,6 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; - ## - # SSL Settings - ## - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE - ssl_prefer_server_ciphers on; - ## # Logging Settings ## diff --git a/salt/roots/base/nginx.sls b/salt/roots/base/nginx.sls index 89f149a..21afb3a 100644 --- a/salt/roots/base/nginx.sls +++ b/salt/roots/base/nginx.sls @@ -19,3 +19,20 @@ nginx-common: - pkg: nginx-common - watch_in: - service: nginx + +{% set nginx_ssl_keydir = salt['pillar.get']('nginx:sslkeydir', '/etc/nginx/ssl/private') %} +{% set nginx_ssl_certdir = salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') %} + +{{ nginx_ssl_certdir }}: + file.directory: + - user: root + - group: root + - mode: 0755 + - makedirs: True + +{{ nginx_ssl_keydir }}: + file.directory: + - user: root + - group: root + - mode: 0750 + - makedirs: True diff --git a/salt/roots/webserver/init.sls b/salt/roots/webserver/init.sls index 22fa765..0fc0155 100644 --- a/salt/roots/webserver/init.sls +++ b/salt/roots/webserver/init.sls @@ -1,2 +1,50 @@ include: - base.nginx + +/etc/nginx/conf.d/logformat.conf: + file.managed: + - user: root + - group: root + - mode: 0644 + - source: salt://webserver/nginx-logformat.conf + - require: + - pkg: nginx + - watch_in: + - service: nginx + +{% set ssldir = salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') %} + +generate-dhparam-nginx: + cmd.run: + - name: openssl dhparam -out {{ ssldir }}/dhparams.pem 2048 + - umask: 022 + - user: root + - group: root + - creates: {{ ssldir }}/dhparams.pem + - require_in: + - file: /etc/nginx/conf.d/ssl.conf + - watch_in: + - service: nginx + +/etc/nginx/conf.d/ssl.conf: + file.managed: + - user: root + - group: root + - mode: 0644 + - source: salt://webserver/nginx-ssl.conf + - template: jinja + - require: + - pkg: nginx + - watch_in: + - service: nginx + +/etc/nginx/snippets/security.conf: + file.managed: + - user: root + - group: root + - mode: 0644 + - source: salt://webserver/nginx-security.conf + - require: + - pkg: nginx + - watch_in: + - service: nginx diff --git a/salt/roots/webserver/nginx-logformat.conf b/salt/roots/webserver/nginx-logformat.conf new file mode 100644 index 0000000..bb26d9e --- /dev/null +++ b/salt/roots/webserver/nginx-logformat.conf @@ -0,0 +1,4 @@ +log_format main '$remote_addr - $remote_user [$time_local] ' + '$server_name ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; diff --git a/salt/roots/webserver/nginx-security.conf b/salt/roots/webserver/nginx-security.conf new file mode 100644 index 0000000..5585189 --- /dev/null +++ b/salt/roots/webserver/nginx-security.conf @@ -0,0 +1,19 @@ +# Security - Basic configuration + location = /favicon.ico { + log_not_found off; + access_log off; + expires max; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } diff --git a/salt/roots/webserver/nginx-ssl.conf b/salt/roots/webserver/nginx-ssl.conf new file mode 100644 index 0000000..e0cb1ef --- /dev/null +++ b/salt/roots/webserver/nginx-ssl.conf @@ -0,0 +1,15 @@ +# Default TLS settings +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_ciphers kEECDH+AESGCM:kEECDH+AES:kEECDH:EDH+AESGCM:kEDH+AES:kEDH:AESGCM:ALL:!LOW:!EXP:!MD5:!aNULL:!eNULL:!RC4:!DSS; +ssl_prefer_server_ciphers on; +ssl_session_cache shared:SSL:10m; + +ssl_dhparam {{ salt['pillar.get']('nginx:sslcertdir') }}/dhparams.pem; + +# OCSP stapling +ssl_stapling on; +ssl_stapling_verify on; + +# use Google's DNS +resolver 8.8.8.8; +resolver_timeout 5s; From b07ab0a14bb232e3116dd903597a464a40b0059a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 4 Oct 2015 20:18:44 +0200 Subject: [PATCH 005/111] add PostgreSQL database and message queues to vagrant box --- salt/pillar/gnuviechadmin/database.sls | 7 +++ salt/pillar/gnuviechadmin/database/common.sls | 4 ++ salt/pillar/gnuviechadmin/init.sls | 21 +++++++ salt/pillar/gnuviechadmin/queues.sls | 62 +++++++++++++++++++ salt/pillar/top.sls | 3 + salt/roots/gnuviechadmin/database.sls | 30 +++++++++ salt/roots/gnuviechadmin/init.sls | 0 salt/roots/gnuviechadmin/queues.sls | 30 +++++++++ salt/roots/postgresql-server/init.sls | 9 +++ salt/roots/rabbitmq-server/init.sls | 11 ++++ 10 files changed, 177 insertions(+) create mode 100644 salt/pillar/gnuviechadmin/database.sls create mode 100644 salt/pillar/gnuviechadmin/database/common.sls create mode 100644 salt/pillar/gnuviechadmin/init.sls create mode 100644 salt/pillar/gnuviechadmin/queues.sls delete mode 100644 salt/roots/gnuviechadmin/init.sls create mode 100644 salt/roots/postgresql-server/init.sls create mode 100644 salt/roots/rabbitmq-server/init.sls diff --git a/salt/pillar/gnuviechadmin/database.sls b/salt/pillar/gnuviechadmin/database.sls new file mode 100644 index 0000000..d150d1c --- /dev/null +++ b/salt/pillar/gnuviechadmin/database.sls @@ -0,0 +1,7 @@ +include: + - gnuviechadmin.database.common + +gnuviechadmin-database: + owner: + user: gnuviechadmin + password: k4TG0oWeJ08urz697GVfavjK diff --git a/salt/pillar/gnuviechadmin/database/common.sls b/salt/pillar/gnuviechadmin/database/common.sls new file mode 100644 index 0000000..a1c69c1 --- /dev/null +++ b/salt/pillar/gnuviechadmin/database/common.sls @@ -0,0 +1,4 @@ +gnuviechadmin-database: + database: gnuviechadmin + hostname: localhost + port: 5432 diff --git a/salt/pillar/gnuviechadmin/init.sls b/salt/pillar/gnuviechadmin/init.sls new file mode 100644 index 0000000..ed911fd --- /dev/null +++ b/salt/pillar/gnuviechadmin/init.sls @@ -0,0 +1,21 @@ +include: + - gnuviechadmin.database + - gnuviechadmin.queues + +gnuviechadmin: + mailfrom: admin@gnuviech-server.de + adminemail: admin@gnuviech-server.de + sitename: Gnuviech Customer Self Service + domainname: localhost + checkout: /srv/www/gnuviechadmin + virtualenv: /home/gva/.virtualenvs/gnuviechadmin + devinstance: True + minosuid: 10000 + minosgid: 10000 + osuserprefix: usr + osuserhomedirbase: /home + osuserdefaultshell: /usr/bin/rssh + uploadserver: upload.example.com + webmail_url: https://webmail.example.com/ + phpmyadmin_url: https://phpmyadmin.example.com/ + phppgadmin_url: https://phppgadmin.example.com/ diff --git a/salt/pillar/gnuviechadmin/queues.sls b/salt/pillar/gnuviechadmin/queues.sls new file mode 100644 index 0000000..5ac2d7b --- /dev/null +++ b/salt/pillar/gnuviechadmin/queues.sls @@ -0,0 +1,62 @@ +gnuviechadmin-queues: + vhost: /gnuviechadmin + owner: + user: gnuviechadmin + password: WxyKeo7Xunhwv29C + users: + cli: + password: bUQ4QEB8yQEfsB0i + perms: + '/gnuviechadmin': + - '.*' + - '.*' + - '.*' + tags: + quotajob: + password: TaNoj2H3ZNDIz1rt + perms: + '/gnuviechadmin': + - '^quotatool$' + - '^quotatool$' + - '^quotatool|amq.default$' + tags: + ldap: + password: tl0ALc4aQBAl0W2e + perms: + '/gnuviechadmin': + - '.*' + - '.*' + - '.*' + tags: + file: + password: StR6EgMjLyNGP1F8 + perms: + '/gnuviechadmin': + - '.*' + - '.*' + - '.*' + tags: + mysql: + password: Bhruvz8Oe9rXxRc7 + perms: + '/gnuviechadmin': + - '.*' + - '.*' + - '.*' + tags: + pgsql: + password: rWOawAtb7MEmGZo3 + perms: + '/gnuviechadmin': + - '.*' + - '.*' + - '.*' + tags: + web: + password: 1fBXqCu175rU7SWA + perms: + '/gnuviechadmin': + - '.*' + - '.*' + - '.*' + tags: diff --git a/salt/pillar/top.sls b/salt/pillar/top.sls index e69de29..2fbfb95 100644 --- a/salt/pillar/top.sls +++ b/salt/pillar/top.sls @@ -0,0 +1,3 @@ +base: + '*': + - gnuviechadmin diff --git a/salt/roots/gnuviechadmin/database.sls b/salt/roots/gnuviechadmin/database.sls index e69de29..1323368 100644 --- a/salt/roots/gnuviechadmin/database.sls +++ b/salt/roots/gnuviechadmin/database.sls @@ -0,0 +1,30 @@ +include: + - postgresql-server + +gnuviechadmin-database: + postgres_user.present: + - name: {{ salt['pillar.get']('gnuviechadmin-database:owner:user') }} + - user: postgres + - password: {{ salt['pillar.get']('gnuviechadmin-database:owner:password') }} + - login: True + - require: + - service: postgresql + postgres_database.present: + - name: {{ salt['pillar.get']('gnuviechadmin-database:database') }} + - user: postgres + - owner: {{ salt['pillar.get']('gnuviechadmin-database:owner:user') }} + - encoding: UTF8 + - template: template0 + - require: + - service: postgresql + - postgres_user: {{ salt['pillar.get']('gnuviechadmin-database:owner:user') }} + +{% for gnuviechadmin_db_role in salt['pillar.get']('gnuviechadmin-database:users') %} +gnuviechadmin-dbuser-{{ gnuviechadmin_db_role }}: + postgres_user.present: + - name: {{ salt['pillar.get']('gnuviechadmin-database:users:' + gnuviechadmin_db_role + ':user') }} + - password: {{ salt['pillar.get']('gnuviechadmin-database:users:' + gnuviechadmin_db_role + ':password') }} + - login: True + - require: + - service: postgresql +{% endfor %} diff --git a/salt/roots/gnuviechadmin/init.sls b/salt/roots/gnuviechadmin/init.sls deleted file mode 100644 index e69de29..0000000 diff --git a/salt/roots/gnuviechadmin/queues.sls b/salt/roots/gnuviechadmin/queues.sls index e69de29..3a33604 100644 --- a/salt/roots/gnuviechadmin/queues.sls +++ b/salt/roots/gnuviechadmin/queues.sls @@ -0,0 +1,30 @@ +include: + - rabbitmq-server + +gnuviechadmin-queues: + rabbitmq_user.present: + - name: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} + - password: {{ salt['pillar.get']('gnuviechadmin-queues:owner:password') }} + rabbitmq_vhost.present: + - name: {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} + - owner: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} + - require: + - rabbitmq_user: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} + +{% for user in salt['pillar.get']('gnuviechadmin-queues:users') %} +gnuviechadmin-queue-user-{{ user }}: + rabbitmq_user.present: + - name: {{ user }} + - password: {{ salt['pillar.get']('gnuviechadmin-queues:users:%s:password' % user) }} +{% if salt['pillar.get']('gnuviechadmin-queues:users:%s:perms' % user) %} + - perms: +{% for vhost, perms in salt['pillar.get']('gnuviechadmin-queues:users:%s:perms' % user).iteritems() %} + - {{ vhost }}: + - {{ perms[0] }} + - {{ perms[1] }} + - {{ perms[2] }} +{% endfor %} +{% endif %} + - require: + - rabbitmq_vhost: {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} +{% endfor %} diff --git a/salt/roots/postgresql-server/init.sls b/salt/roots/postgresql-server/init.sls new file mode 100644 index 0000000..f08ace7 --- /dev/null +++ b/salt/roots/postgresql-server/init.sls @@ -0,0 +1,9 @@ +locales-all: + pkg.installed + +postgresql: + pkg: + - installed + service.running: + - require: + - pkg: postgresql diff --git a/salt/roots/rabbitmq-server/init.sls b/salt/roots/rabbitmq-server/init.sls new file mode 100644 index 0000000..32ac09f --- /dev/null +++ b/salt/roots/rabbitmq-server/init.sls @@ -0,0 +1,11 @@ +rabbitmq-server: + pkg: + - installed + service: + - running + - requires: + - pkg: rabbitmq-server + +guest: + rabbitmq_user: + - absent From 33338af352b41db1d7a94020b715eb2c51a753ff Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 4 Oct 2015 23:02:04 +0200 Subject: [PATCH 006/111] finish vagrant configuration - ignore collected assets - setup virtualenv and environment variables - import additional salt state modules --- .gitignore | 1 + Vagrantfile | 1 + salt/bootstrap.sh | 1 - salt/pillar/gnuviechadmin/init.sls | 3 +- salt/roots/_states/rsa_key.py | 117 +++++++++++++++++++ salt/roots/_states/x509_certificate.py | 61 ++++++++++ salt/roots/gnuviechadmin/gnuviechadmin.nginx | 27 +++++ salt/roots/gnuviechadmin/gvasettings.sh | 24 ++++ salt/roots/gnuviechadmin/webinterface.sls | 97 +++++++++++++++ salt/roots/webserver/nginx-ssl.conf | 2 +- salt/roots/webserver/sslcert.macros.sls | 29 +++++ 11 files changed, 359 insertions(+), 4 deletions(-) create mode 100644 salt/roots/_states/rsa_key.py create mode 100644 salt/roots/_states/x509_certificate.py create mode 100644 salt/roots/gnuviechadmin/gnuviechadmin.nginx create mode 100644 salt/roots/gnuviechadmin/gvasettings.sh create mode 100644 salt/roots/webserver/sslcert.macros.sls diff --git a/.gitignore b/.gitignore index 46f0db6..e505984 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ tags _build/ *.mo .vagrant/ +gnuviechadmin/assets/ diff --git a/Vagrantfile b/Vagrantfile index 1fc6ff5..8aec6d0 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -23,6 +23,7 @@ Vagrant.configure(2) do |config| # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. config.vm.network "forwarded_port", guest: 443, host: 8443 + config.vm.network "forwarded_port", guest: 8000, host: 8000 # Create a private network, which allows host-only access to the machine # using a specific IP. diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index 98f077e..4e85da5 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -27,7 +27,6 @@ EOF cat >/etc/salt/grains < +''' + +from M2Crypto import X509 +from datetime import datetime +import os + + +def _error(ret, err_msg): + ret['result'] = False + ret['comment'] = err_msg + return ret + + +def valid_certificate( + name, mindays=14, keyfile=None, + checkchain=False, trustedcerts=None): + ''' + Checks whether the given certificate file is valid. + + name + The name of the certificate file to check + mindays + Mark the certificate as invalid if it is valid for less then this many + days + ''' + ret = { + 'name': name, + 'changes': {}, + 'result': None, + 'comment': ''} + if not os.path.isfile(name): + return _error( + ret, 'certificate file {0} does not exist'.format(name)) + try: + cert = X509.load_cert(name) + except Exception as e: + return _error( + ret, + 'error loading certificate {0}: {1}'.format(name, e)) + notafter = cert.get_not_after().get_datetime() + delta = notafter - datetime.now(notafter.tzinfo) + if delta.days < mindays: + return _error( + ret, + 'certificate {0} is only valid for {1} more day(s)'.format( + name, delta.days)) + # TODO: check keyfile match + # TODO: check trust chain + ret['comment'] = ( + 'certificate {0} is ok and still valid for {1} days'.format( + name, delta.days)) + ret['result'] = True + return ret diff --git a/salt/roots/gnuviechadmin/gnuviechadmin.nginx b/salt/roots/gnuviechadmin/gnuviechadmin.nginx new file mode 100644 index 0000000..d5768bd --- /dev/null +++ b/salt/roots/gnuviechadmin/gnuviechadmin.nginx @@ -0,0 +1,27 @@ +server { + server_name www.{{ domainname }}; + listen 443 ssl; + + ssl_certificate {{ ssl_certdir }}/{{ domainname }}.crt.pem; + ssl_certificate_key {{ ssl_keydir }}/{{ domainname }}.key.pem; + + if ( $host != '{{ domainname }}') { + return 301 https://{{ domainname }}$request_uri; + } + + client_max_body_size 1M; + gzip on; + gzip_types text/javascript application/x-javascript text/css; + + location /media { + alias /vagrant/gnuviechadmin/media; + } + + location /static { + alias /vagrant/gnuviechadmin/assets; + } + + location / { + proxy_pass http://localhost:8000; + } +} diff --git a/salt/roots/gnuviechadmin/gvasettings.sh b/salt/roots/gnuviechadmin/gvasettings.sh new file mode 100644 index 0000000..16edcd3 --- /dev/null +++ b/salt/roots/gnuviechadmin/gvasettings.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +export DJANGO_SETTINGS_MODULE="gnuviechadmin.settings.production" +export GVA_ADMIN_NAME="Jan Dittberner" +export GVA_ADMIN_EMAIL="{{ salt['pillar.get']('gnuviechadmin:adminemail') }}" +export GVA_PGSQL_DATABASE="{{ salt['pillar.get']('gnuviechadmin-database:database') }}" +export GVA_PGSQL_USER="{{ salt['pillar.get']('gnuviechadmin-database:owner:user') }}" +export GVA_PGSQL_PASSWORD="{{ salt['pillar.get']('gnuviechadmin-database:owner:password') }}" +export GVA_PGSQL_HOSTNAME="{{ salt['pillar.get']('gnuviechadmin-database:hostname') }}" +export GVA_PGSQL_PORT={{ salt['pillar.get']('gnuviechadmin-database:port') }} +export GVA_DOMAIN_NAME="{{ salt['pillar.get']('gnuviechadmin:domainname') }}" +export GVA_SITE_NAME="{{ salt['pillar.get']('gnuviechadmin:sitename') }}" +export GVA_SITE_SECRET="{{ salt['grains.get_or_set_hash']('gnuviechadmin:SECRET_KEY', 50) }}" +export GVA_SITE_ADMINMAIL="{{ salt['pillar.get']('gnuviechadmin:adminemail') }}" +export GVA_MIN_OS_UID={{ salt['pillar.get']('gnuviechadmin:minosuid') }} +export GVA_MIN_OS_GID={{ salt['pillar.get']('gnuviechadmin:minosgid') }} +export GVA_OSUSER_PREFIX="{{ salt['pillar.get']('gnuviechadmin:osuserprefix') }}" +export GVA_OSUSER_HOME_BASEPATH="{{ salt['pillar.get']('gnuviechadmin:osuserhomedirbase') }}" +export GVA_OSUSER_DEFAULT_SHELL="{{ salt['pillar.get']('gnuviechadmin:osuserdefaultshell') }}" +export GVA_BROKER_URL="{{ broker_url }}" +export GVA_OSUSER_UPLOADSERVER="{{ salt['pillar.get']('gnuviechadmin:uploadserver') }}" +export GVA_WEBMAIL_URL="{{ salt['pillar.get']('gnuviechadmin:webmail_url') }}" +export GVA_PHPMYADMIN_URL="{{ salt['pillar.get']('gnuviechadmin:phpmyadmin_url') }}" +export GVA_PHPPGADMIN_URL="{{ salt['pillar.get']('gnuviechadmin:phppgadmin_url') }}" diff --git a/salt/roots/gnuviechadmin/webinterface.sls b/salt/roots/gnuviechadmin/webinterface.sls index e69de29..90bddd4 100644 --- a/salt/roots/gnuviechadmin/webinterface.sls +++ b/salt/roots/gnuviechadmin/webinterface.sls @@ -0,0 +1,97 @@ +include: + - webserver + +gnuviechadmin-packages: + pkg.installed: + - names: + - libpq-dev + - libyaml-dev + - python-virtualenv + - python-dev + - python-pip + +{% import "webserver/sslcert.macros.sls" as sslcert %} + +{% set venv = salt['pillar.get']('gnuviechadmin:virtualenv') %} +{% set domainname = salt['pillar.get']('gnuviechadmin:domainname') %} +{{ sslcert.key_cert(domainname) }} + +{{ venv }}: + file.directory: + - user: vagrant + - group: vagrant + - require: + - cmd: gnuviechadmin-venv + +/home/vagrant/gvasettings.sh: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0640 + - source: salt://gnuviechadmin/gvasettings.sh + - template: jinja + - context: + broker_url: amqp://{{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }}:{{ salt['pillar.get']('gnuviechadmin-queues:owner:password') }}@mq/{{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} + +gnuviechadmin-venv: + cmd.run: + - name: virtualenv {{ venv }} + - user: vagrant + - group: vagrant + - unless: test -f {{ venv }}/bin/pip + +gnuviechadmin-requires: + cmd.run: + - name: {{ venv }}/bin/pip install -U -r requirements/local.txt && touch {{ venv }}/lastinstall + - user: vagrant + - group: vagrant + - cwd: /vagrant + - require: + - file: {{ venv }} + - pkg: python-dev + - pkg: libpq-dev + - unless: test -e {{ venv }}/lastinstall && test /vagrant/requirements/local.txt -ot {{ venv }}/lastinstall && test /vagrant/requirements/base.txt -ot {{ venv }}/lastinstall + +gnuviechadmin-dbschema: + cmd.wait: + - name: . /home/vagrant/gvasettings.sh ; {{ venv }}/bin/python manage.py migrate --noinput + - user: vagrant + - group: vagrant + - cwd: /vagrant/gnuviechadmin + - watch: + - cmd: gnuviechadmin-requires + - file: /home/vagrant/gvasettings.sh + +gnuviechadmin-locale-data-compile: + cmd.wait: + - name: . /home/vagrant/gvasettings.sh ; find /vagrant/gnuviechadmin -type d -name 'locale' | while read dir; do cd $(dirname "$dir") ; {{ venv }}/bin/python /vagrant/gnuviechadmin/manage.py compilemessages ; done + - user: vagrant + - group: vagrant + - cwd: /vagrant/gnuviechadmin + - require: + - file: /home/vagrant/gvasettings.sh + - file: {{ venv }} + +/etc/nginx/sites-available/{{ domainname }}: + file.managed: + - user: root + - group: root + - mode: 0640 + - source: salt://gnuviechadmin/gnuviechadmin.nginx + - template: jinja + - context: + domainname: {{ domainname }} + ssl_keydir: {{ salt['pillar.get']('nginx:sslkeydir', '/etc/nginx/ssl/private') }} + ssl_certdir: {{ salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') }} + - require: + - pkg: nginx + - watch_in: + - service: nginx + +/etc/nginx/sites-enabled/{{ domainname }}: + file.symlink: + - target: /etc/nginx/sites-available/{{ domainname }} + - require: + - file: /etc/nginx/sites-available/{{ domainname }} + - watch_in: + - service: nginx diff --git a/salt/roots/webserver/nginx-ssl.conf b/salt/roots/webserver/nginx-ssl.conf index e0cb1ef..305f31d 100644 --- a/salt/roots/webserver/nginx-ssl.conf +++ b/salt/roots/webserver/nginx-ssl.conf @@ -4,7 +4,7 @@ ssl_ciphers kEECDH+AESGCM:kEECDH+AES:kEECDH:EDH+AESGCM:kEDH+AES:kEDH:AESGCM:ALL: ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; -ssl_dhparam {{ salt['pillar.get']('nginx:sslcertdir') }}/dhparams.pem; +ssl_dhparam {{ salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') }}/dhparams.pem; # OCSP stapling ssl_stapling on; diff --git a/salt/roots/webserver/sslcert.macros.sls b/salt/roots/webserver/sslcert.macros.sls new file mode 100644 index 0000000..528e65d --- /dev/null +++ b/salt/roots/webserver/sslcert.macros.sls @@ -0,0 +1,29 @@ +{%- macro key_cert(domain_name) %} +{% set nginx_ssl_keydir = salt['pillar.get']('nginx:sslkeydir', '/etc/nginx/ssl/private') %} +{% set nginx_ssl_certdir = salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') %} +{% set keyfile = nginx_ssl_keydir + '/' + domain_name + '.key.pem' %} +{% set certfile = nginx_ssl_certdir + '/' + domain_name + '.crt.pem' %} + +{{ keyfile }}: + rsa_key.valid_key: + - bits: {{ salt['pillar.get']('nginx:keylength:' + domain_name, 2048) }} + - require: + - file: {{ nginx_ssl_keydir }} + - require_in: + - file: /etc/nginx/sites-available/{{ domain_name }} + - service: nginx + +{{ certfile }}: + cmd.run: + - name: openssl req -new -x509 -key {{ keyfile }} -subj '/CN={{ domain_name }}' -days 730 -out {{ certfile }} + - require: + - rsa_key: {{ keyfile }} + - creates: {{ certfile }} + x509_certificate.valid_certificate: + - require: + - file: {{ nginx_ssl_certdir }} + - cmd: {{ certfile }} + - require_in: + - file: /etc/nginx/sites-available/{{ domain_name }} + - service: nginx +{% endmacro %} From 6a0f88d7d4974b56c021a516ee78f3c0bc02dfc0 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 12:02:22 +0200 Subject: [PATCH 007/111] manage screenrc and set hostname in vagrant box --- Vagrantfile | 2 ++ salt/roots/base/init.sls | 7 +++++++ salt/roots/base/screenrc | 14 ++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 salt/roots/base/screenrc diff --git a/Vagrantfile b/Vagrantfile index 8aec6d0..a4d3642 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -14,6 +14,8 @@ Vagrant.configure(2) do |config| # boxes at https://atlas.hashicorp.com/search. config.vm.box = "debian/jessie64" + config.vm.hostname = "gva-dev" + # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. diff --git a/salt/roots/base/init.sls b/salt/roots/base/init.sls index 87bb303..db79767 100644 --- a/salt/roots/base/init.sls +++ b/salt/roots/base/init.sls @@ -4,3 +4,10 @@ base-packages: - screen - htop - git + +/home/vagrant/.screenrc: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0644 + - source: salt://base/screenrc diff --git a/salt/roots/base/screenrc b/salt/roots/base/screenrc new file mode 100644 index 0000000..a555c1e --- /dev/null +++ b/salt/roots/base/screenrc @@ -0,0 +1,14 @@ +# vim: syntax=screen + +hardstatus on +hardstatus alwayslastline +hardstatus string "%{= r}[ %{G}%H%{= r} ] %= %{=b b}%-w%{=rb db}%>%n %t%{-}%+w %=%{= r} [ %{G}%c %{M}%D %m-%d %{r}]" + +startup_message off + +defscrollback 10240 + +bind f eval "caption splitonly" "hardstatus ignore" +bind F eval "caption always" "hardstatus alwayslastline" + +defbce "on" From 5578647f33ea13f66a715c6f94f60055e7b50ce1 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 14:01:12 +0200 Subject: [PATCH 008/111] fix locale compilation, use variables for paths - install gettext - define and use variables checkout, home and appdir --- salt/roots/gnuviechadmin/webinterface.sls | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/salt/roots/gnuviechadmin/webinterface.sls b/salt/roots/gnuviechadmin/webinterface.sls index 90bddd4..d18f88e 100644 --- a/salt/roots/gnuviechadmin/webinterface.sls +++ b/salt/roots/gnuviechadmin/webinterface.sls @@ -9,10 +9,14 @@ gnuviechadmin-packages: - python-virtualenv - python-dev - python-pip + - gettext {% import "webserver/sslcert.macros.sls" as sslcert %} {% set venv = salt['pillar.get']('gnuviechadmin:virtualenv') %} +{% set checkout = '/vagrant' %} +{% set home = '/home/vagrant' %} +{% set appdir = checkout + '/gnuviechadmin' %} {% set domainname = salt['pillar.get']('gnuviechadmin:domainname') %} {{ sslcert.key_cert(domainname) }} @@ -23,7 +27,7 @@ gnuviechadmin-packages: - require: - cmd: gnuviechadmin-venv -/home/vagrant/gvasettings.sh: +{{ home }}/gvasettings.sh: file.managed: - user: vagrant - group: vagrant @@ -45,31 +49,32 @@ gnuviechadmin-requires: - name: {{ venv }}/bin/pip install -U -r requirements/local.txt && touch {{ venv }}/lastinstall - user: vagrant - group: vagrant - - cwd: /vagrant + - cwd: {{ checkout }} - require: - file: {{ venv }} - pkg: python-dev - pkg: libpq-dev - - unless: test -e {{ venv }}/lastinstall && test /vagrant/requirements/local.txt -ot {{ venv }}/lastinstall && test /vagrant/requirements/base.txt -ot {{ venv }}/lastinstall + - unless: test -e {{ venv }}/lastinstall && test {{ checkout }}/requirements/local.txt -ot {{ venv }}/lastinstall && test {{ checkout }}/requirements/base.txt -ot {{ venv }}/lastinstall gnuviechadmin-dbschema: cmd.wait: - - name: . /home/vagrant/gvasettings.sh ; {{ venv }}/bin/python manage.py migrate --noinput + - name: . {{ home }}/gvasettings.sh ; unset LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME ; {{ venv }}/bin/python manage.py migrate --noinput - user: vagrant - group: vagrant - - cwd: /vagrant/gnuviechadmin + - cwd: {{ appdir }} - watch: - cmd: gnuviechadmin-requires - - file: /home/vagrant/gvasettings.sh + - file: {{ home }}/gvasettings.sh gnuviechadmin-locale-data-compile: cmd.wait: - - name: . /home/vagrant/gvasettings.sh ; find /vagrant/gnuviechadmin -type d -name 'locale' | while read dir; do cd $(dirname "$dir") ; {{ venv }}/bin/python /vagrant/gnuviechadmin/manage.py compilemessages ; done + - name: . {{ home }}/gvasettings.sh ; find {{ appdir }} -type d -name 'locale' | while read dir; do cd $(dirname "$dir") ; {{ venv }}/bin/python {{ appdir }}/manage.py compilemessages ; done - user: vagrant - group: vagrant - - cwd: /vagrant/gnuviechadmin + - cwd: {{ appdir }} - require: - - file: /home/vagrant/gvasettings.sh + - pkg: gettext + - file: {{ home }}/gvasettings.sh - file: {{ venv }} /etc/nginx/sites-available/{{ domainname }}: @@ -85,8 +90,6 @@ gnuviechadmin-locale-data-compile: ssl_certdir: {{ salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') }} - require: - pkg: nginx - - watch_in: - - service: nginx /etc/nginx/sites-enabled/{{ domainname }}: file.symlink: From 5fe414133e8f27916f5ba89ba0b5f579f616ceb6 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 14:11:11 +0200 Subject: [PATCH 009/111] make settings configurable via pillar, default to local --- salt/pillar/gnuviechadmin/init.sls | 1 + salt/roots/gnuviechadmin/gvasettings.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/salt/pillar/gnuviechadmin/init.sls b/salt/pillar/gnuviechadmin/init.sls index e6e937e..b311790 100644 --- a/salt/pillar/gnuviechadmin/init.sls +++ b/salt/pillar/gnuviechadmin/init.sls @@ -3,6 +3,7 @@ include: - gnuviechadmin.queues gnuviechadmin: + deploymenttype: local mailfrom: admin@gnuviech-server.de adminemail: admin@gnuviech-server.de sitename: Gnuviech Customer Self Service diff --git a/salt/roots/gnuviechadmin/gvasettings.sh b/salt/roots/gnuviechadmin/gvasettings.sh index 16edcd3..94b5aab 100644 --- a/salt/roots/gnuviechadmin/gvasettings.sh +++ b/salt/roots/gnuviechadmin/gvasettings.sh @@ -1,6 +1,6 @@ #!/bin/sh -export DJANGO_SETTINGS_MODULE="gnuviechadmin.settings.production" +export DJANGO_SETTINGS_MODULE="gnuviechadmin.settings.{{ salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') }}" export GVA_ADMIN_NAME="Jan Dittberner" export GVA_ADMIN_EMAIL="{{ salt['pillar.get']('gnuviechadmin:adminemail') }}" export GVA_PGSQL_DATABASE="{{ salt['pillar.get']('gnuviechadmin-database:database') }}" From 3270b435787437cc438eba40a8b8bf55e6dbba18 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 15:27:46 +0200 Subject: [PATCH 010/111] forward rabbitmq webadmin port from vagrant box --- Vagrantfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Vagrantfile b/Vagrantfile index a4d3642..cb2e67a 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -26,6 +26,7 @@ Vagrant.configure(2) do |config| # accessing "localhost:8080" will access port 80 on the guest machine. config.vm.network "forwarded_port", guest: 443, host: 8443 config.vm.network "forwarded_port", guest: 8000, host: 8000 + config.vm.network "forwarded_port", guest: 15672, host: 15672 # Create a private network, which allows host-only access to the machine # using a specific IP. From 68170f7576a91c312df76f5cea91d144f32d6da7 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 15:28:23 +0200 Subject: [PATCH 011/111] enable rabbitmq management and add admin permissions - allow database creation for gnuviechadmin user in local deployments to allow test runs - set administrator tag for gnuviechadmin user in rabbitmq - add all permissions on gnuviechadmin vhost to gnuviechadmin user - enable rabbitmq management plugin --- salt/roots/gnuviechadmin/database.sls | 3 +++ salt/roots/gnuviechadmin/queues.sls | 7 +++++++ salt/roots/rabbitmq-server/init.sls | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/salt/roots/gnuviechadmin/database.sls b/salt/roots/gnuviechadmin/database.sls index 1323368..5e9c963 100644 --- a/salt/roots/gnuviechadmin/database.sls +++ b/salt/roots/gnuviechadmin/database.sls @@ -7,6 +7,9 @@ gnuviechadmin-database: - user: postgres - password: {{ salt['pillar.get']('gnuviechadmin-database:owner:password') }} - login: True + - createdb: {% if salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') == 'local' %}True +{%- else %}False +{%- endif %} - require: - service: postgresql postgres_database.present: diff --git a/salt/roots/gnuviechadmin/queues.sls b/salt/roots/gnuviechadmin/queues.sls index 3a33604..e529dda 100644 --- a/salt/roots/gnuviechadmin/queues.sls +++ b/salt/roots/gnuviechadmin/queues.sls @@ -5,6 +5,13 @@ gnuviechadmin-queues: rabbitmq_user.present: - name: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} - password: {{ salt['pillar.get']('gnuviechadmin-queues:owner:password') }} + - tags: + - administrator + - perms: + - {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }}: + - '.*' + - '.*' + - '.*' rabbitmq_vhost.present: - name: {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} - owner: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} diff --git a/salt/roots/rabbitmq-server/init.sls b/salt/roots/rabbitmq-server/init.sls index 32ac09f..6c3c7f6 100644 --- a/salt/roots/rabbitmq-server/init.sls +++ b/salt/roots/rabbitmq-server/init.sls @@ -9,3 +9,9 @@ rabbitmq-server: guest: rabbitmq_user: - absent + +rabbitmq_management: + rabbitmq_plugin: + - enabled + - watch_in: + - service: rabbitmq-server From 8ebb5cad6ab0034b9acfec2bf2a125c966a3fadc Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 15:30:23 +0200 Subject: [PATCH 012/111] bump dependency versions, fix tests and deprecation warnings --- gnuviechadmin/managemails/admin.py | 2 +- gnuviechadmin/osusers/models.py | 5 ++++- requirements/base.txt | 30 +++++++++++++++--------------- requirements/local.txt | 11 +++++------ requirements/test.txt | 4 ++-- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/gnuviechadmin/managemails/admin.py b/gnuviechadmin/managemails/admin.py index f059fd4..da12f85 100644 --- a/gnuviechadmin/managemails/admin.py +++ b/gnuviechadmin/managemails/admin.py @@ -1,7 +1,7 @@ from django.utils.html import format_html from django.contrib import admin from django import forms -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.utils.translation import ugettext as _ from .models import ( diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 28e93a7..1122ed6 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -533,7 +533,10 @@ class SshPublicKeyManager(models.Manager): raise ValueError('invalid SSH public key') data = parts[1] comment = len(parts) == 3 and parts[2] or "" - keybytes = base64.b64decode(data) + try: + keybytes = base64.b64decode(data) + except TypeError: + raise ValueError('invalid SSH public key') parts = keybytes.split(b'\x00' * 3) alglength = six.byte2int(parts[1]) algname = parts[1][1:1+alglength] diff --git a/requirements/base.txt b/requirements/base.txt index 74be894..b15e7da 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,18 +1,18 @@ -Django==1.7.4 -bpython==0.13.1 -django-braces==1.4.0 -django-model-utils==2.2 -django-crispy-forms==1.4.0 +Django==1.8.5 +bpython==0.14.2 +django-braces==1.8.1 +django-model-utils==2.3.1 +django-crispy-forms==1.5.2 logutils==0.3.3 -psycopg2==2.5.4 -passlib==1.6.2 -celery==3.1.17 -billiard==3.3.0.19 -kombu==3.0.24 -pytz==2014.10 -pyaml==14.12.10 -django-allauth==0.19.0 -oauthlib==0.7.2 +psycopg2==2.6.1 +passlib==1.6.5 +celery==3.1.18 +billiard==3.3.0.20 +kombu==3.0.27 +pytz==2015.6 +pyaml==15.8.2 +django-allauth==0.21.0 +oauthlib==1.0.3 python-openid==2.2.5 requests==2.5.1 -requests-oauthlib==0.4.2 +requests-oauthlib==0.5.0 diff --git a/requirements/local.txt b/requirements/local.txt index 97a9db7..e2bf154 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -1,8 +1,7 @@ # Local development dependencies go here --r base.txt -coverage==3.7.1 -mock==1.0.1 -django-debug-toolbar==1.2.2 -sqlparse==0.1.14 -Sphinx==1.2.3 +-r test.txt +django-debug-toolbar==1.4 +sqlparse==0.1.16 +Sphinx==1.3.1 +snowballstemmer==1.2.0 releases==0.7.0 diff --git a/requirements/test.txt b/requirements/test.txt index 8bf1098..96baf85 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,4 @@ # Test dependencies go here. -r base.txt -coverage==3.7.1 -mock==1.0.1 +coverage==4.0 +mock==1.3.0 From 04871bb488209da65761a48ebcd835595ccd4f6d Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 15:38:45 +0200 Subject: [PATCH 013/111] setup vimrc file for vagrant user --- salt/roots/vim/init.sls | 7 +++++++ salt/roots/vim/vimrc | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 salt/roots/vim/vimrc diff --git a/salt/roots/vim/init.sls b/salt/roots/vim/init.sls index b239835..f8efbee 100644 --- a/salt/roots/vim/init.sls +++ b/salt/roots/vim/init.sls @@ -1,2 +1,9 @@ vim-nox: pkg.installed + +/home/vagrant/.vimrc: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0644 + - source: salt://vim/vimrc diff --git a/salt/roots/vim/vimrc b/salt/roots/vim/vimrc new file mode 100644 index 0000000..19be571 --- /dev/null +++ b/salt/roots/vim/vimrc @@ -0,0 +1,33 @@ +syntax on + +set showcmd +set modeline +set modelines=3 +set expandtab +set shiftwidth=4 +set autoindent +set smarttab +set ruler +set list listchars=tab:▷⋅,trail:⋅,nbsp:⋅ +set cpoptions+=$ +set hlsearch +set virtualedit=all +set guioptions-=T +set guioptions-=m +set wildmenu +set complete=.,w,b,u,t + +filetype plugin indent on + +autocmd BufNewFile,BufRead *.sls set filetype=yaml + +autocmd FileType make set noexpandtab +autocmd FileType python set tabstop=4 shiftwidth=4 autoindent smartindent textwidth=79 +autocmd FileType html set tabstop=2 shiftwidth=2 textwidth=200 smartindent autoindent +autocmd FileType htmldjango set tabstop=2 shiftwidth=2 textwidth=200 +autocmd FileType moin set tabstop=2 shiftwidth=2 +autocmd FileType rst set textwidth=79 +autocmd FileType yaml set tabstop=2 shiftwidth=2 + +set laststatus=2 +set statusline=%f%m%r%h%w\ [TYPE=%Y\ %{&ff}]\ \ [%c\ @\ %l/%L]\ (%p%%)\ [%b\ 0x%B] From 4f36c21d5b00e749e39253bfbf7a7c70195445f5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 16:38:42 +0200 Subject: [PATCH 014/111] add host alias mq --- salt/roots/gnuviechadmin/webinterface.sls | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/salt/roots/gnuviechadmin/webinterface.sls b/salt/roots/gnuviechadmin/webinterface.sls index d18f88e..0c2d5be 100644 --- a/salt/roots/gnuviechadmin/webinterface.sls +++ b/salt/roots/gnuviechadmin/webinterface.sls @@ -1,6 +1,10 @@ include: - webserver +mq: + host.present: + - ip: 127.0.0.1 + gnuviechadmin-packages: pkg.installed: - names: From bcfea10e6fffff9edf95c7e76498840ee9a1131f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 11 Oct 2015 23:17:21 +0200 Subject: [PATCH 015/111] add simplejson to make task serialization work --- requirements/base.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/base.txt b/requirements/base.txt index b15e7da..03200d5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -16,3 +16,4 @@ oauthlib==1.0.3 python-openid==2.2.5 requests==2.5.1 requests-oauthlib==0.5.0 +simplejson==3.8.0 From d5bba7a22d97ea6a9e5c739087f1ac5d16634636 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 12 Oct 2015 00:23:31 +0200 Subject: [PATCH 016/111] asynchronous refactoring - don't execute celery tasks directly - introduce optional parameters to fileserver tasks to allow chaining - handle user/group/key create and delete tasks in new osusers.signals class - adapt unit tests - change TaskResults model to store the task signatures - generalize the local settings' logging configuration --- gnuviechadmin/fileservertasks/tasks.py | 8 +- .../gnuviechadmin/context_processors.py | 2 +- gnuviechadmin/gnuviechadmin/settings/base.py | 1 + gnuviechadmin/gnuviechadmin/settings/local.py | 31 +--- gnuviechadmin/ldaptasks/tasks.py | 1 - gnuviechadmin/osusers/apps.py | 4 + gnuviechadmin/osusers/models.py | 105 +----------- gnuviechadmin/osusers/signals.py | 160 ++++++++++++++++++ gnuviechadmin/osusers/tests/test_models.py | 61 ++++--- .../migrations/0002_auto_20151011_2248.py | 36 ++++ gnuviechadmin/taskresults/models.py | 23 ++- .../taskresults/tests/test_models.py | 28 +-- 12 files changed, 290 insertions(+), 170 deletions(-) create mode 100644 gnuviechadmin/osusers/signals.py create mode 100644 gnuviechadmin/taskresults/migrations/0002_auto_20151011_2248.py diff --git a/gnuviechadmin/fileservertasks/tasks.py b/gnuviechadmin/fileservertasks/tasks.py index c04d395..75ebad0 100644 --- a/gnuviechadmin/fileservertasks/tasks.py +++ b/gnuviechadmin/fileservertasks/tasks.py @@ -9,7 +9,7 @@ from celery import shared_task @shared_task -def setup_file_sftp_userdir(username): +def setup_file_sftp_userdir(username, *args, **kwargs): """ This task creates the home directory for an SFTP user if it does not exist yet. @@ -23,7 +23,7 @@ def setup_file_sftp_userdir(username): @shared_task -def delete_file_sftp_userdir(username): +def delete_file_sftp_userdir(username, *args, **kwargs): """ This task recursively deletes the home directory of an SFTP user if it does not exist yet. @@ -37,7 +37,7 @@ def delete_file_sftp_userdir(username): @shared_task -def setup_file_mail_userdir(username): +def setup_file_mail_userdir(username, *args, **kwargs): """ This task creates the mail base directory for a user if it does not exist yet. @@ -52,7 +52,7 @@ def setup_file_mail_userdir(username): @shared_task -def delete_file_mail_userdir(username): +def delete_file_mail_userdir(username, *args, **kwargs): """ This task recursively deletes the mail base directory for a user if it does not exist yet. diff --git a/gnuviechadmin/gnuviechadmin/context_processors.py b/gnuviechadmin/gnuviechadmin/context_processors.py index 3e71aa2..4221551 100644 --- a/gnuviechadmin/gnuviechadmin/context_processors.py +++ b/gnuviechadmin/gnuviechadmin/context_processors.py @@ -50,7 +50,7 @@ def navigation(request): request.path.endswith('/impressum/') ): context['active_item'] = 'imprint' - else: + elif not viewmodule.startswith('django.contrib.admin'): _LOGGER.debug( 'no special handling for view %s in module %s, fallback to ' 'default active menu item %s', diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 0e79b62..891811d 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -261,6 +261,7 @@ ALLAUTH_APPS = ( LOCAL_APPS = ( 'dashboard', 'taskresults', + 'ldaptasks', 'mysqltasks', 'pgsqltasks', 'fileservertasks', diff --git a/gnuviechadmin/gnuviechadmin/settings/local.py b/gnuviechadmin/gnuviechadmin/settings/local.py index 6cc5823..14c7139 100644 --- a/gnuviechadmin/gnuviechadmin/settings/local.py +++ b/gnuviechadmin/gnuviechadmin/settings/local.py @@ -49,30 +49,13 @@ LOGGING['handlers'].update({ 'formatter': 'simple', } }) -LOGGING['loggers'].update({ - 'gnuviechadmin': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'dashboard': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'domains': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'gvacommon': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'gvawebcore': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'hostingpackages': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'managemails': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'osusers': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'taskresults': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'userdbs': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, - 'websites': { - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}, -}) +LOGGING['loggers'].update(dict( + [(key, {'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}) + for key in [ + 'dashboard', 'domains', 'fileservertasks', 'gvacommon', + 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', + 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', + 'userdbs', 'websites']])) DEBUG_TOOLBAR_PATCH_SETTINGS = False diff --git a/gnuviechadmin/ldaptasks/tasks.py b/gnuviechadmin/ldaptasks/tasks.py index 72acd8d..c90dd4f 100644 --- a/gnuviechadmin/ldaptasks/tasks.py +++ b/gnuviechadmin/ldaptasks/tasks.py @@ -7,7 +7,6 @@ from __future__ import absolute_import from celery import shared_task - @shared_task def create_ldap_group(groupname, gid, descr): """ diff --git a/gnuviechadmin/osusers/apps.py b/gnuviechadmin/osusers/apps.py index 26f373d..0a9c739 100644 --- a/gnuviechadmin/osusers/apps.py +++ b/gnuviechadmin/osusers/apps.py @@ -4,6 +4,7 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the """ from __future__ import unicode_literals + from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ @@ -15,3 +16,6 @@ class OsusersAppConfig(AppConfig): """ name = 'osusers' verbose_name = _('Operating System Users and Groups') + + def ready(self): + import osusers.signals diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 1122ed6..6b4f2a2 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -10,9 +10,12 @@ import logging import os import six +from celery import group + from django.db import models, transaction from django.conf import settings from django.core.exceptions import ValidationError +from django.dispatch import Signal from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ @@ -24,28 +27,13 @@ from passlib.utils import generate_password from taskresults.models import TaskResult -from ldaptasks.tasks import ( - add_ldap_user_to_group, - create_ldap_group, - create_ldap_user, - delete_ldap_group, - delete_ldap_user, - remove_ldap_user_from_group, - set_ldap_user_password, -) - -from fileservertasks.tasks import ( - delete_file_mail_userdir, - delete_file_sftp_userdir, - set_file_ssh_authorized_keys, - setup_file_mail_userdir, - setup_file_sftp_userdir, -) - _LOGGER = logging.getLogger(__name__) +password_set = Signal(providing_args=['instance', 'password']) + + CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _( "You can not use a user's primary group.") @@ -108,9 +96,6 @@ class Group(TimeStampedModel, models.Model): """ super(Group, self).save(*args, **kwargs) - dn = create_ldap_group.delay( - self.groupname, self.gid, self.descr).get() - _LOGGER.info("created LDAP group with dn %s", dn) return self @transaction.atomic @@ -124,10 +109,6 @@ class Group(TimeStampedModel, models.Model): :py:meth:`django.db.Model.delete` """ - TaskResult.objects.create_task_result( - delete_ldap_group.delay(self.groupname), - 'delete_ldap_group' - ) super(Group, self).delete(*args, **kwargs) @@ -249,25 +230,13 @@ class User(TimeStampedModel, models.Model): """ if hasattr(self, 'shadow'): self.shadow.set_password(password) - success = set_ldap_user_password.delay( - self.username, password).get() - if success: - _LOGGER.info( - "successfully set LDAP password for %s", self.username) - else: - _LOGGER.error( - "setting the LDAP password for %s failed", self.username) - return success else: self.shadow = Shadow.objects.create_shadow( user=self, password=password ) - dn = create_ldap_user.delay( - self.username, self.uid, self.group.gid, self.gecos, - self.homedir, self.shell, password - ).get() - _LOGGER.info("set LDAP password for %s", dn) - return True + password_set.send( + sender=self.__class__, password=password, instance=self) + return True def is_sftp_user(self): return self.additionalgroup_set.filter( @@ -288,22 +257,6 @@ class User(TimeStampedModel, models.Model): :rtype: :py:class:`osusers.models.User` """ - dn = create_ldap_user.delay( - self.username, self.uid, self.group.gid, self.gecos, - self.homedir, self.shell, password=None).get() - TaskResult.objects.create_task_result( - setup_file_sftp_userdir.delay(self.username), - 'setup_file_sftp_userdir' - ) - TaskResult.objects.create_task_result( - setup_file_mail_userdir.delay(self.username), - 'setup_file_mail_userdir' - ) - _LOGGER.info( - "created user %(user)s with LDAP dn %(dn)s, scheduled home " - "directory and mail base directory creation.", { - 'user': self, 'dn': dn, - }) return super(User, self).save(*args, **kwargs) @transaction.atomic @@ -318,18 +271,6 @@ class User(TimeStampedModel, models.Model): :py:meth:`django.db.Model.delete` """ - TaskResult.objects.create_task_result( - delete_file_mail_userdir.delay(self.username), - 'delete_file_mail_userdir' - ) - TaskResult.objects.create_task_result( - delete_file_sftp_userdir.delay(self.username), - 'delete_file_sftp_userdir' - ) - for group in [ag.group for ag in self.additionalgroup_set.all()]: - remove_ldap_user_from_group.delay( - self.username, group.groupname).get() - delete_ldap_user.delay(self.username).get() self.group.delete() super(User, self).delete(*args, **kwargs) @@ -460,8 +401,6 @@ class AdditionalGroup(TimeStampedModel, models.Model): :rtype: :py:class:`AdditionalGroup ` """ - add_ldap_user_to_group.delay( - self.user.username, self.group.groupname).get() return super(AdditionalGroup, self).save(*args, **kwargs) @transaction.atomic @@ -474,11 +413,6 @@ class AdditionalGroup(TimeStampedModel, models.Model): :param kwargs: keyword arguments to be passed on to :py:meth:`django.db.Model.delete` """ - TaskResult.objects.create_task_result( - remove_ldap_user_from_group.delay( - self.user.username, self.group.groupname), - 'remove_ldap_user_from_group' - ) super(AdditionalGroup, self).delete(*args, **kwargs) @@ -583,24 +517,3 @@ class SshPublicKey(TimeStampedModel): return "{algorithm} {data} {comment}".format( algorithm=self.algorithm, data=self.data, comment=self.comment ).strip() - - def save(self, **kwargs): - key = super(SshPublicKey, self).save(**kwargs) - TaskResult.objects.create_task_result( - set_file_ssh_authorized_keys.delay( - self.user.username, [ - str(key) for key in - SshPublicKey.objects.filter(user=self.user)]), - 'set_file_ssh_authorized_keys' - ) - return key - - def delete(self, **kwargs): - super(SshPublicKey, self).delete(**kwargs) - TaskResult.objects.create_task_result( - set_file_ssh_authorized_keys.delay( - self.user.username, [ - str(key) for key in - SshPublicKey.objects.filter(user=self.user)]), - 'set_file_ssh_authorized_keys' - ) diff --git a/gnuviechadmin/osusers/signals.py b/gnuviechadmin/osusers/signals.py new file mode 100644 index 0000000..8796554 --- /dev/null +++ b/gnuviechadmin/osusers/signals.py @@ -0,0 +1,160 @@ +""" +This module contains the signal handlers of the :py:mod:`osusers` app. + +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from django.db.models.signals import ( + m2m_changed, + post_delete, + post_save, +) +from django.dispatch import receiver + +from celery import chain, group + +from fileservertasks.tasks import ( + delete_file_mail_userdir, + delete_file_sftp_userdir, + set_file_ssh_authorized_keys, + setup_file_mail_userdir, + setup_file_sftp_userdir, +) +from ldaptasks.tasks import ( + add_ldap_user_to_group, + create_ldap_group, + create_ldap_user, + delete_ldap_group, + delete_ldap_user, + remove_ldap_user_from_group, + set_ldap_user_password, +) +from taskresults.models import TaskResult + +from .models import ( + AdditionalGroup, + Group, + SshPublicKey, + User, + password_set, +) + + +_LOGGER = logging.getLogger(__name__) + + +@receiver(password_set, sender=User) +def handle_user_password_set(sender, instance, password, **kwargs): + taskresult = TaskResult.objects.create_task_result( + 'handle_user_password_set', + set_ldap_user_password.s(instance.username, password)) + _LOGGER.info( + 'LDAP password change has been requested in task %s', + taskresult.task_id) + + +#@receiver(post_save) +#def handle_post_save(sender, **kwargs): +# _LOGGER.debug( +# 'handling post_save signal for %s with args %s', +# sender, kwargs) + + +@receiver(post_save, sender=Group) +def handle_group_created(sender, instance, created, **kwargs): + if created: + taskresult = TaskResult.objects.create_task_result( + 'handle_group_created', + create_ldap_group.s( + instance.groupname, instance.gid, instance.descr)) + _LOGGER.info( + 'LDAP group creation has been requested in task %s', + taskresult.task_id) + _LOGGER.debug( + 'group %s has been %s', instance, created and "created" or "updated") + + +@receiver(post_save, sender=User) +def handle_user_created(sender, instance, created, **kwargs): + if created: + chain = create_ldap_user.s( + instance.username, instance.uid, instance.group.gid, + instance.gecos, instance.homedir, instance.shell, None + ) | setup_file_sftp_userdir.s(instance.username + ) | setup_file_mail_userdir.s(instance.username) + taskresult = TaskResult.objects.create_task_result( + 'handle_user_created', chain) + _LOGGER.info( + 'LDAP user creation has been requested in task %s', + taskresult.task_id) + _LOGGER.debug( + 'user %s has been %s', instance, created and "created" or "updated") + + +@receiver(post_save, sender=AdditionalGroup) +def handle_user_added_to_group(sender, instance, created, **kwargs): + if created: + taskresult = TaskResult.objects.create_task_result( + 'handle_user_added_to_group', + add_ldap_user_to_group.s( + instance.user.username, instance.group.groupname)) + _LOGGER.info( + 'Adding user to LDAP group has been requested in task %s', + taskresult.task_id) + + +@receiver(post_save, sender=SshPublicKey) +@receiver(post_delete, sender=SshPublicKey) +def handle_ssh_keys_changed(sender, instance, **kwargs): + sig = set_file_ssh_authorized_keys.s( + instance.user.username, [ + str(key) for key in + SshPublicKey.objects.filter(user=instance.user)]) + taskresult = TaskResult.objects.create_task_result( + 'handle_ssh_keys_changed', sig) + _LOGGER.info( + 'Change of SSH keys has been requested in task %s', + taskresult.task_id) + + +#@receiver(post_delete) +#def handle_post_delete(sender, **kwargs): +# _LOGGER.debug( +# 'handling post_delete signal for %s with args %s', +# sender, kwargs) + + +@receiver(post_delete, sender=Group) +def handle_group_deleted(sender, instance, **kwargs): + taskresult = TaskResult.objects.create_task_result( + 'handle_group_deleted', + delete_ldap_group.s(instance.groupname)) + _LOGGER.info( + 'LDAP group deletion has been requested in task %s', + taskresult.task_id) + + +@receiver(post_delete, sender=User) +def handle_user_deleted(sender, instance, **kwargs): + chain = delete_file_mail_userdir.s(instance.username + ) | delete_file_sftp_userdir.s(instance.username + ) | delete_ldap_user.s(instance.username) + _LOGGER.debug('chain signature %s', chain) + taskresult = TaskResult.objects.create_task_result( + 'handle_user_deleted', chain) + _LOGGER.info( + 'LDAP user deletion has been requested in task %s', + taskresult.task_id) + + +@receiver(post_delete, sender=AdditionalGroup) +def handle_user_removed_from_group(sender, instance, **kwargs): + taskresult = TaskResult.objects.create_task_result( + 'handle_user_removed_from_group', + remove_ldap_user_from_group.s( + instance.user.username, instance.group.groupname)) + _LOGGER.info( + 'Removing user from LDAP group has been requested in task %s', + taskresult.task_id) diff --git a/gnuviechadmin/osusers/tests/test_models.py b/gnuviechadmin/osusers/tests/test_models.py index 1897234..4189904 100644 --- a/gnuviechadmin/osusers/tests/test_models.py +++ b/gnuviechadmin/osusers/tests/test_models.py @@ -128,8 +128,12 @@ class AdditionalGroupTest(TestCaseWithCeleryTasks): addgroup = AdditionalGroup(user=self.user, group=group2) addgroup.save() taskres = TaskResult.objects.all() - self.assertTrue(len(taskres), 1) - self.assertEqual(taskres[0].task_name, 'setup_file_sftp_userdir') + self.assertTrue(len(taskres), 4) + creators = [r.creator for r in taskres] + for tcount, tcreator in [ + (2, 'handle_group_created'), (1, 'handle_user_created'), + (1, 'handle_user_added_to_group')]: + self.assertEqual(creators.count(tcreator), tcount) def test_delete(self): group2 = Group.objects.create(groupname='test2', gid=1001) @@ -167,9 +171,9 @@ class GroupTest(TestCaseWithCeleryTasks): self.assertEqual(len(Group.objects.all()), 1) group.delete() self.assertEqual(len(Group.objects.all()), 0) - self.assertEqual(len(TaskResult.objects.all()), 1) + self.assertEqual(len(TaskResult.objects.all()), 2) tr = TaskResult.objects.first() - self.assertEqual(tr.task_name, 'delete_ldap_group') + self.assertEqual(tr.creator, 'handle_group_created') class ShadowManagerTest(TestCaseWithCeleryTasks): @@ -275,10 +279,12 @@ class UserManagerTest(TestCaseWithCeleryTasks): def test_create_user_tasks(self): User.objects.create_user(customer=self.customer) taskres = TaskResult.objects.all() - self.assertEqual(len(taskres), 2) - tasknames = [r.task_name for r in taskres] - self.assertEqual(tasknames.count('setup_file_sftp_userdir'), 1) - self.assertEqual(tasknames.count('setup_file_mail_userdir'), 1) + self.assertEqual(len(taskres), 3) + creators = [r.creator for r in taskres] + for creator in [ + 'handle_group_created', 'handle_user_created', + 'handle_user_password_set']: + self.assertIn(creator, creators) def test_create_user_second(self): User.objects.create_user(customer=self.customer) @@ -351,24 +357,26 @@ class UserTest(TestCaseWithCeleryTasks): def test_save(self): user = User.objects.create_user(self.customer) - TaskResult.objects.all().delete() user.save() taskres = TaskResult.objects.all() - self.assertEqual(len(taskres), 2) - task_names = [r.task_name for r in taskres] - self.assertIn('setup_file_sftp_userdir', task_names) - self.assertIn('setup_file_mail_userdir', task_names) + self.assertEqual(len(taskres), 3) + creators = [r.creator for r in taskres] + for task in [ + 'handle_group_created', 'handle_user_created', + 'handle_user_password_set']: + self.assertIn(task, creators) def test_delete_only_user(self): user = User.objects.create_user(self.customer) - TaskResult.objects.all().delete() user.delete() taskres = TaskResult.objects.all() - self.assertEqual(len(taskres), 3) - tasknames = [r.task_name for r in taskres] - self.assertEqual(tasknames.count('delete_file_mail_userdir'), 1) - self.assertEqual(tasknames.count('delete_file_sftp_userdir'), 1) - self.assertEqual(tasknames.count('delete_ldap_group'), 1) + self.assertEqual(len(taskres), 6) + creators = [r.creator for r in taskres] + for task in [ + 'handle_group_created', 'handle_user_created', + 'handle_user_password_set', 'handle_user_deleted', + 'handle_group_deleted', 'handle_user_deleted']: + self.assertIn(task, creators) self.assertEqual(len(User.objects.all()), 0) def test_delete_additional_groups(self): @@ -381,11 +389,12 @@ class UserTest(TestCaseWithCeleryTasks): TaskResult.objects.all().delete() user.delete() taskres = TaskResult.objects.all() - self.assertEqual(len(taskres), 3) - tasknames = [t.task_name for t in taskres] - self.assertEqual(tasknames.count('delete_file_mail_userdir'), 1) - self.assertEqual(tasknames.count('delete_file_sftp_userdir'), 1) - self.assertEqual(tasknames.count('delete_ldap_group'), 1) + self.assertEqual(len(taskres), 5) + creators = [t.creator for t in taskres] + for tcount, tcreator in [ + (2, 'handle_user_removed_from_group'), (2, 'handle_user_deleted'), + (1, 'handle_group_deleted')]: + self.assertEqual(creators.count(tcreator), tcount) self.assertEqual(len(User.objects.all()), 0) self.assertEqual(len(AdditionalGroup.objects.all()), 0) @@ -497,7 +506,7 @@ class SshPublicKeyTest(TestCaseWithCeleryTasks): taskresults = TaskResult.objects.all() self.assertEqual(len(taskresults), 1) self.assertEqual( - taskresults[0].task_name, 'set_file_ssh_authorized_keys') + taskresults[0].creator, 'handle_ssh_keys_changed') def test_call_tasks_on_delete(self): key = SshPublicKey.objects.create_ssh_public_key( @@ -507,4 +516,4 @@ class SshPublicKeyTest(TestCaseWithCeleryTasks): taskresults = TaskResult.objects.all() self.assertEqual(len(taskresults), 1) self.assertEqual( - taskresults[0].task_name, 'set_file_ssh_authorized_keys') + taskresults[0].creator, 'handle_ssh_keys_changed') diff --git a/gnuviechadmin/taskresults/migrations/0002_auto_20151011_2248.py b/gnuviechadmin/taskresults/migrations/0002_auto_20151011_2248.py new file mode 100644 index 0000000..a13aee6 --- /dev/null +++ b/gnuviechadmin/taskresults/migrations/0002_auto_20151011_2248.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('taskresults', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='taskresult', + name='task_name', + ), + migrations.AddField( + model_name='taskresult', + name='creator', + field=models.TextField(default='migrated', verbose_name='Task creator'), + preserve_default=False, + ), + migrations.AddField( + model_name='taskresult', + name='notes', + field=models.TextField(default='', verbose_name='Task notes'), + preserve_default=False, + ), + migrations.AddField( + model_name='taskresult', + name='signature', + field=models.TextField(default='', verbose_name='Task signature'), + preserve_default=False, + ), + ] diff --git a/gnuviechadmin/taskresults/models.py b/gnuviechadmin/taskresults/models.py index 7178c84..399505f 100644 --- a/gnuviechadmin/taskresults/models.py +++ b/gnuviechadmin/taskresults/models.py @@ -12,15 +12,21 @@ from gnuviechadmin.celery import app class TaskResultManager(models.Manager): - def create_task_result(self, asyncresult, name): - taskresult = self.create(task_id=asyncresult.id, task_name=name) + def create_task_result(self, creator, signature, notes=''): + sigstr = str(signature) + result = signature.apply_async() + taskresult = self.create( + task_id=result.task_id, creator=creator, signature=sigstr, + notes=notes) return taskresult @python_2_unicode_compatible class TaskResult(models.Model): task_id = models.CharField(_('Task id'), max_length=36) - task_name = models.CharField(_('Task name'), max_length=64) + signature = models.TextField(_('Task signature')) + creator = models.TextField(_('Task creator')) + notes = models.TextField(_('Task notes')) result = models.TextField(_('Task result')) finished = models.BooleanField(default=False) state = models.CharField(_('Task state'), max_length=16) @@ -32,8 +38,8 @@ class TaskResult(models.Model): verbose_name_plural = _('Task results') def __str__(self): - return "{task_name} ({task_id}): {finished}".format( - task_name=self.task_name, + return "{creator} ({task_id}): {finished}".format( + creator=self.creator, task_id=self.task_id, finished=_('yes') if self.finished else _('no') ) @@ -41,7 +47,8 @@ class TaskResult(models.Model): def fetch_result(self): if not self.finished: ar = app.AsyncResult(self.task_id) - res = ar.get(no_ack=True, timeout=1) - self.result = str(res) self.state = ar.state - self.finished = True + if ar.ready(): + res = ar.get() + self.result = str(res) + self.finished = True diff --git a/gnuviechadmin/taskresults/tests/test_models.py b/gnuviechadmin/taskresults/tests/test_models.py index dcace59..17b1aec 100644 --- a/gnuviechadmin/taskresults/tests/test_models.py +++ b/gnuviechadmin/taskresults/tests/test_models.py @@ -13,24 +13,30 @@ TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5' class TaskResultTest(TestCase): @patch('taskresults.models.app') def test_update_taskstatus_unfinished(self, app): - mock = MagicMock(id=TEST_TASK_UUID, task_name=TEST_TASK_NAME) - mock.ready.return_value = False - tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME) + resultmock = MagicMock(task_id=TEST_TASK_UUID) + resultmock.ready.return_value = False + mock = MagicMock() + mock.apply_async.return_value = resultmock + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, mock) self.assertFalse(tr.finished) mymock = app.AsyncResult(TEST_TASK_UUID) mymock.state = 'SUCCESS' mymock.get.return_value = TEST_RESULT tr.fetch_result() - mymock.get.assert_called_with(no_ack=True, timeout=1) + mymock.get.assert_called_with() self.assertTrue(tr.finished) @patch('taskresults.models.app') def test_update_taskstatus_finished(self, app): - mock = MagicMock(id=TEST_TASK_UUID, task_name=TEST_TASK_NAME) - mock.ready.return_value = True + resultmock = MagicMock(task_id=TEST_TASK_UUID) + resultmock.ready.return_value = True + resultmock.state = 'SUCCESS' + resultmock.result = TEST_RESULT + mock = MagicMock() + mock.apply_async.return_value = resultmock mock.state = 'SUCCESS' mock.result = TEST_RESULT - tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME) + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, mock) tr.fetch_result() self.assertTrue(tr.finished) mymock = app.AsyncResult(TEST_TASK_UUID) @@ -47,8 +53,10 @@ TEST_RESULT.ready.return_value = False class TaskResultManagerTest(TestCase): def test_create_task_result(self): - mock = MagicMock(id=TEST_TASK_UUID) - tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME) + resultmock = MagicMock(task_id=TEST_TASK_UUID) + mock = MagicMock() + mock.apply_async.return_value = resultmock + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, mock) self.assertIsInstance(tr, TaskResult) self.assertEqual(tr.task_id, TEST_TASK_UUID) - self.assertEqual(tr.task_name, TEST_TASK_NAME) + self.assertEqual(tr.creator, TEST_TASK_NAME) From 660ffa9de9832865c0badf33f0521a436661eca2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 12 Oct 2015 18:42:14 +0000 Subject: [PATCH 017/111] bump version to new development version --- gnuviechadmin/gnuviechadmin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py index 2e822b8..31b3a0d 100644 --- a/gnuviechadmin/gnuviechadmin/__init__.py +++ b/gnuviechadmin/gnuviechadmin/__init__.py @@ -1,3 +1,3 @@ from gnuviechadmin.celery import app as celery_app -__version__ = '0.11.3' +__version__ = '0.12.dev1' From 084dd5ba8d81fe561ad859d8ffe6576ce9c95c1f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 12 Oct 2015 18:43:52 +0000 Subject: [PATCH 018/111] add unit tests for dashboard app --- gnuviechadmin/dashboard/tests/__init__.py | 4 ++ gnuviechadmin/dashboard/tests/test_views.py | 72 +++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 gnuviechadmin/dashboard/tests/__init__.py create mode 100644 gnuviechadmin/dashboard/tests/test_views.py diff --git a/gnuviechadmin/dashboard/tests/__init__.py b/gnuviechadmin/dashboard/tests/__init__.py new file mode 100644 index 0000000..b01b093 --- /dev/null +++ b/gnuviechadmin/dashboard/tests/__init__.py @@ -0,0 +1,4 @@ +""" +Tests for :py:mod:`dashboard`. + +""" diff --git a/gnuviechadmin/dashboard/tests/test_views.py b/gnuviechadmin/dashboard/tests/test_views.py new file mode 100644 index 0000000..690507a --- /dev/null +++ b/gnuviechadmin/dashboard/tests/test_views.py @@ -0,0 +1,72 @@ +""" +Tests for :py:mod:`dashboard.views`. + +""" + +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.contrib.auth import get_user_model + +from dashboard.views import ( + IndexView, + UserDashboardView, +) + +User = get_user_model() + +TEST_USER = 'test' +TEST_PASSWORD = 'secret' + + +class IndexViewTest(TestCase): + + def test_index_view(self): + response = self.client.get(reverse('dashboard')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'dashboard/index.html') + + +class UserDashboardViewTest(TestCase): + + def _create_test_user(self): + self.user = User.objects.create(username=TEST_USER) + self.user.set_password(TEST_PASSWORD) + self.user.save() + + def test_user_dashboard_view_no_user(self): + response = self.client.get(reverse( + 'customer_dashboard', kwargs={'slug': TEST_USER})) + self.assertEqual(response.status_code, 404) + + def test_user_dashboard_view_anonymous(self): + User.objects.create(username=TEST_USER) + response = self.client.get(reverse( + 'customer_dashboard', kwargs={'slug': TEST_USER})) + self.assertEqual(response.status_code, 403) + + def test_user_dashboard_view_logged_in_ok(self): + self._create_test_user() + self.assertTrue( + self.client.login(username=TEST_USER, password=TEST_PASSWORD)) + response = self.client.get(reverse( + 'customer_dashboard', kwargs={'slug': TEST_USER})) + self.assertEqual(response.status_code, 200) + + def test_user_dashboard_view_logged_in_template(self): + self._create_test_user() + self.assertTrue( + self.client.login(username=TEST_USER, password=TEST_PASSWORD)) + response = self.client.get(reverse( + 'customer_dashboard', kwargs={'slug': TEST_USER})) + self.assertTemplateUsed(response, 'dashboard/user_dashboard.html') + + def test_user_dashboard_view_logged_in_context_fresh(self): + self._create_test_user() + self.assertTrue( + self.client.login(username=TEST_USER, password=TEST_PASSWORD)) + response = self.client.get(reverse( + 'customer_dashboard', kwargs={'slug': TEST_USER})) + self.assertIn('dashboard_user', response.context) + self.assertEqual(self.user, response.context['dashboard_user']) + self.assertIn('hosting_packages', response.context) + self.assertEqual(len(response.context['hosting_packages']), 0) From c5d9673ac327dab57a8d038b4a8eddd22ae0568e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 12 Oct 2015 20:51:03 +0200 Subject: [PATCH 019/111] remove unnecessary imports --- gnuviechadmin/dashboard/tests/test_views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gnuviechadmin/dashboard/tests/test_views.py b/gnuviechadmin/dashboard/tests/test_views.py index 690507a..dcf5e17 100644 --- a/gnuviechadmin/dashboard/tests/test_views.py +++ b/gnuviechadmin/dashboard/tests/test_views.py @@ -7,10 +7,6 @@ from django.core.urlresolvers import reverse from django.test import TestCase from django.contrib.auth import get_user_model -from dashboard.views import ( - IndexView, - UserDashboardView, -) User = get_user_model() From e51d202abd3968b53ac3f92d68c3ef5fdfa1a2b7 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 12 Oct 2015 22:07:25 +0000 Subject: [PATCH 020/111] start test implementation for contact_form --- gnuviechadmin/contact_form/tests/__init__.py | 4 +++ .../contact_form/tests/test_views.py | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 gnuviechadmin/contact_form/tests/__init__.py create mode 100644 gnuviechadmin/contact_form/tests/test_views.py diff --git a/gnuviechadmin/contact_form/tests/__init__.py b/gnuviechadmin/contact_form/tests/__init__.py new file mode 100644 index 0000000..88bdfc5 --- /dev/null +++ b/gnuviechadmin/contact_form/tests/__init__.py @@ -0,0 +1,4 @@ +""" +Tests for the :py:mod:`contact_form` app. + +""" diff --git a/gnuviechadmin/contact_form/tests/test_views.py b/gnuviechadmin/contact_form/tests/test_views.py new file mode 100644 index 0000000..e5f8401 --- /dev/null +++ b/gnuviechadmin/contact_form/tests/test_views.py @@ -0,0 +1,29 @@ +""" +Tests for :py:mod:`contact_form.views`. + +""" +from __future__ import absolute_import, unicode_literals + +from django.core.urlresolvers import reverse +from django.test import TestCase + + +class ContactFormViewTest(TestCase): + + def test_get_contact_form_template(self): + response = self.client.get(reverse('contact_form')) + self.assertTemplateUsed(response, 'contact_form/contact_form.html') + + def test_get_contact_form_anonymous_status(self): + response = self.client.get(reverse('contact_form')) + self.assertEqual(response.status_code, 200) + + def test_get_contact_form_anonymous_has_empty_form(self): + response = self.client.get(reverse('contact_form')) + self.assertIn('form', response.context) + self.assertEqual(len(response.context['form'].initial), 0) + + def test_get_contact_form_fields_anonymous(self): + response = self.client.get(reverse('contact_form')) + for name in ('name', 'email', 'body'): + self.assertIn(name, response.context['form'].fields) From 2de53757df469673c2163f92a4c73f29b841322f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 13 Oct 2015 22:31:20 +0200 Subject: [PATCH 021/111] improve contact_form test coverage --- gnuviechadmin/contact_form/forms.py | 2 +- .../contact_form/tests/test_forms.py | 92 ++++++++++++++++ .../contact_form/tests/test_views.py | 100 +++++++++++++++++- 3 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 gnuviechadmin/contact_form/tests/test_forms.py diff --git a/gnuviechadmin/contact_form/forms.py b/gnuviechadmin/contact_form/forms.py index d703983..e023fe7 100644 --- a/gnuviechadmin/contact_form/forms.py +++ b/gnuviechadmin/contact_form/forms.py @@ -12,8 +12,8 @@ from django.template import RequestContext from django.template import loader from django.utils.translation import ugettext_lazy as _ -from django.contrib.sites.models import RequestSite from django.contrib.sites.models import Site +from django.contrib.sites.requests import RequestSite from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit diff --git a/gnuviechadmin/contact_form/tests/test_forms.py b/gnuviechadmin/contact_form/tests/test_forms.py new file mode 100644 index 0000000..bade6b1 --- /dev/null +++ b/gnuviechadmin/contact_form/tests/test_forms.py @@ -0,0 +1,92 @@ +""" +Tests for :py:mod:`contact_form.forms`. + +""" +from __future__ import absolute_import, unicode_literals + +import mock +from mock import MagicMock, Mock + +from django.core import mail +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.contrib.sites.models import Site + +from contact_form.forms import ContactForm + +TEST_DATA = { + 'name': 'Test User', + 'email': 'test@example.org', + 'body': 'Test message' +} + + +class ContactFormTest(TestCase): + + def test_constructor_needs_request(self): + with self.assertRaises(KeyError): + form = ContactForm() + + def test_constructor(self): + request = MagicMock() + form = ContactForm(request=request) + self.assertTrue(hasattr(form, 'request')) + self.assertEqual(form.request, request) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse('contact_form')) + self.assertEqual(len(form.helper.inputs), 1) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + def test_constructor_fields(self): + request = MagicMock() + form = ContactForm(request=request) + self.assertEqual(len(form.fields), 3) + self.assertIn('email', form.fields) + self.assertIn('name', form.fields) + self.assertIn('body', form.fields) + self.assertEqual(len(form.data), 0) + + def test_get_context_invalid(self): + request = MagicMock() + form = ContactForm(request=request) + with self.assertRaisesMessage( + ValueError, + 'Cannot generate context from invalid contact form'): + form.get_context() + + def test_get_context_valid_site_installed(self): + request = MagicMock() + form = ContactForm(request=request, data=TEST_DATA) + context = form.get_context() + self.assertIn('site', context) + self.assertIn('name', context) + self.assertIn('email', context) + self.assertIn('body', context) + + def test_get_context_valid_site_not_installed(self): + request = MagicMock() + form = ContactForm(request=request, data=TEST_DATA) + with mock.patch('contact_form.forms.Site') as sitemock: + sitemock._meta.installed = False + context = form.get_context() + self.assertIn('site', context) + self.assertIn('name', context) + self.assertIn('email', context) + self.assertIn('body', context) + + def test_message(self): + request = Mock() + request.META = {'REMOTE_ADDR': '127.0.0.1'} + form = ContactForm(request=request, data=TEST_DATA) + message = form.message() + self.assertIn(TEST_DATA['name'], message) + self.assertIn(TEST_DATA['email'], message) + self.assertIn(TEST_DATA['body'], message) + self.assertIn('127.0.0.1', message) + + def test_subject(self): + request = Mock() + form = ContactForm(request=request, data=TEST_DATA) + subject = form.subject() + self.assertIn(Site.objects.get_current().name, subject) + self.assertIn(TEST_DATA['name'], subject) diff --git a/gnuviechadmin/contact_form/tests/test_views.py b/gnuviechadmin/contact_form/tests/test_views.py index e5f8401..66e84f3 100644 --- a/gnuviechadmin/contact_form/tests/test_views.py +++ b/gnuviechadmin/contact_form/tests/test_views.py @@ -4,12 +4,31 @@ Tests for :py:mod:`contact_form.views`. """ from __future__ import absolute_import, unicode_literals +from django.core import mail from django.core.urlresolvers import reverse from django.test import TestCase +from django.contrib.auth import get_user_model + + +User = get_user_model() + +TEST_USER = 'test' +TEST_PASSWORD = 'secret' +TEST_EMAIL = 'test@example.org' +TEST_NAME = 'Example Tester'.split() +TEST_MESSAGE = ''' +This is a really unimportant test message. +''' + class ContactFormViewTest(TestCase): + def _setup_user(self, **kwargs): + return User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD, + **kwargs) + def test_get_contact_form_template(self): response = self.client.get(reverse('contact_form')) self.assertTemplateUsed(response, 'contact_form/contact_form.html') @@ -21,9 +40,88 @@ class ContactFormViewTest(TestCase): def test_get_contact_form_anonymous_has_empty_form(self): response = self.client.get(reverse('contact_form')) self.assertIn('form', response.context) - self.assertEqual(len(response.context['form'].initial), 0) + form = response.context['form'] + self.assertEqual(len(form.initial), 0) def test_get_contact_form_fields_anonymous(self): response = self.client.get(reverse('contact_form')) for name in ('name', 'email', 'body'): self.assertIn(name, response.context['form'].fields) + + def test_post_empty_form_template(self): + response = self.client.post(reverse('contact_form'), {}) + self.assertTemplateUsed(response, 'contact_form/contact_form.html') + + def test_post_empty_form_status(self): + response = self.client.post(reverse('contact_form'), {}) + self.assertEqual(response.status_code, 200) + + def test_post_empty_form_validation_errors(self): + response = self.client.post(reverse('contact_form'), {}) + self.assertIn('form', response.context) + form = response.context['form'] + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 3) + + def test_post_empty_form_no_mail(self): + response = self.client.post(reverse('contact_form'), {}) + self.assertEqual(len(mail.outbox), 0) + + def test_get_contact_form_logged_in_no_fullname_initial(self): + user = self._setup_user() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('contact_form')) + self.assertIn('form', response.context) + form = response.context['form'] + self.assertEqual( + form.initial, {'name': TEST_USER, 'email': TEST_EMAIL}) + + def test_get_contact_form_logged_in_fullname_initial(self): + user = self._setup_user( + first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('contact_form')) + self.assertIn('form', response.context) + form = response.context['form'] + self.assertEqual( + form.initial, + {'name': " ".join(TEST_NAME), 'email': TEST_EMAIL}) + + def test_post_filled_form_anonymous_redirects(self): + response = self.client.post(reverse('contact_form'), { + 'name': TEST_USER, 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) + self.assertRedirects(response, reverse('contact_success')) + + def test_post_filled_form_anonymous_mail(self): + response = self.client.post(reverse('contact_form'), { + 'name': TEST_USER, 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) + self.assertEqual(len(mail.outbox), 1) + + def test_post_filled_form_logged_in_redirects(self): + user = self._setup_user( + first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(reverse('contact_form'), { + 'name': " ".join(TEST_NAME), 'email': TEST_EMAIL, + 'body': TEST_MESSAGE}) + self.assertRedirects(response, reverse('contact_success')) + + def test_post_filled_form_logged_in_redirects(self): + user = self._setup_user( + first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(reverse('contact_form'), { + 'name': " ".join(TEST_NAME), 'email': TEST_EMAIL, + 'body': TEST_MESSAGE}) + self.assertEqual(len(mail.outbox), 1) + + +class ContactSuccessViewTest(TestCase): + + def test_get_template(self): + response = self.client.get(reverse('contact_success')) + self.assertTemplateUsed(response, 'contact_form/contact_success.html') + + def test_get_status(self): + response = self.client.get(reverse('contact_success')) + self.assertEqual(response.status_code, 200) From 7d7a8941c345d093afa8c2a94dc3b978fdd559a5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 13 Oct 2015 22:31:27 +0200 Subject: [PATCH 022/111] enable line numbers in vim --- salt/roots/vim/vimrc | 1 + 1 file changed, 1 insertion(+) diff --git a/salt/roots/vim/vimrc b/salt/roots/vim/vimrc index 19be571..931195b 100644 --- a/salt/roots/vim/vimrc +++ b/salt/roots/vim/vimrc @@ -16,6 +16,7 @@ set guioptions-=T set guioptions-=m set wildmenu set complete=.,w,b,u,t +set number filetype plugin indent on From f5759f319427e0795c2ce94484765fe601bdf846 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 15 Oct 2015 19:50:18 +0000 Subject: [PATCH 023/111] Add tests for domains app Add missing tests for domains.forms and domains.models. --- gnuviechadmin/domains/forms.py | 4 +- gnuviechadmin/domains/models.py | 2 +- gnuviechadmin/domains/tests/test_forms.py | 105 +++++++++++++++++++++ gnuviechadmin/domains/tests/test_models.py | 79 +++++++++++++++- 4 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 gnuviechadmin/domains/tests/test_forms.py diff --git a/gnuviechadmin/domains/forms.py b/gnuviechadmin/domains/forms.py index fff18cb..5ad0532 100644 --- a/gnuviechadmin/domains/forms.py +++ b/gnuviechadmin/domains/forms.py @@ -21,7 +21,9 @@ from .models import HostingDomain def relative_domain_validator(value): """ - This validator ensures that the given value is a valid lowercase + This validator ensures that the given value is a valid lowercase domain + name. + """ if len(value) > 254: raise forms.ValidationError( diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index c827f2c..d545964 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -27,7 +27,7 @@ class DomainBase(TimeStampedModel): abstract = True def __str__(self): - return self.name + return self.domain @python_2_unicode_compatible diff --git a/gnuviechadmin/domains/tests/test_forms.py b/gnuviechadmin/domains/tests/test_forms.py new file mode 100644 index 0000000..df93be4 --- /dev/null +++ b/gnuviechadmin/domains/tests/test_forms.py @@ -0,0 +1,105 @@ +""" +Tests for :py:mod:`domains.forms`. + +""" +from __future__ import absolute_import, unicode_literals + +from mock import MagicMock, Mock, patch + +from django.core.urlresolvers import reverse +from django.forms import ValidationError +from django.test import TestCase +from django.utils.translation import ugettext_lazy as _ + +from domains.forms import relative_domain_validator, CreateHostingDomainForm + + +class RelativeDomainValidatorTest(TestCase): + + def test_valid_domainname(self): + relative_domain_validator('example.org') + + def test_domain_name_too_long(self): + with self.assertRaisesMessage( + ValidationError, _('host name too long')): + relative_domain_validator('e' * 255) + + def test_domain_name_part_too_long(self): + with self.assertRaisesMessage( + ValidationError, _('invalid domain name')): + relative_domain_validator('a' * 64 + '.org') + + def test_domain_name_illegal_characters(self): + with self.assertRaisesMessage( + ValidationError, _('invalid domain name')): + relative_domain_validator('eXampl3.org') + + def test_domain_name_starts_with_dash(self): + with self.assertRaisesMessage( + ValidationError, _('invalid domain name')): + relative_domain_validator('-example.org') + + def test_domain_name_ends_with_dash(self): + with self.assertRaisesMessage( + ValidationError, _('invalid domain name')): + relative_domain_validator('example-.org') + + +class CreateHostingDomainFormTest(TestCase): + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError): + CreateHostingDomainForm(instance) + + def test_constructor(self): + hostingpackage = Mock(id=42) + instance = MagicMock() + form = CreateHostingDomainForm(instance, hostingpackage=hostingpackage) + self.assertTrue(hasattr(form, 'hosting_package')) + self.assertEqual(form.hosting_package, hostingpackage) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'create_hosting_domain', kwargs={'package': 42})) + self.assertEqual(len(form.helper.layout.fields), 2) + self.assertEqual(form.helper.layout.fields[1].name, 'submit') + + def test_domain_field_has_relative_domain_validator(self): + hostingpackage = Mock(id=42) + instance = MagicMock() + form = CreateHostingDomainForm(instance, hostingpackage=hostingpackage) + self.assertIn( + relative_domain_validator, form.fields['domain'].validators) + + def test_clean(self): + hostingpackage = Mock(id=42) + instance = MagicMock() + form = CreateHostingDomainForm( + instance, hostingpackage=hostingpackage, + data={'domain': 'example.org'}) + self.assertTrue(form.is_valid()) + self.assertIn('hosting_package', form.cleaned_data) + self.assertEqual(hostingpackage, form.cleaned_data['hosting_package']) + + def test_save(self): + hostingpackage = Mock(id=42) + instance = MagicMock() + form = CreateHostingDomainForm( + instance, hostingpackage=hostingpackage, + data={'domain': 'example.org'}) + self.assertTrue(form.is_valid()) + with patch('domains.forms.HostingDomain') as domain: + form.save() + domain.objects.create_for_hosting_package.assert_called_with( + commit=True, **form.cleaned_data) + form.save(commit=False) + domain.objects.create_for_hosting_package.assert_called_with( + commit=False, **form.cleaned_data) + + def test_save_m2m(self): + hostingpackage = Mock(id=42) + instance = MagicMock() + form = CreateHostingDomainForm( + instance, hostingpackage=hostingpackage, + data={'domain': 'example.org'}) + form.save_m2m() diff --git a/gnuviechadmin/domains/tests/test_models.py b/gnuviechadmin/domains/tests/test_models.py index 233844b..76fdcc8 100644 --- a/gnuviechadmin/domains/tests/test_models.py +++ b/gnuviechadmin/domains/tests/test_models.py @@ -1,9 +1,84 @@ -from django.test import TestCase +""" +Tests for :py:mod:`domains.models`. -from domains.models import MailDomain +""" +from __future__ import absolute_import, unicode_literals + +from mock import Mock, MagicMock, patch + +from django.test import TestCase +from django.contrib.auth import get_user_model + +from domains.models import ( + DomainBase, + MailDomain, + HostingDomain, +) +from hostingpackages.models import ( + CustomerHostingPackage, + HostingPackageTemplate, +) + + +User = get_user_model() + +TEST_USER = 'test' + + +class DomainBaseTest(TestCase): + + def test__str__(self): + db = DomainBase(domain='test') + self.assertEqual(str(db), 'test') class MailDomainTest(TestCase): + def test__str__(self): md = MailDomain.objects.create(domain='example.org') self.assertEqual(str(md), 'example.org') + + def test_get_mailaddresses(self): + md = MailDomain.objects.create(domain='example.org') + from managemails.models import MailAddress + addrmock = MailAddress(localpart='info') + md.mailaddress_set.add(addrmock) + self.assertIn(addrmock, md.get_mailaddresses()) + self.assertIn(addrmock, md.mailaddresses) + + +class HostingDomainManagerTest(TestCase): + + def _setup_hosting_package(self): + template = HostingPackageTemplate.objects.create( + name='testpackagetemplate', mailboxcount=0, diskspace=1, + diskspace_unit=0) + customer = User.objects.create_user(username=TEST_USER) + package = CustomerHostingPackage.objects.create_from_template( + customer, template, 'testpackage') + with patch('hostingpackages.models.settings') as hmsettings: + hmsettings.OSUSER_DEFAULT_GROUPS = [] + package.save() + return package + + def test_create_for_hosting_package_with_commit(self): + package = self._setup_hosting_package() + hostingdomain = HostingDomain.objects.create_for_hosting_package( + package, 'example.org', True) + + self.assertIsNotNone(hostingdomain) + self.assertTrue(hostingdomain.customer, package.customer) + + def test_create_for_hosting_package_no_commit(self): + package = self._setup_hosting_package() + hostingdomain = HostingDomain.objects.create_for_hosting_package( + package, 'example.org', False) + + self.assertIsNotNone(hostingdomain) + self.assertTrue(hostingdomain.customer, package.customer) + +class HostingDomainTest(TestCase): + + def test__str__(self): + hostingdomain = HostingDomain(domain='test') + self.assertEqual(str(hostingdomain), 'test') From fbfd3cf41b463d315c5978ee42d49b973d0cb6a4 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 25 Oct 2015 17:25:28 +0000 Subject: [PATCH 024/111] Add tests for domains.views This commit adds a test class to test the CreateHostingDomain view in domains.views. --- gnuviechadmin/domains/tests/test_views.py | 141 ++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 gnuviechadmin/domains/tests/test_views.py diff --git a/gnuviechadmin/domains/tests/test_views.py b/gnuviechadmin/domains/tests/test_views.py new file mode 100644 index 0000000..4873bcf --- /dev/null +++ b/gnuviechadmin/domains/tests/test_views.py @@ -0,0 +1,141 @@ +""" +Tests for :py:mod:`domains.views`. + +""" +from __future__ import absolute_import, unicode_literals + +from mock import patch, MagicMock + +from django.core.urlresolvers import reverse +from django.test import TestCase + +from django.contrib.auth import get_user_model + +from hostingpackages.models import ( + CustomerHostingPackage, + HostingPackageTemplate, +) + +from domains.views import CreateHostingDomain + + +User = get_user_model() + +TEST_USER = 'test' +TEST_PASSWORD = 'secret' +TEST_EMAIL = 'test@example.org' +TEST_NAME = 'Example Tester'.split() + + +class CreateHostingDomainTest(TestCase): + + def _setup_hosting_package(self, customer): + template = HostingPackageTemplate.objects.create( + name='testpackagetemplate', mailboxcount=0, diskspace=1, + diskspace_unit=0) + package = CustomerHostingPackage.objects.create_from_template( + customer, template, 'testpackage') + with patch('hostingpackages.models.settings') as hmsettings: + hmsettings.OSUSER_DEFAULT_GROUPS = [] + package.save() + return package + + def test_get_anonymous(self): + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': 1})) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': package.id})) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': package.id})) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': package.id})) + self.assertTemplateUsed(response, 'domains/hostingdomain_create.html') + + def test_get_no_package_found(self): + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': 1})) + self.assertEqual(response.status_code, 404) + + def test_get_get_form_kwargs(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = CreateHostingDomain( + request=MagicMock(), kwargs={'package': str(package.id)}) + the_kwargs= view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], package) + + def test_get_context_data_has_hosting_package(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': package.id})) + self.assertIn('hostingpackage', response.context) + self.assertEqual(response.context['hostingpackage'], package) + + def test_get_context_data_has_customer(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(reverse('create_hosting_domain', + kwargs={'package': package.id})) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], customer) + + def test_form_valid_redirect(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(reverse('create_hosting_domain', + kwargs={'package': package.id}), data={'domain': 'example.org'}) + self.assertRedirects(response, package.get_absolute_url()) + + def test_form_valid_message(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer) + admin = User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(reverse('create_hosting_domain', + kwargs={'package': package.id}), follow=True, + data={'domain': 'example.org'}) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + 'Successfully created domain example.org', str(messages[0])) From 64b4302b874eacc18062aef6df1af9f65cf93eab Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 25 Oct 2015 18:30:27 +0100 Subject: [PATCH 025/111] Set vim as default editor Use the alternatives system to set vim as default editor. --- salt/roots/vim/init.sls | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/salt/roots/vim/init.sls b/salt/roots/vim/init.sls index f8efbee..fa234e3 100644 --- a/salt/roots/vim/init.sls +++ b/salt/roots/vim/init.sls @@ -1,6 +1,12 @@ vim-nox: pkg.installed +editor: + alternatives.set: + - path: /usr/bin/vim.nox + - require: + - pkg: vim-nox + /home/vagrant/.vimrc: file.managed: - user: vagrant From 8e954a1a8c8062c6576a5baa41263c5c4ad9e6e6 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 5 Nov 2015 19:18:03 +0100 Subject: [PATCH 026/111] Update to Django 1.8.6 --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 03200d5..9fb024f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django==1.8.5 +Django==1.8.6 bpython==0.14.2 django-braces==1.8.1 django-model-utils==2.3.1 From 1df2534cf359371d267b9261e5ccb4cc5e0458f9 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 5 Nov 2015 20:54:21 +0000 Subject: [PATCH 027/111] Add DNS models This commit add model classes closely matching the tables defined in PowerDNS' schema as described at https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The commit includes the model definitions a schema migration including PostgreSQL specific CHECK constraints and the registration in the Django admin interface. addresses #17 --- gnuviechadmin/domains/admin.py | 16 +- .../migrations/0003_auto_20151105_2133.py | 159 ++++++++++++ gnuviechadmin/domains/models.py | 235 +++++++++++++++++- 3 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py diff --git a/gnuviechadmin/domains/admin.py b/gnuviechadmin/domains/admin.py index 997b49d..87be497 100644 --- a/gnuviechadmin/domains/admin.py +++ b/gnuviechadmin/domains/admin.py @@ -6,9 +6,23 @@ with the django admin site. from django.contrib import admin from .models import ( - MailDomain, + DNSComment, + DNSCryptoKey, + DNSDomain, + DNSDomainMetadata, + DNSRecord, + DNSSupermaster, + DNSTSIGKey, HostingDomain, + MailDomain, ) admin.site.register(MailDomain) admin.site.register(HostingDomain) +admin.site.register(DNSComment) +admin.site.register(DNSCryptoKey) +admin.site.register(DNSDomain) +admin.site.register(DNSDomainMetadata) +admin.site.register(DNSRecord) +admin.site.register(DNSSupermaster) +admin.site.register(DNSTSIGKey) diff --git a/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py b/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py new file mode 100644 index 0000000..caf2e72 --- /dev/null +++ b/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +from django.conf import settings +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('domains', '0002_auto_20150124_1909'), + ] + + operations = [ + migrations.CreateModel( + name='DNSComment', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=255)), + ('commenttype', models.CharField(max_length=10, db_column='type')), + ('modified_at', models.IntegerField()), + ('comment', models.CharField(max_length=65535)), + ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + ), + migrations.CreateModel( + name='DNSCryptoKey', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('flags', models.IntegerField()), + ('active', models.BooleanField(default=True)), + ('content', models.TextField()), + ], + ), + migrations.CreateModel( + name='DNSDomain', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('domain', models.CharField(unique=True, max_length=255, verbose_name='domain name')), + ('master', models.CharField(max_length=128, null=True, blank=True)), + ('last_check', models.IntegerField(null=True)), + ('domaintype', models.CharField(max_length=6, db_column='type', choices=[('MASTER', 'Master'), ('SLAVE', 'Slave'), ('NATIVE', 'Native')])), + ('notified_serial', models.IntegerField(null=True)), + ('customer', models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ], + options={ + 'verbose_name': 'DNS domain', + 'verbose_name_plural': 'DNS domains', + }, + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name + CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))''' + ), + migrations.CreateModel( + name='DNSDomainMetadata', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('kind', models.CharField(max_length=32)), + ('content', models.TextField()), + ('domain', models.ForeignKey(to='domains.DNSDomain')), + ], + ), + migrations.CreateModel( + name='DNSRecord', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(db_index=True, max_length=255, null=True, blank=True)), + ('recordtype', models.CharField(max_length=10, null=True, db_column='type', blank=True)), + ('content', models.CharField(max_length=65535, null=True, blank=True)), + ('ttl', models.IntegerField(null=True)), + ('prio', models.IntegerField(null=True)), + ('change_date', models.IntegerField(null=True)), + ('disabled', models.BooleanField(default=False)), + ('ordername', models.CharField(max_length=255)), + ('auth', models.BooleanField(default=True)), + ('domain', models.ForeignKey(to='domains.DNSDomain')), + ], + options={ + 'verbose_name': 'DNS record', + 'verbose_name_plural': 'DNS records', + }, + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + ), + migrations.RunSQL( + '''CREATE INDEX recordorder ON domains_dnsrecord (domain_id, + ordername text_pattern_ops)''' + ), + migrations.CreateModel( + name='DNSSupermaster', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('ip', models.GenericIPAddressField()), + ('nameserver', models.CharField(max_length=255)), + ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='DNSTSIGKey', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=255)), + ('algorithm', models.CharField(max_length=50)), + ('secret', models.CharField(max_length=255)), + ], + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + ), + migrations.AlterField( + model_name='hostingdomain', + name='domain', + field=models.CharField(unique=True, max_length=255, verbose_name='domain name'), + ), + migrations.AlterField( + model_name='maildomain', + name='domain', + field=models.CharField(unique=True, max_length=255, verbose_name='domain name'), + ), + migrations.AddField( + model_name='dnscryptokey', + name='domain', + field=models.ForeignKey(to='domains.DNSDomain'), + ), + migrations.AddField( + model_name='dnscomment', + name='domain', + field=models.ForeignKey(to='domains.DNSDomain'), + ), + migrations.AlterUniqueTogether( + name='dnssupermaster', + unique_together=set([('ip', 'nameserver')]), + ), + migrations.AlterUniqueTogether( + name='dnstsigkey', + unique_together=set([('name', 'algorithm')]), + ), + migrations.AlterIndexTogether( + name='dnsrecord', + index_together=set([('name', 'recordtype')]), + ), + migrations.AlterIndexTogether( + name='dnscomment', + index_together=set([('name', 'commenttype'), ('domain', 'modified_at')]), + ), + ] diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index d545964..d94cb3c 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -10,6 +10,14 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ from model_utils.models import TimeStampedModel +from model_utils import Choices + + +DNS_DOMAIN_TYPES = Choices( + ('MASTER', _('Master')), + ('SLAVE', _('Slave')), + ('NATIVE', _('Native')), +) @python_2_unicode_compatible @@ -18,7 +26,7 @@ class DomainBase(TimeStampedModel): This is the base model for domains. """ - domain = models.CharField(_('domain name'), max_length=128, unique=True) + domain = models.CharField(_('domain name'), max_length=255, unique=True) customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True, null=True) @@ -96,3 +104,228 @@ class HostingDomain(DomainBase): def __str__(self): return self.domain + + +@python_2_unicode_compatible +class DNSDomain(DomainBase): + """ + This model represents a DNS zone. The model is similar to the domain table + in the PowerDNS schema specified in + https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + 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 + + +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/. + + 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') + 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'] + ] + + +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/. + + 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')) + + class Meta: + unique_together = ( + ('ip', 'nameserver') + ) + + +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/. + + 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') + 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')) + comment = models.CharField(max_length=65535) + # check constraint is added via RunSQL in migration + + class Meta: + index_together = [ + ['name', 'commenttype'], + ['domain', 'modified_at'] + ] + + +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/. + + 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') + kind = models.CharField(max_length=32) + content = models.TextField() + + +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/. + + 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') + flags = models.IntegerField() + active = models.BooleanField(default=True) + content = models.TextField() + + +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/. + + 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) + secret = models.CharField(max_length=255) + # check constraint is added via RunSQL in migration + + class Meta: + unique_together = [ + ['name', 'algorithm'] + ] From c058cc7b1d50abe68e72fb4aa3a6e69ef387d715 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 7 Nov 2015 16:10:38 +0000 Subject: [PATCH 028/111] Improve DNS table models This commit adds Meta information and __str__ methods to all DNS table models. The new methods are now covered with new tests. The new constants DNS_DOMAIN_METADATA_KINDS and DNS_TSIG_KEY_ALGORITHMS are defined and used in the DNSDomainMetadata and DNSTSIGKey models. A matching database schema migration is added. Addresses #17 --- .../migrations/0004_auto_20151107_1708.py | 44 ++++++++++ gnuviechadmin/domains/models.py | 84 ++++++++++++++++++- gnuviechadmin/domains/tests/test_models.py | 72 +++++++++++++++- 3 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py diff --git a/gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py b/gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py new file mode 100644 index 0000000..40d407d --- /dev/null +++ b/gnuviechadmin/domains/migrations/0004_auto_20151107_1708.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('domains', '0003_auto_20151105_2133'), + ] + + operations = [ + migrations.AlterModelOptions( + name='dnscomment', + options={'verbose_name': 'DNS comment', 'verbose_name_plural': 'DNS comments'}, + ), + migrations.AlterModelOptions( + name='dnscryptokey', + options={'verbose_name': 'DNS crypto key', 'verbose_name_plural': 'DNS crypto keys'}, + ), + migrations.AlterModelOptions( + name='dnsdomainmetadata', + options={'verbose_name': 'DNS domain metadata item', 'verbose_name_plural': 'DNS domain metadata items'}, + ), + migrations.AlterModelOptions( + name='dnssupermaster', + options={'verbose_name': 'DNS supermaster', 'verbose_name_plural': 'DNS supermasters'}, + ), + migrations.AlterModelOptions( + name='dnstsigkey', + options={'verbose_name': 'DNS TSIG key', 'verbose_name_plural': 'DNS TSIG keys'}, + ), + migrations.AlterField( + model_name='dnsdomainmetadata', + name='kind', + field=models.CharField(max_length=32, choices=[('ALLOW-DNSUPDATE-FROM', 'ALLOW-DNSUPDATE-FROM'), ('ALSO-NOTIFY', 'ALSO-NOTIFY'), ('AXFR-MASTER-TSIG', 'AXFR-MASTER-TSIG'), ('AXFR-SOURCE', 'AXFR-SOURCE'), ('FORWARD-DNSUPDATE', 'FORWARD-DNSUPDATE'), ('GSS-ACCEPTOR-PRINCIPAL', 'GSS-ACCEPTOR-PRINCIPAL'), ('GSS-ALLOW-AXFR-PRINCIPAL', 'GSS-ALLOW-AXFR-PRINCIPAL'), ('LUA-AXFR-SCRIPT', 'LUA-AXFR-SCRIPT'), ('NSEC3NARROW', 'NSEC3NARROW'), ('NSEC3PARAM', 'NSEC3PARAM'), ('PRESIGNED', 'PRESIGNED'), ('PUBLISH_CDNSKEY', 'PUBLISH_CDNSKEY'), ('PUBLISH_CDS', 'PUBLISH_CDS'), ('SOA-EDIT', 'SOA-EDIT'), ('SOA-EDIT-DNSUPDATE', 'SOA-EDIT-DNSUPDATE'), ('TSIG-ALLOW-AXFR', 'TSIG-ALLOW-AXFR'), ('TSIG-ALLOW-DNSUPDATE', 'TSIG-ALLOW-DNSUPDATE')]), + ), + migrations.AlterField( + model_name='dnstsigkey', + name='algorithm', + field=models.CharField(max_length=50, choices=[('hmac-md5', 'HMAC MD5'), ('hmac-sha1', 'HMAC SHA1'), ('hmac-sha224', 'HMAC SHA224'), ('hmac-sha256', 'HMAC SHA256'), ('hmac-sha384', 'HMAC SHA384'), ('hmac-sha512', 'HMAC SHA512')]), + ), + ] diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index d94cb3c..ed6640a 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -19,6 +19,36 @@ DNS_DOMAIN_TYPES = Choices( ('NATIVE', _('Native')), ) +# see https://doc.powerdns.com/md/authoritative/domainmetadata/ +DNS_DOMAIN_METADATA_KINDS = Choices( + 'ALLOW-DNSUPDATE-FROM', + 'ALSO-NOTIFY', + 'AXFR-MASTER-TSIG', + 'AXFR-SOURCE', + 'FORWARD-DNSUPDATE', + 'GSS-ACCEPTOR-PRINCIPAL', + 'GSS-ALLOW-AXFR-PRINCIPAL', + 'LUA-AXFR-SCRIPT', + 'NSEC3NARROW', + 'NSEC3PARAM', + 'PRESIGNED', + 'PUBLISH_CDNSKEY', + 'PUBLISH_CDS', + 'SOA-EDIT', + 'SOA-EDIT-DNSUPDATE', + 'TSIG-ALLOW-AXFR', + 'TSIG-ALLOW-DNSUPDATE', +) + +DNS_TSIG_KEY_ALGORITHMS = Choices( + ('hmac-md5', _('HMAC MD5')), + ('hmac-sha1', _('HMAC SHA1')), + ('hmac-sha224', _('HMAC SHA224')), + ('hmac-sha256', _('HMAC SHA256')), + ('hmac-sha384', _('HMAC SHA384')), + ('hmac-sha512', _('HMAC SHA512')), +) + @python_2_unicode_compatible class DomainBase(TimeStampedModel): @@ -144,6 +174,7 @@ class DNSDomain(DomainBase): return self.domain +@python_2_unicode_compatible class DNSRecord(models.Model): """ This model represents a DNS record. The model is similar to the record @@ -195,7 +226,12 @@ class DNSRecord(models.Model): ['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 @@ -217,15 +253,24 @@ class DNSSupermaster(models.Model): settings.AUTH_USER_MODEL, verbose_name=_('customer')) 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/. + in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The + comments table is used to store user comments related to individual DNS + records. CREATE TABLE comments ( id SERIAL PRIMARY KEY, @@ -257,17 +302,26 @@ class DNSComment(models.Model): # 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/. CREATE TABLE domainmetadata ( id SERIAL PRIMARY KEY, @@ -279,10 +333,19 @@ class DNSDomainMetadata(models.Model): CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); """ domain = models.ForeignKey('DNSDomain') - kind = models.CharField(max_length=32) + 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 @@ -304,7 +367,16 @@ class DNSCryptoKey(models.Model): 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 @@ -321,11 +393,17 @@ class DNSTSIGKey(models.Model): CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); """ name = models.CharField(max_length=255) - algorithm = models.CharField(max_length=50) + 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) diff --git a/gnuviechadmin/domains/tests/test_models.py b/gnuviechadmin/domains/tests/test_models.py index 76fdcc8..d116d5d 100644 --- a/gnuviechadmin/domains/tests/test_models.py +++ b/gnuviechadmin/domains/tests/test_models.py @@ -10,9 +10,16 @@ from django.test import TestCase from django.contrib.auth import get_user_model from domains.models import ( + DNSComment, + DNSCryptoKey, + DNSDomain, + DNSDomainMetadata, + DNSRecord, + DNSSupermaster, + DNSTSIGKey, DomainBase, - MailDomain, HostingDomain, + MailDomain, ) from hostingpackages.models import ( CustomerHostingPackage, @@ -27,14 +34,14 @@ TEST_USER = 'test' class DomainBaseTest(TestCase): - def test__str__(self): + def test___str__(self): db = DomainBase(domain='test') self.assertEqual(str(db), 'test') class MailDomainTest(TestCase): - def test__str__(self): + def test___str__(self): md = MailDomain.objects.create(domain='example.org') self.assertEqual(str(md), 'example.org') @@ -77,8 +84,65 @@ class HostingDomainManagerTest(TestCase): self.assertIsNotNone(hostingdomain) self.assertTrue(hostingdomain.customer, package.customer) + class HostingDomainTest(TestCase): - def test__str__(self): + def test___str__(self): hostingdomain = HostingDomain(domain='test') self.assertEqual(str(hostingdomain), 'test') + + +class DNSDomainTest(TestCase): + + def test___str__(self): + dnsdomain = DNSDomain(domain='test') + self.assertEqual(str(dnsdomain), 'test') + + +class DNSRecordTest(TestCase): + + def test___str__(self): + dnsrecord = DNSRecord( + name='localhost', recordtype='A', content='127.0.0.1') + self.assertEqual(str(dnsrecord), 'localhost IN A 127.0.0.1') + + +class DNSSupermasterTest(TestCase): + + def test___str__(self): + dnssupermaster = DNSSupermaster( + ip='127.0.0.1', nameserver='dns.example.org') + self.assertEqual(str(dnssupermaster), '127.0.0.1 dns.example.org') + + +class DNSCommentTest(TestCase): + + def test___str__(self): + dnscomment = DNSComment( + name='localhost', commenttype='A', comment='good stuff') + self.assertEqual(str(dnscomment), 'localhost IN A: good stuff') + + +class DNSDomainMetadataTest(TestCase): + + def test___str__(self): + dnsdomain = DNSDomain(domain='test') + dnsdomainmetadata = DNSDomainMetadata( + domain=dnsdomain, kind='SOA-EDIT', content='INCEPTION') + self.assertEqual(str(dnsdomainmetadata), 'test SOA-EDIT INCEPTION') + + +class DNSCryptoKeyTest(TestCase): + + def test___str__(self): + dnsdomain = DNSDomain(domain='test') + dnscryptokey = DNSCryptoKey(domain=dnsdomain, content='testvalue') + self.assertEqual(str(dnscryptokey), 'test testvalue') + + +class DNSTSIGKeyTest(TestCase): + + def test___str__(self): + dnstsigkey = DNSTSIGKey( + name='testkey', algorithm='hmac-md5', secret='dummykey') + self.assertEqual(str(dnstsigkey), 'testkey hmac-md5 XXXX') From 337947f50c044500b1cfca230911e304cc7cef85 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 7 Nov 2015 22:17:43 +0100 Subject: [PATCH 029/111] Update documentation This commit adds documentation how to setup PowerDNS to use the gnuviechadmin DNS schema. The queries are provided in a PowerDNS configuration file. Addresses #17 --- docs/conf.py | 2 +- docs/install.rst | 14 +++ docs/pdns.local.gva_queries.conf | 198 +++++++++++++++++++++++++++++++ gnuviechadmin/domains/models.py | 171 ++++++++++++++------------ 4 files changed, 307 insertions(+), 78 deletions(-) create mode 100644 docs/pdns.local.gva_queries.conf diff --git a/docs/conf.py b/docs/conf.py index a1cec22..5fdf12e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -104,7 +104,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/install.rst b/docs/install.rst index 9e08482..2ac2b6e 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -43,3 +43,17 @@ In development:: For production:: $ pip install -r requirements.txt + +PowerDNS setup +============== + +The models in :py:mod:`domains.models` are meant to be used together with a +PowerDNS setup with the generic PostgreSQL backend +(https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/). The +database schema differs a bit from the original schema to fit the Django model +conventions. To make PowerDNS work you have to redefine the SQL statements by +copying the following content to +:file:`/etc/powerdns/pdns.d/pdns.local.gva_queries.conf`. + +.. literalinclude:: pdns.local.gva_queries.conf + :language: properties diff --git a/docs/pdns.local.gva_queries.conf b/docs/pdns.local.gva_queries.conf new file mode 100644 index 0000000..392b43b --- /dev/null +++ b/docs/pdns.local.gva_queries.conf @@ -0,0 +1,198 @@ +# Regular queries +gpgsql-basic-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \ + FROM domains_dnsrecord \ + WHERE disabled=false AND type='%s' AND name=E'%s' +gpgsql-id-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \ + FROM domains_dnsrecord \ + WHERE disabled=false AND type='%s' AND name=E'%s' AND domain_id=%d +gpgsql-any-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \ + FROM domains_dnsrecord \ + WHERE disabled=false AND name=E'%s' +gpgsql-any-id-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \ + FROM domains_dnsrecord \ + WHERE disabled=false AND name=E'%s' AND domain_id=%d +gpgsql-list-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \ + FROM domains_dnsrecord \ + WHERE (disabled=false OR %d::bool) AND domain_id='%d' \ + ORDER BY name, type + +# Master/slave queries +gpgsql-master-zone-query=SELECT master \ + FROM domains_dnsdomain \ + WHERE domain=E'%s' AND type='SLAVE' +gpgsql-info-zone-query=SELECT id, domain, master, last_check, notified_serial, type \ + FROM domains_dnsdomain \ + WHERE domain=E'%s' +gpgsql-info-all-slaves-query=SELECT id, domain, master, last_check, type \ + FROM domains_dnsdomain \ + WHERE type='SLAVE' +gpgsql-supermaster-query=SELECT customer \ + FROM domains_dnssupermaster \ + WHERE ip='%s' AND nameserver=E'%s' +gpgsql-insert-slave-query=INSERT INTO domains_dnsdomain \ + (type, domain, master, account) \ + VALUES ('SLAVE', E'%s', E'%s', E'%s') +gpgsql-insert-record-query=INSERT INTO domains_dnsrecord \ + (content, ttl, prio, type, domain_id, disabled, name, auth) \ + VALUES (E'%s', %d, %d, '%s', %d, %d::bool, E'%s', '%d') +gpgsql-update-serial-query=UPDATE domains_dnsdomain \ + SET notified_serial=%d \ + WHERE id=%d +gpgsql-update-lastcheck-query=UPDATE domains_dnsdomain \ + SET last_check=%d \ + WHERE id=%d +gpgsql-info-all-master-query=SELECT id, domain, master, last_check, notified_serial, type \ + FROM domains_dnsdomain \ + WHERE type='MASTER' +gpgsql-delete-zone-query=DELETE FROM domains_dnsrecord \ + WHERE domain_id=%d + +# Comment queries +gpgsql-list-comments-query=SELECT domain_id, name, type, modified_at, customer, comment \ + FROM domains_dnscomment \ + WHERE domain_id=%d +gpgsql-insert-comment-query=INSERT INTO domains_dnscomment \ + (domain_id, name, type, modified_at, customer, comment) \ + VALUES (%d, E'%s', E'%s', %d, E'%s', E'%s') +gpgsql-delete-comment-rrset-query=DELETE FROM domains_dnscomment \ + WHERE domain_id=%d AND name=E'%s' AND type=E'%s' +gpgsql-delete-comments-query=DELETE FROM domains_dnscomment \ + WHERE domain_id=%d + +# Crypto key queries +gpgsql-activate-domain-key-query=UPDATE domains_dnscryptokey \ + SET active=true \ + WHERE domain_id=( \ + SELECT id \ + FROM domains_dnsdomain \ + WHERE domain=E'%s' \ + ) AND domains_dnscryptokey.id=%d +gpgsql-add-domain-key-query=INSERT INTO domains_dnscryptokey \ + (domain_id, flags, active, content) \ + SELECT id, %d, (%d = 1), '%s' FROM domains_dnsdomain \ + WHERE domain=E'%s' +gpgsql-clear-domain-all-keys-query=DELETE FROM domains_dnscryptokey \ + WHERE domain_id=( \ + SELECT id FROM domains_dnsdomain \ + WHERE domain=E'%s' \ + ) +gpgsql-deactivate-domain-key-query=UPDATE domains_dnscryptokey \ + SET active=false \ + WHERE domain_id=( \ + SELECT id FROM domains_dnsdomain \ + WHERE domain=E'%s' \ + ) AND domains_dnscryptokey.id=%d +gpgsql-list-domain-keys-query=SELECT domains_dnscryptokey.id, flags, CASE WHEN active THEN 1 ELSE 0 END AS active, content \ + FROM domains_dnsdomain, domains_cryptokey \ + WHERE domains_dnscryptokey.domain_id=domains_dnsdomain.id AND domain=E'%s' +gpgsql-remove-domain-key-query=DELETE FROM domains_dnscryptokey \ + WHERE domain_id=( \ + SELECT id FROM domains_dnsdomain \ + WHERE domain=E'%s' \ + ) AND domains_dnscryptokey.id=%d + +# TSIG key queries +gpgsql-delete-tsig-key-query=DELETE FROM domains_dnstsigkey \ + WHERE name='%s' +gpgsql-get-tsig-key-query=SELECT algorithm, secret \ + FROM domains_dnstsigkey \ + WHERE name=E'%s' +gpgsql-get-tsig-keys-query=SELECT name, algorithm, secret \ + FROM domains_dnstsigkey +gpgsql-set-tsig-key-query=INSERT INTO domains_dnstsigkey \ + (name, algorithm, secret) \ + VALUES ('%s', '%s', '%s') + +# Metadata queries +gpgsql-clear-domain-all-metadata-query=DELETE FROM domains_dnsdomainmetadata \ + WHERE domain_id=( \ + SELECT id FROM domains_dnsdomain \ + WHERE domain=E'%s' \ + ) +gpgsql-clear-domain-metadata-query=DELETE FROM domains_dnsdomainmetadata \ + WHERE domain_id=( \ + SELECT id FROM domains_dnsdomain \ + WHERE domain=E'%s' \ + ) AND domains_dnsdomainmetadata.kind=E'%s' +gpgsql-get-all-domain-metadata-query=SELECT kind, content \ + FROM domains_dnsdomain, domains_dnsdomainmetadata \ + WHERE domains_dnsdomainmetadata.domain_id=domains_dnsdomain.id AND domain=E'%s' +gpgsql-get-domain-metadata-query=SELECT content \ + FROM domains_dnsdomain, domains_dnsdomainmetadata \ + WHERE domains_dnsdomainmetadata.domain_id=domains_dnsdomain.id AND domain=E'%s' AND domains_dnsdomainmetadata.kind=E'%s' +gpgsql-set-domain-metadata-query=INSERT INTO domains_dnsdomainmetadata \ + (domain_id, kind, content) \ + SELECT id, '%s', '%s' FROM domains_dnsdomain \ + WHERE domain=E'%s' + +# Record queries +gpgsql-delete-empty-non-terminal-query=DELETE FROM domains_dnsrecord \ + WHERE domain_id='%d' AND name='%s' AND type IS NULL +gpgsql-delete-names-query=DELETE FROM domains_dnsrecord \ + WHERE domain_id=%d AND name=E'%s' +gpgsql-delete-rrset-query=DELETE FROM domains_dnsrecord \ + WHERE domain_id=%d AND name=E'%s' AND type=E'%s' +gpgsql-get-order-after-query=SELECT ordername FROM domains_dnsrecord \ + WHERE disabled=false AND ordername ~>~ E'%s' AND domain_id=%d AND ordername IS NOT NULL \ + ORDER BY 1 USING ~<~ LIMIT 1 +gpgsql-get-order-before-query=SELECT ordername, name FROM domains_dnsrecord \ + WHERE disabled=false AND ordername ~<=~ E'%s' AND domain_id=%d AND ordername IS NOT NULL \ + ORDER BY 1 USING ~>~ LIMIT 1 +gpgsql-get-order-first-query=SELECT ordername, name FROM domains_dnsrecord \ + WHERE disabled=false AND domain_id=%d AND ordername IS NOT NULL \ + ORDER BY 1 USING ~<~ LIMIT 1 +gpgsql-get-order-last-query=SELECT ordername, name FROM domains_dnsrecord \ + WHERE disabled=false AND ordername != '' AND domain_id=%d AND ordername IS NOT NULL \ + ORDER BY 1 USING ~>~ LIMIT 1 +gpgsql-insert-empty-non-terminal-query=INSERT INTO domains_dnsrecord \ + (domain_id, name, type, disabled, auth) \ + VALUES ('%d', '%s', null, false, true) + gpgsql-insert-ent-order-query=INSERT INTO domains_dnsrecord \ + (type, domain_id, disabled, name, ordername, auth) \ + VALUES (null, '%d', false, E'%s', E'%s', '%d') +gpgsql-insert-ent-query=INSERT INTO domains_dnsrecord \ + (type, domain_id, disabled, name, auth) \ + VALUES (null, '%d', false, E'%s', '%d') +gpgsql-insert-record-order-query=INSERT INTO domains_dnsrecord \ + (content, ttl, prio, type, domain_id, disabled, name, ordername, auth) \ + VALUES (E'%s', %d, %d, '%s', %d, %d::bool, E'%s', E'%s', '%d') +gpgsql-list-subzone-query=SELECT content, ttl, prio, type, domain_id, disabled::int, name, auth::int \ + FROM domains_dnsrecord \ + WHERE disabled=false AND (name=E'%s' OR name like E'%s') AND domain_id='%d' +gpgsql-nullify-ordername-and-auth-query=UPDATE domains_dnsrecord \ + SET ordername=NULL, auth=false \ + WHERE name=E'%s' AND type=E'%s' AND domain_id='%d' AND disabled=false +gpgsql-nullify-ordername-and-update-auth-query=UPDATE domains_dnsrecord \ + SET ordername=NULL, auth=%d::bool \ + WHERE domain_id='%d' AND name='%s' AND disabled=false +gpgsql-remove-empty-non-terminals-from-zone-query=DELETE FROM domains_dnsrecord \ + WHERE domain_id='%d' AND type IS NULL +gpgsql-set-auth-on-ds-record-query=UPDATE domains_dnsrecord \ + SET auth=true \ + WHERE domain_id='%d' AND name='%s' AND type='DS' AND disabled=false +gpgsql-set-order-and-auth-query=UPDATE domains_dnsrecord \ + SET ordername=E'%s', auth=%d::bool \ + WHERE name=E'%s' AND domain_id='%d' AND disabled=false +gpgsql-zone-lastchange-query=SELECT MAX(change_date) FROM domains_dnsrecord \ + WHERE domain_id=%d + +# Domain queries +gpgsql-delete-domain-query=DELETE FROM domains_dnsdomain \ + WHERE domain=E'%s' +gpgsql-insert-zone-query=INSERT INTO domains_dnsdomain \ + (type, domain) \ + VALUES ('NATIVE', E'%s') +gpgsql-update-kind-query=UPDATE domains_dnsdomain \ + SET type='%s' \ + WHERE domain='%s' +gpgsql-update-master-query=UPDATE domains_dnsdomain \ + SET master='%s' \ + WHERE domain='%s' + +# Mixed queries +gpgsql-get-all-domains-query=SELECT domains_dnsdomain.id, domains_dnsdomain.domain, domains_dnsrecord.content, \ + domains_dnsdomain.type, domains_dnsdomain.master, domains_dnsdomain.notified_serial, domains_dnsdomain.last_check \ + FROM domains_dnsdomain \ + LEFT JOIN domains_dnsrecord \ + ON domains_dnsrecord.domain_id=domains_dnsdomain.id AND domains_dnsrecord.type='SOA' AND domains_dnsrecord.name=domains_dnsdomain.domain \ + WHERE domains_dnsrecord.disabled=false OR %d::bool diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index ed6640a..405b2b5 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -143,18 +143,20 @@ class DNSDomain(DomainBase): in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. - 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))) - ); + .. code-block:: sql - CREATE UNIQUE INDEX name_index ON domains(name); + 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 @@ -181,28 +183,30 @@ class DNSRecord(models.Model): table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. - 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))) - ); + .. code-block:: sql - 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); + 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') @@ -238,12 +242,14 @@ class DNSSupermaster(models.Model): specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. - CREATE TABLE supermasters ( - ip INET NOT NULL, - nameserver VARCHAR(255) NOT NULL, - account VARCHAR(40) NOT NULL, - PRIMARY KEY(ip, nameserver) - ); + .. 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() @@ -272,23 +278,25 @@ class DNSComment(models.Model): comments table is used to store user comments related to individual DNS records. - 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))) - ); + .. code-block:: sql - 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); + 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') @@ -323,14 +331,17 @@ class DNSDomainMetadata(models.Model): The domainmetadata table is used to store domain meta data as described in https://doc.powerdns.com/md/authoritative/domainmetadata/. - CREATE TABLE domainmetadata ( - id SERIAL PRIMARY KEY, - domain_id INT REFERENCES domains(id) ON DELETE CASCADE, - kind VARCHAR(32), - content TEXT - ); + .. 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); - CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); """ domain = models.ForeignKey('DNSDomain') kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS) @@ -352,15 +363,18 @@ class DNSCryptoKey(models.Model): specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. - CREATE TABLE cryptokeys ( - id SERIAL PRIMARY KEY, - domain_id INT REFERENCES domains(id) ON DELETE CASCADE, - flags INT NOT NULL, - active BOOL, - content TEXT - ); + .. 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); - CREATE INDEX domainidindex ON cryptokeys(domain_id); """ domain = models.ForeignKey('DNSDomain') flags = models.IntegerField() @@ -382,15 +396,18 @@ 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/. - 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))) - ); + .. 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); - 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) From de0f3b8ca1f7661159936d59c4582baac220cc8b Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 8 Nov 2015 12:14:53 +0100 Subject: [PATCH 030/111] Add translations for domains.models addresses #17 --- .../domains/locale/de/LC_MESSAGES/django.po | 125 +++++++++++++++--- 1 file changed, 109 insertions(+), 16 deletions(-) diff --git a/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po index 5dc5688..13ab012 100644 --- a/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: gnuviechadmin domains\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" -"PO-Revision-Date: 2015-01-27 19:06+0100\n" +"POT-Creation-Date: 2015-11-08 12:00+0100\n" +"PO-Revision-Date: 2015-11-08 12:02+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" "Language: de\n" @@ -16,58 +16,151 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.10\n" +"X-Generator: Poedit 1.8.6\n" "X-Poedit-SourceCharset: UTF-8\n" -#: domains/apps.py:17 +#: apps.py:17 msgid "Domains" msgstr "Domains" -#: domains/forms.py:28 +#: forms.py:30 tests/test_forms.py:24 msgid "host name too long" msgstr "zu langer Hostname" -#: domains/forms.py:31 +#: forms.py:33 tests/test_forms.py:29 tests/test_forms.py:34 +#: tests/test_forms.py:39 tests/test_forms.py:44 msgid "invalid domain name" msgstr "ungültiger Domainname" -#: domains/forms.py:54 +#: forms.py:56 msgid "Add Hosting Domain" msgstr "Hostingdomain hinzufügen" -#: domains/models.py:21 +#: models.py:17 +msgid "Master" +msgstr "Master" + +#: models.py:18 +msgid "Slave" +msgstr "Slave" + +#: models.py:19 +msgid "Native" +msgstr "Native" + +#: models.py:44 +msgid "HMAC MD5" +msgstr "HMAC MD5" + +#: models.py:45 +msgid "HMAC SHA1" +msgstr "HMAC SHA1" + +#: models.py:46 +msgid "HMAC SHA224" +msgstr "HMAC SHA224" + +#: models.py:47 +msgid "HMAC SHA256" +msgstr "HMAC SHA256" + +#: models.py:48 +msgid "HMAC SHA384" +msgstr "HMAC SHA384" + +#: models.py:49 +msgid "HMAC SHA512" +msgstr "HMAC SHA512" + +#: models.py:59 msgid "domain name" msgstr "Domainname" -#: domains/models.py:23 +#: models.py:61 models.py:259 models.py:308 msgid "customer" msgstr "Kunde" -#: domains/models.py:42 +#: models.py:80 msgid "Mail domain" msgstr "E-Maildomain" -#: domains/models.py:43 +#: models.py:81 msgid "Mail domains" msgstr "E-Maildomains" -#: domains/models.py:87 +#: models.py:125 msgid "mail domain" msgstr "E-Maildomain" -#: domains/models.py:88 +#: models.py:126 msgid "assigned mail domain for this domain" msgstr "zugeordnete E-Maildomain für diese Domain" -#: domains/models.py:94 +#: models.py:132 msgid "Hosting domain" msgstr "Hostingdomain" -#: domains/models.py:95 +#: models.py:133 msgid "Hosting domains" msgstr "Hostingdomains" -#: domains/views.py:58 +#: models.py:172 +msgid "DNS domain" +msgstr "DNS-Domain" + +#: models.py:173 +msgid "DNS domains" +msgstr "DNS-Domains" + +#: models.py:227 +msgid "DNS record" +msgstr "DNS-Record" + +#: models.py:228 +msgid "DNS records" +msgstr "DNS-Records" + +#: models.py:262 +msgid "DNS supermaster" +msgstr "DNS-Supermaster" + +#: models.py:263 +msgid "DNS supermasters" +msgstr "DNS-Supermasters" + +#: models.py:313 +msgid "DNS comment" +msgstr "DNS-Kommentar" + +#: models.py:314 +msgid "DNS comments" +msgstr "DNS-Kommentare" + +#: models.py:351 +msgid "DNS domain metadata item" +msgstr "DNS-Domainmetadaten-Eintrag" + +#: models.py:352 +msgid "DNS domain metadata items" +msgstr "DNS-Domainmetadaten-Einträge" + +#: models.py:385 +msgid "DNS crypto key" +msgstr "DNS-Cryposchlüssel" + +#: models.py:386 +msgid "DNS crypto keys" +msgstr "DNS-Cryptoschlüssel" + +#: models.py:418 +msgid "DNS TSIG key" +msgstr "DNS-TSIG-Schlüssel" + +#: models.py:419 +msgid "DNS TSIG keys" +msgstr "DNS-TSIG-Schlüssel" + +#: views.py:58 #, python-brace-format msgid "Successfully created domain {domainname}" msgstr "Domain {domainname} erfolgreich angelegt" From 7bcb0d3100550c74c3f8abce67a86cc8140306c5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 22 Nov 2015 13:13:42 +0000 Subject: [PATCH 031/111] Add tests for gnuviechadmin.context_processors This commit adds new tests for the gnuviechadmin.context_processors module. The module gnuviechadmin.tests has been moved into a separate directory for a more clear structure. --- gnuviechadmin/gnuviechadmin/tests/__init__.py | 2 + .../tests/test_contextprocessors.py | 129 ++++++++++++++++++ .../{tests.py => tests/test_settings.py} | 9 +- .../gnuviechadmin/tests/test_wsgi.py | 11 ++ 4 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 gnuviechadmin/gnuviechadmin/tests/__init__.py create mode 100644 gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py rename gnuviechadmin/gnuviechadmin/{tests.py => tests/test_settings.py} (82%) create mode 100644 gnuviechadmin/gnuviechadmin/tests/test_wsgi.py diff --git a/gnuviechadmin/gnuviechadmin/tests/__init__.py b/gnuviechadmin/gnuviechadmin/tests/__init__.py new file mode 100644 index 0000000..df1ff9a --- /dev/null +++ b/gnuviechadmin/gnuviechadmin/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- python -*- +# -*- coding: utf-8 -*- diff --git a/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py b/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py new file mode 100644 index 0000000..51ba516 --- /dev/null +++ b/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py @@ -0,0 +1,129 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +""" +This module contains tests for :py:mod:`gnuviechadmin.context_processors`. + +""" + +from mock import MagicMock + +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import HttpRequest +from django.test import TestCase + +from django.contrib.auth import get_user_model + +from gnuviechadmin import __version__ as gvaversion +from gnuviechadmin.context_processors import navigation + +User = get_user_model() + + +class NavigationContextProcessorTest(TestCase): + + EXPECTED_ITEMS = ( + 'webmail_url', 'phpmyadmin_url', 'phppgadmin_url', + 'active_item' + ) + + def test_ajax_request(self): + response = self.client.get( + '/', HTTP_X_REQUESTED_WITH='XMLHttpRequest') + for item in self.EXPECTED_ITEMS: + self.assertNotIn(item, response.context) + + def _check_static_urls(self, context): + self.assertEqual( + context['webmail_url'], settings.GVA_LINK_WEBMAIL) + self.assertEqual( + context['phpmyadmin_url'], settings.GVA_LINK_PHPMYADMIN) + self.assertEqual( + context['phppgadmin_url'], settings.GVA_LINK_PHPPGADMIN) + + def test_index_page_context(self): + response = self.client.get('/') + for item in self.EXPECTED_ITEMS: + self.assertIn(item, response.context) + self._check_static_urls(response.context) + self.assertEqual(response.context['active_item'], 'dashboard') + + def test_contact_page_context(self): + response = self.client.get(reverse('contact_form')) + for item in self.EXPECTED_ITEMS: + self.assertIn(item, response.context) + self._check_static_urls(response.context) + self.assertEqual(response.context['active_item'], 'contact') + + def test_hostingpackage_page_context(self): + user = User.objects.create_user('test', password='test') + self.client.login(username='test', password='test') + response = self.client.get( + reverse('hosting_packages', kwargs={'user': 'test'})) + for item in self.EXPECTED_ITEMS: + self.assertIn(item, response.context) + self._check_static_urls(response.context) + self.assertEqual(response.context['active_item'], 'hostingpackage') + + def _test_page_context_by_viewmodule(self, viewmodule, expecteditem): + request = HttpRequest() + request.resolver_match = MagicMock() + request.resolver_match.func.__module__ = viewmodule + context = navigation(request) + for item in self.EXPECTED_ITEMS: + self.assertIn(item, context) + self._check_static_urls(context) + self.assertEqual(context['active_item'], expecteditem) + + def test_osusers_page_context(self): + self._test_page_context_by_viewmodule( + 'osusers.views', 'hostingpackage') + + def test_userdbs_page_context(self): + self._test_page_context_by_viewmodule( + 'userdbs.views', 'hostingpackage') + + def test_managemails_page_context(self): + self._test_page_context_by_viewmodule( + 'managemails.views', 'hostingpackage') + + def test_websites_page_context(self): + self._test_page_context_by_viewmodule( + 'websites.views', 'hostingpackage') + + def test_domains_page_context(self): + self._test_page_context_by_viewmodule( + 'domains.views', 'hostingpackage') + + def test_allauth_account_page_context(self): + self._test_page_context_by_viewmodule( + 'allauth.account.views', 'account') + + def test_allauth_socialaccount_page_context(self): + self._test_page_context_by_viewmodule( + 'allauth.socialaccount.views', 'account') + + def test_imprint_page_context(self): + response = self.client.get(reverse('imprint')) + for item in self.EXPECTED_ITEMS: + self.assertIn(item, response.context) + self._check_static_urls(response.context) + self.assertEqual(response.context['active_item'], 'imprint') + + def test_no_resolver_match(self): + request = HttpRequest() + context = navigation(request) + self._check_static_urls(context) + self.assertEqual(context['active_item'], 'dashboard') + + def test_admin_module(self): + self._test_page_context_by_viewmodule( + 'django.contrib.admin.foo', 'dashboard') + + +class VersionInfoContextProcessorTest(TestCase): + + def test_version_info_in_context(self): + response = self.client.get('/') + self.assertIn('gnuviechadmin_version', response.context) + self.assertEqual(response.context['gnuviechadmin_version'], gvaversion) diff --git a/gnuviechadmin/gnuviechadmin/tests.py b/gnuviechadmin/gnuviechadmin/tests/test_settings.py similarity index 82% rename from gnuviechadmin/gnuviechadmin/tests.py rename to gnuviechadmin/gnuviechadmin/tests/test_settings.py index 9c35fda..ed69779 100644 --- a/gnuviechadmin/gnuviechadmin/tests.py +++ b/gnuviechadmin/gnuviechadmin/tests/test_settings.py @@ -1,3 +1,5 @@ +# -*- python -*- +# -*- coding: utf-8 -*- import os from unittest import TestCase @@ -19,10 +21,3 @@ class GetEnvVariableTest(TestCase): get_env_variable('missingvariable') self.assertEqual( str(e.exception), 'Set the missingvariable environment variable') - - -class WSGITest(TestCase): - - def test_wsgi_application(self): - from gnuviechadmin import wsgi - self.assertIsNotNone(wsgi.application) diff --git a/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py b/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py new file mode 100644 index 0000000..022a0ec --- /dev/null +++ b/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py @@ -0,0 +1,11 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +from unittest import TestCase + + +class WSGITest(TestCase): + + def test_wsgi_application(self): + from gnuviechadmin import wsgi + self.assertIsNotNone(wsgi.application) + From be0531ec30c7686c41900dd9849e883b95f51a65 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 22 Nov 2015 14:31:04 +0100 Subject: [PATCH 032/111] Add test for gnuviechadmin.celery This commit adds a refactoring for gnuviechadmin.celery to make the installed apps detection testable. The test is added in gnuviechadmin.tests.test_celery. Debug code in gnuviechadmin.urls is no excluded from coverage reporting. --- gnuviechadmin/gnuviechadmin/celery.py | 5 ++++- gnuviechadmin/gnuviechadmin/tests/test_celery.py | 10 ++++++++++ gnuviechadmin/gnuviechadmin/urls.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 gnuviechadmin/gnuviechadmin/tests/test_celery.py diff --git a/gnuviechadmin/gnuviechadmin/celery.py b/gnuviechadmin/gnuviechadmin/celery.py index b8be2e5..285c2f6 100644 --- a/gnuviechadmin/gnuviechadmin/celery.py +++ b/gnuviechadmin/gnuviechadmin/celery.py @@ -12,5 +12,8 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', app = Celery('gnuviechadmin') +def get_installed_apps(): + return settings.INSTALLED_APPS + app.config_from_object('django.conf:settings') -app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) +app.autodiscover_tasks(get_installed_apps) diff --git a/gnuviechadmin/gnuviechadmin/tests/test_celery.py b/gnuviechadmin/gnuviechadmin/tests/test_celery.py new file mode 100644 index 0000000..3dd9795 --- /dev/null +++ b/gnuviechadmin/gnuviechadmin/tests/test_celery.py @@ -0,0 +1,10 @@ +from unittest import TestCase + +from gnuviechadmin.celery import get_installed_apps +from django.conf import settings + + +class GetInstalledAppsTest(TestCase): + + def test_get_installed_apps(self): + self.assertEqual(get_installed_apps(), settings.INSTALLED_APPS) diff --git a/gnuviechadmin/gnuviechadmin/urls.py b/gnuviechadmin/gnuviechadmin/urls.py index 7d1f972..762d675 100644 --- a/gnuviechadmin/gnuviechadmin/urls.py +++ b/gnuviechadmin/gnuviechadmin/urls.py @@ -27,7 +27,7 @@ urlpatterns += patterns( # Uncomment the next line to serve media files in dev. # urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -if settings.DEBUG: +if settings.DEBUG: # pragma: no cover import debug_toolbar urlpatterns += patterns('', url(r'^__debug__/', include(debug_toolbar.urls)), From b11055807faa93de512306038f22fff5122bc5ee Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 22 Nov 2015 14:03:47 +0000 Subject: [PATCH 033/111] Clean PEP8 violations --- gnuviechadmin/domains/models.py | 30 +++--- gnuviechadmin/domains/tests/test_views.py | 47 ++++++---- gnuviechadmin/gnuviechadmin/celery.py | 1 + .../gnuviechadmin/context_processors.py | 1 - .../gnuviechadmin/settings/__init__.py | 1 - gnuviechadmin/gnuviechadmin/settings/base.py | 92 +++++++++---------- gnuviechadmin/gnuviechadmin/settings/local.py | 20 ++-- .../gnuviechadmin/settings/production.py | 20 ++-- .../gnuviechadmin/tests/test_wsgi.py | 1 - gnuviechadmin/gnuviechadmin/wsgi.py | 5 +- gnuviechadmin/hostingpackages/models.py | 3 +- gnuviechadmin/hostingpackages/views.py | 3 +- gnuviechadmin/ldaptasks/tasks.py | 1 + gnuviechadmin/manage.py | 3 +- gnuviechadmin/managemails/models.py | 4 +- gnuviechadmin/osusers/admin.py | 8 +- gnuviechadmin/osusers/signals.py | 29 +++--- gnuviechadmin/osusers/tests/test_models.py | 11 ++- gnuviechadmin/userdbs/models.py | 3 +- gnuviechadmin/webtasks/tasks.py | 2 + 20 files changed, 153 insertions(+), 132 deletions(-) diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index 405b2b5..323d0da 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -153,7 +153,8 @@ class DNSDomain(DomainBase): 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))) + CONSTRAINT c_lowercase_name CHECK ( + ((name)::TEXT = LOWER((name)::TEXT))) ); CREATE UNIQUE INDEX name_index ON domains(name); @@ -162,8 +163,8 @@ class DNSDomain(DomainBase): # 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') + 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 @@ -200,20 +201,22 @@ class DNSRecord(models.Model): CONSTRAINT domain_exists FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE, - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + 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); + CREATE INDEX recordorder ON records ( + domain_id, ordername text_pattern_ops); """ domain = models.ForeignKey('DNSDomain') - 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') + 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) @@ -291,7 +294,8 @@ class DNSComment(models.Model): CONSTRAINT domain_exists FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE, - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + CONSTRAINT c_lowercase_name CHECK ( + ((name)::TEXT = LOWER((name)::TEXT))) ); CREATE INDEX comments_domain_id_idx ON comments (domain_id); @@ -403,14 +407,16 @@ class DNSTSIGKey(models.Model): name VARCHAR(255), algorithm VARCHAR(50), secret VARCHAR(255), - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + 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) + 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 diff --git a/gnuviechadmin/domains/tests/test_views.py b/gnuviechadmin/domains/tests/test_views.py index 4873bcf..8577b39 100644 --- a/gnuviechadmin/domains/tests/test_views.py +++ b/gnuviechadmin/domains/tests/test_views.py @@ -41,8 +41,8 @@ class CreateHostingDomainTest(TestCase): return package def test_get_anonymous(self): - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': 1})) + response = self.client.get( + reverse('create_hosting_domain', kwargs={'package': 1})) self.assertEqual(response.status_code, 403) def test_get_regular_user(self): @@ -50,8 +50,9 @@ class CreateHostingDomainTest(TestCase): TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) package = self._setup_hosting_package(customer) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': package.id})) + response = self.client.get( + reverse('create_hosting_domain', + kwargs={'package': package.id})) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): @@ -60,8 +61,9 @@ class CreateHostingDomainTest(TestCase): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': package.id})) + response = self.client.get( + reverse('create_hosting_domain', + kwargs={'package': package.id})) self.assertEqual(response.status_code, 200) def test_get_template(self): @@ -70,16 +72,18 @@ class CreateHostingDomainTest(TestCase): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': package.id})) + response = self.client.get( + reverse('create_hosting_domain', + kwargs={'package': package.id})) self.assertTemplateUsed(response, 'domains/hostingdomain_create.html') def test_get_no_package_found(self): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': 1})) + response = self.client.get( + reverse('create_hosting_domain', + kwargs={'package': 1})) self.assertEqual(response.status_code, 404) def test_get_get_form_kwargs(self): @@ -90,7 +94,7 @@ class CreateHostingDomainTest(TestCase): self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = CreateHostingDomain( request=MagicMock(), kwargs={'package': str(package.id)}) - the_kwargs= view.get_form_kwargs() + the_kwargs = view.get_form_kwargs() self.assertIn('hostingpackage', the_kwargs) self.assertEqual(the_kwargs['hostingpackage'], package) @@ -100,8 +104,9 @@ class CreateHostingDomainTest(TestCase): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': package.id})) + response = self.client.get( + reverse('create_hosting_domain', + kwargs={'package': package.id})) self.assertIn('hostingpackage', response.context) self.assertEqual(response.context['hostingpackage'], package) @@ -111,8 +116,9 @@ class CreateHostingDomainTest(TestCase): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('create_hosting_domain', - kwargs={'package': package.id})) + response = self.client.get( + reverse('create_hosting_domain', + kwargs={'package': package.id})) self.assertIn('customer', response.context) self.assertEqual(response.context['customer'], customer) @@ -122,8 +128,10 @@ class CreateHostingDomainTest(TestCase): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post(reverse('create_hosting_domain', - kwargs={'package': package.id}), data={'domain': 'example.org'}) + response = self.client.post( + reverse('create_hosting_domain', + kwargs={'package': package.id}), + data={'domain': 'example.org'}) self.assertRedirects(response, package.get_absolute_url()) def test_form_valid_message(self): @@ -132,8 +140,9 @@ class CreateHostingDomainTest(TestCase): admin = User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post(reverse('create_hosting_domain', - kwargs={'package': package.id}), follow=True, + response = self.client.post( + reverse('create_hosting_domain', + kwargs={'package': package.id}), follow=True, data={'domain': 'example.org'}) messages = list(response.context['messages']) self.assertEqual(len(messages), 1) diff --git a/gnuviechadmin/gnuviechadmin/celery.py b/gnuviechadmin/gnuviechadmin/celery.py index 285c2f6..b9a95d2 100644 --- a/gnuviechadmin/gnuviechadmin/celery.py +++ b/gnuviechadmin/gnuviechadmin/celery.py @@ -12,6 +12,7 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', app = Celery('gnuviechadmin') + def get_installed_apps(): return settings.INSTALLED_APPS diff --git a/gnuviechadmin/gnuviechadmin/context_processors.py b/gnuviechadmin/gnuviechadmin/context_processors.py index 4221551..09f1226 100644 --- a/gnuviechadmin/gnuviechadmin/context_processors.py +++ b/gnuviechadmin/gnuviechadmin/context_processors.py @@ -13,7 +13,6 @@ from gnuviechadmin import __version__ as gvaversion _LOGGER = logging.getLogger(__name__) - def navigation(request): """ Add navigation items to the request context. diff --git a/gnuviechadmin/gnuviechadmin/settings/__init__.py b/gnuviechadmin/gnuviechadmin/settings/__init__.py index 8b13789..e69de29 100644 --- a/gnuviechadmin/gnuviechadmin/settings/__init__.py +++ b/gnuviechadmin/gnuviechadmin/settings/__init__.py @@ -1 +0,0 @@ - diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 891811d..f5fdf4e 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -27,7 +27,7 @@ def get_env_variable(var_name): raise ImproperlyConfigured(error_msg) -########## PATH CONFIGURATION +# ######### PATH CONFIGURATION # Absolute filesystem path to the Django project directory: DJANGO_ROOT = dirname(dirname(abspath(__file__))) @@ -40,19 +40,19 @@ SITE_NAME = basename(DJANGO_ROOT) # Add our project to our pythonpath, this way we don't need to type our project # name in our dotted import paths: path.append(DJANGO_ROOT) -########## END PATH CONFIGURATION +# ######### END PATH CONFIGURATION -########## DEBUG CONFIGURATION +# ######### DEBUG CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug DEBUG = False # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug TEMPLATE_DEBUG = DEBUG -########## END DEBUG CONFIGURATION +# ######### END DEBUG CONFIGURATION -########## MANAGER CONFIGURATION +# ######### MANAGER CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins ADMINS = ( (get_env_variable('GVA_ADMIN_NAME'), get_env_variable('GVA_ADMIN_EMAIL')), @@ -60,10 +60,10 @@ ADMINS = ( # See: https://docs.djangoproject.com/en/dev/ref/settings/#managers MANAGERS = ADMINS -########## END MANAGER CONFIGURATION +# ######### END MANAGER CONFIGURATION -########## DATABASE CONFIGURATION +# ######### DATABASE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases DATABASES = { 'default': { @@ -75,10 +75,10 @@ DATABASES = { 'PORT': get_env_variable('GVA_PGSQL_PORT'), } } -########## END DATABASE CONFIGURATION +# ######### END DATABASE CONFIGURATION -########## GENERAL CONFIGURATION +# ######### GENERAL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone TIME_ZONE = 'Europe/Berlin' @@ -98,7 +98,7 @@ USE_L10N = True # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz USE_TZ = True -########## END GENERAL CONFIGURATION +# ######### END GENERAL CONFIGURATION LOCALE_PATHS = ( @@ -106,59 +106,59 @@ LOCALE_PATHS = ( ) -########## MEDIA CONFIGURATION +# ######### MEDIA CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root MEDIA_ROOT = normpath(join(SITE_ROOT, 'media')) # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url MEDIA_URL = '/media/' -########## END MEDIA CONFIGURATION +# ######### END MEDIA CONFIGURATION -########## STATIC FILE CONFIGURATION +# ######### STATIC FILE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root STATIC_ROOT = normpath(join(SITE_ROOT, 'assets')) # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url STATIC_URL = '/static/' -# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS +# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # noqa STATICFILES_DIRS = ( normpath(join(SITE_ROOT, 'static')), ) -# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders +# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) -########## END STATIC FILE CONFIGURATION +# ######### END STATIC FILE CONFIGURATION -########## SECRET CONFIGURATION +# ######### SECRET CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Note: This key should only be used for development and testing. SECRET_KEY = get_env_variable('GVA_SITE_SECRET') -########## END SECRET CONFIGURATION +# ######### END SECRET CONFIGURATION -########## SITE CONFIGURATION +# ######### SITE CONFIGURATION # Hosts/domain names that are valid for this site # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts ALLOWED_HOSTS = [] -########## END SITE CONFIGURATION +# ######### END SITE CONFIGURATION -########## FIXTURE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS +# ######### FIXTURE CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS # noqa FIXTURE_DIRS = ( normpath(join(SITE_ROOT, 'fixtures')), ) -########## END FIXTURE CONFIGURATION +# ######### END FIXTURE CONFIGURATION -########## TEMPLATE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors +# ######### TEMPLATE CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors # noqa TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', @@ -186,10 +186,10 @@ TEMPLATE_LOADERS = ( TEMPLATE_DIRS = ( normpath(join(SITE_ROOT, 'templates')), ) -########## END TEMPLATE CONFIGURATION +# ######### END TEMPLATE CONFIGURATION -########## MIDDLEWARE CONFIGURATION +# ######### MIDDLEWARE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes MIDDLEWARE_CLASSES = ( # Default Django middleware. @@ -202,7 +202,7 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.locale.LocaleMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) -########## END MIDDLEWARE CONFIGURATION +# ######### END MIDDLEWARE CONFIGURATION AUTHENTICATION_BACKENDS = ( @@ -214,18 +214,18 @@ AUTHENTICATION_BACKENDS = ( ) -########## URL CONFIGURATION +# ######### URL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf ROOT_URLCONF = '%s.urls' % SITE_NAME -########## END URL CONFIGURATION +# ######### END URL CONFIGURATION -########## TEST RUNNER CONFIGURATION +# ######### TEST RUNNER CONFIGURATION TEST_RUNNER = 'django.test.runner.DiscoverRunner' -########## END TEST RUNNER CONFIGURATION +# ######### END TEST RUNNER CONFIGURATION -########## APP CONFIGURATION +# ######### APP CONFIGURATION DJANGO_APPS = ( # Default Django apps: 'django.contrib.auth', @@ -285,23 +285,23 @@ MESSAGE_TAGS = { messages.SUCCESS: 'alert-success', messages.WARNING: 'alert-warning', } -########## END APP CONFIGURATION +# ######### END APP CONFIGURATION -########## ALLAUTH CONFIGURATION +# ######### ALLAUTH CONFIGURATION ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_EMAIL_VERIFICATION = 'mandatory' LOGIN_REDIRECT_URL = '/' SOCIALACCOUNT_QUERY_EMAIL = True -########## END ALLAUTH CONFIGURATION +# ######### END ALLAUTH CONFIGURATION -########## CRISPY FORMS CONFIGURATION +# ######### CRISPY FORMS CONFIGURATION CRISPY_TEMPLATE_PACK = 'bootstrap3' -########## END CRISPY_FORMS CONFIGURATION +# ######### END CRISPY_FORMS CONFIGURATION -########## LOGGING CONFIGURATION +# ######### LOGGING CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging # A sample logging configuration. The only tangible logging # performed by this configuration is to send an email to @@ -340,16 +340,16 @@ LOGGING = { }, } } -########## END LOGGING CONFIGURATION +# ######### END LOGGING CONFIGURATION -########## WSGI CONFIGURATION +# ######### WSGI CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME -########## END WSGI CONFIGURATION +# ######### END WSGI CONFIGURATION -########## CELERY CONFIGURATION +# ######### CELERY CONFIGURATION BROKER_URL = get_env_variable('GVA_BROKER_URL') CELERY_RESULT_BACKEND = 'amqp' CELERY_RESULT_PERSISTENT = True @@ -362,10 +362,10 @@ CELERY_ENABLE_UTC = True CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' -########## END CELERY CONFIGURATION +# ######### END CELERY CONFIGURATION -########## CUSTOM APP CONFIGURATION +# ######### CUSTOM APP CONFIGURATION OSUSER_MINUID = int(get_env_variable('GVA_MIN_OS_UID')) OSUSER_MINGID = int(get_env_variable('GVA_MIN_OS_GID')) OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX') @@ -379,4 +379,4 @@ OSUSER_UPLOAD_SERVER = get_env_variable('GVA_OSUSER_UPLOADSERVER') GVA_LINK_WEBMAIL = get_env_variable('GVA_WEBMAIL_URL') GVA_LINK_PHPMYADMIN = get_env_variable('GVA_PHPMYADMIN_URL') GVA_LINK_PHPPGADMIN = get_env_variable('GVA_PHPPGADMIN_URL') -########## END CUSTOM APP CONFIGURATION +# ######### END CUSTOM APP CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/local.py b/gnuviechadmin/gnuviechadmin/settings/local.py index 14c7139..9eba627 100644 --- a/gnuviechadmin/gnuviechadmin/settings/local.py +++ b/gnuviechadmin/gnuviechadmin/settings/local.py @@ -7,33 +7,33 @@ from __future__ import absolute_import from .base import * -########## DEBUG CONFIGURATION +# ######### DEBUG CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug DEBUG = True # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug TEMPLATE_DEBUG = DEBUG -########## END DEBUG CONFIGURATION +# ######### END DEBUG CONFIGURATION -########## EMAIL CONFIGURATION +# ######### EMAIL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -########## END EMAIL CONFIGURATION +# ######### END EMAIL CONFIGURATION -########## CACHE CONFIGURATION +# ######### CACHE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', } } -########## END CACHE CONFIGURATION +# ######### END CACHE CONFIGURATION -########## TOOLBAR CONFIGURATION -# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup +# ######### TOOLBAR CONFIGURATION +# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa INSTALLED_APPS += ( 'debug_toolbar', ) @@ -50,7 +50,7 @@ LOGGING['handlers'].update({ } }) LOGGING['loggers'].update(dict( - [(key, {'handlers': ['console'], 'level': 'DEBUG', 'propagate': True,}) + [(key, {'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }) for key in [ 'dashboard', 'domains', 'fileservertasks', 'gvacommon', 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', @@ -61,4 +61,4 @@ DEBUG_TOOLBAR_PATCH_SETTINGS = False # http://django-debug-toolbar.readthedocs.org/en/latest/installation.html INTERNAL_IPS = ('127.0.0.1', '10.0.2.2') -########## END TOOLBAR CONFIGURATION +# ######### END TOOLBAR CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/production.py b/gnuviechadmin/gnuviechadmin/settings/production.py index f19e735..a1ed73f 100644 --- a/gnuviechadmin/gnuviechadmin/settings/production.py +++ b/gnuviechadmin/gnuviechadmin/settings/production.py @@ -6,12 +6,12 @@ from __future__ import absolute_import from .base import * -########## HOST CONFIGURATION -# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production +# ######### HOST CONFIGURATION +# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production # noqa ALLOWED_HOSTS = [SITES_DOMAIN_NAME] -########## END HOST CONFIGURATION +# ######### END HOST CONFIGURATION -########## EMAIL CONFIGURATION +# ######### EMAIL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' @@ -23,14 +23,14 @@ DEFAULT_FROM_EMAIL = get_env_variable('GVA_SITE_ADMINMAIL') # See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email SERVER_EMAIL = get_env_variable('GVA_SITE_ADMINMAIL') -########## END EMAIL CONFIGURATION +# ######### END EMAIL CONFIGURATION -########## CACHE CONFIGURATION +# ######### CACHE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches -#CACHES = {} -########## END CACHE CONFIGURATION +# CACHES = {} +# ######### END CACHE CONFIGURATION -########## ALLAUTH PRODUCTION CONFIGURATION +# ######### ALLAUTH PRODUCTION CONFIGURATION ACCOUNT_EMAIL_SUBJECT_PREFIX = '[Jan Dittberner IT-Consulting & -Solutions] ' ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https' -########## END ALLAUTH PRODUCTION CONFIGURATION +# ######### END ALLAUTH PRODUCTION CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py b/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py index 022a0ec..0aa9b53 100644 --- a/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py +++ b/gnuviechadmin/gnuviechadmin/tests/test_wsgi.py @@ -8,4 +8,3 @@ class WSGITest(TestCase): def test_wsgi_application(self): from gnuviechadmin import wsgi self.assertIsNotNone(wsgi.application) - diff --git a/gnuviechadmin/gnuviechadmin/wsgi.py b/gnuviechadmin/gnuviechadmin/wsgi.py index df7c0c9..04d5488 100644 --- a/gnuviechadmin/gnuviechadmin/wsgi.py +++ b/gnuviechadmin/gnuviechadmin/wsgi.py @@ -24,12 +24,13 @@ path.append(SITE_ROOT) # if running multiple sites in the same mod_wsgi process. To fix this, use # mod_wsgi daemon mode with each site in its own daemon process, or use # os.environ["DJANGO_SETTINGS_MODULE"] = "jajaja.settings" -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.production") +os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.production") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. -from django.core.wsgi import get_wsgi_application +from django.core.wsgi import get_wsgi_application # noqa application = get_wsgi_application() # Apply WSGI middleware here. diff --git a/gnuviechadmin/hostingpackages/models.py b/gnuviechadmin/hostingpackages/models.py index 2e5ff43..f1d8735 100644 --- a/gnuviechadmin/hostingpackages/models.py +++ b/gnuviechadmin/hostingpackages/models.py @@ -362,8 +362,7 @@ class CustomerHostingPackage(HostingPackageBase): def may_add_database(self): return ( CustomerUserDatabaseOption.objects.filter( - hosting_package=self).count() - > + hosting_package=self).count() > UserDatabase.objects.filter( db_user__osuser=self.osuser).count() ) diff --git a/gnuviechadmin/hostingpackages/views.py b/gnuviechadmin/hostingpackages/views.py index abe265e..d07f4cc 100644 --- a/gnuviechadmin/hostingpackages/views.py +++ b/gnuviechadmin/hostingpackages/views.py @@ -80,7 +80,8 @@ class CreateCustomerHostingPackage(CreateHostingPackage): get_user_model(), username=self.kwargs['user']) def get_context_data(self, **kwargs): - context = super(CreateCustomerHostingPackage, self).get_context_data(**kwargs) + context = super( + CreateCustomerHostingPackage, self).get_context_data(**kwargs) context['customer'] = self.get_customer_object() return context diff --git a/gnuviechadmin/ldaptasks/tasks.py b/gnuviechadmin/ldaptasks/tasks.py index c90dd4f..72acd8d 100644 --- a/gnuviechadmin/ldaptasks/tasks.py +++ b/gnuviechadmin/ldaptasks/tasks.py @@ -7,6 +7,7 @@ from __future__ import absolute_import from celery import shared_task + @shared_task def create_ldap_group(groupname, gid, descr): """ diff --git a/gnuviechadmin/manage.py b/gnuviechadmin/manage.py index c950b97..c6003db 100755 --- a/gnuviechadmin/manage.py +++ b/gnuviechadmin/manage.py @@ -3,7 +3,8 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.local") + os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.local") from django.core.management import execute_from_command_line diff --git a/gnuviechadmin/managemails/models.py b/gnuviechadmin/managemails/models.py index 397189e..b8d15f1 100644 --- a/gnuviechadmin/managemails/models.py +++ b/gnuviechadmin/managemails/models.py @@ -200,12 +200,12 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model): forwards = MailAddressForward.objects.filter( mailaddress=self).all() for item in forwards: - if not item.target in addresses: + if item.target not in addresses: item.delete() else: retval.append(item) for target in addresses: - if not target in [item.target for item in forwards]: + if target not in [item.target for item in forwards]: mafwd = MailAddressForward(mailaddress=self, target=target) if commit: mafwd.save() diff --git a/gnuviechadmin/osusers/admin.py b/gnuviechadmin/osusers/admin.py index 91df5a8..e622fab 100644 --- a/gnuviechadmin/osusers/admin.py +++ b/gnuviechadmin/osusers/admin.py @@ -349,10 +349,10 @@ class SshPublicKeyAdmin(admin.ModelAdmin): for user in users: TaskResult.objects.create_task_result( set_file_ssh_authorized_keys.delay( - User.objects.get(uid=user).username, [ - str(key) for key in SshPublicKey.objects.filter( - user_id=user) - ]), + User.objects.get(uid=user).username, + [str(key) for key in SshPublicKey.objects.filter( + user_id=user)] + ), 'set_file_ssh_authorized_keys' ) perform_delete_selected.short_description = _( diff --git a/gnuviechadmin/osusers/signals.py b/gnuviechadmin/osusers/signals.py index 8796554..bb76ee5 100644 --- a/gnuviechadmin/osusers/signals.py +++ b/gnuviechadmin/osusers/signals.py @@ -55,11 +55,11 @@ def handle_user_password_set(sender, instance, password, **kwargs): taskresult.task_id) -#@receiver(post_save) -#def handle_post_save(sender, **kwargs): -# _LOGGER.debug( -# 'handling post_save signal for %s with args %s', -# sender, kwargs) +# @receiver(post_save) +# def handle_post_save(sender, **kwargs): +# _LOGGER.debug( +# 'handling post_save signal for %s with args %s', +# sender, kwargs) @receiver(post_save, sender=Group) @@ -82,7 +82,8 @@ def handle_user_created(sender, instance, created, **kwargs): chain = create_ldap_user.s( instance.username, instance.uid, instance.group.gid, instance.gecos, instance.homedir, instance.shell, None - ) | setup_file_sftp_userdir.s(instance.username + ) | setup_file_sftp_userdir.s( + instance.username ) | setup_file_mail_userdir.s(instance.username) taskresult = TaskResult.objects.create_task_result( 'handle_user_created', chain) @@ -119,11 +120,11 @@ def handle_ssh_keys_changed(sender, instance, **kwargs): taskresult.task_id) -#@receiver(post_delete) -#def handle_post_delete(sender, **kwargs): -# _LOGGER.debug( -# 'handling post_delete signal for %s with args %s', -# sender, kwargs) +# @receiver(post_delete) +# def handle_post_delete(sender, **kwargs): +# _LOGGER.debug( +# 'handling post_delete signal for %s with args %s', +# sender, kwargs) @receiver(post_delete, sender=Group) @@ -138,8 +139,10 @@ def handle_group_deleted(sender, instance, **kwargs): @receiver(post_delete, sender=User) def handle_user_deleted(sender, instance, **kwargs): - chain = delete_file_mail_userdir.s(instance.username - ) | delete_file_sftp_userdir.s(instance.username + chain = delete_file_mail_userdir.s( + instance.username + ) | delete_file_sftp_userdir.s( + instance.username ) | delete_ldap_user.s(instance.username) _LOGGER.debug('chain signature %s', chain) taskresult = TaskResult.objects.create_task_result( diff --git a/gnuviechadmin/osusers/tests/test_models.py b/gnuviechadmin/osusers/tests/test_models.py index 4189904..50ed035 100644 --- a/gnuviechadmin/osusers/tests/test_models.py +++ b/gnuviechadmin/osusers/tests/test_models.py @@ -56,10 +56,10 @@ sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV ---- END SSH2 PUBLIC KEY ----""" EXAMPLE_KEY_4_OPENSSH = "".join(( -"ssh-rsa ", -"AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", -"YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", -"5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=" + "ssh-rsa ", + "AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", + "YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", + "5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=" )) EXAMPLE_KEY_5_RFC4716_MULTILINE = """---- BEGIN SSH2 PUBLIC KEY ---- @@ -392,7 +392,8 @@ class UserTest(TestCaseWithCeleryTasks): self.assertEqual(len(taskres), 5) creators = [t.creator for t in taskres] for tcount, tcreator in [ - (2, 'handle_user_removed_from_group'), (2, 'handle_user_deleted'), + (2, 'handle_user_removed_from_group'), + (2, 'handle_user_deleted'), (1, 'handle_group_deleted')]: self.assertEqual(creators.count(tcreator), tcount) self.assertEqual(len(User.objects.all()), 0) diff --git a/gnuviechadmin/userdbs/models.py b/gnuviechadmin/userdbs/models.py index 4cc2b76..adcc2e7 100644 --- a/gnuviechadmin/userdbs/models.py +++ b/gnuviechadmin/userdbs/models.py @@ -98,7 +98,6 @@ class DatabaseUserManager(models.Manager): return db_user - @python_2_unicode_compatible class DatabaseUser(TimeStampedModel, models.Model): osuser = models.ForeignKey(OsUser) @@ -204,7 +203,7 @@ class UserDatabaseManager(models.Manager): @transaction.atomic def create_userdatabase_with_user( - self, db_type, osuser, password=None, commit=True): + self, db_type, osuser, password=None, commit=True): """ Creates a new user database with a new user. diff --git a/gnuviechadmin/webtasks/tasks.py b/gnuviechadmin/webtasks/tasks.py index e7e1bf9..4abd4af 100644 --- a/gnuviechadmin/webtasks/tasks.py +++ b/gnuviechadmin/webtasks/tasks.py @@ -47,6 +47,7 @@ def enable_web_vhost(sitename): """ + @shared_task def delete_web_vhost_config(sitename): """ @@ -70,6 +71,7 @@ def create_web_php_fpm_pool_config(username): """ + @shared_task def delete_web_php_fpm_pool_config(username): """ From 28fc535f9e908f616c5e24c3c8d8cef9db16746c Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 22 Nov 2015 14:43:02 +0000 Subject: [PATCH 034/111] Add tests for gvawebcore This commit adds tests for gvawebcore.forms and gvawebcore.views. --- gnuviechadmin/gvawebcore/tests/__init__.py | 0 gnuviechadmin/gvawebcore/tests/test_forms.py | 29 +++++++++++++++++ gnuviechadmin/gvawebcore/tests/test_views.py | 34 ++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 gnuviechadmin/gvawebcore/tests/__init__.py create mode 100644 gnuviechadmin/gvawebcore/tests/test_forms.py create mode 100644 gnuviechadmin/gvawebcore/tests/test_views.py diff --git a/gnuviechadmin/gvawebcore/tests/__init__.py b/gnuviechadmin/gvawebcore/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/gvawebcore/tests/test_forms.py b/gnuviechadmin/gvawebcore/tests/test_forms.py new file mode 100644 index 0000000..e657829 --- /dev/null +++ b/gnuviechadmin/gvawebcore/tests/test_forms.py @@ -0,0 +1,29 @@ +""" +This module contains tests for :py:mod:`gvawebcore.forms`. + +""" + +from unittest import TestCase + +from gvawebcore.forms import PasswordModelFormMixin, PASSWORD_MISMATCH_ERROR + + +class PasswordModelFormMixinTest(TestCase): + + def test_form_properties(self): + form = PasswordModelFormMixin() + self.assertIn('password1', form.fields) + self.assertIn('password2', form.fields) + + def test_clean_password_same(self): + form = PasswordModelFormMixin(data={ + 'password1': 'secret', 'password2': 'secret'}) + self.assertTrue(form.is_valid()) + self.assertEqual('secret', form.clean_password2()) + + def test_clean_password_different(self): + form = PasswordModelFormMixin(data={ + 'password1': 'onesecret', 'password2': 'other'}) + self.assertFalse(form.is_valid()) + self.assertIn('password2', form.errors) + self.assertIn(PASSWORD_MISMATCH_ERROR, form.errors['password2']) diff --git a/gnuviechadmin/gvawebcore/tests/test_views.py b/gnuviechadmin/gvawebcore/tests/test_views.py new file mode 100644 index 0000000..6c4ca4b --- /dev/null +++ b/gnuviechadmin/gvawebcore/tests/test_views.py @@ -0,0 +1,34 @@ +""" +This model contains tests for :py:mod:`gvawebcore.views`. + +""" + +from unittest import TestCase + +from mock import patch, Mock + +from gvawebcore.views import HostingPackageAndCustomerMixin + + +class HostingPackageAndCustomerMixinTest(TestCase): + + class TestView(HostingPackageAndCustomerMixin): + + kwargs = {'package': '1'} + + @patch('gvawebcore.views.get_object_or_404') + def test_get_hosting_package(self, get_object_or_404): + get_object_or_404.return_value = 'A package' + view = self.TestView() + self.assertEqual('A package', view.get_hosting_package()) + + def test_get_hosting_package_cached(self): + view = self.TestView() + view.hostingpackage = 'Cached package' + self.assertEqual('Cached package', view.get_hosting_package()) + + @patch('gvawebcore.views.get_object_or_404') + def test_get_customer_object(self, get_object_or_404): + get_object_or_404.return_value = Mock(customer='A customer') + view = self.TestView() + self.assertEqual('A customer', view.get_customer_object()) From 78f54d0c9287bccec248c132358a255148440921 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 22 Nov 2015 18:40:40 +0000 Subject: [PATCH 035/111] Add QA tool config to setup.cfg This commit adds configuration for coverage, pep8 and flake8 to setup.cfg. The .coveragerc is not needed anymore and is removed. --- .gitignore | 1 + gnuviechadmin/{.coveragerc => setup.cfg} | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) rename gnuviechadmin/{.coveragerc => setup.cfg} (55%) diff --git a/.gitignore b/.gitignore index e505984..2716883 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ _build/ *.mo .vagrant/ gnuviechadmin/assets/ +coverage-report/ diff --git a/gnuviechadmin/.coveragerc b/gnuviechadmin/setup.cfg similarity index 55% rename from gnuviechadmin/.coveragerc rename to gnuviechadmin/setup.cfg index a787368..e83e084 100644 --- a/gnuviechadmin/.coveragerc +++ b/gnuviechadmin/setup.cfg @@ -1,5 +1,16 @@ -[run] -source = gnuviechadmin,contact_form,dashboard,domains,gvawebcore,managemails,osusers,taskresults,userdbs +[pep8] +exclude = migrations -[report] +[flake8] +exclude = migrations + +[coverage:run] +source = gnuviechadmin,contact_form,dashboard,domains,gvawebcore,managemails,osusers,taskresults,userdbs +branch = True + +[coverage:report] omit = */migrations/*,*/tests/*.py,*/tests.py,gnuviechadmin/settings/local.py,gnuviechadmin/settings/production.py +show_missing = True + +[coverage:html] +directory = ../coverage-report From 03a7dc032050c255b64f082be4ea5e2771d28d25 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 22 Nov 2015 18:41:45 +0000 Subject: [PATCH 036/111] Add test for managemails.forms This commit adds tests for managemails.forms. A refactoring TODO is added to MailAddressFieldMixin and pragma: no cover has been added to code paths not reachable if no new constants are added to the MAILBOX_OR_FORWARDS constant array. --- gnuviechadmin/managemails/forms.py | 12 +- gnuviechadmin/managemails/tests/test_forms.py | 563 ++++++++++++++++++ 2 files changed, 571 insertions(+), 4 deletions(-) diff --git a/gnuviechadmin/managemails/forms.py b/gnuviechadmin/managemails/forms.py index bd6a056..c35214f 100644 --- a/gnuviechadmin/managemails/forms.py +++ b/gnuviechadmin/managemails/forms.py @@ -120,6 +120,7 @@ class MailAddressFieldMixin(forms.Form): label=_('Mailbox'), required=False, ) + # TODO: refactor as separate field class returning a list forwards = forms.CharField( label=_('Forwards'), required=False, @@ -187,6 +188,7 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin): return localpart def clean(self): + super(AddMailAddressForm, self).clean() data = self.cleaned_data if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox: if not data['mailbox']: @@ -194,7 +196,8 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin): elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards: if 'forwards' not in data or not data['forwards']: self.add_error('forwards', _('No forward addresses selected')) - else: + else: # pragma: no cover + # should not happen because of the field's validation raise forms.ValidationError( _('Illegal choice for target of the mail address')) @@ -213,7 +216,7 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin): if target_choice == MAILBOX_OR_FORWARDS.mailbox: mabox = self.instance.set_mailbox(data['mailbox'], commit=False) elif target_choice == MAILBOX_OR_FORWARDS.forwards: - targets = [part.strip() for part in data['forwards'].split()] + targets = [part.strip() for part in data['forwards'].split(',')] fwds = self.instance.set_forward_addresses(targets, commit=False) mailaddress = super(AddMailAddressForm, self).save(commit) if commit: @@ -275,7 +278,8 @@ class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin): elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards: if 'forwards' not in data or not data['forwards']: self.add_error('forwards', _('No forward addresses selected')) - else: + else: # pragma: no cover + # should not happen because of the field's validation raise forms.ValidationError( _('Illegal choice for target of the mail address')) @@ -291,4 +295,4 @@ class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin): elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards: targets = [part.strip() for part in data['forwards'].split(',')] self.instance.set_forward_addresses(targets, commit) - return self.instance + return super(EditMailAddressForm, self).save(commit) diff --git a/gnuviechadmin/managemails/tests/test_forms.py b/gnuviechadmin/managemails/tests/test_forms.py index e69de29..abcd144 100644 --- a/gnuviechadmin/managemails/tests/test_forms.py +++ b/gnuviechadmin/managemails/tests/test_forms.py @@ -0,0 +1,563 @@ +""" +This module provides tests for :py:mod:`managemails.forms`. + +""" +from __future__ import absolute_import, unicode_literals + +from mock import MagicMock, Mock, patch, ANY + +from django.core.urlresolvers import reverse +from django.forms import ValidationError +from django.test import TestCase + +from managemails.forms import ( + AddMailAddressForm, + ChangeMailboxPasswordForm, + CreateMailboxForm, + EditMailAddressForm, + MAILBOX_OR_FORWARDS, + MailAddressFieldMixin, + multiple_email_validator, +) + + +class CreateMailboxFormTest(TestCase): + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError): + CreateMailboxForm(instance) + + def test_constructor(self): + hostingpackage = Mock(id=42) + instance = MagicMock() + form = CreateMailboxForm(instance, hostingpackage=hostingpackage) + self.assertTrue(hasattr(form, 'hosting_package')) + self.assertEqual(form.hosting_package, hostingpackage) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'create_mailbox', kwargs={'package': 42})) + self.assertIn('password1', form.fields) + self.assertIn('password2', form.fields) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + @patch('managemails.forms.Mailbox.objects') + def test_save(self, mailbox_objects): + osuser = MagicMock() + hostingpackage = Mock(id=42, osuser=osuser) + instance = MagicMock() + form = CreateMailboxForm( + instance=instance, hostingpackage=hostingpackage, + data={'password1': 'secret', 'password2': 'secret'}) + mailbox_objects.get_next_mailbox_name.return_value = 'mailbox23' + self.assertTrue(form.is_valid()) + form.save(commit=False) + self.assertEqual(osuser, form.instance.osuser) + self.assertEqual('mailbox23', form.instance.username) + instance.set_password.assert_called_with('secret') + + +class ChangeMailboxPasswordFormTest(TestCase): + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError): + ChangeMailboxPasswordForm(instance) + + def test_constructor(self): + hostingpackage = Mock(id=42) + instance = MagicMock(username='testuser') + form = ChangeMailboxPasswordForm( + instance=instance, hostingpackage=hostingpackage) + self.assertTrue(hasattr(form, 'hosting_package')) + self.assertEqual(form.hosting_package, hostingpackage) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'change_mailbox_password', kwargs={ + 'package': 42, 'slug': 'testuser'})) + self.assertIn('password1', form.fields) + self.assertIn('password2', form.fields) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + def test_save(self): + hostingpackage = Mock(id=42) + instance = MagicMock(username='testuser') + form = ChangeMailboxPasswordForm( + instance=instance, hostingpackage=hostingpackage, + data={'password1': 'newsecret', 'password2': 'newsecret'}) + self.assertTrue(form.is_valid()) + form.save(commit=False) + instance.set_password.assert_called_with('newsecret') + + +class MultipleEmailValidatorTest(TestCase): + + def test_valid_single_address(self): + self.assertEqual( + 'test@example.org', + multiple_email_validator('test@example.org')) + + def test_valid_multiple_addresses(self): + self.assertEqual( + 'test1@example.org,test2@example.org', + multiple_email_validator('test1@example.org,test2@example.org')) + + def test_empty(self): + self.assertEqual( + '', multiple_email_validator('')) + + def test_none(self): + self.assertIsNone(multiple_email_validator(None)) + + def test_invalid_single_address(self): + with self.assertRaises(ValidationError): + multiple_email_validator('no@ddress') + + def test_invalid_multiple_addresses(self): + with self.assertRaises(ValidationError): + multiple_email_validator('test1@example.org,no@ddress') + + +class MailAddressFieldMixinTest(TestCase): + + def test_fields_defined(self): + form = MailAddressFieldMixin() + self.assertIn('mailbox_or_forwards', form.fields) + self.assertIn('mailbox', form.fields) + self.assertIn('forwards', form.fields) + + +class AddMailAddressFormTest(TestCase): + + def setUp(self): + self.patcher1 = patch('managemails.forms.Mailbox.objects') + self.patcher2 = patch('managemails.forms.MailAddress.objects') + self.mailbox_objects = self.patcher1.start() + self.mailaddress_objects = self.patcher2.start() + + def tearDown(self): + self.patcher2.stop() + self.patcher1.stop() + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError): + AddMailAddressForm(instance=instance, maildomain=MagicMock()) + + def test_constructor_needs_maildomain(self): + instance = MagicMock() + with self.assertRaises(KeyError): + AddMailAddressForm(instance=instance, hostingpackage=MagicMock()) + + def test_constructor(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain) + self.mailbox_objects.unused.assert_called_with(osuser=osuser) + self.assertIn('mailbox_or_forwards', form.fields) + self.assertIn('mailbox', form.fields) + self.assertIn('forwards', form.fields) + self.assertTrue(hasattr(form, 'hosting_package')) + self.assertEqual(form.hosting_package, hostingpackage) + self.assertTrue(hasattr(form, 'maildomain')) + self.assertEqual(form.maildomain, maildomain) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'add_mailaddress', kwargs={ + 'package': 42, 'domain': 'example.org'})) + self.assertEqual(len(form.helper.layout), 2) + self.assertEqual(form.helper.layout[1].name, 'submit') + + def test_clean_localpart_valid(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'forwards': 'test2@example.org' + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertTrue(form.is_valid()) + self.assertEqual('test', form.clean_localpart()) + + def test_clean_localpart_duplicate(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'forwards': 'test2@example.org' + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = True + self.assertFalse(form.is_valid()) + self.assertIn('localpart', form.errors) + + def test_clean_no_mailbox_choice(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertFalse(form.is_valid()) + self.assertIn('mailbox', form.errors) + + def test_clean_no_forward_address_choice(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertFalse(form.is_valid()) + self.assertIn('forwards', form.errors) + + def test_save_with_forwards_no_commit(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'forwards': 'test2@example.org,test3@example.org' + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertTrue(form.is_valid()) + address1 = MagicMock(mailaddress='test2@example.org') + address2 = MagicMock(mailaddress='test3@example.org') + instance.set_forward_addresses.return_value = [address1, address2] + form.save(commit=False) + self.assertEqual(maildomain, instance.domain) + instance.set_forward_addresses.assert_called_with([ + 'test2@example.org', 'test3@example.org'], commit=False) + address1.save.assert_not_called() + address2.save.assert_not_called() + instance.save.assert_not_called() + + def test_save_with_forwards_commit(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'forwards': 'test2@example.org,test3@example.org' + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertTrue(form.is_valid()) + address1 = MagicMock(mailaddress='test2@example.org') + address2 = MagicMock(mailaddress='test3@example.org') + instance.set_forward_addresses.return_value = [address1, address2] + form.save(commit=True) + self.assertEqual(maildomain, instance.domain) + instance.set_forward_addresses.assert_called_with([ + 'test2@example.org', 'test3@example.org'], commit=False) + address1.save.assert_called_with() + address2.save.assert_called_with() + instance.save.assert_called_with() + + def test_save_with_mailbox_no_commit(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + 'mailbox': 'mailbox23', + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertTrue(form.is_valid()) + mailbox = MagicMock(osuser=osuser, username='testuserp01') + instance.set_mailbox.return_value = mailbox + form.save(commit=False) + self.assertEqual(maildomain, instance.domain) + instance.set_mailbox.assert_called_with(ANY, commit=False) + mailbox.save.assert_not_called() + instance.save.assert_not_called() + + def test_save_with_mailbox_commit(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + 'mailbox': 'mailbox23', + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertTrue(form.is_valid()) + mailbox = MagicMock(osuser=osuser, username='testuserp01') + instance.set_mailbox.return_value = mailbox + form.save(commit=True) + self.assertEqual(maildomain, instance.domain) + instance.set_mailbox.assert_called_with(ANY, commit=False) + instance.set_mailbox.return_value.save.assert_called_with() + mailbox.save.assert_called_with() + instance.save.assert_called_with() + + def test_save_with_other_choice(self): + instance = MagicMock() + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = AddMailAddressForm( + instance=instance, hostingpackage=hostingpackage, + maildomain=maildomain, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + 'mailbox': 'mailbox23', + }) + self.mailaddress_objects.filter( + domain=maildomain, localpart='test' + ).exists.return_value = False + self.assertTrue(form.is_valid()) + form.cleaned_data['mailbox_or_forwards'] = -1 + address1 = MagicMock(mailaddress='test2@example.org') + address2 = MagicMock(mailaddress='test3@example.org') + instance.set_forward_addresses.return_value = [address1, address2] + mailbox = MagicMock(osuser=osuser, username='testuserp01') + instance.set_mailbox.return_value = mailbox + form.save(commit=True) + instance.set_mailbox.assert_not_called() + instance.set_forward_addresses.assert_not_called() + address1.save.assert_not_called() + address2.save.assert_not_called() + mailbox.save.assert_not_called() + instance.save.assert_called_with() + + +class EditMailAddressFormTest(TestCase): + + def setUp(self): + self.patcher1 = patch('managemails.forms.Mailbox.objects') + self.patcher2 = patch('managemails.forms.MailAddress.objects') + self.mailbox_objects = self.patcher1.start() + self.mailaddress_objects = self.patcher2.start() + + def tearDown(self): + self.patcher2.stop() + self.patcher1.stop() + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError): + EditMailAddressForm(instance=instance, maildomain=MagicMock()) + + def test_constructor_needs_maildomain(self): + instance = MagicMock() + with self.assertRaises(KeyError): + EditMailAddressForm(instance=instance, hostingpackage=MagicMock()) + + def test_constructor(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage) + self.mailbox_objects.unused_or_own.assert_called_with(instance, osuser) + self.assertIn('mailbox_or_forwards', form.fields) + self.assertIn('mailbox', form.fields) + self.assertIn('forwards', form.fields) + self.assertTrue(hasattr(form, 'hosting_package')) + self.assertEqual(form.hosting_package, hostingpackage) + self.assertTrue(hasattr(form, 'maildomain')) + self.assertEqual(form.maildomain, maildomain) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'edit_mailaddress', kwargs={ + 'package': 42, + 'domain': 'example.org', + 'pk': 23})) + self.assertEqual(len(form.helper.layout), 2) + self.assertEqual(form.helper.layout[1].name, 'submit') + + def test_clean_no_mailbox_choice(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + }) + self.assertFalse(form.is_valid()) + self.assertIn('mailbox', form.errors) + + def test_clean_no_forward_address_choice(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + }) + self.assertFalse(form.is_valid()) + self.assertIn('forwards', form.errors) + + def test_save_with_forwards_no_commit(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'forwards': 'test2@example.org,test3@example.org' + }) + self.assertTrue(form.is_valid()) + address1 = MagicMock(mailaddress='test2@example.org') + address2 = MagicMock(mailaddress='test3@example.org') + instance.set_forward_addresses.return_value = [address1, address2] + form.save(commit=False) + instance.set_forward_addresses.assert_called_with( + ['test2@example.org', 'test3@example.org'], False) + address1.save.assert_not_called() + address2.save.assert_not_called() + instance.save.assert_not_called() + + def test_save_with_forwards_commit(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'forwards': 'test2@example.org,test3@example.org' + }) + self.assertTrue(form.is_valid()) + address1 = MagicMock(mailaddress='test2@example.org') + address2 = MagicMock(mailaddress='test3@example.org') + instance.set_forward_addresses.return_value = [address1, address2] + form.save(commit=True) + instance.set_forward_addresses.assert_called_with( + ['test2@example.org', 'test3@example.org'], True) + instance.save.assert_called_with() + + def test_save_with_mailbox_no_commit(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + 'mailbox': 'mailbox23', + }) + self.assertTrue(form.is_valid()) + mailbox = MagicMock(osuser=osuser, username='testuserp01') + instance.set_mailbox.return_value = mailbox + form.save(commit=False) + instance.set_mailbox.assert_called_with(ANY, False) + mailbox.save.assert_not_called() + instance.save.assert_not_called() + + def test_save_with_mailbox_commit(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + 'mailbox': 'mailbox23', + }) + self.assertTrue(form.is_valid()) + mailbox = MagicMock(osuser=osuser, username='testuserp01') + instance.set_mailbox.return_value = mailbox + self.mailbox_objects.unused_or_own.get.return_value = mailbox + form.save(commit=True) + instance.set_mailbox.assert_called_with(ANY, True) + instance.save.assert_called_with() + + def test_save_with_other_choice(self): + instance = MagicMock(id=23) + osuser = Mock(username='testuser') + hostingpackage = MagicMock(id=42, osuser=osuser) + maildomain = MagicMock(domain='example.org') + form = EditMailAddressForm( + instance=instance, maildomain=maildomain, + hostingpackage=hostingpackage, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, + 'mailbox': 'mailbox23', + }) + self.assertTrue(form.is_valid()) + form.cleaned_data['mailbox_or_forwards'] = -1 + form.save(commit=True) + instance.set_mailbox.assert_not_called() + instance.save.assert_called_with() From 1cfd4327da1acad7b9f51b05c45a60513a7ebe78 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 28 Nov 2015 14:07:34 +0000 Subject: [PATCH 037/111] Add tests for managemails.models This commit adds tests for managemails.models to improve the test coverage of that model. There are some changes to the classes in managemails.models too: - add a method create_mailbox to MailboxManager - properly handle uncommited mailaddresses in MailAddress.set_mailbox and MailAddress.set_forward_addresses --- gnuviechadmin/managemails/models.py | 24 +- .../managemails/tests/test_models.py | 284 +++++++++++++++++- 2 files changed, 305 insertions(+), 3 deletions(-) diff --git a/gnuviechadmin/managemails/models.py b/gnuviechadmin/managemails/models.py index b8d15f1..e24f65b 100644 --- a/gnuviechadmin/managemails/models.py +++ b/gnuviechadmin/managemails/models.py @@ -85,6 +85,23 @@ class MailboxManager(models.Manager): active=True, osuser=osuser, ) + def create_mailbox(self, osuser, password=None, commit=True): + """ + Create a new mailbox for the given operating system user. + + :param osuser: a :py:class:`osuser.models.OsUser` instance + :param password: an optional password + :param commit: whether the mailbox should be commited to the database + :return: mailbox instance + :rtype: :py:class:`managemails.models.Mailbox` + + """ + mailbox = self.create( + osuser=osuser, username=self.get_next_mailbox_name(osuser)) + if password is not None: + mailbox.set_password(password) + return mailbox + @python_2_unicode_compatible class Mailbox(ActivateAbleMixin, TimeStampedModel): @@ -176,9 +193,12 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model): for mafwd in MailAddressForward.objects.filter(mailaddress=self): mafwd.delete() else: - mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox) if commit: + self.save() + mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox) mabox.save() + else: + mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox) return mabox def set_forward_addresses(self, addresses, commit=True): @@ -211,6 +231,8 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model): mafwd.save() retval.append(mafwd) else: + if commit: + self.save() for target in addresses: mafwd = MailAddressForward(mailaddress=self, target=target) if commit: diff --git a/gnuviechadmin/managemails/tests/test_models.py b/gnuviechadmin/managemails/tests/test_models.py index 4b20b0c..16ac111 100644 --- a/gnuviechadmin/managemails/tests/test_models.py +++ b/gnuviechadmin/managemails/tests/test_models.py @@ -1,4 +1,11 @@ -from django.test import TestCase +""" +This module contains tests for :py:mod:`managemails.models` +""" +from __future__ import unicode_literals + +from mock import patch + +from django.test import TestCase, TransactionTestCase from django.test.utils import override_settings from django.contrib.auth import get_user_model @@ -21,6 +28,7 @@ Customer = get_user_model() BROKER_BACKEND='memory' ) class MailboxTest(TestCase): + def setUp(self): super(MailboxTest, self).setUp() self.customer = Customer.objects.create_user('test') @@ -37,9 +45,281 @@ class MailboxTest(TestCase): mb.set_password('test') self.assertEqual(str(mb), 'test') + @patch('managemails.models.create_file_mailbox') + def test_save(self, create_file_mailbox_task): + user = User.objects.create_user(self.customer) + mb = Mailbox.objects.create_mailbox(user) + self.assertIsNotNone(mb.pk) + create_file_mailbox_task.delay.assert_called_with( + user.username, mb.username) + + @patch('managemails.models.delete_file_mailbox') + def test_delete(self, delete_file_mailbox_task): + user = User.objects.create_user(self.customer) + mb = Mailbox.objects.create_mailbox(user) + mb.delete() + self.assertIsNone(mb.pk) + delete_file_mailbox_task.delay.assert_called_with( + user.username, mb.username) + + def test_get_mailaddresses(self): + user = User.objects.create_user(self.customer) + mb = Mailbox.objects.create_mailbox(user) + md = MailDomain.objects.create(domain='example.org') + address = MailAddress.objects.create(localpart='test', domain=md) + address.set_mailbox(mb) + mailaddresses = mb.get_mailaddresses() + self.assertEqual(len(mailaddresses), 1) + self.assertIn(address, mailaddresses) + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class MailAddressTest(TransactionTestCase): -class MailAddressTest(TestCase): def test__str__(self): md = MailDomain.objects.create(domain='example.org') ma = MailAddress.objects.create(localpart='test', domain=md) self.assertEqual(str(ma), 'test@example.org') + + def test_set_mailbox_fresh(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb) + self.assertIn(ma, mb.get_mailaddresses()) + + def test_set_mailbox_reassing(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb) + mb2 = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb2) + self.assertIn(ma, mb2.get_mailaddresses()) + self.assertNotIn(ma, mb.get_mailaddresses()) + + def test_set_mailbox_with_forwards(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_forward_addresses(['test2@example.org']) + ma.set_mailbox(mb) + self.assertEqual(ma.mailaddressforward_set.count(), 0) + self.assertIn(ma, mb.get_mailaddresses()) + + def test_set_mailbox_with_unsaved_address(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb) + self.assertIn(ma, mb.get_mailaddresses()) + + def test_set_mailbox_fresh_no_commit(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb, commit=False) + self.assertNotIn(ma, mb.get_mailaddresses()) + + def test_set_mailbox_with_unsaved_address_no_commit(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb, commit=False) + self.assertNotIn(ma, mb.get_mailaddresses()) + + def test_set_forward_addresses_fresh(self): + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + ma.set_forward_addresses(['test2@example.org']) + self.assertQuerysetEqual( + ma.mailaddressforward_set.all(), ['test2@example.org'], + lambda(maf): maf.target) + + def test_set_forward_addresses_unsaved(self): + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress(localpart='test', domain=md) + ma.set_forward_addresses(['test2@example.org']) + self.assertQuerysetEqual( + ma.mailaddressforward_set.all(), ['test2@example.org'], + lambda(maf): maf.target) + + def test_set_forward_addresses_replace_forwards(self): + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + ma.set_forward_addresses(['test2@example.org']) + ma.set_forward_addresses(['test3@example.org']) + self.assertQuerysetEqual( + ma.mailaddressforward_set.all(), ['test3@example.org'], + lambda(maf): maf.target) + + def test_set_forward_addresses_add_forwards(self): + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + ma.set_forward_addresses(['test2@example.org']) + ma.set_forward_addresses(['test2@example.org', 'test3@example.org']) + self.assertQuerysetEqual( + ma.mailaddressforward_set.all(), + ['test2@example.org', 'test3@example.org'], + lambda(maf): maf.target, + ordered=False) + + def test_set_forward_addresses_replace_mailbox(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb) + ma.set_forward_addresses(['test2@example.org']) + self.assertNotIn(ma, mb.get_mailaddresses()) + self.assertQuerysetEqual( + ma.mailaddressforward_set.all(), ['test2@example.org'], + lambda(maf): maf.target) + + def test_set_forward_addresses_fresh_no_commit(self): + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress.objects.create(localpart='test', domain=md) + mafwds = ma.set_forward_addresses(['test2@example.org'], commit=False) + self.assertEqual(ma.mailaddressforward_set.count(), 0) + self.assertEqual(mafwds[0].target, 'test2@example.org') + + def test_set_forward_address_unsaved_no_commit(self): + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress(localpart='test', domain=md) + mafwds = ma.set_forward_addresses(['test2@example.org'], commit=False) + self.assertEqual(ma.mailaddressforward_set.count(), 0) + self.assertEqual(mafwds[0].target, 'test2@example.org') + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class MailboxManagerTest(TransactionTestCase): + + def setUp(self): + super(MailboxManagerTest, self).setUp() + self.customer = Customer.objects.create_user('test') + self.user = User.objects.create_user(self.customer) + + def test_get_next_mailbox_name_fresh(self): + mailboxname = Mailbox.objects.get_next_mailbox_name(self.user) + self.assertEqual(mailboxname, '{}p01'.format(self.user.username)) + + def test_get_next_mailbox_name_second(self): + Mailbox.objects.create_mailbox(self.user) + mailboxname = Mailbox.objects.get_next_mailbox_name(self.user) + self.assertEqual(mailboxname, '{}p02'.format(self.user.username)) + + def test_get_next_mailbox_name_gap_detection(self): + mailboxes = [ + Mailbox.objects.create_mailbox(self.user) for i in range(3) + ] + mailboxes[1].delete() + mailboxname = Mailbox.objects.get_next_mailbox_name(self.user) + self.assertEqual(mailboxname, '{}p02'.format(self.user.username)) + + def test_unused_or_own_fresh(self): + md = MailDomain.objects.create(domain='example.org') + address = MailAddress.objects.create(localpart='test', domain=md) + mailboxes = Mailbox.objects.unused_or_own(address, self.user) + self.assertQuerysetEqual(mailboxes, []) + + def test_unused_or_own_unassigned(self): + md = MailDomain.objects.create(domain='example.org') + address = MailAddress.objects.create(localpart='test', domain=md) + mailboxes = [ + Mailbox.objects.create_mailbox(self.user) for i in range(2) + ] + assignable = Mailbox.objects.unused_or_own(address, self.user) + self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes]) + + def test_unused_or_own_assigned(self): + md = MailDomain.objects.create(domain='example.org') + address = MailAddress.objects.create(localpart='test', domain=md) + mailboxes = [ + Mailbox.objects.create_mailbox(self.user) for i in range(2) + ] + address.set_mailbox(mailboxes[0]) + assignable = Mailbox.objects.unused_or_own(address, self.user) + self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes]) + + def test_unused_or_own_assigned_other(self): + md = MailDomain.objects.create(domain='example.org') + address = MailAddress.objects.create(localpart='test', domain=md) + address2 = MailAddress.objects.create(localpart='test2', domain=md) + mailboxes = [ + Mailbox.objects.create_mailbox(self.user) for i in range(2) + ] + address2.set_mailbox(mailboxes[0]) + assignable = Mailbox.objects.unused_or_own(address, self.user) + self.assertQuerysetEqual(assignable, [repr(mailboxes[1])]) + + def test_unused_fresh(self): + mailboxes = Mailbox.objects.unused(self.user) + self.assertQuerysetEqual(mailboxes, []) + + def test_unused_unassigned(self): + mailbox = Mailbox.objects.create_mailbox(self.user) + mailboxes = Mailbox.objects.unused(self.user) + self.assertQuerysetEqual(mailboxes, [repr(mailbox)]) + + def test_unused_assigned(self): + md = MailDomain.objects.create(domain='example.org') + address = MailAddress.objects.create(localpart='test', domain=md) + mailboxes = [ + Mailbox.objects.create_mailbox(self.user) for i in range(2) + ] + address.set_mailbox(mailboxes[0]) + assignable = Mailbox.objects.unused(self.user) + self.assertQuerysetEqual(assignable, [repr(mailboxes[1])]) + + def test_create_mailbox_no_password(self): + mailbox = Mailbox.objects.create_mailbox(self.user) + self.assertEqual(mailbox.osuser, self.user) + self.assertEqual(mailbox.username, '{}p01'.format(self.user.username)) + self.assertEqual(mailbox.password, '') + + def test_create_mailbox_with_password(self): + mailbox = Mailbox.objects.create_mailbox(self.user, 'test') + self.assertEqual(mailbox.osuser, self.user) + self.assertEqual(mailbox.username, '{}p01'.format(self.user.username)) + self.assertTrue(sha512_crypt.verify('test', mailbox.password)) + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class MailAddressMailboxTest(TestCase): + + def setUp(self): + super(MailAddressMailboxTest, self).setUp() + self.customer = Customer.objects.create_user('test') + + def test___str__(self): + user = User.objects.create_user(self.customer) + md = MailDomain.objects.create(domain='example.org') + ma = MailAddress(localpart='test', domain=md) + mb = Mailbox.objects.create_mailbox(user) + ma.set_mailbox(mb) + self.assertEqual(str(ma.mailaddressmailbox), mb.username) From f2c3f64a870178e863b2e624fc2a1dccc4382fa2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 28 Nov 2015 19:00:03 +0000 Subject: [PATCH 038/111] Use MD5PasswordHasher for tests This commit changes the password hasher for test runs to the MD5PasswordHasher to speed up password hashing during test runs. --- gnuviechadmin/gnuviechadmin/settings/test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gnuviechadmin/gnuviechadmin/settings/test.py b/gnuviechadmin/gnuviechadmin/settings/test.py index e133281..cc4cdc2 100644 --- a/gnuviechadmin/gnuviechadmin/settings/test.py +++ b/gnuviechadmin/gnuviechadmin/settings/test.py @@ -1,3 +1,7 @@ from __future__ import absolute_import from .base import * + +PASSWORD_HASHERS = ( + 'django.contrib.auth.hashers.MD5PasswordHasher', +) From 653320547984af0271ba72a5f65af8741ef8a41f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 28 Nov 2015 19:01:10 +0000 Subject: [PATCH 039/111] Add TODO comments for mailbox task calls --- gnuviechadmin/managemails/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnuviechadmin/managemails/models.py b/gnuviechadmin/managemails/models.py index e24f65b..760551c 100644 --- a/gnuviechadmin/managemails/models.py +++ b/gnuviechadmin/managemails/models.py @@ -130,10 +130,12 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel): self.password = sha512_crypt.encrypt(password) def save(self, *args, **kwargs): + # TODO: refactor to use signals create_file_mailbox.delay(self.osuser.username, self.username).get() super(Mailbox, self).save(*args, **kwargs) def delete(self, *args, **kwargs): + # TODO: refactor to use signals delete_file_mailbox.delay(self.osuser.username, self.username).get() super(Mailbox, self).delete(*args, **kwargs) From 30600ce10733105d4363cf53fe34e80ff20bbc03 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 28 Nov 2015 19:01:52 +0000 Subject: [PATCH 040/111] Add tests for managemails.views This commit adds a test suite for the views defined in managemails.views. One issue discovered when writing tests is fixed by checking the request method in CreateMailbox.dispatch. --- gnuviechadmin/managemails/tests/test_views.py | 661 ++++++++++++++++++ gnuviechadmin/managemails/views.py | 9 +- 2 files changed, 666 insertions(+), 4 deletions(-) diff --git a/gnuviechadmin/managemails/tests/test_views.py b/gnuviechadmin/managemails/tests/test_views.py index e69de29..dc0f6dd 100644 --- a/gnuviechadmin/managemails/tests/test_views.py +++ b/gnuviechadmin/managemails/tests/test_views.py @@ -0,0 +1,661 @@ +""" +Tests for :py:mod:`managemails.views`. + +""" +from __future__ import absolute_import, unicode_literals + +try: + from unittest.mock import patch, MagicMock +except ImportError: + from mock import patch, MagicMock + +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.test.utils import override_settings +from django.contrib.auth import get_user_model + +from domains.models import MailDomain +from hostingpackages.models import ( + CustomerHostingPackage, + HostingPackageTemplate, +) + +from managemails.forms import MAILBOX_OR_FORWARDS +from managemails.models import MailAddress, Mailbox +from managemails.views import ( + AddMailAddress, + ChangeMailboxPassword, + CreateMailbox, + EditMailAddress, +) + + +User = get_user_model() + +TEST_USER = 'test' +TEST_PASSWORD = 'secret' +TEST_EMAIL = 'test@example.org' +TEST_NAME = 'Example Tester'.split() + + +class HostingPackageAwareTestMixin(object): + + def _setup_hosting_package(self, customer, mailboxcount): + template = HostingPackageTemplate.objects.create( + name='testpackagetemplate', mailboxcount=mailboxcount, + diskspace=1, diskspace_unit=0) + package = CustomerHostingPackage.objects.create_from_template( + customer, template, 'testpackage') + with patch('hostingpackages.models.settings') as hmsettings: + hmsettings.OSUSER_DEFAULT_GROUPS = [] + package.save() + return package + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class CreateMailboxTest(HostingPackageAwareTestMixin, TestCase): + + def test_get_anonymous(self): + response = self.client.get( + reverse('create_mailbox', kwargs={'package': 1})) + self.assertEqual(response.status_code, 404) + + def test_get_regular_user(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('create_mailbox', kwargs={'package': package.pk})) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + User.objects.create_user( + 'test2', email='test2@example.org', password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get( + reverse('create_mailbox', kwargs={'package': package.pk})) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + customer = User.objects.create_user('customer') + package = self._setup_hosting_package(customer, 1) + User.objects.create_superuser( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('create_mailbox', kwargs={'package': package.pk})) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('create_mailbox', kwargs={'package': package.pk})) + self.assertTemplateUsed(response, 'managemails/mailbox_create.html') + + def test_get_no_more_mailboxes(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 0) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('create_mailbox', kwargs={'package': package.pk})) + self.assertEqual(response.status_code, 403) + self.assertEqual( + response.content, + 'You are not allowed to add more mailboxes to this hosting' + ' package') + + def test_get_context_data(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('create_mailbox', kwargs={'package': package.pk})) + self.assertIn('hostingpackage', response.context) + self.assertEqual(response.context['hostingpackage'], package) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], customer) + + def test_get_form_kwargs(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = CreateMailbox( + request=MagicMock(), kwargs={'package': str(package.pk)}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], package) + + def test_form_valid_redirect(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('create_mailbox', kwargs={'package': package.pk}), + data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + self.assertRedirects(response, package.get_absolute_url()) + + def test_form_valid_message(self): + customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + package = self._setup_hosting_package(customer, 1) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('create_mailbox', kwargs={'package': package.pk}), + follow=True, + data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + 'Mailbox usr01p01 created successfully.', str(messages[0])) + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class ChangeMailboxPasswordTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer, 1) + self.mailbox = Mailbox.objects.create_mailbox( + self.package.osuser, TEST_PASSWORD) + + def test_get_anonymous(self): + response = self.client.get( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username})) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username})) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', email='test2@example.org', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username})) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email='admin@example.org', password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username})) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username})) + self.assertTemplateUsed( + response, 'managemails/mailbox_setpassword.html') + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username})) + self.assertIn('hostingpackage', response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + + def test_get_form_kwargs(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = ChangeMailboxPassword(request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'slug': self.mailbox.username}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username}), + data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('change_mailbox_password', kwargs={ + 'package': self.package.pk, + 'slug': self.mailbox.username}), + follow=True, + data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), + 'Successfully set new password for mailbox usr01p01.') + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class AddMailAddressTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer, 1) + self.maildomain = MailDomain.objects.create( + domain='example.org', customer=self.customer) + + def test_get_anonymous(self): + response = self.client.get( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + })) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + })) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', email='test2@example.org', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + })) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email='admin@example.org', password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + })) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + })) + self.assertTemplateUsed( + response, 'managemails/mailaddress_create.html') + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + })) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + + def test_get_form_kwargs(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = AddMailAddress(request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'domain': self.maildomain.domain}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn('maildomain', the_kwargs) + self.assertEqual(the_kwargs['maildomain'], self.maildomain) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + }), + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'mailbox': '', + 'forwards': 'test2@example.org', + }) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('add_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain + }), + follow=True, + data={ + 'localpart': 'test', + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'mailbox': '', + 'forwards': 'test2@example.org', + }) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), + 'Successfully added mail address test@example.org') + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class DeleteMailAddressTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer, 1) + self.maildomain = MailDomain.objects.create( + domain='example.org', customer=self.customer) + self.mailaddress = MailAddress.objects.create( + localpart='test', domain=self.maildomain) + self.mailaddress.set_forward_addresses(['test2@example.org']) + + def test_get_anonymous(self): + response = self.client.get( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', email='test2@example.org', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email='admin@example.org', password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertTemplateUsed( + response, 'managemails/mailaddress_confirm_delete.html') + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + self.assertIn('hostingpackage', response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertIn('maildomain', response.context) + self.assertEqual(response.context['maildomain'], self.maildomain) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('delete_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk}), + data={}) + self.assertRedirects(response, self.package.get_absolute_url()) + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class EditMailAddressTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer, 1) + self.maildomain = MailDomain.objects.create( + domain='example.org', customer=self.customer) + + def _set_mailaddress_with_forward(self): + self.mailaddress = MailAddress.objects.create( + localpart='test', domain=self.maildomain) + self.mailaddress.set_forward_addresses(['test2@example.org']) + + def _set_mailaddress_with_mailbox(self): + self.mailaddress = MailAddress.objects.create( + localpart='test', domain=self.maildomain) + self.mailbox = Mailbox.objects.create_mailbox(self.package.osuser) + self.mailaddress.set_mailbox(self.mailbox) + + def test_get_anonymous(self): + self._set_mailaddress_with_forward() + response = self.client.get( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + self._set_mailaddress_with_forward() + User.objects.create_user( + 'test2', email='test2@example.org', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + self._set_mailaddress_with_forward() + User.objects.create_superuser( + 'admin', email='admin@example.org', password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertTemplateUsed(response, 'managemails/mailaddress_edit.html') + + def test_get_context_data(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk})) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + + def test_get_form_kwargs(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = EditMailAddress( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'domain': self.maildomain.domain, + 'pk': str(self.mailaddress.pk)}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn('maildomain', the_kwargs) + self.assertEqual(the_kwargs['maildomain'], self.maildomain) + + def test_get_initial_with_forwards(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = EditMailAddress( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'domain': self.maildomain.domain, + 'pk': str(self.mailaddress.pk)}) + initial = view.get_initial() + self.assertIn('mailbox_or_forwards', initial) + self.assertEqual( + initial['mailbox_or_forwards'], MAILBOX_OR_FORWARDS.forwards) + self.assertIn('forwards', initial) + self.assertEqual(initial['forwards'], 'test2@example.org') + self.assertNotIn('mailbox', initial) + + def test_get_initial_with_mailbox(self): + self._set_mailaddress_with_mailbox() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = EditMailAddress( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'domain': self.maildomain.domain, + 'pk': str(self.mailaddress.pk)}) + initial = view.get_initial() + self.assertIn('mailbox_or_forwards', initial) + self.assertEqual( + initial['mailbox_or_forwards'], MAILBOX_OR_FORWARDS.mailbox) + self.assertIn('mailbox', initial) + self.assertEqual(initial['mailbox'], self.mailbox) + self.assertNotIn('forwards', initial) + + def test_get_initial_with_unassigned_address(self): + self.mailaddress = MailAddress.objects.create( + localpart='test', domain=self.maildomain) + view = EditMailAddress( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'domain': self.maildomain.domain, + 'pk': str(self.mailaddress.pk)}) + initial = view.get_initial() + self.assertNotIn('mailbox_or_forwards', initial) + self.assertNotIn('forwards', initial) + self.assertNotIn('mailbox', initial) + + def test_form_valid_redirect(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk}), + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'mailbox': '', + 'forwards': 'test2@example.org,test3@example.org' + }) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self._set_mailaddress_with_forward() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + reverse('edit_mailaddress', kwargs={ + 'package': self.package.pk, + 'domain': self.maildomain.domain, + 'pk': self.mailaddress.pk}), + follow=True, + data={ + 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, + 'mailbox': '', + 'forwards': 'test2@example.org,test3@example.org' + }) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), + 'Successfully updated mail address test@example.org targets.') diff --git a/gnuviechadmin/managemails/views.py b/gnuviechadmin/managemails/views.py index e5590b1..80a9337 100644 --- a/gnuviechadmin/managemails/views.py +++ b/gnuviechadmin/managemails/views.py @@ -46,10 +46,11 @@ class CreateMailbox( def dispatch(self, request, *args, **kwargs): resp = super(CreateMailbox, self).dispatch(request, *args, **kwargs) - if not self.get_hosting_package().may_add_mailbox(): - resp = HttpResponseForbidden( - _('You are not allowed to add more mailboxes to this' - ' hosting package')) + if request.method != 'POST': + if not self.get_hosting_package().may_add_mailbox(): + resp = HttpResponseForbidden( + _('You are not allowed to add more mailboxes to this' + ' hosting package')) return resp def get_context_data(self, **kwargs): From fb1f31a9bcea4c980c5966afb989ca1bd543ed8e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 5 Dec 2015 13:47:41 +0000 Subject: [PATCH 041/111] Fix flake8 warnings This commit fixes all warnings created by flake8 by removing unused imports and variable assignments. Some wrongly indented lines are now indented correctly. --- .../contact_form/tests/test_forms.py | 3 +- .../contact_form/tests/test_views.py | 32 +++++++++---------- gnuviechadmin/domains/tests/test_models.py | 2 +- gnuviechadmin/domains/tests/test_views.py | 16 +++++----- gnuviechadmin/gnuviechadmin/__init__.py | 3 +- gnuviechadmin/gnuviechadmin/settings/local.py | 3 +- .../gnuviechadmin/settings/production.py | 3 +- gnuviechadmin/gnuviechadmin/settings/test.py | 3 +- .../tests/test_contextprocessors.py | 2 +- gnuviechadmin/osusers/apps.py | 7 +++- gnuviechadmin/osusers/models.py | 4 --- gnuviechadmin/osusers/signals.py | 21 ++++++------ 12 files changed, 50 insertions(+), 49 deletions(-) diff --git a/gnuviechadmin/contact_form/tests/test_forms.py b/gnuviechadmin/contact_form/tests/test_forms.py index bade6b1..61eb115 100644 --- a/gnuviechadmin/contact_form/tests/test_forms.py +++ b/gnuviechadmin/contact_form/tests/test_forms.py @@ -7,7 +7,6 @@ from __future__ import absolute_import, unicode_literals import mock from mock import MagicMock, Mock -from django.core import mail from django.core.urlresolvers import reverse from django.test import TestCase from django.contrib.sites.models import Site @@ -25,7 +24,7 @@ class ContactFormTest(TestCase): def test_constructor_needs_request(self): with self.assertRaises(KeyError): - form = ContactForm() + ContactForm() def test_constructor(self): request = MagicMock() diff --git a/gnuviechadmin/contact_form/tests/test_views.py b/gnuviechadmin/contact_form/tests/test_views.py index 66e84f3..41d98b7 100644 --- a/gnuviechadmin/contact_form/tests/test_views.py +++ b/gnuviechadmin/contact_form/tests/test_views.py @@ -26,8 +26,8 @@ class ContactFormViewTest(TestCase): def _setup_user(self, **kwargs): return User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD, - **kwargs) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD, + **kwargs) def test_get_contact_form_template(self): response = self.client.get(reverse('contact_form')) @@ -64,28 +64,28 @@ class ContactFormViewTest(TestCase): self.assertEqual(len(form.errors), 3) def test_post_empty_form_no_mail(self): - response = self.client.post(reverse('contact_form'), {}) + self.client.post(reverse('contact_form'), {}) self.assertEqual(len(mail.outbox), 0) def test_get_contact_form_logged_in_no_fullname_initial(self): - user = self._setup_user() + self._setup_user() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(reverse('contact_form')) self.assertIn('form', response.context) form = response.context['form'] self.assertEqual( - form.initial, {'name': TEST_USER, 'email': TEST_EMAIL}) + form.initial, {'name': TEST_USER, 'email': TEST_EMAIL}) def test_get_contact_form_logged_in_fullname_initial(self): - user = self._setup_user( - first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self._setup_user( + first_name=TEST_NAME[0], last_name=TEST_NAME[1]) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(reverse('contact_form')) self.assertIn('form', response.context) form = response.context['form'] self.assertEqual( - form.initial, - {'name': " ".join(TEST_NAME), 'email': TEST_EMAIL}) + form.initial, + {'name': " ".join(TEST_NAME), 'email': TEST_EMAIL}) def test_post_filled_form_anonymous_redirects(self): response = self.client.post(reverse('contact_form'), { @@ -93,24 +93,24 @@ class ContactFormViewTest(TestCase): self.assertRedirects(response, reverse('contact_success')) def test_post_filled_form_anonymous_mail(self): - response = self.client.post(reverse('contact_form'), { + self.client.post(reverse('contact_form'), { 'name': TEST_USER, 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) self.assertEqual(len(mail.outbox), 1) def test_post_filled_form_logged_in_redirects(self): - user = self._setup_user( - first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self._setup_user( + first_name=TEST_NAME[0], last_name=TEST_NAME[1]) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post(reverse('contact_form'), { 'name': " ".join(TEST_NAME), 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) self.assertRedirects(response, reverse('contact_success')) - def test_post_filled_form_logged_in_redirects(self): - user = self._setup_user( - first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + def test_post_filled_form_logged_in_mail(self): + self._setup_user( + first_name=TEST_NAME[0], last_name=TEST_NAME[1]) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post(reverse('contact_form'), { + self.client.post(reverse('contact_form'), { 'name': " ".join(TEST_NAME), 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) self.assertEqual(len(mail.outbox), 1) diff --git a/gnuviechadmin/domains/tests/test_models.py b/gnuviechadmin/domains/tests/test_models.py index d116d5d..d942b78 100644 --- a/gnuviechadmin/domains/tests/test_models.py +++ b/gnuviechadmin/domains/tests/test_models.py @@ -4,7 +4,7 @@ Tests for :py:mod:`domains.models`. """ from __future__ import absolute_import, unicode_literals -from mock import Mock, MagicMock, patch +from mock import patch from django.test import TestCase from django.contrib.auth import get_user_model diff --git a/gnuviechadmin/domains/tests/test_views.py b/gnuviechadmin/domains/tests/test_views.py index 8577b39..567f227 100644 --- a/gnuviechadmin/domains/tests/test_views.py +++ b/gnuviechadmin/domains/tests/test_views.py @@ -58,7 +58,7 @@ class CreateHostingDomainTest(TestCase): def test_get_staff_user(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( @@ -69,7 +69,7 @@ class CreateHostingDomainTest(TestCase): def test_get_template(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( @@ -78,7 +78,7 @@ class CreateHostingDomainTest(TestCase): self.assertTemplateUsed(response, 'domains/hostingdomain_create.html') def test_get_no_package_found(self): - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( @@ -89,7 +89,7 @@ class CreateHostingDomainTest(TestCase): def test_get_get_form_kwargs(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = CreateHostingDomain( @@ -101,7 +101,7 @@ class CreateHostingDomainTest(TestCase): def test_get_context_data_has_hosting_package(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( @@ -113,7 +113,7 @@ class CreateHostingDomainTest(TestCase): def test_get_context_data_has_customer(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( @@ -125,7 +125,7 @@ class CreateHostingDomainTest(TestCase): def test_form_valid_redirect(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( @@ -137,7 +137,7 @@ class CreateHostingDomainTest(TestCase): def test_form_valid_message(self): customer = User.objects.create_user('customer') package = self._setup_hosting_package(customer) - admin = User.objects.create_superuser( + User.objects.create_superuser( TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py index 31b3a0d..60f195b 100644 --- a/gnuviechadmin/gnuviechadmin/__init__.py +++ b/gnuviechadmin/gnuviechadmin/__init__.py @@ -1,3 +1,4 @@ -from gnuviechadmin.celery import app as celery_app +# import celery_app to initialize it +from gnuviechadmin.celery import app as celery_app # NOQA __version__ = '0.12.dev1' diff --git a/gnuviechadmin/gnuviechadmin/settings/local.py b/gnuviechadmin/gnuviechadmin/settings/local.py index 9eba627..b584a58 100644 --- a/gnuviechadmin/gnuviechadmin/settings/local.py +++ b/gnuviechadmin/gnuviechadmin/settings/local.py @@ -4,7 +4,8 @@ from __future__ import absolute_import -from .base import * +# use import * to import all settings from base +from .base import * # NOQA # ######### DEBUG CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/production.py b/gnuviechadmin/gnuviechadmin/settings/production.py index a1ed73f..6be4255 100644 --- a/gnuviechadmin/gnuviechadmin/settings/production.py +++ b/gnuviechadmin/gnuviechadmin/settings/production.py @@ -4,7 +4,8 @@ from __future__ import absolute_import -from .base import * +# use import * to import all settings from base +from .base import * # NOQA # ######### HOST CONFIGURATION # See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production # noqa diff --git a/gnuviechadmin/gnuviechadmin/settings/test.py b/gnuviechadmin/gnuviechadmin/settings/test.py index cc4cdc2..6d0a685 100644 --- a/gnuviechadmin/gnuviechadmin/settings/test.py +++ b/gnuviechadmin/gnuviechadmin/settings/test.py @@ -1,6 +1,7 @@ from __future__ import absolute_import -from .base import * +# use import * to import all settings from base +from .base import * # NOQA PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', diff --git a/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py b/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py index 51ba516..9379ad3 100644 --- a/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py +++ b/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py @@ -56,7 +56,7 @@ class NavigationContextProcessorTest(TestCase): self.assertEqual(response.context['active_item'], 'contact') def test_hostingpackage_page_context(self): - user = User.objects.create_user('test', password='test') + User.objects.create_user('test', password='test') self.client.login(username='test', password='test') response = self.client.get( reverse('hosting_packages', kwargs={'user': 'test'})) diff --git a/gnuviechadmin/osusers/apps.py b/gnuviechadmin/osusers/apps.py index 0a9c739..79276a1 100644 --- a/gnuviechadmin/osusers/apps.py +++ b/gnuviechadmin/osusers/apps.py @@ -18,4 +18,9 @@ class OsusersAppConfig(AppConfig): verbose_name = _('Operating System Users and Groups') def ready(self): - import osusers.signals + """ + Takes care of importing the signal handlers of the :py:mod:`osusers` + app. + + """ + import osusers.signals # NOQA diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 6b4f2a2..5d5b586 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -10,8 +10,6 @@ import logging import os import six -from celery import group - from django.db import models, transaction from django.conf import settings from django.core.exceptions import ValidationError @@ -25,8 +23,6 @@ from model_utils.models import TimeStampedModel from passlib.hash import sha512_crypt from passlib.utils import generate_password -from taskresults.models import TaskResult - _LOGGER = logging.getLogger(__name__) diff --git a/gnuviechadmin/osusers/signals.py b/gnuviechadmin/osusers/signals.py index bb76ee5..a74442b 100644 --- a/gnuviechadmin/osusers/signals.py +++ b/gnuviechadmin/osusers/signals.py @@ -7,14 +7,11 @@ from __future__ import absolute_import, unicode_literals import logging from django.db.models.signals import ( - m2m_changed, post_delete, post_save, ) from django.dispatch import receiver -from celery import chain, group - from fileservertasks.tasks import ( delete_file_mail_userdir, delete_file_sftp_userdir, @@ -80,11 +77,11 @@ def handle_group_created(sender, instance, created, **kwargs): def handle_user_created(sender, instance, created, **kwargs): if created: chain = create_ldap_user.s( - instance.username, instance.uid, instance.group.gid, - instance.gecos, instance.homedir, instance.shell, None - ) | setup_file_sftp_userdir.s( - instance.username - ) | setup_file_mail_userdir.s(instance.username) + instance.username, instance.uid, instance.group.gid, + instance.gecos, instance.homedir, instance.shell, None + ) | setup_file_sftp_userdir.s( + instance.username + ) | setup_file_mail_userdir.s(instance.username) taskresult = TaskResult.objects.create_task_result( 'handle_user_created', chain) _LOGGER.info( @@ -140,10 +137,10 @@ def handle_group_deleted(sender, instance, **kwargs): @receiver(post_delete, sender=User) def handle_user_deleted(sender, instance, **kwargs): chain = delete_file_mail_userdir.s( - instance.username - ) | delete_file_sftp_userdir.s( - instance.username - ) | delete_ldap_user.s(instance.username) + instance.username + ) | delete_file_sftp_userdir.s( + instance.username + ) | delete_ldap_user.s(instance.username) _LOGGER.debug('chain signature %s', chain) taskresult = TaskResult.objects.create_task_result( 'handle_user_deleted', chain) From 4f39c0d2c4acfee676bde07e1232e39f0a5a5516 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 5 Dec 2015 22:23:25 +0000 Subject: [PATCH 042/111] Add tests for osusers.admin This commit raises the test coverage for osusers.admin to 100% by adding tests for UserAdmin, GroupAdmin, SshPublicKeyCreationForm and SshPublicKeyAdmin. The commit adds a refactoring TODO to SshPublicKeyAdmin.perform_delete_selected because the asynchronous background task should be launched from a signal handler. --- gnuviechadmin/osusers/admin.py | 14 +- gnuviechadmin/osusers/tests/test_admin.py | 163 +++++++++++++++++++++- 2 files changed, 170 insertions(+), 7 deletions(-) diff --git a/gnuviechadmin/osusers/admin.py b/gnuviechadmin/osusers/admin.py index e622fab..addb70b 100644 --- a/gnuviechadmin/osusers/admin.py +++ b/gnuviechadmin/osusers/admin.py @@ -178,7 +178,7 @@ class UserAdmin(admin.ModelAdmin): """ actions = super(UserAdmin, self).get_actions(request) - if 'delete_selected' in actions: + if 'delete_selected' in actions: # pragma: no cover del actions['delete_selected'] return actions @@ -219,7 +219,7 @@ class GroupAdmin(admin.ModelAdmin): """ actions = super(GroupAdmin, self).get_actions(request) - if 'delete_selected' in actions: + if 'delete_selected' in actions: # pragma: no cover del actions['delete_selected'] return actions @@ -257,6 +257,7 @@ class SshPublicKeyCreationForm(forms.ModelForm): self.add_error( 'publickeytext', forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER)) + super(SshPublicKeyCreationForm, self).clean() def save(self, commit=True): """ @@ -347,13 +348,14 @@ class SshPublicKeyAdmin(admin.ModelAdmin): ]) queryset.delete() for user in users: + # TODO: move to model/signal TaskResult.objects.create_task_result( - set_file_ssh_authorized_keys.delay( + 'perform_delete_selected', + set_file_ssh_authorized_keys.s( User.objects.get(uid=user).username, [str(key) for key in SshPublicKey.objects.filter( user_id=user)] - ), - 'set_file_ssh_authorized_keys' + ) ) perform_delete_selected.short_description = _( 'Delete selected SSH public keys') @@ -371,7 +373,7 @@ class SshPublicKeyAdmin(admin.ModelAdmin): """ actions = super(SshPublicKeyAdmin, self).get_actions(request) - if 'delete_selected' in actions: + if 'delete_selected' in actions: # pragma: no cover del actions['delete_selected'] return actions diff --git a/gnuviechadmin/osusers/tests/test_admin.py b/gnuviechadmin/osusers/tests/test_admin.py index 405116f..3ab2be9 100644 --- a/gnuviechadmin/osusers/tests/test_admin.py +++ b/gnuviechadmin/osusers/tests/test_admin.py @@ -5,15 +5,22 @@ from django.test.utils import override_settings from django.contrib.auth import get_user_model -from mock import Mock +from mock import MagicMock, Mock, patch +from osusers.forms import ( + INVALID_SSH_PUBLIC_KEY, + DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, +) from osusers.models import ( Group, + SshPublicKey, User, ) from osusers.admin import ( GroupAdmin, PASSWORD_MISMATCH_ERROR, + SshPublicKeyAdmin, + SshPublicKeyCreationForm, UserAdmin, UserCreationForm, ) @@ -119,8 +126,29 @@ class UserAdminTest(CustomerTestCase): for index in range(len(inlines)): self.assertIsInstance(inlines[index], UserAdmin.inlines[index]) + @override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' + ) + def test_perform_delete_selected(self): + user = User.objects.create_user(customer=self.customer) + self.uadmin.perform_delete_selected( + Mock(name='request'), User.objects.filter(uid=user.uid)) + self.assertEqual(User.objects.filter(uid=user.uid).count(), 0) + + def test_get_actions(self): + requestmock = MagicMock(name='request') + self.assertNotIn( + 'delete_selected', + self.uadmin.get_actions(requestmock)) + self.assertIn( + 'perform_delete_selected', + self.uadmin.get_actions(requestmock)) + class GroupAdminTest(TestCase): + def setUp(self): site = AdminSite() self.gadmin = GroupAdmin(Group, site) @@ -142,3 +170,136 @@ class GroupAdminTest(TestCase): self.assertEqual(len(inlines), len(GroupAdmin.inlines)) for index in range(len(inlines)): self.assertIsInstance(inlines[index], GroupAdmin.inlines[index]) + + def test_perform_delete_selected(self): + group = Group.objects.create(gid=1000, groupname='test') + self.gadmin.perform_delete_selected( + Mock(name='request'), Group.objects.filter(gid=group.gid)) + self.assertEqual(Group.objects.filter(gid=group.gid).count(), 0) + + def test_get_actions(self): + requestmock = MagicMock(name='request') + self.assertNotIn( + 'delete_selected', + self.gadmin.get_actions(requestmock)) + self.assertIn( + 'perform_delete_selected', + self.gadmin.get_actions(requestmock)) + + +class SshPublicKeyCreationFormTest(CustomerTestCase): + + @patch('osusers.admin.SshPublicKey.objects') + def test_clean_publickeytext_valid_key(self, sshpkmanager): + form = SshPublicKeyCreationForm() + sshpkmanager.parse_keytext = MagicMock(side_effect=ValueError) + form.cleaned_data = {'publickeytext': 'wrongkey'} + with self.assertRaises(forms.ValidationError) as ve: + form.clean_publickeytext() + self.assertEqual(ve.exception.message, INVALID_SSH_PUBLIC_KEY) + + @patch('osusers.admin.SshPublicKey.objects') + def test_clean_publickeytext_invalid_key(self, sshpkmanager): + form = SshPublicKeyCreationForm() + sshpkmanager.parse_keytext = MagicMock(return_value='goodkey') + form.cleaned_data = {'publickeytext': 'goodkey'} + self.assertEqual(form.clean_publickeytext(), 'goodkey') + + def test_clean_missing_data(self): + form = SshPublicKeyCreationForm() + form.cleaned_data = {} + form.clean() + self.assertEqual(len(form.errors), 0) + + @patch('osusers.admin.SshPublicKey.objects.parse_keytext') + def test_clean_once(self, parse_keytext): + parse_keytext.return_value = ('good', 'key', 'comment') + user = User.objects.create_user(customer=self.customer) + form = SshPublicKeyCreationForm() + form.cleaned_data = { + 'user': user, + 'publickeytext': 'good key comment' + } + form.clean() + self.assertEqual(len(form.errors), 0) + + @patch('osusers.admin.SshPublicKey.objects.parse_keytext') + def test_clean_again(self, parse_keytext): + parse_keytext.return_value = ('good', 'key', 'comment') + user = User.objects.create_user(customer=self.customer) + SshPublicKey.objects.create( + user=user, algorithm='good', data='key', comment='comment') + form = SshPublicKeyCreationForm() + form.cleaned_data = { + 'user': user, + 'publickeytext': 'good key comment' + } + form.clean() + self.assertIn('publickeytext', form.errors) + self.assertEqual( + form.errors['publickeytext'], + [DUPLICATE_SSH_PUBLIC_KEY_FOR_USER]) + + @patch('osusers.admin.SshPublicKey.objects.parse_keytext') + def test_save(self, parse_keytext): + parse_keytext.return_value = ('good', 'key', 'comment') + user = User.objects.create_user(customer=self.customer) + form = SshPublicKeyCreationForm() + form.cleaned_data = { + 'user': user, + 'publickeytext': 'good key comment' + } + form.instance.user = user + form.save() + self.assertTrue( + SshPublicKey.objects.filter( + user=user, algorithm='good', data='key')) + + +class SshPublicKeyAdminTest(CustomerTestCase): + + def setUp(self): + site = AdminSite() + self.sadmin = SshPublicKeyAdmin(SshPublicKey, site) + super(SshPublicKeyAdminTest, self).setUp() + + def test_get_form_no_instance(self): + form = self.sadmin.get_form(request=Mock(name='request')) + self.assertEqual(form.Meta.model, SshPublicKey) + + def test_get_form_with_instance(self): + user = User.objects.create_user(customer=self.customer) + key = SshPublicKey.objects.create( + user=user, algorithm='good', data='key', comment='comment') + form = self.sadmin.get_form(request=Mock(name='request'), obj=key) + self.assertEqual(form.Meta.model, SshPublicKey) + self.assertEqual( + form.Meta.fields, + ['user', 'comment', 'algorithm', 'data']) + + def test_get_readonly_fields_no_instance(self): + readonly_fields = self.sadmin.get_readonly_fields( + request=Mock(name='request')) + self.assertEqual(readonly_fields, []) + + def test_get_readonly_fields_with_instance(self): + readonly_fields = self.sadmin.get_readonly_fields( + request=Mock(name='request'), obj=Mock()) + self.assertEqual(readonly_fields, ['algorithm', 'data']) + + def test_perform_delete_selected(self): + user = User.objects.create_user(customer=self.customer) + key = SshPublicKey.objects.create( + user=user, algorithm='good', data='key', comment='comment') + self.sadmin.perform_delete_selected( + Mock(name='request'), SshPublicKey.objects.filter(id=key.id)) + self.assertFalse(SshPublicKey.objects.filter(id=key.id).exists()) + + def test_get_actions(self): + requestmock = MagicMock(name='request') + self.assertNotIn( + 'delete_selected', + self.sadmin.get_actions(requestmock)) + self.assertIn( + 'perform_delete_selected', + self.sadmin.get_actions(requestmock)) From 8616b2d6c991bd1e91cc076c84ba05fc4bb3257d Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 6 Dec 2015 15:35:23 +0100 Subject: [PATCH 043/111] Add tests for osusers.forms This commit adds test classes for osusers.forms in osusers.tests.test_forms. --- gnuviechadmin/osusers/tests/test_forms.py | 195 ++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 gnuviechadmin/osusers/tests/test_forms.py diff --git a/gnuviechadmin/osusers/tests/test_forms.py b/gnuviechadmin/osusers/tests/test_forms.py new file mode 100644 index 0000000..a5dec33 --- /dev/null +++ b/gnuviechadmin/osusers/tests/test_forms.py @@ -0,0 +1,195 @@ +""" +This module provides tests for :py:mod:`osusers.forms`. + +""" +from __future__ import absolute_import, unicode_literals + +from mock import MagicMock, Mock, patch + +from django import forms +from django.core.urlresolvers import reverse +from django.test import TestCase + +from django.contrib.auth import get_user_model + +from passlib.hash import sha512_crypt + +from osusers.forms import ( + AddSshPublicKeyForm, + ChangeOsUserPasswordForm, + DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, + EditSshPublicKeyCommentForm, + INVALID_SSH_PUBLIC_KEY, +) + +Customer = get_user_model() + +from osusers.models import SshPublicKey, User + + +class AddSshPublicKeyFormTest(TestCase): + """ + Test for :py:class:`osusers.forms.AddSshPublicKeyForm`. + + """ + + def _setup_hostingpackage(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer=customer) + self.hostingpackage = Mock(id=42, osuser=user) + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError) as ke: + AddSshPublicKeyForm(instance) + self.assertEqual(ke.exception.args[0], 'hostingpackage') + + def test_constructor(self): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + self.assertTrue(hasattr(form, 'osuser')) + self.assertEqual(form.osuser, self.hostingpackage.osuser) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'add_ssh_key', kwargs={'package': self.hostingpackage.id})) + self.assertIn('publickeytext', form.fields) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + @patch('osusers.forms.SshPublicKey.objects.parse_keytext') + def test_clean_publickeytext_invalid(self, parse_keytext): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {'publickeytext': 'a bad key'} + parse_keytext.side_effect = ValueError + with self.assertRaises(forms.ValidationError) as ve: + form.clean_publickeytext() + self.assertEqual(ve.exception.message, INVALID_SSH_PUBLIC_KEY) + + @patch('osusers.forms.SshPublicKey.objects.parse_keytext') + def test_clean_publickeytext_valid(self, parse_keytext): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {'publickeytext': 'good key comment'} + retval = form.clean_publickeytext() + self.assertEqual(retval, 'good key comment') + + def test_clean_none(self): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {'publickeytext': None} + form.clean() + self.assertIsNone(form.cleaned_data['publickeytext']) + + @patch('osusers.forms.SshPublicKey.objects.parse_keytext') + def test_clean_fresh(self, parse_keytext): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + sshpubkey = 'good key comment' + form.cleaned_data = {'publickeytext': sshpubkey} + parse_keytext.return_value = sshpubkey.split(' ') + form.clean() + self.assertEqual( + form.cleaned_data['publickeytext'], 'good key comment') + + @patch('osusers.forms.SshPublicKey.objects.parse_keytext') + def test_clean_duplicate(self, parse_keytext): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + SshPublicKey.objects.create( + user=self.hostingpackage.osuser, algorithm='good', data='key', + comment='comment') + sshpubkey = 'good key comment' + form.cleaned_data = {'publickeytext': sshpubkey} + parse_keytext.return_value = sshpubkey.split(' ') + form.clean() + self.assertIn('publickeytext', form.errors) + self.assertIn( + DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, + form.errors['publickeytext']) + + @patch('osusers.admin.SshPublicKey.objects.parse_keytext') + def test_save(self, parse_keytext): + self._setup_hostingpackage() + instance = MagicMock() + form = AddSshPublicKeyForm( + instance, hostingpackage=self.hostingpackage) + sshpubkey = 'good key comment' + form.cleaned_data = {'publickeytext': sshpubkey} + parse_keytext.return_value = sshpubkey.split(' ') + retval = form.save() + self.assertTrue(isinstance(retval, SshPublicKey)) + self.assertEqual(retval.algorithm, 'good') + self.assertEqual(retval.data, 'key') + self.assertEqual(retval.comment, 'comment') + + +class ChangeOsUserPasswordFormTest(TestCase): + """ + Test for :py:class:`osusers.forms.ChangeOsUserPasswordForm`. + + """ + + def _setup_user(self): + customer = Customer.objects.create_user('test') + self.user = User.objects.create_user(customer=customer) + + def test_constructor(self): + self._setup_user() + form = ChangeOsUserPasswordForm(instance=self.user) + self.assertTrue(hasattr(form, 'instance')) + self.assertEqual(form.instance, self.user) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'set_osuser_password', kwargs={'slug': self.user.username})) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + def test_save(self): + self._setup_user() + form = ChangeOsUserPasswordForm(instance=self.user) + form.cleaned_data = {'password1': 'test'} + user = form.save() + self.assertTrue(sha512_crypt.verify('test', user.shadow.passwd)) + + +class EditSshPublicKeyCommentFormTest(TestCase): + """ + Test for :py:class:`osusers.forms.EditSshPublicKeyCommentForm`. + + """ + + def _setup_hostingpackage(self): + customer = Customer.objects.create_user('test') + user = User.objects.create_user(customer=customer) + self.hostingpackage = Mock(id=42, osuser=user) + + def test_constructor_needs_hostingpackage(self): + instance = MagicMock() + with self.assertRaises(KeyError) as ke: + EditSshPublicKeyCommentForm(instance) + self.assertEqual(ke.exception.args[0], 'hostingpackage') + + def test_constructor(self): + self._setup_hostingpackage() + instance = MagicMock(id=1) + form = EditSshPublicKeyCommentForm( + instance=instance, hostingpackage=self.hostingpackage) + self.assertTrue(hasattr(form, 'osuser')) + self.assertEqual(form.osuser, self.hostingpackage.osuser) + self.assertIn('comment', form.fields) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'edit_ssh_key_comment', + kwargs={'package': self.hostingpackage.id, 'pk': instance.id})) + self.assertEqual(form.helper.inputs[0].name, 'submit') From 28ff099df9d3542c7332d05eda7d2764290ed648 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 6 Dec 2015 17:47:27 +0100 Subject: [PATCH 044/111] Improve osusers.models coverage This commit adds tests for more corner cases of SshPublicKeyManager.parse_keytext to raise the test coverage to 100%. The method now handles invalid keys more thoroughly. --- gnuviechadmin/osusers/models.py | 2 + gnuviechadmin/osusers/tests/test_models.py | 45 +++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 5d5b586..68167cb 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -468,6 +468,8 @@ class SshPublicKeyManager(models.Manager): except TypeError: raise ValueError('invalid SSH public key') parts = keybytes.split(b'\x00' * 3) + if len(parts) < 2: + raise ValueError('invalid SSH public key') alglength = six.byte2int(parts[1]) algname = parts[1][1:1+alglength] return algname, data, comment diff --git a/gnuviechadmin/osusers/tests/test_models.py b/gnuviechadmin/osusers/tests/test_models.py index 50ed035..bb8d5c9 100644 --- a/gnuviechadmin/osusers/tests/test_models.py +++ b/gnuviechadmin/osusers/tests/test_models.py @@ -1,3 +1,5 @@ +# -*- coding: utf8 -*- +from __future__ import unicode_literals from datetime import date from django.conf import settings @@ -91,6 +93,19 @@ n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5 sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV ---- END SSH2 PUBLIC KEY ----""" +EXAMPLE_KEY_7_NO_COMMENT = """---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb +YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ +5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= +---- END SSH2 PUBLIC KEY ----""" + +EXAMPLE_KEY_8_OPENSSH_BROKEN = "".join(( + "ssh-rsa ", + "AschrÖdderöd" +)) + +EXAMPLE_KEY_9_RFC4716_ONLY_HEADER = "---- BEGIN SSH2 PUBLIC KEY ----" + Customer = get_user_model() @@ -135,6 +150,13 @@ class AdditionalGroupTest(TestCaseWithCeleryTasks): (1, 'handle_user_added_to_group')]: self.assertEqual(creators.count(tcreator), tcount) + def test_save_again(self): + group2 = Group.objects.create(groupname='test2', gid=1001) + TaskResult.objects.all().delete() + group2.save() + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 0) + def test_delete(self): group2 = Group.objects.create(groupname='test2', gid=1001) addgroup = AdditionalGroup.objects.create(user=self.user, group=group2) @@ -442,7 +464,7 @@ class SshPublicKeyManagerTest(TestCaseWithCeleryTasks): self.assertGreater(len(res[1]), 40) self.assertEqual(res[2], '') - def test_parse_keytext_invalid(self): + def test_parse_keytext_invalid_multiline(self): with self.assertRaises(ValueError): SshPublicKey.objects.parse_keytext("\r\n".join(["xx"]*10)) @@ -454,6 +476,19 @@ class SshPublicKeyManagerTest(TestCaseWithCeleryTasks): self.assertGreater(len(res[1]), 40) self.assertEqual(res[2], "DSA Public Key for use with MyIsp") + def test_parse_keytext_invalid_empty_rfc4716_header(self): + with self.assertRaises(ValueError): + SshPublicKey.objects.parse_keytext( + EXAMPLE_KEY_9_RFC4716_ONLY_HEADER) + + def test_parse_keytext_no_comment(self): + res = SshPublicKey.objects.parse_keytext( + EXAMPLE_KEY_7_NO_COMMENT) + self.assertEqual(len(res), 3) + self.assertEqual(res[0], 'ssh-rsa') + self.assertGreater(len(res[1]), 40) + self.assertEqual(res[2], '') + def test_parse_keytext_multiline_comment(self): res = SshPublicKey.objects.parse_keytext( EXAMPLE_KEY_5_RFC4716_MULTILINE) @@ -462,6 +497,14 @@ class SshPublicKeyManagerTest(TestCaseWithCeleryTasks): self.assertGreater(len(res[1]), 40) self.assertEqual(res[2], "DSA Public Key for use with MyIsp") + def test_parse_keytext_invalid(self): + with self.assertRaises(ValueError): + SshPublicKey.objects.parse_keytext('invalid') + + def test_parse_keytext_invalid_openssh(self): + with self.assertRaises(ValueError): + SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_8_OPENSSH_BROKEN) + def test_create_ssh_public_key(self): customer = Customer.objects.create_user('test') user = User.objects.create_user(customer) From 4e54b6fcc5eac09c924d6cbfa131698c86d1be3e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 6 Dec 2015 17:49:10 +0100 Subject: [PATCH 045/111] Add osusers.views tests This commit adds tests for the osusers.views module. An incompatibility with Django 1.8 in EditSshPublicKeyComment has been fixed. --- gnuviechadmin/osusers/tests/test_views.py | 404 ++++++++++++++++++++++ gnuviechadmin/osusers/views.py | 3 +- 2 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 gnuviechadmin/osusers/tests/test_views.py diff --git a/gnuviechadmin/osusers/tests/test_views.py b/gnuviechadmin/osusers/tests/test_views.py new file mode 100644 index 0000000..401bbdf --- /dev/null +++ b/gnuviechadmin/osusers/tests/test_views.py @@ -0,0 +1,404 @@ +""" +This module provides tests for :py:mod:`osusers.views`. + +""" +from __future__ import absolute_import, unicode_literals + +try: + from unittest.mock import patch, MagicMock +except ImportError: + from mock import patch, MagicMock + +from django.core.urlresolvers import reverse +from django.test import TestCase, TransactionTestCase +from django.contrib.auth import get_user_model + +from hostingpackages.models import ( + CustomerHostingPackage, + HostingPackageTemplate, +) + +from osusers.models import SshPublicKey +from osusers.views import ( + AddSshPublicKey, + DeleteSshPublicKey, + EditSshPublicKeyComment, +) + + +User = get_user_model() + +TEST_USER = 'test' +TEST_PASSWORD = 'secret' +TEST_EMAIL = 'test@example.org' +EXAMPLE_KEY = "".join(( + "ssh-rsa ", + "AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", + "YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", + "5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=" +)) + + +class HostingPackageAwareTestMixin(object): + + def _setup_hosting_package(self, customer): + template = HostingPackageTemplate.objects.create( + name='testpackagetemplate', mailboxcount=10, diskspace=1, + diskspace_unit=0) + package = CustomerHostingPackage.objects.create_from_template( + customer, template, 'testpackage') + with patch('hostingpackages.models.settings') as hmsettings: + hmsettings.OSUSER_DEFAULT_GROUPS = [] + package.save() + return package + + +class AddSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + + def _get_url(self): + return reverse('add_ssh_key', kwargs={'package': self.package.id}) + + def test_get_anonymous(self): + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertTemplateUsed(response, 'osusers/sshpublickey_create.html') + + def test_get_form_kwargs(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = AddSshPublicKey( + request=MagicMock(), kwargs={'package': str(self.package.pk)}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + self.assertIn('osuser', response.context) + self.assertEqual( + response.context['osuser'], self.package.osuser.username) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), + data={'publickeytext': EXAMPLE_KEY}) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), follow=True, + data={'publickeytext': EXAMPLE_KEY}) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + 'Successfully added new ssh-rsa SSH public key.'.format( + username=self.package.osuser.username + ), str(messages[0])) + + +class DeleteSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + self.sshkey = SshPublicKey.objects.create( + user=self.package.osuser, algorithm='good', data='key', + comment='comment') + + def _get_url(self): + return reverse( + 'delete_ssh_key', kwargs={ + 'package': self.package.id, + 'pk': self.sshkey.id + }) + + def test_get_anonymous(self): + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertTemplateUsed( + response, 'osusers/sshpublickey_confirm_delete.html') + + def test_get_queryset(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = DeleteSshPublicKey( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'pk': str(self.sshkey.pk) + }) + queryset = view.get_queryset() + self.assertQuerysetEqual(queryset, [repr(self.sshkey)]) + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + for key in ('hostingpackage', 'customer', 'osuser'): + self.assertIn(key, response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertEqual(response.context['customer'], self.customer) + self.assertEqual( + response.context['osuser'], self.package.osuser.username) + + def test_get_success_url(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), + data={'comment': 'new comment'}) + self.assertRedirects(response, reverse('list_ssh_keys', kwargs={ + 'package': self.package.id})) + + +class EditSshPublicKeyCommentTest( + HostingPackageAwareTestMixin, TransactionTestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + self.sshkey = SshPublicKey.objects.create( + user=self.package.osuser, algorithm='good', data='key', + comment='comment') + + def _get_url(self): + return reverse( + 'edit_ssh_key_comment', kwargs={ + 'package': self.package.id, + 'pk': self.sshkey.id + }) + + def test_get_anonymous(self): + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertTemplateUsed( + response, 'osusers/sshpublickey_edit_comment.html') + + def test_get_queryset(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = EditSshPublicKeyComment( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'pk': str(self.sshkey.pk) + }) + queryset = view.get_queryset() + self.assertQuerysetEqual(queryset, [repr(self.sshkey)]) + + def test_get_form_kwargs(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = EditSshPublicKeyComment( + request=MagicMock(), kwargs={ + 'package': str(self.package.pk), + 'pk': str(self.sshkey.pk) + }) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + for key in ('hostingpackage', 'customer', 'osuser'): + self.assertIn(key, response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertEqual(response.context['customer'], self.customer) + self.assertEqual( + response.context['osuser'], self.package.osuser.username) + + def test_get_success_url(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), + data={'comment': 'new comment'}) + self.assertRedirects(response, reverse('list_ssh_keys', kwargs={ + 'package': self.package.id})) + + +class ListSshPublicKeysTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + + def _get_url(self): + return reverse('list_ssh_keys', kwargs={'package': self.package.id}) + + def test_get_anonymous(self): + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertTemplateUsed(response, 'osusers/sshpublickey_list.html') + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + for key in ('hostingpackage', 'customer', 'osuser'): + self.assertIn(key, response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertEqual(response.context['customer'], self.customer) + self.assertEqual( + response.context['osuser'], self.package.osuser.username) + + +class SetOsUserPasswordTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + + def _get_url(self): + return reverse('set_osuser_password', kwargs={ + 'slug': self.package.osuser.username}) + + def test_get_anonymous(self): + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertTemplateUsed(response, 'osusers/user_setpassword.html') + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), + data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), follow=True, + data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + 'New password for {username} has been set successfully.'.format( + username=self.package.osuser.username + ), str(messages[0])) diff --git a/gnuviechadmin/osusers/views.py b/gnuviechadmin/osusers/views.py index 3c8c637..2c4e81d 100644 --- a/gnuviechadmin/osusers/views.py +++ b/gnuviechadmin/osusers/views.py @@ -89,7 +89,7 @@ class AddSshPublicKey( key = form.save() messages.success( self.request, - _('Successfully added new {algorithm} SSH public key').format( + _('Successfully added new {algorithm} SSH public key.').format( algorithm=key.algorithm) ) return redirect(self.get_hosting_package()) @@ -162,7 +162,6 @@ class EditSshPublicKeyComment( """ model = SshPublicKey context_object_name = 'key' - fields = ['comment'] template_name_suffix = '_edit_comment' form_class = EditSshPublicKeyCommentForm From e96aac82fcdd79bd7a840a4a374741bc18a32fc2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 6 Dec 2015 18:47:11 +0100 Subject: [PATCH 046/111] Add tests for taskresults app This commit adds tests for the taskresults management command fetch_taskresults and completes the test coverage for taskresults.models. --- .../taskresults/tests/management/__init__.py | 0 .../tests/management/commands/__init__.py | 0 .../commands/test_fetch_taskresults.py | 73 +++++++++++++++++++ .../taskresults/tests/test_models.py | 71 ++++++++++++------ 4 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 gnuviechadmin/taskresults/tests/management/__init__.py create mode 100644 gnuviechadmin/taskresults/tests/management/commands/__init__.py create mode 100644 gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py diff --git a/gnuviechadmin/taskresults/tests/management/__init__.py b/gnuviechadmin/taskresults/tests/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/taskresults/tests/management/commands/__init__.py b/gnuviechadmin/taskresults/tests/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py b/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py new file mode 100644 index 0000000..6b533f0 --- /dev/null +++ b/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py @@ -0,0 +1,73 @@ +""" +This module provides tests for the +:py:mod:`taskresults.management.commands.fetch_taskresults` Django management +command. + +""" +from __future__ import unicode_literals + +try: + from unittest.mock import patch, MagicMock +except ImportError: + from mock import patch, MagicMock + +from django.test import TestCase + +from taskresults.models import TaskResult + +from taskresults.management.commands.fetch_taskresults import Command + + +TEST_TASK_UUID = '3120f6a8-2665-4fa3-a785-79efd28bfe92' +TEST_TASK_NAME = 'test.task' +TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5' + + +@patch('taskresults.models.app.AsyncResult') +class FetchTaskResultsCommandTest(TestCase): + + def test_handle_unfinished(self, asyncresult): + resultmock = MagicMock(task_id=TEST_TASK_UUID) + sigmock = MagicMock() + sigmock.apply_async.return_value = resultmock + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) + self.assertFalse(tr.finished) + self.assertEqual(tr.result, '') + self.assertEqual(tr.state, '') + + aresult = asyncresult.return_value + aresult.state = 'PENDING' + aresult.ready.return_value = False + + Command().handle() + + tr = TaskResult.objects.get(task_id=TEST_TASK_UUID) + self.assertTrue(asyncresult.called_with(TEST_TASK_UUID)) + self.assertTrue(aresult.ready.called_with()) + self.assertFalse(tr.finished) + self.assertEqual(tr.result, '') + self.assertEqual(tr.state, 'PENDING') + + def test_handle_finished(self, asyncresult): + resultmock = MagicMock(task_id=TEST_TASK_UUID) + sigmock = MagicMock() + sigmock.apply_async.return_value = resultmock + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) + self.assertFalse(tr.finished) + self.assertEqual(tr.result, '') + self.assertEqual(tr.state, '') + + aresult = asyncresult.return_value + aresult.state = 'SUCCESS' + aresult.ready.return_value = True + aresult.get.return_value = TEST_TASK_RESULT + + Command().handle() + + tr = TaskResult.objects.get(task_id=TEST_TASK_UUID) + self.assertTrue(asyncresult.called_with(TEST_TASK_UUID)) + self.assertTrue(aresult.ready.called_with()) + self.assertTrue(aresult.get.called_with()) + self.assertTrue(tr.finished) + self.assertEqual(tr.result, TEST_TASK_RESULT) + self.assertEqual(tr.state, 'SUCCESS') diff --git a/gnuviechadmin/taskresults/tests/test_models.py b/gnuviechadmin/taskresults/tests/test_models.py index 17b1aec..68d7a87 100644 --- a/gnuviechadmin/taskresults/tests/test_models.py +++ b/gnuviechadmin/taskresults/tests/test_models.py @@ -1,7 +1,16 @@ +""" +This module provides tests for :py:mod:`taskresults.models`. + +""" from __future__ import absolute_import, unicode_literals + +try: + from unittest.mock import patch, MagicMock +except ImportError: + from mock import patch, MagicMock + from django.test import TestCase -from mock import patch, MagicMock from taskresults.models import TaskResult @@ -11,38 +20,52 @@ TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5' class TaskResultTest(TestCase): - @patch('taskresults.models.app') - def test_update_taskstatus_unfinished(self, app): + @patch('taskresults.models.app.AsyncResult') + def test_update_taskstatus_unfinished(self, asyncresult): resultmock = MagicMock(task_id=TEST_TASK_UUID) - resultmock.ready.return_value = False - mock = MagicMock() - mock.apply_async.return_value = resultmock - tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, mock) + sigmock = MagicMock() + sigmock.apply_async.return_value = resultmock + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) self.assertFalse(tr.finished) - mymock = app.AsyncResult(TEST_TASK_UUID) - mymock.state = 'SUCCESS' - mymock.get.return_value = TEST_RESULT + mymock = asyncresult.return_value + mymock.state = 'PENDING' + mymock.ready.return_value = False tr.fetch_result() - mymock.get.assert_called_with() - self.assertTrue(tr.finished) + mymock.get.assert_not_called() + self.assertFalse(tr.finished) - @patch('taskresults.models.app') - def test_update_taskstatus_finished(self, app): + @patch('taskresults.models.app.AsyncResult') + def test_update_taskstatus_finished(self, asyncresult): resultmock = MagicMock(task_id=TEST_TASK_UUID) - resultmock.ready.return_value = True - resultmock.state = 'SUCCESS' - resultmock.result = TEST_RESULT - mock = MagicMock() - mock.apply_async.return_value = resultmock - mock.state = 'SUCCESS' - mock.result = TEST_RESULT - tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, mock) + sigmock = MagicMock() + sigmock.apply_async.return_value = resultmock + aresult = asyncresult.return_value + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) + self.assertFalse(tr.finished) + aresult = asyncresult.return_value + aresult.state = 'SUCCESS' + aresult.ready.return_value = True + aresult.get.return_value = TEST_TASK_RESULT tr.fetch_result() + self.assertTrue(aresult.get.called_with()) + self.assertEqual(aresult.get.call_count, 1) self.assertTrue(tr.finished) - mymock = app.AsyncResult(TEST_TASK_UUID) + self.assertEqual(tr.result, str(TEST_TASK_RESULT)) tr.fetch_result() - self.assertEqual(mymock.get.call_count, 1) + self.assertEqual(aresult.get.call_count, 1) self.assertTrue(tr.finished) + self.assertEqual(tr.result, str(TEST_TASK_RESULT)) + + def test___str__(self): + resultmock = MagicMock(task_id=TEST_TASK_UUID) + sigmock = MagicMock() + sigmock.apply_async.return_value = resultmock + tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) + self.assertEqual(str(tr), "{name} ({taskid}): no".format( + name=TEST_TASK_NAME, taskid=TEST_TASK_UUID)) + tr.finished = True + self.assertEqual(str(tr), "{name} ({taskid}): yes".format( + name=TEST_TASK_NAME, taskid=TEST_TASK_UUID)) TEST_RESULT = MagicMock() From 1649e4592ea4f155cf327839daf94c2b775d6751 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 7 Dec 2015 00:22:13 +0000 Subject: [PATCH 047/111] Add tests for userdbs app This commit adds a set of unit tests for the userdbs app. Some tests will fail because a refactoring to signals comes with the next commit. --- gnuviechadmin/userdbs/tests/__init__.py | 0 .../userdbs/tests/templatetags/__init__.py | 0 .../userdbs/tests/templatetags/test_userdb.py | 45 +++ gnuviechadmin/userdbs/tests/test_admin.py | 172 ++++++++++ gnuviechadmin/userdbs/tests/test_forms.py | 135 ++++++++ gnuviechadmin/userdbs/tests/test_models.py | 295 ++++++++++++++++++ gnuviechadmin/userdbs/tests/test_signals.py | 66 ++++ 7 files changed, 713 insertions(+) create mode 100644 gnuviechadmin/userdbs/tests/__init__.py create mode 100644 gnuviechadmin/userdbs/tests/templatetags/__init__.py create mode 100644 gnuviechadmin/userdbs/tests/templatetags/test_userdb.py create mode 100644 gnuviechadmin/userdbs/tests/test_admin.py create mode 100644 gnuviechadmin/userdbs/tests/test_forms.py create mode 100644 gnuviechadmin/userdbs/tests/test_models.py create mode 100644 gnuviechadmin/userdbs/tests/test_signals.py diff --git a/gnuviechadmin/userdbs/tests/__init__.py b/gnuviechadmin/userdbs/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/userdbs/tests/templatetags/__init__.py b/gnuviechadmin/userdbs/tests/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/userdbs/tests/templatetags/test_userdb.py b/gnuviechadmin/userdbs/tests/templatetags/test_userdb.py new file mode 100644 index 0000000..120f18c --- /dev/null +++ b/gnuviechadmin/userdbs/tests/templatetags/test_userdb.py @@ -0,0 +1,45 @@ +""" +This module provides tests for the functions in +:py:mod:`userdbs.templatetags.userdb`. + +""" +from __future__ import unicode_literals + +from unittest import TestCase + +from django.utils.translation import gettext as _ + +from userdbs.models import DB_TYPES +from userdbs.templatetags.userdb import db_type_icon_class, db_type_name + + +class UserdbTemplateTagTests(TestCase): + """ + Test suite for :py:mod:`userdbs.templatetags.userdb` functions. + + """ + + def test_db_type_icon_class_unknown(self): + self.assertEqual( + db_type_icon_class({'db_type': 'unknown'}), + 'icon-database') + + def test_db_type_icon_class_mysql(self): + self.assertEqual( + db_type_icon_class({'db_type': DB_TYPES.mysql}), + 'icon-mysql') + + def test_db_type_icon_class_pgsql(self): + self.assertEqual( + db_type_icon_class({'db_type': DB_TYPES.pgsql}), + 'icon-postgres') + + def test_db_type_name_mysql(self): + self.assertEqual( + db_type_name({'db_type': DB_TYPES.mysql}), + _(DB_TYPES[DB_TYPES.mysql])) + + def test_db_type_name_pgsql(self): + self.assertEqual( + db_type_name({'db_type': DB_TYPES.pgsql}), + _(DB_TYPES[DB_TYPES.pgsql])) diff --git a/gnuviechadmin/userdbs/tests/test_admin.py b/gnuviechadmin/userdbs/tests/test_admin.py new file mode 100644 index 0000000..592eb0a --- /dev/null +++ b/gnuviechadmin/userdbs/tests/test_admin.py @@ -0,0 +1,172 @@ +""" +This module provides tests for :py:mod:`userdbs.admin`. + +""" +from __future__ import absolute_import + +from django.contrib.admin import AdminSite +from django.test import TestCase + +from userdbs.admin import (DatabaseUserAdmin, DatabaseUserCreationForm, + UserDatabaseAdmin, UserDatabaseCreationForm) +from userdbs.models import DB_TYPES, DatabaseUser, UserDatabase + +try: + from unittest.mock import MagicMock, Mock, patch +except ImportError: + from mock import MagicMock, Mock, patch + + +class DatabaseUserCreationFormTest(TestCase): + + @patch('userdbs.admin.DatabaseUser.objects.create_database_user') + def test_save(self, create_database_user): + create_database_user.return_value = Mock() + form = DatabaseUserCreationForm() + mockuser = Mock(name='osuser') + form.cleaned_data = { + 'osuser': mockuser, + 'db_type': DB_TYPES.pgsql + } + retval = form.save() + self.assertTrue(create_database_user.called_with( + osuser=mockuser, db_type=DB_TYPES.pgsql, commit=True)) + self.assertEqual(retval, create_database_user.return_value) + + def test_save_m2m_returns_none(self): + form = DatabaseUserCreationForm() + self.assertIsNone(form.save_m2m()) + + +class UserDatabaseCreationFormTest(TestCase): + + @patch('userdbs.admin.UserDatabase.objects.create_userdatabase') + def test_save(self, create_userdatabase): + create_userdatabase.return_value = Mock() + form = UserDatabaseCreationForm() + mockuser = Mock(name='mockuser') + form.cleaned_data = {'db_user': mockuser} + retval = form.save() + self.assertTrue(create_userdatabase.called_with( + db_user=mockuser, commit=True)) + self.assertEqual(retval, create_userdatabase.return_value) + + def test_save_m2m_returns_none(self): + form = UserDatabaseCreationForm() + self.assertIsNone(form.save_m2m()) + + +class DatabaseUserAdminTest(TestCase): + + def setUp(self): + site = AdminSite() + self.dbuadmin = DatabaseUserAdmin(DatabaseUser, site) + super(DatabaseUserAdminTest, self).setUp() + + def test_get_form_with_instance(self): + form = self.dbuadmin.get_form( + Mock(name='request'), obj=Mock(name='dbuser')) + self.assertEqual( + form.Meta.fields, + ['osuser', 'name', 'db_type'] + ) + + def test_get_form_without_instance(self): + form = self.dbuadmin.get_form(Mock(name='request')) + self.assertEqual( + form.Meta.fields, + ['osuser', 'db_type'] + ) + + def test_get_readonly_fields_with_instance(self): + fields = self.dbuadmin.get_readonly_fields( + Mock(name='request'), obj=Mock(name='dbuser')) + self.assertEqual( + fields, ['osuser', 'name', 'db_type']) + + def test_get_readonly_fields_without_instance(self): + fields = self.dbuadmin.get_readonly_fields( + Mock(name='request')) + self.assertEqual(fields, []) + + def test_save_model_change(self): + objmock = Mock() + self.dbuadmin.save_model(Mock(name='request'), objmock, Mock(), True) + self.assertTrue(objmock.create_in_database.not_called()) + + def test_save_model_no_change(self): + objmock = Mock() + self.dbuadmin.save_model(Mock(name='request'), objmock, Mock(), False) + self.assertTrue(objmock.create_in_database.called_with()) + + def test_perform_delete_selected(self): + usermock = Mock() + selected = Mock() + selected.all.return_value = [usermock] + self.dbuadmin.perform_delete_selected(Mock(name='request'), selected) + self.assertTrue(selected.all.called_with()) + self.assertTrue(usermock.delete.called_with()) + + def test_get_actions(self): + requestmock = MagicMock(name='request') + self.assertNotIn( + 'delete_selected', + self.dbuadmin.get_actions(requestmock)) + self.assertIn( + 'perform_delete_selected', + self.dbuadmin.get_actions(requestmock)) + + +class UserDatabaseAdminTest(TestCase): + + def setUp(self): + site = AdminSite() + self.udbadmin = UserDatabaseAdmin(UserDatabase, site) + super(UserDatabaseAdminTest, self).setUp() + + def test_get_form_with_instance(self): + form = self.udbadmin.get_form( + Mock(name='request'), obj=Mock(name='userdb')) + self.assertEqual(form.Meta.fields, ['db_name', 'db_user']) + + def test_get_form_without_instance(self): + form = self.udbadmin.get_form(Mock(name='request')) + self.assertEqual(form.Meta.fields, ['db_user']) + + def test_get_readonly_fields_with_instance(self): + fields = self.udbadmin.get_readonly_fields( + Mock(name='request'), obj=Mock(name='userdb')) + self.assertEqual( + fields, ['db_name', 'db_user']) + + def test_get_readonly_fields_without_instance(self): + fields = self.udbadmin.get_readonly_fields( + Mock(name='request')) + self.assertEqual(fields, []) + + def test_save_model_change(self): + objmock = Mock() + self.udbadmin.save_model(Mock(name='request'), objmock, Mock(), True) + self.assertTrue(objmock.create_in_database.not_called()) + + def test_save_model_no_change(self): + objmock = Mock() + self.udbadmin.save_model(Mock(name='request'), objmock, Mock(), False) + self.assertTrue(objmock.create_in_database.called_with()) + + def test_perform_delete_selected(self): + userdbmock = Mock() + selected = Mock() + selected.all.return_value = [userdbmock] + self.udbadmin.perform_delete_selected(Mock(name='request'), selected) + self.assertTrue(selected.all.called_with()) + self.assertTrue(userdbmock.delete.called_with()) + + def test_get_actions(self): + requestmock = MagicMock(name='request') + self.assertNotIn( + 'delete_selected', + self.udbadmin.get_actions(requestmock)) + self.assertIn( + 'perform_delete_selected', + self.udbadmin.get_actions(requestmock)) diff --git a/gnuviechadmin/userdbs/tests/test_forms.py b/gnuviechadmin/userdbs/tests/test_forms.py new file mode 100644 index 0000000..977e582 --- /dev/null +++ b/gnuviechadmin/userdbs/tests/test_forms.py @@ -0,0 +1,135 @@ +""" +This module provides tests for :py:mod:`userdbs.forms`. + +""" +from __future__ import unicode_literals + +from django import forms +from django.contrib.auth import get_user_model +from django.core.urlresolvers import reverse +from django.test import TestCase + +from userdbs.forms import AddUserDatabaseForm, ChangeDatabaseUserPasswordForm +from userdbs.models import DB_TYPES + +try: + from unittest.mock import MagicMock, Mock, patch +except ImportError: + from mock import MagicMock, Mock, patch + + +Customer = get_user_model() + + +class AddUserDatabaseFormTest(TestCase): + """ + Test class for :py:class:`userdbs.forms.AddUserDatabaseForm`. + + """ + + def _setup_hostingpackage(self): + self.hostingpackage = Mock(id=42) + + def test_constructor_needs_hostingpackage(self): + with self.assertRaises(KeyError) as ke: + AddUserDatabaseForm(instance=Mock()) + self.assertEqual(ke.exception.args[0], 'hostingpackage') + + def test_constructor_needs_dbtypes(self): + with self.assertRaises(KeyError) as ke: + AddUserDatabaseForm(instance=Mock(), hostingpackage=Mock()) + self.assertEqual(ke.exception.args[0], 'dbtypes') + + def test_constructor_one_dbtype(self): + self._setup_hostingpackage() + dbtypes = [(DB_TYPES.pgsql, DB_TYPES[DB_TYPES.pgsql])] + form = AddUserDatabaseForm( + instance=MagicMock(), hostingpackage=self.hostingpackage, + dbtypes=dbtypes) + self.assertIn('db_type', form.fields) + self.assertEqual(form.fields['db_type'].choices, dbtypes) + self.assertTrue(isinstance( + form.fields['db_type'].widget, + forms.HiddenInput)) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'add_userdatabase', kwargs={'package': self.hostingpackage.id})) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + def test_constructor_multiple_dbtypes(self): + self._setup_hostingpackage() + dbtypes = [ + (DB_TYPES.pgsql, DB_TYPES[DB_TYPES.pgsql]), + (DB_TYPES.mysql, DB_TYPES[DB_TYPES.mysql]) + ] + form = AddUserDatabaseForm( + instance=MagicMock(), hostingpackage=self.hostingpackage, + dbtypes=dbtypes) + self.assertIn('db_type', form.fields) + self.assertEqual(form.fields['db_type'].choices, dbtypes) + self.assertTrue(isinstance( + form.fields['db_type'].widget, + forms.RadioSelect)) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'add_userdatabase', kwargs={'package': self.hostingpackage.id})) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + @patch('userdbs.forms.UserDatabase.objects.create_userdatabase_with_user') + def test_save(self, create_userdatabase_with_user): + self._setup_hostingpackage() + dbtypes = [ + (DB_TYPES.pgsql, DB_TYPES[DB_TYPES.pgsql]), + (DB_TYPES.mysql, DB_TYPES[DB_TYPES.mysql]) + ] + form = AddUserDatabaseForm( + instance=MagicMock(), hostingpackage=self.hostingpackage, + dbtypes=dbtypes) + form.cleaned_data = { + 'db_type': DB_TYPES.pgsql, + 'password1': 'secret', + } + form.save() + self.assertTrue(create_userdatabase_with_user.called_with( + DB_TYPES.pgsql, self.hostingpackage.osuser, + password='secret', commit=True)) + + +class ChangeDatabaseUserPasswordFormTest(TestCase): + """ + Test class for :py:class:`userdbs.forms.ChangeDatabaseUserPasswordForm`. + + """ + + def _setup_hostingpackage(self): + self.hostingpackage = Mock(id=42) + + def test_constructor_needs_hostingpackage(self): + with self.assertRaises(KeyError) as ke: + ChangeDatabaseUserPasswordForm(instance=Mock()) + self.assertEqual(ke.exception.args[0], 'hostingpackage') + + def test_constructor(self): + self._setup_hostingpackage() + instance = MagicMock() + instance.name = 'test' + form = ChangeDatabaseUserPasswordForm( + instance=instance, hostingpackage=self.hostingpackage) + self.assertIn('password1', form.fields) + self.assertIn('password2', form.fields) + self.assertTrue(hasattr(form, 'helper')) + self.assertEqual(form.helper.form_action, reverse( + 'change_dbuser_password', kwargs={ + 'slug': 'test', 'package': 42 + })) + self.assertEqual(form.helper.inputs[0].name, 'submit') + + def test_save(self): + instance = MagicMock() + instance.name = 'test' + self._setup_hostingpackage() + form = ChangeDatabaseUserPasswordForm( + instance=instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {'password1': 'secret'} + form.save() + self.assertTrue(instance.set_password.called_with('secret')) diff --git a/gnuviechadmin/userdbs/tests/test_models.py b/gnuviechadmin/userdbs/tests/test_models.py new file mode 100644 index 0000000..d8b1024 --- /dev/null +++ b/gnuviechadmin/userdbs/tests/test_models.py @@ -0,0 +1,295 @@ +""" +This module provides tests for :py:mod:`userdbs.models`. + +""" +from __future__ import unicode_literals + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.test.utils import override_settings + +from osusers.models import User +from taskresults.models import TaskResult +from userdbs.models import DB_TYPES, DatabaseUser, UserDatabase + +Customer = get_user_model() + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class TestCaseWithCeleryTasks(TestCase): + pass + + +class DatabaseUserManagerTest(TestCaseWithCeleryTasks): + """ + Test case for :py:class:`userdbs.models.DatabaseUserManager`. + + """ + def setUp(self): + self.customer = Customer.objects.create_user(username='testcustomer') + self.osuser = User.objects.create_user(customer=self.customer) + TaskResult.objects.all().delete() + + def test_create_database_user_with_name(self): + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.pgsql, 'testname', 'secret') + self.assertEqual(dbu.name, 'testname') + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.pgsql) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(taskres[0].creator, 'handle_dbuser_created') + self.assertEqual(taskres[0].notes, 'pgsql user creation') + + def test_create_database_user_with_name_no_commit(self): + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.pgsql, 'testname', 'secret', False) + self.assertEqual(dbu.name, 'testname') + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.pgsql) + self.assertFalse(TaskResult.objects.exists()) + + def test_create_database_user_generate_name(self): + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.pgsql) + self.assertEqual(dbu.name, '{user}db01'.format( + user=self.osuser.username)) + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.pgsql) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(taskres[0].creator, 'handle_dbuser_created') + self.assertEqual(taskres[0].notes, 'pgsql user creation') + + def test_create_database_user_multiple_generate_name(self): + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, '{user}db01'.format( + user=self.osuser.username)) + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.mysql) + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, '{user}db02'.format( + user=self.osuser.username)) + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.mysql) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 2) + self.assertEqual(taskres[0].creator, 'handle_dbuser_created') + self.assertEqual(taskres[0].notes, 'mysql user creation') + self.assertEqual(taskres[1].creator, 'handle_dbuser_created') + self.assertEqual(taskres[1].notes, 'mysql user creation') + + def test_create_database_user_multiple_gap_generate_name(self): + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, '{user}db01'.format( + user=self.osuser.username)) + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.mysql) + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, '{user}db02'.format( + user=self.osuser.username)) + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.mysql) + DatabaseUser.objects.get( + name='{user}db01'.format(user=self.osuser.username)).delete() + dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, '{user}db01'.format( + user=self.osuser.username)) + self.assertEqual(dbu.osuser, self.osuser) + self.assertEqual(dbu.db_type, DB_TYPES.mysql) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 4) + self.assertEqual(taskres[0].creator, 'handle_dbuser_created') + self.assertEqual(taskres[0].notes, 'mysql user creation') + self.assertEqual(taskres[1].creator, 'handle_dbuser_created') + self.assertEqual(taskres[1].notes, 'mysql user creation') + self.assertEqual(taskres[2].creator, 'handle_dbuser_deleted') + self.assertEqual(taskres[2].notes, 'mysql user deletion') + self.assertEqual(taskres[3].creator, 'handle_dbuser_created') + self.assertEqual(taskres[3].notes, 'mysql user creation') + + +class DatabaseUserTest(TestCaseWithCeleryTasks): + """ + Test case for :py:class:`userdbs.models.DatabaseUser`. + + """ + def setUp(self): + self.customer = Customer.objects.create_user(username='testcustomer') + self.osuser = User.objects.create_user(customer=self.customer) + self.dbu = DatabaseUser.objects.create_database_user( + self.osuser, DB_TYPES.pgsql) + TaskResult.objects.all().delete() + + def test___str__(self): + self.assertEqual( + str(self.dbu), + '{user}db01 (PostgreSQL for {user})'.format( + user=self.osuser.username)) + + def test_set_password_pgsql(self): + self.dbu.set_password('secret') + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(taskres[0].creator, 'handle_dbuser_password_set') + self.assertEqual(taskres[0].notes, 'pgsql password change') + + def test_set_password_mysql(self): + self.dbu.db_type = DB_TYPES.mysql + self.dbu.save() + self.dbu.set_password('secret') + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(taskres[0].creator, 'handle_dbuser_password_set') + self.assertEqual(taskres[0].notes, 'mysql password change') + + def test_delete_no_dbs(self): + self.dbu.delete() + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(taskres[0].creator, 'handle_dbuser_deleted') + self.assertEqual(taskres[0].notes, 'pgsql user deletion') + + def test_delete_with_dbs(self): + db = UserDatabase.objects.create_userdatabase(self.dbu) + dbid = db.id + self.dbu.delete() + self.assertFalse(UserDatabase.objects.filter(id=dbid).exists()) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 3) + self.assertEqual(taskres[0].creator, 'handle_userdb_created') + self.assertEqual(taskres[0].notes, 'pgsql database creation') + self.assertEqual(taskres[1].creator, 'handle_userdb_deleted') + self.assertEqual(taskres[1].notes, 'pgsql database deletion') + self.assertEqual(taskres[2].creator, 'handle_dbuser_deleted') + self.assertEqual(taskres[2].notes, 'pgsql user deletion') + + +class UserDatabaseManagerTest(TestCaseWithCeleryTasks): + """ + Test case for :py:class:`userdbs.models.UserDatabaseManager`. + + """ + def setUp(self): + self.customer = Customer.objects.create_user(username='testcustomer') + self.osuser = User.objects.create_user(customer=self.customer) + TaskResult.objects.all().delete() + + def _create_database_user(self, dbtype): + self.dbu = DatabaseUser.objects.create_database_user( + self.osuser, dbtype) + TaskResult.objects.all().delete() + + def test_create_userdatabase_with_user_mysql(self): + db = UserDatabase.objects.create_userdatabase_with_user( + DB_TYPES.mysql, self.osuser) + self.assertEqual(db.db_name, '{user}db01'.format( + user=self.osuser.username)) + self.assertEqual(db.db_user.name, '{user}db01'.format( + user=self.osuser.username)) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 2) + self.assertEqual(taskres[0].creator, 'handle_dbuser_created') + self.assertEqual(taskres[0].notes, 'mysql user creation') + self.assertEqual(taskres[1].creator, 'handle_userdb_created') + self.assertEqual(taskres[1].notes, 'mysql database creation') + + def test_create_userdatabase_with_user_pgsql(self): + db = UserDatabase.objects.create_userdatabase_with_user( + DB_TYPES.pgsql, self.osuser) + self.assertEqual(db.db_name, '{user}db01'.format( + user=self.osuser.username)) + self.assertEqual(db.db_user.name, '{user}db01'.format( + user=self.osuser.username)) + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 2) + self.assertEqual(taskres[0].creator, 'handle_dbuser_created') + self.assertEqual(taskres[0].notes, 'pgsql user creation') + self.assertEqual(taskres[1].creator, 'handle_userdb_created') + self.assertEqual(taskres[1].notes, 'pgsql database creation') + + def test_create_userdatabase_given_name_no_new_write(self): + self._create_database_user(DB_TYPES.pgsql) + db = UserDatabase.objects.create_userdatabase(self.dbu, db_name='test') + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(db.db_name, 'test') + TaskResult.objects.all().delete() + db.save() + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 0) + + def test_create_userdatabase_given_name(self): + self._create_database_user(DB_TYPES.pgsql) + db = UserDatabase.objects.create_userdatabase(self.dbu, db_name='test') + self.assertEqual(db.db_name, 'test') + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 1) + self.assertEqual(db.db_name, 'test') + + def test_create_userdatabase_generate_name_no_commit(self): + self._create_database_user(DB_TYPES.pgsql) + db = UserDatabase.objects.create_userdatabase(self.dbu, commit=False) + self.assertEqual(db.db_name, self.dbu.name) + self.assertFalse(TaskResult.objects.all().exists()) + + def test_create_userdatabase_generate_name(self): + self._create_database_user(DB_TYPES.pgsql) + db = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(db.db_name, self.dbu.name) + + def test_create_userdatabase_multiple_generate_name(self): + self._create_database_user(DB_TYPES.pgsql) + db = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(db.db_name, self.dbu.name) + db = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(db.db_name, '{user}_02'.format(user=self.dbu.name)) + + def test_create_userdatabase_multiple_gap_generate_name(self): + self._create_database_user(DB_TYPES.pgsql) + db = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(db.db_name, self.dbu.name) + dbx = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(dbx.db_name, '{user}_02'.format(user=self.dbu.name)) + db = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(db.db_name, '{user}_03'.format(user=self.dbu.name)) + dbx.delete() + db = UserDatabase.objects.create_userdatabase(self.dbu) + self.assertEqual(db.db_name, '{user}_02'.format(user=self.dbu.name)) + + +class UserDatabaseTest(TestCaseWithCeleryTasks): + """ + Test case for :py:class:`userdbs.models.UserDabase`. + + """ + def test___str__(self): + customer = Customer.objects.create_user(username='testcustomer') + osuser = User.objects.create_user(customer=customer) + db = UserDatabase.objects.create_userdatabase_with_user( + DB_TYPES.pgsql, osuser) + self.assertEqual( + str(db), + '{user}db01 ({dbuser})'.format( + user=osuser.username, dbuser=db.db_user)) + + def test_delete_mysql_db(self): + customer = Customer.objects.create_user(username='testcustomer') + osuser = User.objects.create_user(customer=customer) + TaskResult.objects.all().delete() + db = UserDatabase.objects.create_userdatabase_with_user( + DB_TYPES.mysql, osuser) + db.delete() + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 3) + self.assertEqual(taskres[2].creator, 'handle_userdb_deleted') + self.assertEqual(taskres[2].notes, 'mysql database deletion') diff --git a/gnuviechadmin/userdbs/tests/test_signals.py b/gnuviechadmin/userdbs/tests/test_signals.py new file mode 100644 index 0000000..c39a0b5 --- /dev/null +++ b/gnuviechadmin/userdbs/tests/test_signals.py @@ -0,0 +1,66 @@ +""" +This module contains explicit tests for corner cases in +:py:mod:`userdbs.signals` that are not handled by the tests in +:py:mod:`userdbs.tests.test_models`. + +""" +from __future__ import unicode_literals + +from django.test import TestCase +from django.test.utils import override_settings + +from taskresults.models import TaskResult +from userdbs.signals import (handle_dbuser_created, handle_dbuser_deleted, + handle_dbuser_password_set, handle_userdb_created, + handle_userdb_deleted) + +try: + from unittest.mock import Mock +except ImportError: + from mock import Mock + + +@override_settings( + CELERY_ALWAYS_EAGER=True, + CELERY_CACHE_BACKEND='memory', + BROKER_BACKEND='memory' +) +class TestCaseWithCeleryTasks(TestCase): + pass + + +class TestWithUnknownDBType(TestCaseWithCeleryTasks): + + def test_handle_dbuser_password_set_unknown(self): + instance = Mock(data={'name': 'test', 'db_type': -1}) + handle_dbuser_password_set(Mock(name='sender'), instance, 'secret') + self.assertFalse(TaskResult.objects.exists()) + + def test_handle_dbuser_create_unknown(self): + instance = Mock(data={'name': 'test', 'db_type': -1}) + handle_dbuser_created( + Mock(name='sender'), instance, True, password='secret') + self.assertFalse(TaskResult.objects.exists()) + + def test_handle_dbuser_deleted_unknown(self): + instance = Mock(data={'name': 'test', 'db_type': -1}) + handle_dbuser_deleted(Mock(name='sender'), instance) + self.assertFalse(TaskResult.objects.exists()) + + def test_handle_userdb_created_unknown(self): + instance = Mock( + data={ + 'db_name': 'test', + 'db_user': Mock(data={'name': 'test', 'db_type': -1, }) + }) + handle_userdb_created(Mock(name='sender'), instance, True) + self.assertFalse(TaskResult.objects.exists()) + + def test_handle_userdb_deleted_unknown(self): + instance = Mock( + data={ + 'db_name': 'test', + 'db_user': Mock(data={'name': 'test', 'db_type': -1, }) + }) + handle_userdb_deleted(Mock(name='sender'), instance) + self.assertFalse(TaskResult.objects.exists()) From c9a9fa11b23a09a154da419e7b6d585fc69ed803 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 7 Dec 2015 00:23:07 +0000 Subject: [PATCH 048/111] Refactor userdbs app to use signals This commit isolates the celery task invocations of the userdbs app into signal handlers. All celery interaction is now asynchronously handled in userdbs.signals. --- gnuviechadmin/userdbs/admin.py | 6 +- gnuviechadmin/userdbs/apps.py | 8 ++ gnuviechadmin/userdbs/models.py | 95 ++-------------- gnuviechadmin/userdbs/signals.py | 190 +++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 91 deletions(-) create mode 100644 gnuviechadmin/userdbs/signals.py diff --git a/gnuviechadmin/userdbs/admin.py b/gnuviechadmin/userdbs/admin.py index 7a498c8..9b807e2 100644 --- a/gnuviechadmin/userdbs/admin.py +++ b/gnuviechadmin/userdbs/admin.py @@ -170,7 +170,7 @@ class DatabaseUserAdmin(admin.ModelAdmin): """ actions = super(DatabaseUserAdmin, self).get_actions(request) - if 'delete_selected' in actions: + if 'delete_selected' in actions: # pragma: no cover del actions['delete_selected'] return actions @@ -250,8 +250,6 @@ class UserDatabaseAdmin(admin.ModelAdmin): databases """ - for dbuser in queryset.all(): - dbuser.delete() for database in queryset.all(): database.delete() perform_delete_selected.short_description = _( @@ -270,7 +268,7 @@ class UserDatabaseAdmin(admin.ModelAdmin): """ actions = super(UserDatabaseAdmin, self).get_actions(request) - if 'delete_selected' in actions: + if 'delete_selected' in actions: # pragma: no cover del actions['delete_selected'] return actions diff --git a/gnuviechadmin/userdbs/apps.py b/gnuviechadmin/userdbs/apps.py index 40fd5ec..304f4d2 100644 --- a/gnuviechadmin/userdbs/apps.py +++ b/gnuviechadmin/userdbs/apps.py @@ -16,3 +16,11 @@ class UserdbsAppConfig(AppConfig): """ name = 'userdbs' verbose_name = _('Database Users and their Databases') + + def ready(self): + """ + Takes care of importing the signal handlers of the :py:mod:`userdbs` + app. + + """ + import userdbs.signals # NOQA diff --git a/gnuviechadmin/userdbs/models.py b/gnuviechadmin/userdbs/models.py index adcc2e7..78f9ea7 100644 --- a/gnuviechadmin/userdbs/models.py +++ b/gnuviechadmin/userdbs/models.py @@ -1,33 +1,14 @@ from __future__ import unicode_literals -from django.db import models -from django.db import transaction +from django.db import models, transaction +from django.dispatch import Signal from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ - from model_utils import Choices from model_utils.models import TimeStampedModel -from passlib.utils import generate_password - from osusers.models import User as OsUser -from mysqltasks.tasks import ( - create_mysql_database, - create_mysql_user, - delete_mysql_database, - delete_mysql_user, - set_mysql_userpassword, -) -from pgsqltasks.tasks import ( - create_pgsql_database, - create_pgsql_user, - delete_pgsql_database, - delete_pgsql_user, - set_pgsql_userpassword, -) - - DB_TYPES = Choices( (0, 'pgsql', _('PostgreSQL')), (1, 'mysql', _('MySQL')), @@ -37,6 +18,9 @@ Database type choice enumeration. """ +password_set = Signal(providing_args=['instance', 'password']) + + class DatabaseUserManager(models.Manager): """ Default Manager for :py:class:`userdbs.models.DatabaseUser`. @@ -93,7 +77,6 @@ class DatabaseUserManager(models.Manager): db_user = DatabaseUser( osuser=osuser, db_type=db_type, name=username) if commit: - db_user.create_in_database(password=password) db_user.save() return db_user @@ -120,34 +103,15 @@ class DatabaseUser(TimeStampedModel, models.Model): 'osuser': self.osuser.username, } - def create_in_database(self, password=None): - """ - Create this user in the target database. - - :param str password: initial password for the database user - """ - if password is None: - password = generate_password() - # TODO: send GPG encrypted mail with this information - if self.db_type == DB_TYPES.pgsql: - create_pgsql_user.delay(self.name, password).get() - elif self.db_type == DB_TYPES.mysql: - create_mysql_user.delay(self.name, password).get() - else: - raise ValueError('Unknown database type %d' % self.db_type) - + @transaction.atomic def set_password(self, password): """ Set an existing user's password. :param str password: new password for the database user """ - if self.db_type == DB_TYPES.pgsql: - set_pgsql_userpassword.delay(self.name, password).get(timeout=5) - elif self.db_type == DB_TYPES.mysql: - set_mysql_userpassword.delay(self.name, password).get(timeout=5) - else: - raise ValueError('Unknown database type %d' % self.db_type) + password_set.send( + sender=self.__class__, password=password, instance=self) @transaction.atomic def delete(self, *args, **kwargs): @@ -163,12 +127,6 @@ class DatabaseUser(TimeStampedModel, models.Model): """ for database in self.userdatabase_set.all(): database.delete() - if self.db_type == DB_TYPES.pgsql: - delete_pgsql_user.delay(self.name).get(propagate=False, timeout=5) - elif self.db_type == DB_TYPES.mysql: - delete_mysql_user.delay(self.name).get(propagate=False, timeout=5) - else: - raise ValueError('Unknown database type %d' % self.db_type) super(DatabaseUser, self).delete(*args, **kwargs) @@ -236,7 +194,6 @@ class UserDatabaseManager(models.Manager): db_name = self._get_next_dbname(db_user) database = UserDatabase(db_user=db_user, db_name=db_name) if commit: - database.create_in_database() database.save() return database @@ -260,39 +217,3 @@ class UserDatabase(TimeStampedModel, models.Model): 'db_name': self.db_name, 'db_user': self.db_user, } - - def create_in_database(self): - """ - Create this database (schema) in the target database. - - """ - # TODO: send GPG encrypted mail with this information - if self.db_user.db_type == DB_TYPES.pgsql: - create_pgsql_database.delay(self.db_name, self.db_user.name).get() - elif self.db_user.db_type == DB_TYPES.mysql: - create_mysql_database.delay(self.db_name, self.db_user.name).get() - else: - raise ValueError('Unknown database type %d' % self.db_type) - - @transaction.atomic - def delete(self, *args, **kwargs): - """ - Delete the database (schema) from the target database and the Django - database. - - :param args: positional arguments for - :py:meth:`django.db.models.Model.delete` - :param kwargs: keyword arguments for - :py:meth:`django.db.models.Model.delete` - - """ - db_user = self.db_user - if db_user.db_type == DB_TYPES.pgsql: - delete_pgsql_database.delay(self.db_name).get() - elif db_user.db_type == DB_TYPES.mysql: - delete_mysql_database.delay(self.db_name, db_user.name).get() - else: - raise ValueError('Unknown database type %d' % self.db_type) - super(UserDatabase, self).delete(*args, **kwargs) - if not db_user.userdatabase_set.exists(): - db_user.delete() diff --git a/gnuviechadmin/userdbs/signals.py b/gnuviechadmin/userdbs/signals.py new file mode 100644 index 0000000..fde20ac --- /dev/null +++ b/gnuviechadmin/userdbs/signals.py @@ -0,0 +1,190 @@ +""" +This module contains the signal handlers of the :py:mod:`userdbs` app. + +""" +from __future__ import unicode_literals + +import logging + +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver +from passlib.utils import generate_password + +from mysqltasks.tasks import (create_mysql_database, create_mysql_user, + delete_mysql_database, delete_mysql_user, + set_mysql_userpassword) +from pgsqltasks.tasks import (create_pgsql_database, create_pgsql_user, + delete_pgsql_database, delete_pgsql_user, + set_pgsql_userpassword) +from taskresults.models import TaskResult + +from .models import DB_TYPES, DatabaseUser, UserDatabase, password_set + +_LOGGER = logging.getLogger(__name__) + + +@receiver(password_set, sender=DatabaseUser) +def handle_dbuser_password_set(sender, instance, password, **kwargs): + """ + Signal handler triggered by password changes for + :py:class:`userdbs.models.DatabaseUser` instances. + + """ + if instance.db_type == DB_TYPES.mysql: + taskresult = TaskResult.objects.create_task_result( + 'handle_dbuser_password_set', + set_mysql_userpassword.s(instance.name, password), + 'mysql password change') + _LOGGER.info( + 'MySQL password change has been requested in task %s', + taskresult.task_id) + elif instance.db_type == DB_TYPES.pgsql: + taskresult = TaskResult.objects.create_task_result( + 'handle_dbuser_password_set', + set_pgsql_userpassword.s(instance.name, password), + 'pgsql password change') + _LOGGER.info( + 'PostgreSQL password change has been requested in task %s', + taskresult.task_id) + else: + _LOGGER.warning( + 'Password change has been requested for unknown database %s' + ' the request has been ignored.', + instance.db_type) + + +@receiver(post_save, sender=DatabaseUser) +def handle_dbuser_created(sender, instance, created, **kwargs): + """ + Signal handler triggered after the creation of or updates to + :py:class:`userdbs.models.DatabaseUser` instances. + + """ + if created: + password = kwargs.get('password', generate_password()) + # TODO: send GPG encrypted mail with this information + if instance.db_type == DB_TYPES.mysql: + taskresult = TaskResult.objects.create_task_result( + 'handle_dbuser_created', + create_mysql_user.s(instance.name, password), + 'mysql user creation') + _LOGGER.info( + 'A new MySQL user %s creation has been requested in task %s', + instance.name, taskresult.task_id) + elif instance.db_type == DB_TYPES.pgsql: + taskresult = TaskResult.objects.create_task_result( + 'handle_dbuser_created', + create_pgsql_user.s(instance.name, password), + 'pgsql user creation') + _LOGGER.info( + 'A new PostgreSQL user %s creation has been requested in task' + ' %s', + instance.name, taskresult.task_id) + else: + _LOGGER.warning( + 'created DatabaseUser for unknown database type %s', + instance.db_type) + _LOGGER.debug( + 'database user %s has been %s', + instance, created and "created" or "updated") + + +@receiver(post_delete, sender=DatabaseUser) +def handle_dbuser_deleted(sender, instance, **kwargs): + """ + Signal handler triggered after the deletion of + :py:class:`userdbs.models.DatabaseUser` instances. + + """ + if instance.db_type == DB_TYPES.mysql: + taskresult = TaskResult.objects.create_task_result( + 'handle_dbuser_deleted', + delete_mysql_user.s(instance.name), + 'mysql user deletion') + _LOGGER.info( + 'MySQL user %s deletion has been requested in task %s', + instance.name, taskresult.task_id) + elif instance.db_type == DB_TYPES.pgsql: + taskresult = TaskResult.objects.create_task_result( + 'handle_dbuser_deleted', + delete_pgsql_user.s(instance.name), + 'pgsql user deletion') + _LOGGER.info( + 'PostgreSQL user %s deletion has been requested in task %s', + instance.name, taskresult.task_id) + else: + _LOGGER.warning( + 'deleted DatabaseUser %s for unknown database type %s', + instance.name, instance.db_type) + _LOGGER.debug( + 'database user %s has been deleted', instance) + + +@receiver(post_save, sender=UserDatabase) +def handle_userdb_created(sender, instance, created, **kwargs): + """ + Signal handler triggered after the creation of or updates to + :py:class:`userdbs.models.UserDatabase` instances. + + """ + if created: + if instance.db_user.db_type == DB_TYPES.mysql: + taskresult = TaskResult.objects.create_task_result( + 'handle_userdb_created', + create_mysql_database.s( + instance.db_name, instance.db_user.name), + 'mysql database creation') + _LOGGER.info( + 'The creation of a new MySQL database %s has been requested in' + ' task %s', + instance.db_name, taskresult.task_id) + elif instance.db_user.db_type == DB_TYPES.pgsql: + taskresult = TaskResult.objects.create_task_result( + 'handle_userdb_created', + create_pgsql_database.s( + instance.db_name, instance.db_user.name), + 'pgsql database creation') + _LOGGER.info( + 'The creation of a new PostgreSQL database %s has been' + ' requested in task %s', + instance.db_name, taskresult.task_id) + else: + _LOGGER.warning( + 'created UserDatabase for unknown database type %s', + instance.db_user.db_type) + _LOGGER.debug( + 'database %s has been %s', + instance, created and "created" or "updated") + + +@receiver(post_delete, sender=UserDatabase) +def handle_userdb_deleted(sender, instance, **kwargs): + """ + Signal handler triggered after the deletion of + :py:class:`userdbs.models.UserDatabase` instances. + + """ + if instance.db_user.db_type == DB_TYPES.mysql: + taskresult = TaskResult.objects.create_task_result( + 'handle_userdb_deleted', + delete_mysql_database.s(instance.db_name, instance.db_user.name), + 'mysql database deletion') + _LOGGER.info( + 'The deletion of MySQL database %s has been requested in task %s', + instance.db_name, taskresult.task_id) + elif instance.db_user.db_type == DB_TYPES.pgsql: + taskresult = TaskResult.objects.create_task_result( + 'handle_userdb_deleted', + delete_pgsql_database.s(instance.db_name), + 'pgsql database deletion') + _LOGGER.info( + 'The deletion of PostgreSQL database %s has been requested in ' + ' task %s', + instance.db_name, taskresult.task_id) + else: + _LOGGER.warning( + 'deleted UserDatabase %s of unknown type %s', + instance.db_name, instance.db_type) + pass + _LOGGER.debug( + 'database %s has been deleted', instance) From 1b30b1a38c1aa0ecf5c4c58f82433caf394d42d5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 7 Dec 2015 00:24:23 +0000 Subject: [PATCH 049/111] Define logging for tests --- gnuviechadmin/gnuviechadmin/settings/test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gnuviechadmin/gnuviechadmin/settings/test.py b/gnuviechadmin/gnuviechadmin/settings/test.py index 6d0a685..a079403 100644 --- a/gnuviechadmin/gnuviechadmin/settings/test.py +++ b/gnuviechadmin/gnuviechadmin/settings/test.py @@ -6,3 +6,17 @@ from .base import * # NOQA PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', ) +LOGGING['handlers'].update({ + 'console': { + 'level': 'ERROR', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + } +}) +LOGGING['loggers'].update(dict( + [(key, {'handlers': ['console'], 'level': 'ERROR', 'propagate': True, }) + for key in [ + 'dashboard', 'domains', 'fileservertasks', 'gvacommon', + 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', + 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', + 'userdbs', 'websites']])) From 085b12641651ab1768a7b287f5a96559c6019f7f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 19 Dec 2015 20:11:23 +0100 Subject: [PATCH 050/111] Update to Django 1.9 - update all dependencies - fix deprecation warnings and errors for Django 1.9 compatibility --- gnuviechadmin/contact_form/urls.py | 7 +-- gnuviechadmin/dashboard/urls.py | 7 +-- gnuviechadmin/domains/models.py | 4 -- gnuviechadmin/domains/tests/test_forms.py | 2 +- gnuviechadmin/domains/tests/test_models.py | 11 +--- gnuviechadmin/domains/urls.py | 7 +-- gnuviechadmin/gnuviechadmin/settings/base.py | 56 +++++++++---------- gnuviechadmin/gnuviechadmin/settings/local.py | 2 +- gnuviechadmin/gnuviechadmin/urls.py | 23 ++++---- gnuviechadmin/hostingpackages/urls.py | 7 +-- gnuviechadmin/managemails/admin.py | 2 +- gnuviechadmin/managemails/tests/test_admin.py | 2 +- gnuviechadmin/managemails/urls.py | 7 +-- gnuviechadmin/osusers/urls.py | 7 +-- gnuviechadmin/templates/account/login.html | 5 +- .../socialaccount/snippets/provider_list.html | 3 +- gnuviechadmin/userdbs/urls.py | 7 +-- gnuviechadmin/websites/urls.py | 7 +-- requirements/base.txt | 21 +++---- requirements/local.txt | 6 +- requirements/test.txt | 2 +- 21 files changed, 85 insertions(+), 110 deletions(-) diff --git a/gnuviechadmin/contact_form/urls.py b/gnuviechadmin/contact_form/urls.py index 6963969..e3f01ca 100644 --- a/gnuviechadmin/contact_form/urls.py +++ b/gnuviechadmin/contact_form/urls.py @@ -4,7 +4,7 @@ URL patterns for the contact_form views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( ContactFormView, @@ -12,8 +12,7 @@ from .views import ( ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', ContactFormView.as_view(), name='contact_form'), url(r'^success/$', ContactSuccessView.as_view(), name='contact_success'), -) +] diff --git a/gnuviechadmin/dashboard/urls.py b/gnuviechadmin/dashboard/urls.py index f7172d8..ffa741b 100644 --- a/gnuviechadmin/dashboard/urls.py +++ b/gnuviechadmin/dashboard/urls.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( IndexView, @@ -8,9 +8,8 @@ from .views import ( ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', IndexView.as_view(), name='dashboard'), url(r'^user/(?P[\w0-9@.+-_]+)/$', UserDashboardView.as_view(), name='customer_dashboard'), -) +] diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index 323d0da..8e80329 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -50,7 +50,6 @@ DNS_TSIG_KEY_ALGORITHMS = Choices( ) -@python_2_unicode_compatible class DomainBase(TimeStampedModel): """ This is the base model for domains. @@ -64,9 +63,6 @@ class DomainBase(TimeStampedModel): class Meta: abstract = True - def __str__(self): - return self.domain - @python_2_unicode_compatible class MailDomain(DomainBase): diff --git a/gnuviechadmin/domains/tests/test_forms.py b/gnuviechadmin/domains/tests/test_forms.py index df93be4..746432f 100644 --- a/gnuviechadmin/domains/tests/test_forms.py +++ b/gnuviechadmin/domains/tests/test_forms.py @@ -9,7 +9,7 @@ from mock import MagicMock, Mock, patch from django.core.urlresolvers import reverse from django.forms import ValidationError from django.test import TestCase -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext as _ from domains.forms import relative_domain_validator, CreateHostingDomainForm diff --git a/gnuviechadmin/domains/tests/test_models.py b/gnuviechadmin/domains/tests/test_models.py index d942b78..abe4614 100644 --- a/gnuviechadmin/domains/tests/test_models.py +++ b/gnuviechadmin/domains/tests/test_models.py @@ -17,7 +17,6 @@ from domains.models import ( DNSRecord, DNSSupermaster, DNSTSIGKey, - DomainBase, HostingDomain, MailDomain, ) @@ -32,13 +31,6 @@ User = get_user_model() TEST_USER = 'test' -class DomainBaseTest(TestCase): - - def test___str__(self): - db = DomainBase(domain='test') - self.assertEqual(str(db), 'test') - - class MailDomainTest(TestCase): def test___str__(self): @@ -48,8 +40,7 @@ class MailDomainTest(TestCase): def test_get_mailaddresses(self): md = MailDomain.objects.create(domain='example.org') from managemails.models import MailAddress - addrmock = MailAddress(localpart='info') - md.mailaddress_set.add(addrmock) + addrmock = MailAddress.objects.create(localpart='info', domain=md) self.assertIn(addrmock, md.get_mailaddresses()) self.assertIn(addrmock, md.mailaddresses) diff --git a/gnuviechadmin/domains/urls.py b/gnuviechadmin/domains/urls.py index d762b84..2d63fe4 100644 --- a/gnuviechadmin/domains/urls.py +++ b/gnuviechadmin/domains/urls.py @@ -4,13 +4,12 @@ This module defines the URL patterns for domain related views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import CreateHostingDomain -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^(?P\d+)/create$', CreateHostingDomain.as_view(), name='create_hosting_domain'), -) +] diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index f5fdf4e..5e81e2c 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -46,9 +46,6 @@ path.append(DJANGO_ROOT) # ######### DEBUG CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug DEBUG = False - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug -TEMPLATE_DEBUG = DEBUG # ######### END DEBUG CONFIGURATION @@ -158,34 +155,31 @@ FIXTURE_DIRS = ( # ######### TEMPLATE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors # noqa -TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.static', - 'django.core.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.core.context_processors.request', - # allauth specific context processors - 'allauth.account.context_processors.account', - 'allauth.socialaccount.context_processors.socialaccount', - # custom context processors - 'gnuviechadmin.context_processors.navigation', - 'gnuviechadmin.context_processors.version_info', -) - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs -TEMPLATE_DIRS = ( - normpath(join(SITE_ROOT, 'templates')), -) +# See: https://docs.djangoproject.com/en/1.9/ref/settings/#std:setting-TEMPLATES # noqa +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + normpath(join(SITE_ROOT, 'templates')), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', + # custom context processors + 'gnuviechadmin.context_processors.navigation', + 'gnuviechadmin.context_processors.version_info', + ], + }, + }, +] # ######### END TEMPLATE CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/local.py b/gnuviechadmin/gnuviechadmin/settings/local.py index b584a58..7fa7460 100644 --- a/gnuviechadmin/gnuviechadmin/settings/local.py +++ b/gnuviechadmin/gnuviechadmin/settings/local.py @@ -13,7 +13,7 @@ from .base import * # NOQA DEBUG = True # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug -TEMPLATE_DEBUG = DEBUG +TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # ######### END DEBUG CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/urls.py b/gnuviechadmin/gnuviechadmin/urls.py index 762d675..cc214b8 100644 --- a/gnuviechadmin/gnuviechadmin/urls.py +++ b/gnuviechadmin/gnuviechadmin/urls.py @@ -1,13 +1,13 @@ from __future__ import absolute_import -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.conf import settings from django.contrib import admin +from django.contrib.flatpages import views admin.autodiscover() -urlpatterns = patterns( - '', +urlpatterns = [ url(r'', include('dashboard.urls')), url(r'^accounts/', include('allauth.urls')), url(r'^database/', include('userdbs.urls')), @@ -16,19 +16,18 @@ urlpatterns = patterns( url(r'^website/', include('websites.urls')), url(r'^mail/', include('managemails.urls')), url(r'^osuser/', include('osusers.urls')), - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^contact/', include('contact_form.urls')), -) -urlpatterns += patterns( - 'django.contrib.flatpages.views', - url(r'^impressum/$', 'flatpage', {'url': '/impressum/'}, name='imprint'), -) + url(r'^impressum/$', views.flatpage, { + 'url': '/impressum/' + }, name='imprint'), +] # Uncomment the next line to serve media files in dev. # urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: # pragma: no cover import debug_toolbar - urlpatterns += patterns('', - url(r'^__debug__/', include(debug_toolbar.urls)), - ) + urlpatterns += [ + url(r'^__debug__/', debug_toolbar.urls), + ] diff --git a/gnuviechadmin/hostingpackages/urls.py b/gnuviechadmin/hostingpackages/urls.py index 150256e..4d0201b 100644 --- a/gnuviechadmin/hostingpackages/urls.py +++ b/gnuviechadmin/hostingpackages/urls.py @@ -4,7 +4,7 @@ This module defines the URL patterns for hosting package related views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( AddHostingOption, @@ -17,8 +17,7 @@ from .views import ( ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^create$', CreateHostingPackage.as_view(), name='create_hosting_package'), url(r'^allpackages/', @@ -35,4 +34,4 @@ urlpatterns = patterns( HostingOptionChoices.as_view(), name='hosting_option_choices'), url(r'^(?P\d+)/add-option/(?P\w+)/(?P\d+)$', AddHostingOption.as_view(), name='add_hosting_option'), -) +] diff --git a/gnuviechadmin/managemails/admin.py b/gnuviechadmin/managemails/admin.py index da12f85..8bb386f 100644 --- a/gnuviechadmin/managemails/admin.py +++ b/gnuviechadmin/managemails/admin.py @@ -32,7 +32,7 @@ class ReadOnlyPasswordHashField(forms.Field): def bound_data(self, data, initial): return initial - def _has_changed(self, initial, data): + def has_changed(self, initial, data): return False diff --git a/gnuviechadmin/managemails/tests/test_admin.py b/gnuviechadmin/managemails/tests/test_admin.py index 91a6808..67496d6 100644 --- a/gnuviechadmin/managemails/tests/test_admin.py +++ b/gnuviechadmin/managemails/tests/test_admin.py @@ -52,7 +52,7 @@ class ReadOnlyPasswordHashFieldTest(TestCase): def test__has_changed(self): field = ReadOnlyPasswordHashField() - self.assertFalse(field._has_changed('new', 'old')) + self.assertFalse(field.has_changed('new', 'old')) class CustomerTestCase(TestCase): diff --git a/gnuviechadmin/managemails/urls.py b/gnuviechadmin/managemails/urls.py index c37a83e..fcd4ef7 100644 --- a/gnuviechadmin/managemails/urls.py +++ b/gnuviechadmin/managemails/urls.py @@ -5,7 +5,7 @@ views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( AddMailAddress, @@ -15,8 +15,7 @@ from .views import ( EditMailAddress, ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^(?P\d+)/mailbox/create$', CreateMailbox.as_view(), name='create_mailbox'), url(r'^(?P\d+)/mailbox/(?P[\w0-9]+)/setpassword$', @@ -29,4 +28,4 @@ urlpatterns = patterns( url(r'^(?P\d+)/mailaddress/(?P[\w0-9-.]+)/(?P\d+)' r'/delete$', DeleteMailAddress.as_view(), name='delete_mailaddress'), -) +] diff --git a/gnuviechadmin/osusers/urls.py b/gnuviechadmin/osusers/urls.py index 52c2657..086a2bf 100644 --- a/gnuviechadmin/osusers/urls.py +++ b/gnuviechadmin/osusers/urls.py @@ -4,7 +4,7 @@ This module defines the URL patterns for operating system user related views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( AddSshPublicKey, @@ -15,8 +15,7 @@ from .views import ( ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^(?P[\w0-9@.+-_]+)/setpassword$', SetOsUserPassword.as_view(), name='set_osuser_password'), url(r'^(?P\d+)/ssh-keys/$', ListSshPublicKeys.as_view(), @@ -27,4 +26,4 @@ urlpatterns = patterns( EditSshPublicKeyComment.as_view(), name='edit_ssh_key_comment'), url(r'^(?P\d+)/ssh-keys/(?P\d+)/delete$', DeleteSshPublicKey.as_view(), name='delete_ssh_key'), -) +] diff --git a/gnuviechadmin/templates/account/login.html b/gnuviechadmin/templates/account/login.html index f3cb8f1..88862b6 100644 --- a/gnuviechadmin/templates/account/login.html +++ b/gnuviechadmin/templates/account/login.html @@ -1,11 +1,12 @@ {% extends "account/base.html" %} -{% load account crispy_forms_tags i18n %} +{% load account socialaccount crispy_forms_tags i18n %} {% block title %}{{ block.super }} - {% trans "Sign In" %}{% endblock title %} {% block page_title %}{% trans "Sign In" %}{% endblock page_title %} {% block content %} -{% if socialaccount.providers %} +{% get_providers as socialaccount_providers %} +{% if socialaccount_providers %}

{% blocktrans with site.name as site_name %}Please sign in with one of your existing third party accounts. Or, sign up for a {{site_name}} account and sign in below:{% endblocktrans %}

diff --git a/gnuviechadmin/templates/socialaccount/snippets/provider_list.html b/gnuviechadmin/templates/socialaccount/snippets/provider_list.html index 238fbd1..ad23f55 100644 --- a/gnuviechadmin/templates/socialaccount/snippets/provider_list.html +++ b/gnuviechadmin/templates/socialaccount/snippets/provider_list.html @@ -1,6 +1,7 @@ {% load socialaccount %} -{% for provider in socialaccount.providers %} +{% get_providers as socialaccount_providers %} +{% for provider in socialaccount_providers %} {% if provider.id == "openid" %} {% for brand in provider.get_brands %}
  • diff --git a/gnuviechadmin/userdbs/urls.py b/gnuviechadmin/userdbs/urls.py index c31cf4d..6aee543 100644 --- a/gnuviechadmin/userdbs/urls.py +++ b/gnuviechadmin/userdbs/urls.py @@ -4,7 +4,7 @@ This module defines the URL patterns for user database views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( AddUserDatabase, @@ -12,12 +12,11 @@ from .views import ( DeleteUserDatabase, ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^(?P\d+)/create$', AddUserDatabase.as_view(), name='add_userdatabase'), url(r'^(?P\d+)/(?P[\w0-9]+)/setpassword', ChangeDatabaseUserPassword.as_view(), name='change_dbuser_password'), url(r'^(?P\d+)/(?P[\w0-9]+)/delete', DeleteUserDatabase.as_view(), name='delete_userdatabase'), -) +] diff --git a/gnuviechadmin/websites/urls.py b/gnuviechadmin/websites/urls.py index 4bcd5e0..1fba405 100644 --- a/gnuviechadmin/websites/urls.py +++ b/gnuviechadmin/websites/urls.py @@ -4,7 +4,7 @@ This module defines the URL patterns for website related views. """ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( AddWebsite, @@ -12,10 +12,9 @@ from .views import ( ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^(?P\d+)/(?P[\w0-9.-]+)/create$', AddWebsite.as_view(), name='add_website'), url(r'^(?P\d+)/(?P[\w0-9.-]+)/(?P\d+)/delete$', DeleteWebsite.as_view(), name='delete_website'), -) +] diff --git a/requirements/base.txt b/requirements/base.txt index 9fb024f..8fc1b75 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,19 +1,20 @@ -Django==1.8.6 +Django==1.9 +curtsies==0.1.21 bpython==0.14.2 django-braces==1.8.1 -django-model-utils==2.3.1 +django-model-utils==2.4 django-crispy-forms==1.5.2 logutils==0.3.3 psycopg2==2.6.1 passlib==1.6.5 -celery==3.1.18 -billiard==3.3.0.20 -kombu==3.0.27 -pytz==2015.6 +celery==3.1.19 +billiard==3.3.0.22 +kombu==3.0.32 +pytz==2015.7 pyaml==15.8.2 -django-allauth==0.21.0 +django-allauth==0.24.1 oauthlib==1.0.3 python-openid==2.2.5 -requests==2.5.1 -requests-oauthlib==0.5.0 -simplejson==3.8.0 +requests==2.9.0 +requests-oauthlib==0.6.0 +simplejson==3.8.1 diff --git a/requirements/local.txt b/requirements/local.txt index e2bf154..a049baf 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -1,7 +1,7 @@ # Local development dependencies go here -r test.txt django-debug-toolbar==1.4 -sqlparse==0.1.16 -Sphinx==1.3.1 +sqlparse==0.1.18 +Sphinx==1.3.3 snowballstemmer==1.2.0 -releases==0.7.0 +releases==1.0.0 diff --git a/requirements/test.txt b/requirements/test.txt index 96baf85..67a8663 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,4 @@ # Test dependencies go here. -r base.txt -coverage==4.0 +coverage==4.0.3 mock==1.3.0 From 6a6009e7f20af6de788bd9f89a736dd5fcdd4cda Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 26 Dec 2015 12:29:22 +0100 Subject: [PATCH 051/111] Fix template error in passwort reset response --- gnuviechadmin/templates/account/password_reset_done.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuviechadmin/templates/account/password_reset_done.html b/gnuviechadmin/templates/account/password_reset_done.html index 5bbc142..000ebae 100644 --- a/gnuviechadmin/templates/account/password_reset_done.html +++ b/gnuviechadmin/templates/account/password_reset_done.html @@ -2,7 +2,7 @@ {% load account i18n %} {% block title %}{{ block.super }} - {% trans "Password Reset" %}{% endblock title %} -{% block page_title %}{% trans "Password Reset" %}{% endblocktrans page_title %} +{% block page_title %}{% trans "Password Reset" %}{% endblock page_title %} {% block content %} {% if user.is_authenticated %} From b8893e92d7d52e01298dfe63daba532fc7fa1b23 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 9 Jan 2016 14:26:52 +0000 Subject: [PATCH 052/111] Add timestamps to task result model --- .../migrations/0003_auto_20160109_1524.py | 31 +++++++++++++++++++ gnuviechadmin/taskresults/models.py | 5 ++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 gnuviechadmin/taskresults/migrations/0003_auto_20160109_1524.py diff --git a/gnuviechadmin/taskresults/migrations/0003_auto_20160109_1524.py b/gnuviechadmin/taskresults/migrations/0003_auto_20160109_1524.py new file mode 100644 index 0000000..665c5f8 --- /dev/null +++ b/gnuviechadmin/taskresults/migrations/0003_auto_20160109_1524.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.1 on 2016-01-09 14:24 +from __future__ import unicode_literals + +from django.db import migrations +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('taskresults', '0002_auto_20151011_2248'), + ] + + operations = [ + migrations.AlterModelOptions( + name='taskresult', + options={'ordering': ['created'], 'verbose_name': 'Task result', 'verbose_name_plural': 'Task results'}, + ), + migrations.AddField( + model_name='taskresult', + name='created', + field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), + ), + migrations.AddField( + model_name='taskresult', + name='modified', + field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), + ), + ] diff --git a/gnuviechadmin/taskresults/models.py b/gnuviechadmin/taskresults/models.py index 399505f..64819eb 100644 --- a/gnuviechadmin/taskresults/models.py +++ b/gnuviechadmin/taskresults/models.py @@ -10,6 +10,8 @@ from django.utils.translation import ugettext as _ from gnuviechadmin.celery import app +from model_utils.models import TimeStampedModel + class TaskResultManager(models.Manager): def create_task_result(self, creator, signature, notes=''): @@ -22,7 +24,7 @@ class TaskResultManager(models.Manager): @python_2_unicode_compatible -class TaskResult(models.Model): +class TaskResult(TimeStampedModel): task_id = models.CharField(_('Task id'), max_length=36) signature = models.TextField(_('Task signature')) creator = models.TextField(_('Task creator')) @@ -36,6 +38,7 @@ class TaskResult(models.Model): class Meta: verbose_name = _('Task result') verbose_name_plural = _('Task results') + ordering = ['created'] def __str__(self): return "{creator} ({task_id}): {finished}".format( From de501cfddebbe28d09dcfbd6794480c5e15a40be Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 9 Jan 2016 14:46:04 +0000 Subject: [PATCH 053/111] Fix Django deprecation warning Django 1.9 deprecates passing a context directly to the render function in template loader. This commit creates a proper template rendering context before rendering the mails sent from the contact form. --- gnuviechadmin/contact_form/forms.py | 11 ++++++++--- gnuviechadmin/templates/contact_form/contact_form.txt | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gnuviechadmin/contact_form/forms.py b/gnuviechadmin/contact_form/forms.py index e023fe7..e531b93 100644 --- a/gnuviechadmin/contact_form/forms.py +++ b/gnuviechadmin/contact_form/forms.py @@ -52,11 +52,16 @@ class ContactForm(forms.Form): self.request, dict(self.cleaned_data, site=site)) def message(self): - return loader.render_to_string(self.template_name, self.get_context()) + context = self.get_context() + template_context = context.flatten() + template_context.update({ + 'remote_ip': context.request.META['REMOTE_ADDR'] + }) + return loader.render_to_string(self.template_name, template_context) def subject(self): - subject = loader.render_to_string( - self.subject_template_name, self.get_context()) + context = self.get_context().flatten() + subject = loader.render_to_string(self.subject_template_name, context) return ''.join(subject.splitlines()) def save(self, fail_silently=False): diff --git a/gnuviechadmin/templates/contact_form/contact_form.txt b/gnuviechadmin/templates/contact_form/contact_form.txt index 6adab33..b015009 100644 --- a/gnuviechadmin/templates/contact_form/contact_form.txt +++ b/gnuviechadmin/templates/contact_form/contact_form.txt @@ -1,4 +1,4 @@ -User {{ name }} <{{ email }}> from IP address {{ request.META.REMOTE_ADDR }} +User {{ name }} <{{ email }}> from IP address {{ remote_ip }} sent the following message via the contact form at {{ site }}{% url 'contact_form' %}: From ed0a93bb3d571f33040723bf2d3669ded9fe7fb8 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 9 Jan 2016 14:47:37 +0000 Subject: [PATCH 054/111] Update dependencies - Update to Django 1.9.1, crispy-forms 1.6.0, kombu 3.0.33, requests 2.9.1 and snowballstemmer 1.2.1 --- requirements/base.txt | 8 ++++---- requirements/local.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 8fc1b75..6ad2297 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,20 +1,20 @@ -Django==1.9 +Django==1.9.1 curtsies==0.1.21 bpython==0.14.2 django-braces==1.8.1 django-model-utils==2.4 -django-crispy-forms==1.5.2 +django-crispy-forms==1.6.0 logutils==0.3.3 psycopg2==2.6.1 passlib==1.6.5 celery==3.1.19 billiard==3.3.0.22 -kombu==3.0.32 +kombu==3.0.33 pytz==2015.7 pyaml==15.8.2 django-allauth==0.24.1 oauthlib==1.0.3 python-openid==2.2.5 -requests==2.9.0 +requests==2.9.1 requests-oauthlib==0.6.0 simplejson==3.8.1 diff --git a/requirements/local.txt b/requirements/local.txt index a049baf..1292b4f 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -3,5 +3,5 @@ django-debug-toolbar==1.4 sqlparse==0.1.18 Sphinx==1.3.3 -snowballstemmer==1.2.0 +snowballstemmer==1.2.1 releases==1.0.0 From a8e28fd59568c749fc6b86bf8ecc64fd5aacd514 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 26 Jan 2016 13:24:22 +0100 Subject: [PATCH 055/111] Update system during provisioning --- salt/roots/base/init.sls | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/salt/roots/base/init.sls b/salt/roots/base/init.sls index db79767..f8edda5 100644 --- a/salt/roots/base/init.sls +++ b/salt/roots/base/init.sls @@ -1,6 +1,6 @@ base-packages: pkg.installed: - - names: + - pkgs: - screen - htop - git @@ -11,3 +11,7 @@ base-packages: - group: vagrant - mode: 0644 - source: salt://base/screenrc + +update-system: + pkg.uptodate: + - refresh: True From e9fc8b7f891e580ff08ef22107e0528bc8c79dd2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 28 Jan 2016 13:44:29 +0000 Subject: [PATCH 056/111] Push coverage for osusers to 100% --- gnuviechadmin/osusers/tests/test_models.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/gnuviechadmin/osusers/tests/test_models.py b/gnuviechadmin/osusers/tests/test_models.py index bb8d5c9..27c3f74 100644 --- a/gnuviechadmin/osusers/tests/test_models.py +++ b/gnuviechadmin/osusers/tests/test_models.py @@ -153,7 +153,10 @@ class AdditionalGroupTest(TestCaseWithCeleryTasks): def test_save_again(self): group2 = Group.objects.create(groupname='test2', gid=1001) TaskResult.objects.all().delete() - group2.save() + addgroup = AdditionalGroup(user=self.user, group=group2) + addgroup.save() + TaskResult.objects.all().delete() + addgroup.save() taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 0) @@ -187,6 +190,19 @@ class GroupTest(TestCaseWithCeleryTasks): def test_save(self): group = Group(gid=10000, groupname='test') self.assertIs(group.save(), group) + taskres = TaskResult.objects.all() + self.assertTrue(len(taskres), 1) + creators = [r.creator for r in taskres] + for tcount, tcreator in [ + (1, 'handle_group_created')]: + self.assertEqual(creators.count(tcreator), tcount) + + def test_save_again(self): + group = Group.objects.create(gid=10000, groupname='test') + taskres = TaskResult.objects.all().delete() + group.save() + taskres = TaskResult.objects.all() + self.assertEqual(len(taskres), 0) def test_delete(self): group = Group.objects.create(gid=10000, groupname='test') From e6c38b632bc2dc0317a2ed8594c2ed68912d59fa Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 28 Jan 2016 16:17:50 +0100 Subject: [PATCH 057/111] Add test for userdbs.views.AddUserDatabase This commit adds tests for userdbs.views.AddUserDatabase, the view itself has been modified so that it is only reachable when the hosting package actually has database options available. --- gnuviechadmin/userdbs/tests/test_views.py | 202 ++++++++++++++++++++++ gnuviechadmin/userdbs/views.py | 4 + 2 files changed, 206 insertions(+) create mode 100644 gnuviechadmin/userdbs/tests/test_views.py diff --git a/gnuviechadmin/userdbs/tests/test_views.py b/gnuviechadmin/userdbs/tests/test_views.py new file mode 100644 index 0000000..2eab856 --- /dev/null +++ b/gnuviechadmin/userdbs/tests/test_views.py @@ -0,0 +1,202 @@ +""" +This module provides tests for :py:mod:`userdbs.views`. + +""" +from __future__ import absolute_import, unicode_literals + +try: + from unittest.mock import patch, MagicMock +except ImportError: + from mock import patch, MagicMock + +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.contrib.auth import get_user_model + +from hostingpackages.models import ( + CustomerHostingPackage, + CustomerUserDatabaseOption, + HostingPackageTemplate, + UserDatabaseOption, +) + +from userdbs.models import DB_TYPES, UserDatabase +from userdbs.views import AddUserDatabase + + +User = get_user_model() + +TEST_USER = 'test' +TEST_PASSWORD = 'secret' +TEST_EMAIL = 'test@example.org' + + +class HostingPackageAwareTestMixin(object): + + def _setup_hosting_package(self, customer): + template = HostingPackageTemplate.objects.create( + name='testpackagetemplate', mailboxcount=10, diskspace=1, + diskspace_unit=0) + package = CustomerHostingPackage.objects.create_from_template( + customer, template, 'testpackage') + with patch('hostingpackages.models.settings') as hmsettings: + hmsettings.OSUSER_DEFAULT_GROUPS = [] + package.save() + return package + + +class CustomerUserDatabaseOptionAwareTestMixin(object): + + def __init__(self, *args, **kwargs): + super(CustomerUserDatabaseOptionAwareTestMixin, self).__init__( + *args, **kwargs) + self._templates = {} + + def _setup_userdatabaseoption(self, number, dbtype): + key = "{}_{}".format(dbtype, number) + if key not in self._templates: + self._templates[key] = UserDatabaseOption.objects.create( + number=number, db_type=dbtype) + return self._templates[key] + + def _create_userdatabase_option(self, number=1, dbtype=DB_TYPES.pgsql): + return CustomerUserDatabaseOption.objects.create( + template=self._setup_userdatabaseoption(number, dbtype), + number=number, db_type=dbtype, hosting_package=self.package) + + +class AddUserDatabaseTest( + HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, + TestCase +): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + + def _get_url(self): + return reverse( + 'add_userdatabase', kwargs={'package': self.package.id}) + + def test_get_anonymous(self): + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user_nodboption(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 400) + + def test_get_regular_user(self): + self._create_userdatabase_option() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user_nodboption(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 400) + + def test_get_staff_user(self): + self._create_userdatabase_option() + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 200) + + def test_get_regular_user_nofree_db(self): + db_option = self._create_userdatabase_option() + UserDatabase.objects.create_userdatabase_with_user( + db_option.db_type, self.package.osuser) + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertEqual(response.status_code, 400) + + def test_get_form_kwargs(self): + db_option = self._create_userdatabase_option() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = AddUserDatabase( + request=MagicMock(), kwargs={'package': str(self.package.pk)}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn('dbtypes', the_kwargs) + self.assertEqual( + the_kwargs['dbtypes'], + [(db_option.db_type, DB_TYPES[db_option.db_type])], + ) + + def test_get_template(self): + self._create_userdatabase_option() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url()) + self.assertTemplateUsed(response, 'userdbs/userdatabase_create.html') + + def test_form_valid_redirect(self): + db_option = self._create_userdatabase_option() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), + data={ + 'db_type': db_option.db_type, 'password1': TEST_PASSWORD, + 'password2': TEST_PASSWORD}) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + db_option = self._create_userdatabase_option() + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(), follow=True, + data={ + 'db_type': db_option.db_type, 'password1': TEST_PASSWORD, + 'password2': TEST_PASSWORD}) + db = UserDatabase.objects.filter( + db_user__osuser=self.package.osuser).get() + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), ( + 'Successfully create new {type} database {dbname} for user ' + '{dbuser}').format( + type=db.db_user.db_type, dbname=db.db_name, + dbuser=db.db_user)) + + +class ChangeDatabaseUserPasswordTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + + def _get_url(self, userdatabase): + return reverse( + 'change_dbuser_password', kwargs={ + 'package': self.package.id, + 'slug': userdatabase.db_name}) + + +class DeleteUserDatabaseTest(HostingPackageAwareTestMixin, TestCase): + + def setUp(self): + self.customer = User.objects.create_user( + username=TEST_USER, password=TEST_PASSWORD) + self.package = self._setup_hosting_package(self.customer) + + def _get_url(self, userdatabase): + return reverse( + 'delete_userdatabase', kwargs={ + 'package': self.package.id, + 'slug': userdatabase.db_name}) diff --git a/gnuviechadmin/userdbs/views.py b/gnuviechadmin/userdbs/views.py index e8794d6..201617e 100644 --- a/gnuviechadmin/userdbs/views.py +++ b/gnuviechadmin/userdbs/views.py @@ -4,6 +4,7 @@ This module defines views for user database handling. """ from __future__ import absolute_import, unicode_literals +from django.core.exceptions import SuspiciousOperation from django.shortcuts import redirect from django.utils.translation import ugettext as _ from django.views.generic.edit import ( @@ -48,6 +49,9 @@ class AddUserDatabase( db_user__db_type=opt['db_type']).count() if dbs_of_type < opt['number']: retval.append((opt['db_type'], DB_TYPES[opt['db_type']])) + if len(retval) < 1: + raise SuspiciousOperation( + _("The hosting package has no database products assigned.")) return retval def get_form_kwargs(self): From 1f700fc06a6763f6d44029ad2a993e0257412cb7 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 09:40:14 +0000 Subject: [PATCH 058/111] Add tests for userdbs.views.DeleteUserDatabase This commit adds tests for the DeleteUserDatabase view and improves the grammar of flash messages of the userdbs.views module. --- gnuviechadmin/userdbs/tests/test_views.py | 61 ++++++++++++++++++++++- gnuviechadmin/userdbs/views.py | 6 +-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/gnuviechadmin/userdbs/tests/test_views.py b/gnuviechadmin/userdbs/tests/test_views.py index 2eab856..a0ca19c 100644 --- a/gnuviechadmin/userdbs/tests/test_views.py +++ b/gnuviechadmin/userdbs/tests/test_views.py @@ -169,7 +169,7 @@ class AddUserDatabaseTest( self.assertEqual( str(messages[0]), ( 'Successfully create new {type} database {dbname} for user ' - '{dbuser}').format( + '{dbuser}.').format( type=db.db_user.db_type, dbname=db.db_name, dbuser=db.db_user)) @@ -188,15 +188,72 @@ class ChangeDatabaseUserPasswordTest(HostingPackageAwareTestMixin, TestCase): 'slug': userdatabase.db_name}) -class DeleteUserDatabaseTest(HostingPackageAwareTestMixin, TestCase): +class DeleteUserDatabaseTest( + HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, + TestCase +): def setUp(self): self.customer = User.objects.create_user( username=TEST_USER, password=TEST_PASSWORD) self.package = self._setup_hosting_package(self.customer) + template = self._create_userdatabase_option() + self.database = UserDatabase.objects.create_userdatabase_with_user( + template.db_type, self.package.osuser) def _get_url(self, userdatabase): return reverse( 'delete_userdatabase', kwargs={ 'package': self.package.id, 'slug': userdatabase.db_name}) + + def test_get_anonymous(self): + response = self.client.get(self._get_url(self.database)) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.database)) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.database)) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.database)) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.database)) + self.assertTemplateUsed( + response, 'userdbs/userdatabase_confirm_delete.html') + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.database)) + self.assertIn('database', response.context) + self.assertEqual(response.context['database'], self.database) + self.assertIn('hostingpackage', response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(self._get_url(self.database)) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(self._get_url(self.database), follow=True) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "Database deleted.") diff --git a/gnuviechadmin/userdbs/views.py b/gnuviechadmin/userdbs/views.py index 201617e..d16eb5a 100644 --- a/gnuviechadmin/userdbs/views.py +++ b/gnuviechadmin/userdbs/views.py @@ -65,7 +65,7 @@ class AddUserDatabase( messages.success( self.request, _('Successfully create new {type} database {dbname} for user ' - '{dbuser}').format( + '{dbuser}.').format( type=userdatabase.db_user.db_type, dbname=userdatabase.db_name, dbuser=userdatabase.db_user) ) @@ -101,7 +101,7 @@ class ChangeDatabaseUserPassword( db_user = form.save() messages.success( self.request, - _('Successfully changed password of database user {dbuser}' + _('Successfully changed password of database user {dbuser}.' ).format(dbuser=db_user.name) ) return redirect(self.get_hosting_package()) @@ -130,6 +130,6 @@ class DeleteUserDatabase( def get_success_url(self): messages.success( self.request, - _('Database deleted'), + _('Database deleted.'), ) return self.get_hosting_package().get_absolute_url() From 0a0524f1f0704b9520e84e35b7326deb9f31f96e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 10:03:24 +0000 Subject: [PATCH 059/111] Add tests for userdbs.views.ChangeDatabaseUserPassword This commit adds tests for the ChangeDatabaseUserPassword view. --- gnuviechadmin/userdbs/tests/test_views.py | 81 +++++++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/gnuviechadmin/userdbs/tests/test_views.py b/gnuviechadmin/userdbs/tests/test_views.py index a0ca19c..89aa381 100644 --- a/gnuviechadmin/userdbs/tests/test_views.py +++ b/gnuviechadmin/userdbs/tests/test_views.py @@ -21,7 +21,7 @@ from hostingpackages.models import ( ) from userdbs.models import DB_TYPES, UserDatabase -from userdbs.views import AddUserDatabase +from userdbs.views import AddUserDatabase, ChangeDatabaseUserPassword User = get_user_model() @@ -174,18 +174,89 @@ class AddUserDatabaseTest( dbuser=db.db_user)) -class ChangeDatabaseUserPasswordTest(HostingPackageAwareTestMixin, TestCase): +class ChangeDatabaseUserPasswordTest( + HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, + TestCase +): def setUp(self): self.customer = User.objects.create_user( username=TEST_USER, password=TEST_PASSWORD) self.package = self._setup_hosting_package(self.customer) + template = self._create_userdatabase_option() + database = UserDatabase.objects.create_userdatabase_with_user( + template.db_type, self.package.osuser) + self.dbuser = database.db_user - def _get_url(self, userdatabase): + def _get_url(self, dbuser): return reverse( 'change_dbuser_password', kwargs={ - 'package': self.package.id, - 'slug': userdatabase.db_name}) + 'package': self.package.id, 'slug': dbuser.name}) + + def test_get_anonymous(self): + response = self.client.get(self._get_url(self.dbuser)) + self.assertEqual(response.status_code, 403) + + def test_get_regular_user(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.dbuser)) + self.assertEqual(response.status_code, 200) + + def test_get_other_regular_user(self): + User.objects.create_user( + 'test2', password=TEST_PASSWORD) + self.client.login(username='test2', password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.dbuser)) + self.assertEqual(response.status_code, 403) + + def test_get_staff_user(self): + User.objects.create_superuser( + 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username='admin', password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.dbuser)) + self.assertEqual(response.status_code, 200) + + def test_get_template(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.dbuser)) + self.assertTemplateUsed( + response, 'userdbs/databaseuser_setpassword.html') + + def test_get_form_kwargs(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + view = ChangeDatabaseUserPassword(request=MagicMock(), kwargs={ + 'package': str(self.package.pk), 'slug': self.dbuser.name}) + the_kwargs = view.get_form_kwargs() + self.assertIn('hostingpackage', the_kwargs) + self.assertEqual(the_kwargs['hostingpackage'], self.package) + + def test_get_context_data(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.get(self._get_url(self.dbuser)) + self.assertIn('dbuser', response.context) + self.assertEqual(response.context['dbuser'], self.dbuser) + self.assertIn('hostingpackage', response.context) + self.assertEqual(response.context['hostingpackage'], self.package) + self.assertIn('customer', response.context) + self.assertEqual(response.context['customer'], self.customer) + + def test_form_valid_redirect(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post(self._get_url(self.dbuser), data={ + 'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + self.assertRedirects(response, self.package.get_absolute_url()) + + def test_form_valid_message(self): + self.client.login(username=TEST_USER, password=TEST_PASSWORD) + response = self.client.post( + self._get_url(self.dbuser), follow=True, data={ + 'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), + 'Successfully changed password of database user {dbuser}.'.format( + dbuser=self.dbuser.name)) class DeleteUserDatabaseTest( From e7006ac4a6b480d4ea8237d7b8b47d1d3744b08a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 10:04:59 +0000 Subject: [PATCH 060/111] Fix encoding name This commit fixes the encoding specification of osusers.tests.test_models. --- gnuviechadmin/osusers/tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuviechadmin/osusers/tests/test_models.py b/gnuviechadmin/osusers/tests/test_models.py index 27c3f74..7286683 100644 --- a/gnuviechadmin/osusers/tests/test_models.py +++ b/gnuviechadmin/osusers/tests/test_models.py @@ -1,4 +1,4 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from __future__ import unicode_literals from datetime import date From 37b18a17af1a4f66eabd7211e251156e61d55d1f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 11:07:41 +0100 Subject: [PATCH 061/111] Update German translation This commit updates the German translation files and adds missing translations. --- .../locale/de/LC_MESSAGES/django.po | 10 +-- .../domains/locale/de/LC_MESSAGES/django.po | 77 ++++++++-------- .../gvacommon/locale/de/LC_MESSAGES/django.po | 2 +- .../locale/de/LC_MESSAGES/django.po | 2 +- .../locale/de/LC_MESSAGES/django.po | 32 +++---- gnuviechadmin/locale/de/LC_MESSAGES/django.po | 56 ++++++------ .../locale/de/LC_MESSAGES/django.po | 46 +++++----- .../osusers/locale/de/LC_MESSAGES/django.po | 90 +++++++++---------- .../locale/de/LC_MESSAGES/django.po | 32 ++++--- .../userdbs/locale/de/LC_MESSAGES/django.po | 49 +++++----- .../websites/locale/de/LC_MESSAGES/django.po | 2 +- 11 files changed, 205 insertions(+), 193 deletions(-) diff --git a/gnuviechadmin/contact_form/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/contact_form/locale/de/LC_MESSAGES/django.po index f559bc8..ffeeaa3 100644 --- a/gnuviechadmin/contact_form/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/contact_form/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: contact_form\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-01 19:02+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-02-01 19:03+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" @@ -19,18 +19,18 @@ msgstr "" "X-Generator: Poedit 1.6.10\n" "X-Poedit-SourceCharset: UTF-8\n" -#: forms.py:27 +#: contact_form/forms.py:27 msgid "Your name" msgstr "Ihr Name" -#: forms.py:28 +#: contact_form/forms.py:28 msgid "Your email address" msgstr "Ihre E-Mailadresse" -#: forms.py:29 +#: contact_form/forms.py:29 msgid "Your message" msgstr "Ihre Nachricht" -#: forms.py:41 +#: contact_form/forms.py:41 msgid "Send message" msgstr "Nachricht senden" diff --git a/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po index 13ab012..4e559af 100644 --- a/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gnuviechadmin domains\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-11-08 12:00+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-11-08 12:02+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" @@ -19,148 +19,149 @@ msgstr "" "X-Generator: Poedit 1.8.6\n" "X-Poedit-SourceCharset: UTF-8\n" -#: apps.py:17 +#: domains/apps.py:17 msgid "Domains" msgstr "Domains" -#: forms.py:30 tests/test_forms.py:24 +#: domains/forms.py:30 domains/tests/test_forms.py:24 msgid "host name too long" msgstr "zu langer Hostname" -#: forms.py:33 tests/test_forms.py:29 tests/test_forms.py:34 -#: tests/test_forms.py:39 tests/test_forms.py:44 +#: domains/forms.py:33 domains/tests/test_forms.py:29 +#: domains/tests/test_forms.py:34 domains/tests/test_forms.py:39 +#: domains/tests/test_forms.py:44 msgid "invalid domain name" msgstr "ungültiger Domainname" -#: forms.py:56 +#: domains/forms.py:56 msgid "Add Hosting Domain" msgstr "Hostingdomain hinzufügen" -#: models.py:17 +#: domains/models.py:17 msgid "Master" msgstr "Master" -#: models.py:18 +#: domains/models.py:18 msgid "Slave" msgstr "Slave" -#: models.py:19 +#: domains/models.py:19 msgid "Native" msgstr "Native" -#: models.py:44 +#: domains/models.py:44 msgid "HMAC MD5" msgstr "HMAC MD5" -#: models.py:45 +#: domains/models.py:45 msgid "HMAC SHA1" msgstr "HMAC SHA1" -#: models.py:46 +#: domains/models.py:46 msgid "HMAC SHA224" msgstr "HMAC SHA224" -#: models.py:47 +#: domains/models.py:47 msgid "HMAC SHA256" msgstr "HMAC SHA256" -#: models.py:48 +#: domains/models.py:48 msgid "HMAC SHA384" msgstr "HMAC SHA384" -#: models.py:49 +#: domains/models.py:49 msgid "HMAC SHA512" msgstr "HMAC SHA512" -#: models.py:59 +#: domains/models.py:58 msgid "domain name" msgstr "Domainname" -#: models.py:61 models.py:259 models.py:308 +#: domains/models.py:60 domains/models.py:258 domains/models.py:308 msgid "customer" msgstr "Kunde" -#: models.py:80 +#: domains/models.py:76 msgid "Mail domain" msgstr "E-Maildomain" -#: models.py:81 +#: domains/models.py:77 msgid "Mail domains" msgstr "E-Maildomains" -#: models.py:125 +#: domains/models.py:121 msgid "mail domain" msgstr "E-Maildomain" -#: models.py:126 +#: domains/models.py:122 msgid "assigned mail domain for this domain" msgstr "zugeordnete E-Maildomain für diese Domain" -#: models.py:132 +#: domains/models.py:128 msgid "Hosting domain" msgstr "Hostingdomain" -#: models.py:133 +#: domains/models.py:129 msgid "Hosting domains" msgstr "Hostingdomains" -#: models.py:172 +#: domains/models.py:169 msgid "DNS domain" msgstr "DNS-Domain" -#: models.py:173 +#: domains/models.py:170 msgid "DNS domains" msgstr "DNS-Domains" -#: models.py:227 +#: domains/models.py:226 msgid "DNS record" msgstr "DNS-Record" -#: models.py:228 +#: domains/models.py:227 msgid "DNS records" msgstr "DNS-Records" -#: models.py:262 +#: domains/models.py:261 msgid "DNS supermaster" msgstr "DNS-Supermaster" -#: models.py:263 +#: domains/models.py:262 msgid "DNS supermasters" msgstr "DNS-Supermasters" -#: models.py:313 +#: domains/models.py:313 msgid "DNS comment" msgstr "DNS-Kommentar" -#: models.py:314 +#: domains/models.py:314 msgid "DNS comments" msgstr "DNS-Kommentare" -#: models.py:351 +#: domains/models.py:351 msgid "DNS domain metadata item" msgstr "DNS-Domainmetadaten-Eintrag" -#: models.py:352 +#: domains/models.py:352 msgid "DNS domain metadata items" msgstr "DNS-Domainmetadaten-Einträge" -#: models.py:385 +#: domains/models.py:385 msgid "DNS crypto key" msgstr "DNS-Cryposchlüssel" -#: models.py:386 +#: domains/models.py:386 msgid "DNS crypto keys" msgstr "DNS-Cryptoschlüssel" -#: models.py:418 +#: domains/models.py:420 msgid "DNS TSIG key" msgstr "DNS-TSIG-Schlüssel" -#: models.py:419 +#: domains/models.py:421 msgid "DNS TSIG keys" msgstr "DNS-TSIG-Schlüssel" -#: views.py:58 +#: domains/views.py:58 #, python-brace-format msgid "Successfully created domain {domainname}" msgstr "Domain {domainname} erfolgreich angelegt" diff --git a/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po index 15de43e..eb13bcc 100644 --- a/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gvacommon\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-01-24 18:25+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" diff --git a/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po index 8fffff1..6b80207 100644 --- a/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gvawebcore\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-01-25 11:49+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" diff --git a/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po index e9f078b..d8edc2e 100644 --- a/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gnuviechadmin hostingpackages\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-01-25 15:49+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" @@ -167,67 +167,67 @@ msgstr "Kundenhostingpakete" msgid "{name} for {customer}" msgstr "{name} für {customer}" -#: hostingpackages/models.py:405 hostingpackages/models.py:427 +#: hostingpackages/models.py:404 hostingpackages/models.py:426 msgid "hosting package" msgstr "Hostingpaket" -#: hostingpackages/models.py:408 +#: hostingpackages/models.py:407 msgid "hosting domain" msgstr "Hostingdomain" -#: hostingpackages/models.py:430 +#: hostingpackages/models.py:429 msgid "customer hosting option" msgstr "kundenspezifische Hostingoption" -#: hostingpackages/models.py:431 +#: hostingpackages/models.py:430 msgid "customer hosting options" msgstr "kundenspezifische Hostingoptionen" -#: hostingpackages/models.py:443 +#: hostingpackages/models.py:442 msgid "disk space option template" msgstr "Speicherplatzoptionsvorlage" -#: hostingpackages/models.py:445 +#: hostingpackages/models.py:444 msgid "The disk space option template that this disk space option is based on" msgstr "" "Die Speicherplatzoptionsvorlage auf der diese Speicherplatzoption aufgebaut " "ist" -#: hostingpackages/models.py:459 +#: hostingpackages/models.py:458 msgid "user database option template" msgstr "Nutzerdatenbankoptionsvorlage" -#: hostingpackages/models.py:461 +#: hostingpackages/models.py:460 msgid "The user database option template that this database option is based on" msgstr "" "Die Nutzerdatenbankoptionsvorlage auf der diese Datenbankoption aufgebaut ist" -#: hostingpackages/models.py:475 +#: hostingpackages/models.py:474 msgid "mailbox option template" msgstr "Postfachoptionsvorlage" -#: hostingpackages/models.py:477 +#: hostingpackages/models.py:476 msgid "The mailbox option template that this mailbox option is based on" msgstr "Die Postfachoptionsvorlage auf der diese Postfachoption aufgebaut ist" -#: hostingpackages/views.py:60 hostingpackages/views.py:93 +#: hostingpackages/views.py:60 hostingpackages/views.py:94 #, python-brace-format msgid "Started setup of new hosting package {name}." msgstr "Einrichtung des Hostingpakets {name} wurde gestartet." -#: hostingpackages/views.py:152 +#: hostingpackages/views.py:186 msgid "Disk space" msgstr "Speicherplatz" -#: hostingpackages/views.py:155 +#: hostingpackages/views.py:189 msgid "Mailboxes" msgstr "Postfächer" -#: hostingpackages/views.py:158 +#: hostingpackages/views.py:192 msgid "Databases" msgstr "Datenbanken" -#: hostingpackages/views.py:228 +#: hostingpackages/views.py:262 #, python-brace-format msgid "Successfully added option {option} to hosting package {package}." msgstr "Option {option} erfolgreich zum Hostingpaket {package} hinzugefügt." diff --git a/gnuviechadmin/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/locale/de/LC_MESSAGES/django.po index d141bce..b67e270 100644 --- a/gnuviechadmin/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gnuviechadmin\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-01 19:04+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-02-01 19:04+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" @@ -181,12 +181,12 @@ msgstr "" "Mailadresse des Benutzers %(user_display)s ist." #: templates/account/login.html:4 templates/account/login.html.py:5 -#: templates/account/login.html:29 templates/base.html:82 +#: templates/account/login.html:30 templates/base.html.py:81 #: templates/registration/login.html:4 msgid "Sign In" msgstr "Anmelden" -#: templates/account/login.html:9 +#: templates/account/login.html:10 #, python-format msgid "" "Please sign in with one\n" @@ -198,11 +198,11 @@ msgstr "" "href=\"%(signup_url)s\">registrieren Sie sich für ein Konto auf " "%(site_name)s und melden Sie sich unten an:" -#: templates/account/login.html:16 +#: templates/account/login.html:17 msgid "or" msgstr "oder" -#: templates/account/login.html:18 +#: templates/account/login.html:19 #, python-format msgid "" "If you have not created an account yet, then please\n" @@ -211,7 +211,7 @@ msgstr "" "Wenn Sie noch kein Konto haben, Registrieren Sie " "sich bitte erst." -#: templates/account/login.html:28 +#: templates/account/login.html:29 msgid "Forgot Password?" msgstr "Passwort vergessen?" @@ -342,12 +342,12 @@ msgstr "Ihr Passwort wurde geändert." msgid "Set Password" msgstr "Passwort setzen" -#: templates/account/signup.html:4 templates/socialaccount/signup.html:4 +#: templates/account/signup.html:4 templates/socialaccount/signup.html.py:4 msgid "Signup" msgstr "Registrieren" #: templates/account/signup.html:5 templates/account/signup.html.py:15 -#: templates/base.html:83 templates/socialaccount/signup.html:5 +#: templates/base.html:82 templates/socialaccount/signup.html.py:5 #: templates/socialaccount/signup.html:15 msgid "Sign Up" msgstr "Registrieren" @@ -425,86 +425,86 @@ msgstr "" "Hinweis: Sie können Ihre E-" "Mailadresse noch ändern." -#: templates/base.html:44 +#: templates/base.html:43 msgid "Dashboard" msgstr "Dashboard" -#: templates/base.html:50 templates/base.html.py:57 +#: templates/base.html:49 templates/base.html.py:56 msgid "Hosting" msgstr "Hosting" -#: templates/base.html:52 +#: templates/base.html:51 #: templates/hostingpackages/customerhostingpackage_list.html:5 #: templates/hostingpackages/customerhostingpackage_list.html:13 msgid "Your hosting packages" msgstr "Ihre Hostingpakete" -#: templates/base.html:53 +#: templates/base.html:52 #: templates/hostingpackages/customerhostingpackage_admin_list.html:3 #: templates/hostingpackages/customerhostingpackage_admin_list.html:4 msgid "All hosting packages" msgstr "Alle Hostingpakete" -#: templates/base.html:60 +#: templates/base.html:59 msgid "Links" msgstr "Links" -#: templates/base.html:62 +#: templates/base.html:61 msgid "Web based mail system" msgstr "Webbasiertes E-Mailsystem" -#: templates/base.html:62 +#: templates/base.html:61 msgid "Webmail" msgstr "Webmail" -#: templates/base.html:63 +#: templates/base.html:62 msgid "phpMyAdmin - MySQL database administration tool" msgstr "phpMyAdmin - MySQL-Datenbankverwaltungswerkzeug" -#: templates/base.html:63 +#: templates/base.html:62 msgid "phpMyAdmin" msgstr "phpMyAdmin" -#: templates/base.html:64 +#: templates/base.html:63 msgid "phpPgAdmin - PostgreSQL database administration tool" msgstr "phpPgAdmin - PostgreSQL-Datenbankverwaltungswerkzeug" -#: templates/base.html:64 +#: templates/base.html:63 msgid "phpPgAdmin" msgstr "phpPgAdmin" -#: templates/base.html:67 +#: templates/base.html:66 msgid "Imprint" msgstr "Impressum" -#: templates/base.html:68 templates/contact_form/contact_form.html:4 +#: templates/base.html:67 templates/contact_form/contact_form.html.py:4 #: templates/contact_form/contact_form.html:5 #: templates/contact_form/contact_success.html:4 #: templates/contact_form/contact_success.html:5 msgid "Contact" msgstr "Kontakt" -#: templates/base.html:73 +#: templates/base.html:72 msgid "My Account" msgstr "Mein Konto" -#: templates/base.html:75 +#: templates/base.html:74 msgid "Admin site" msgstr "Adminsite" -#: templates/base.html:76 +#: templates/base.html:75 msgid "Change Email" msgstr "E-Mail ändern" -#: templates/base.html:77 +#: templates/base.html:76 msgid "Social Accounts" msgstr "Konten in sozialen Netzwerken" -#: templates/base.html:78 +#: templates/base.html:77 msgid "Logout" msgstr "Abmelden" -#: templates/base.html:89 +#: templates/base.html:88 #, python-format msgid "" "Signed in as %(user_display)s" -#: templates/base.html:102 +#: templates/base.html:101 msgid "Close" msgstr "Schließen" diff --git a/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po index 9aefc44..6e53cbc 100644 --- a/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: managemails\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-01-25 22:17+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" @@ -23,7 +23,7 @@ msgstr "" msgid "Passwords don't match" msgstr "Passwörter stimmen nicht überein" -#: managemails/admin.py:21 managemails/tests/test_admin.py:37 +#: managemails/admin.py:21 managemails/tests/test_admin.py:40 msgid "Hash" msgstr "Hash-Code" @@ -47,11 +47,11 @@ msgstr "Deaktivieren" msgid "Mailboxes and Mail Addresses" msgstr "Postfächer und E-Mailadressen" -#: managemails/forms.py:29 managemails/forms.py:120 managemails/models.py:103 +#: managemails/forms.py:29 managemails/forms.py:120 managemails/models.py:120 msgid "Mailbox" msgstr "Postfach" -#: managemails/forms.py:30 managemails/forms.py:124 +#: managemails/forms.py:30 managemails/forms.py:125 msgid "Forwards" msgstr "Weiterleitungen" @@ -67,84 +67,84 @@ msgstr "Passwort setzen" msgid "Mailbox or Forwards" msgstr "Postfach oder Weiterleitungen" -#: managemails/forms.py:128 +#: managemails/forms.py:129 msgid "Please enter one or more email addresses separated by commas." msgstr "" "Bitte geben Sie eine oder mehrere durch Kommata getrennte E-Mailadressen ein." -#: managemails/forms.py:175 +#: managemails/forms.py:176 msgid "Add mail address" msgstr "E-Mailadresse hinzufügen" -#: managemails/forms.py:185 +#: managemails/forms.py:186 msgid "This mail address is already in use." msgstr "Diese E-Mailadresse wird bereits verwendet." -#: managemails/forms.py:193 managemails/forms.py:274 +#: managemails/forms.py:195 managemails/forms.py:277 msgid "No mailbox selected" msgstr "Kein Postfach ausgewählt" -#: managemails/forms.py:196 managemails/forms.py:277 +#: managemails/forms.py:198 managemails/forms.py:280 msgid "No forward addresses selected" msgstr "Keine Weiterleitungsadressen ausgewählt" -#: managemails/forms.py:199 managemails/forms.py:280 +#: managemails/forms.py:202 managemails/forms.py:284 msgid "Illegal choice for target of the mail address" msgstr "Ungültige Auswahl für das Ziel der E-Mailadresse" -#: managemails/forms.py:267 +#: managemails/forms.py:270 msgid "Change mail address targets" msgstr "E-Mailadressziele ändern" -#: managemails/models.py:104 +#: managemails/models.py:121 msgid "Mailboxes" msgstr "Postfächer" -#: managemails/models.py:145 +#: managemails/models.py:164 msgid "local part" msgstr "Lokaler Teil" -#: managemails/models.py:146 +#: managemails/models.py:165 msgid "domain" msgstr "Domain" -#: managemails/models.py:151 +#: managemails/models.py:170 msgid "Mail address" msgstr "E-Mailadresse" -#: managemails/models.py:152 +#: managemails/models.py:171 msgid "Mail addresses" msgstr "E-Mailadressen" -#: managemails/models.py:229 +#: managemails/models.py:253 msgid "mailaddress" msgstr "E-Mailadresse" -#: managemails/models.py:230 +#: managemails/models.py:254 msgid "mailbox" msgstr "Postfach" -#: managemails/views.py:51 +#: managemails/views.py:52 msgid "You are not allowed to add more mailboxes to this hosting package" msgstr "Sie können keine weiteren Postfächer zu diesem Hostingpaket hinzufügen" -#: managemails/views.py:70 +#: managemails/views.py:71 #, python-brace-format msgid "Mailbox {mailbox} created successfully." msgstr "Postfach {mailbox} erfolgreich angelegt." -#: managemails/views.py:105 +#: managemails/views.py:106 #, python-brace-format msgid "Successfully set new password for mailbox {mailbox}." msgstr "" "Für das Postfach {mailbox} wurde erfolgreich ein neues Passwort gesetzt." -#: managemails/views.py:144 +#: managemails/views.py:145 #, python-brace-format msgid "Successfully added mail address {mailaddress}" msgstr "E-Mailadresse {mailaddress} erfolgreich hinzugefügt" -#: managemails/views.py:222 +#: managemails/views.py:223 #, python-brace-format msgid "Successfully updated mail address {mailaddress} targets." msgstr "Ziele der E-Mailadresse {mailaddress} erfolgreich aktualisiert." diff --git a/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po index b2723bd..81dba23 100644 --- a/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: osusers\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-01 02:12+0100\n" -"PO-Revision-Date: 2015-02-01 02:17+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" +"PO-Revision-Date: 2016-01-29 11:07+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" "Language: de\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.10\n" +"X-Generator: Poedit 1.8.6\n" "X-Poedit-SourceCharset: UTF-8\n" #: osusers/admin.py:53 @@ -45,11 +45,11 @@ msgstr "" "Öffentlicher Teil eines SSH-Schlüssels entweder im OpenSSH- oder im RFC-4716-" "Format" -#: osusers/admin.py:359 +#: osusers/admin.py:361 msgid "Delete selected SSH public keys" msgstr "Ausgewählte SSH-Schlüssel löschen" -#: osusers/apps.py:17 +#: osusers/apps.py:18 msgid "Operating System Users and Groups" msgstr "Betriebssystemnutzer- und Gruppen" @@ -69,108 +69,108 @@ msgstr "Passwort setzen" msgid "Add SSH public key" msgstr "SSH-Schlüssel hinzufügen" -#: osusers/forms.py:134 +#: osusers/forms.py:135 msgid "Change Comment" msgstr "Kommentar ändern" -#: osusers/models.py:50 +#: osusers/models.py:34 msgid "You can not use a user's primary group." msgstr "Sie können nicht die primäre Gruppe des Nutzers verwenden." -#: osusers/models.py:80 +#: osusers/models.py:64 msgid "Group name" msgstr "Gruppenname" -#: osusers/models.py:82 +#: osusers/models.py:66 msgid "Group ID" msgstr "Gruppen-ID" -#: osusers/models.py:83 +#: osusers/models.py:67 msgid "Description" msgstr "Beschreibung" -#: osusers/models.py:85 +#: osusers/models.py:69 msgid "Group password" msgstr "Gruppenpasswort" -#: osusers/models.py:90 osusers/models.py:224 +#: osusers/models.py:74 osusers/models.py:201 msgid "Group" msgstr "Gruppe" -#: osusers/models.py:91 +#: osusers/models.py:75 msgid "Groups" msgstr "Gruppen" -#: osusers/models.py:221 +#: osusers/models.py:198 msgid "User name" msgstr "Nutzername" -#: osusers/models.py:223 +#: osusers/models.py:200 msgid "User ID" msgstr "Nutzer-ID" -#: osusers/models.py:225 +#: osusers/models.py:202 msgid "Gecos field" msgstr "GECOS-Feld" -#: osusers/models.py:226 +#: osusers/models.py:203 msgid "Home directory" msgstr "Home-Verzeichnis" -#: osusers/models.py:227 +#: osusers/models.py:204 msgid "Login shell" msgstr "Loginshell" -#: osusers/models.py:233 osusers/models.py:373 osusers/models.py:566 +#: osusers/models.py:210 osusers/models.py:310 osusers/models.py:501 msgid "User" msgstr "Nutzer" -#: osusers/models.py:234 +#: osusers/models.py:211 msgid "Users" msgstr "Nutzer" -#: osusers/models.py:374 +#: osusers/models.py:311 msgid "Encrypted password" msgstr "Verschlüsseltes Passwort" -#: osusers/models.py:376 +#: osusers/models.py:313 msgid "Date of last change" msgstr "Datum der letzten Änderung" -#: osusers/models.py:377 +#: osusers/models.py:314 msgid "This is expressed in days since Jan 1, 1970" msgstr "Ausgedrückt als Tage seit dem 1. Januar 1970" -#: osusers/models.py:380 +#: osusers/models.py:317 msgid "Minimum age" msgstr "Minimales Alter" -#: osusers/models.py:381 +#: osusers/models.py:318 msgid "Minimum number of days before the password can be changed" msgstr "Minmale Anzahl von Tagen bevor das Passwort geändert werden kann" -#: osusers/models.py:385 +#: osusers/models.py:322 msgid "Maximum age" msgstr "Maximales Alter" -#: osusers/models.py:386 +#: osusers/models.py:323 msgid "Maximum number of days after which the password has to be changed" msgstr "" "Maximale Anzahl von Tagen, nach denen das Passwort geändert werden muss" -#: osusers/models.py:390 +#: osusers/models.py:327 msgid "Grace period" msgstr "Duldungsperiode" -#: osusers/models.py:391 +#: osusers/models.py:328 msgid "The number of days before the password is going to expire" msgstr "Anzahl von Tagen nach denen das Passwort verfällt" -#: osusers/models.py:395 +#: osusers/models.py:332 msgid "Inactivity period" msgstr "Inaktivitätsperiode" -#: osusers/models.py:396 +#: osusers/models.py:333 msgid "" "The number of days after the password has expired during which the password " "should still be accepted" @@ -178,53 +178,53 @@ msgstr "" "Die Anzahl von Tagen für die ein verfallenes Passwort noch akzeptiert werden " "soll" -#: osusers/models.py:400 +#: osusers/models.py:337 msgid "Account expiration date" msgstr "Kontoverfallsdatum" -#: osusers/models.py:401 +#: osusers/models.py:338 msgid "" "The date of expiration of the account, expressed as number of days since Jan " "1, 1970" msgstr "Kontoverfallsdatum in Tagen seit dem 1. Januar 1970" -#: osusers/models.py:408 +#: osusers/models.py:345 msgid "Shadow password" msgstr "Shadow-Passwort" -#: osusers/models.py:409 +#: osusers/models.py:346 msgid "Shadow passwords" msgstr "Shadow-Passwörter" -#: osusers/models.py:435 +#: osusers/models.py:372 msgid "Additional group" msgstr "Weitere Gruppe" -#: osusers/models.py:436 +#: osusers/models.py:373 msgid "Additional groups" msgstr "Weitere Gruppen" -#: osusers/models.py:567 +#: osusers/models.py:502 msgid "Algorithm" msgstr "Algorithmus" -#: osusers/models.py:568 +#: osusers/models.py:503 msgid "Key bytes" msgstr "Schlüsselbytes" -#: osusers/models.py:569 +#: osusers/models.py:504 msgid "Base64 encoded key bytes" msgstr "Base64-kodierte Schlüsselbytes" -#: osusers/models.py:570 +#: osusers/models.py:505 msgid "Comment" msgstr "Kommentar" -#: osusers/models.py:575 +#: osusers/models.py:510 msgid "SSH public key" msgstr "Öffentlicher SSH-Schlüssel" -#: osusers/models.py:576 +#: osusers/models.py:511 msgid "SSH public keys" msgstr "Öffentliche SSH-Schlüssel" @@ -235,8 +235,8 @@ msgstr "Für {username} wurde erfolgreich ein neues Passwort gesetzt." #: osusers/views.py:92 #, python-brace-format -msgid "Successfully added new {algorithm} SSH public key" -msgstr "Neuer {algorithm}-SSH-Schlüssel erfolgreich hinzugefügt" +msgid "Successfully added new {algorithm} SSH public key." +msgstr "Neuer {algorithm}-SSH-Schlüssel erfolgreich hinzugefügt." #~ msgid "Passwords don't match" #~ msgstr "Passwörter stimmen nicht überein" diff --git a/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po index 0decaab..104a2ad 100644 --- a/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: gnuviechadmin taskresults\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" -"PO-Revision-Date: 2015-01-17 16:00+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" +"PO-Revision-Date: 2016-01-29 11:07+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" "Language: de\n" @@ -16,33 +16,41 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.10\n" +"X-Generator: Poedit 1.8.6\n" "X-Poedit-SourceCharset: UTF-8\n" -#: taskresults/models.py:22 +#: taskresults/models.py:28 msgid "Task id" msgstr "Task-Id" -#: taskresults/models.py:23 -msgid "Task name" -msgstr "Taskname" +#: taskresults/models.py:29 +msgid "Task signature" +msgstr "Tasksignatur" -#: taskresults/models.py:24 taskresults/models.py:31 +#: taskresults/models.py:30 +msgid "Task creator" +msgstr "Taskersteller" + +#: taskresults/models.py:31 +msgid "Task notes" +msgstr "Tasknotizen" + +#: taskresults/models.py:32 taskresults/models.py:39 msgid "Task result" msgstr "Taskergebnis" -#: taskresults/models.py:26 +#: taskresults/models.py:34 msgid "Task state" msgstr "Taskstatus" -#: taskresults/models.py:32 +#: taskresults/models.py:40 msgid "Task results" msgstr "Taskergebnisse" -#: taskresults/models.py:38 +#: taskresults/models.py:47 msgid "yes" msgstr "ja" -#: taskresults/models.py:38 +#: taskresults/models.py:47 msgid "no" msgstr "nein" diff --git a/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po index 618f3cb..ca534d3 100644 --- a/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: gnuviechadmin userdbs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" -"PO-Revision-Date: 2015-01-26 13:44+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" +"PO-Revision-Date: 2016-01-29 11:06+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" "Language: de\n" @@ -16,14 +16,14 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.10\n" +"X-Generator: Poedit 1.8.6\n" "X-Poedit-SourceCharset: UTF-8\n" #: userdbs/admin.py:158 msgid "Delete selected database users" msgstr "Ausgewählte Datenbanknutzer löschen" -#: userdbs/admin.py:258 +#: userdbs/admin.py:256 msgid "Delete selected user databases" msgstr "Ausgewählte Nutzerdatenbanken löschen" @@ -43,53 +43,56 @@ msgstr "Datenbank anlegen" msgid "Set password" msgstr "Passwort setzen" -#: userdbs/models.py:32 +#: userdbs/models.py:13 msgid "PostgreSQL" msgstr "PostgreSQL" -#: userdbs/models.py:33 +#: userdbs/models.py:14 msgid "MySQL" msgstr "MySQL" -#: userdbs/models.py:106 +#: userdbs/models.py:88 msgid "username" msgstr "Benutzername" -#: userdbs/models.py:108 +#: userdbs/models.py:90 msgid "database type" msgstr "Datenbanktyp" -#: userdbs/models.py:114 userdbs/models.py:250 +#: userdbs/models.py:96 userdbs/models.py:206 msgid "database user" msgstr "Datenbanknutzer" -#: userdbs/models.py:115 +#: userdbs/models.py:97 msgid "database users" msgstr "Datenbanknutzer" -#: userdbs/models.py:249 +#: userdbs/models.py:205 msgid "database name" msgstr "Datenbankname" -#: userdbs/models.py:256 +#: userdbs/models.py:212 msgid "user database" msgstr "Nutzerdatenbank" -#: userdbs/models.py:257 +#: userdbs/models.py:213 msgid "user specific database" msgstr "nutzerspezifische Datenbank" -#: userdbs/views.py:63 -#, python-brace-format -msgid "Successfully create new {type} database {dbname} for user {dbuser}" -msgstr "" -"Neue {type}-Datenbank {dbname} für Benutzer {dbuser} erfolgreich angelegt" +#: userdbs/views.py:54 +msgid "The hosting package has no database products assigned." +msgstr "Dem Hostingpaket sind keine Datenbankprodukte zugewiesen." -#: userdbs/views.py:100 +#: userdbs/views.py:67 #, python-brace-format -msgid "Successfully changed password of database user {dbuser}" +msgid "Successfully create new {type} database {dbname} for user {dbuser}." +msgstr "Neue {type}-Datenbank {dbname} für Benutzer {dbuser} erfolgreich angelegt." + +#: userdbs/views.py:104 +#, python-brace-format +msgid "Successfully changed password of database user {dbuser}." msgstr "Passwort des Datenbanknutzers {dbuser} wurde erfolgreich geändert." -#: userdbs/views.py:129 -msgid "Database deleted" -msgstr "Datenbank gelöscht" +#: userdbs/views.py:133 +msgid "Database deleted." +msgstr "Datenbank gelöscht." diff --git a/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po index c0c1851..ad09e6f 100644 --- a/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: websites gnuviechadmin app\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-27 18:55+0100\n" +"POT-Creation-Date: 2016-01-29 11:04+0100\n" "PO-Revision-Date: 2015-01-27 19:00+0100\n" "Last-Translator: Jan Dittberner \n" "Language-Team: Jan Dittberner \n" From 5b48a3f2dbe2319448b59aaa0d3a5e7d860ab1a9 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 13:26:18 +0000 Subject: [PATCH 062/111] Use gvacommon from own repository This commit removes the included gvacommon copy. Gvacommon has its own development repository and is now added as a dependency via requirements/base.txt. --- gnuviechadmin/gvacommon/.gitignore | 3 -- gnuviechadmin/gvacommon/__init__.py | 0 gnuviechadmin/gvacommon/celeryrouters.py | 15 ------- .../gvacommon/locale/de/LC_MESSAGES/django.po | 24 ----------- gnuviechadmin/gvacommon/viewmixins.py | 42 ------------------- requirements/base.txt | 1 + 6 files changed, 1 insertion(+), 84 deletions(-) delete mode 100644 gnuviechadmin/gvacommon/.gitignore delete mode 100644 gnuviechadmin/gvacommon/__init__.py delete mode 100644 gnuviechadmin/gvacommon/celeryrouters.py delete mode 100644 gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po delete mode 100644 gnuviechadmin/gvacommon/viewmixins.py diff --git a/gnuviechadmin/gvacommon/.gitignore b/gnuviechadmin/gvacommon/.gitignore deleted file mode 100644 index 5f1ace6..0000000 --- a/gnuviechadmin/gvacommon/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.*.swp -*.pyc -.ropeproject/ diff --git a/gnuviechadmin/gvacommon/__init__.py b/gnuviechadmin/gvacommon/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/gnuviechadmin/gvacommon/celeryrouters.py b/gnuviechadmin/gvacommon/celeryrouters.py deleted file mode 100644 index 44c4b4f..0000000 --- a/gnuviechadmin/gvacommon/celeryrouters.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -class GvaRouter(object): - - def route_for_task(self, task, args=None, kwargs=None): - for route in ['ldap', 'file', 'mysql', 'pgsql', 'web']: - if route in task: - return { - 'exchange': route, - 'exchange_type': 'direct', - 'queue': route, - } - return None diff --git a/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po deleted file mode 100644 index eb13bcc..0000000 --- a/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po +++ /dev/null @@ -1,24 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: gvacommon\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-01-29 11:04+0100\n" -"PO-Revision-Date: 2015-01-24 18:25+0100\n" -"Last-Translator: Jan Dittberner \n" -"Language-Team: Jan Dittberner \n" -"Language: de\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.10\n" -"X-Poedit-SourceCharset: UTF-8\n" - -#: gvacommon/viewmixins.py:29 -msgid "You are not allowed to view this page." -msgstr "Sie haben nicht die nötigen Berechtigungen um diese Seite zu sehen." diff --git a/gnuviechadmin/gvacommon/viewmixins.py b/gnuviechadmin/gvacommon/viewmixins.py deleted file mode 100644 index fc7f106..0000000 --- a/gnuviechadmin/gvacommon/viewmixins.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -This module defines mixins for gnuviechadmin views. - -""" -from __future__ import unicode_literals - -from django.http import HttpResponseForbidden -from django.utils.translation import ugettext as _ - -from braces.views import LoginRequiredMixin - - -class StaffOrSelfLoginRequiredMixin(LoginRequiredMixin): - """ - Mixin that makes sure that a user is logged in and matches the current - customer or is a staff user. - - """ - - def dispatch(self, request, *args, **kwargs): - if ( - request.user.is_staff or - request.user == self.get_customer_object() - ): - return super(StaffOrSelfLoginRequiredMixin, self).dispatch( - request, *args, **kwargs - ) - return HttpResponseForbidden( - _('You are not allowed to view this page.') - ) - - def get_customer_object(self): - """ - Views based on this mixin have to implement this method to return - the customer that must be an object of the same class as the - django.contrib.auth user type. - - :return: customer - :rtype: settings.AUTH_USER_MODEL - - """ - raise NotImplemented("subclass has to implement get_customer_object") diff --git a/requirements/base.txt b/requirements/base.txt index 6ad2297..3488e0c 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -18,3 +18,4 @@ python-openid==2.2.5 requests==2.9.1 requests-oauthlib==0.6.0 simplejson==3.8.1 +-e git+https://git.gnuviech-server.de/gvacommon.git@0.2.1#egg=gvacommon From 41fbb58def15612fac8cf75c645e6856d0c0f1cb Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 14:53:12 +0100 Subject: [PATCH 063/111] Switch to AGPLv3+ licensing --- COPYING | 661 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE.txt | 22 -- docs/index.rst | 7 +- 3 files changed, 666 insertions(+), 24 deletions(-) create mode 100644 COPYING delete mode 100644 LICENSE.txt diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 268230d..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2014, 2015 Jan Dittberner. - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/index.rst b/docs/index.rst index a4707d9..c40633d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,9 +12,12 @@ Welcome to gnuviechadmin's documentation! License ------- -gnuviechadmin is licensed under the terms of the MIT license: +gnuviechadmin is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. -.. include:: ../LICENSE.txt +.. include:: ../COPYING :literal: Contents From 201852064618595c74f8778a60e0aa49d308a9fb Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 14:08:01 +0000 Subject: [PATCH 064/111] Start changelog for next release This commit updates the changelog, switches to git code browsing for the release links and uses correct semantic versioning. --- docs/changelog.rst | 12 ++++++++++++ docs/conf.py | 2 +- gnuviechadmin/gnuviechadmin/__init__.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b597907..5e0e720 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,18 @@ Changelog ========= +* :support:`-` switch licensing to AGPLv3+ +* :support:`-` add a Vagrant setup to ease development +* :support:`-` add example provisioning defined as saltstack states +* :feature:`-` let all celery tasks run asynchronously and move task processing + to signal handlers +* :feature:`-` add unit tests for all the code +* :feature:`-` add proper configuration for coverage, flake8 and pep8 +* :feature:`-` update to Django 1.9.1 +* :support:`-` use gvacommon from separate repository +* :feature:`17` add DNS zone management +* :support:`-` update documentation + * :release:`0.11.3 <2015-02-21>` * :bug:`-` fix handling of OpenSSH formatted keys with whitespace in comments * :bug:`-` the ssh key list does not show SSH keys of other users anymore diff --git a/docs/conf.py b/docs/conf.py index 5fdf12e..5968c1e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,7 +37,7 @@ extensions = ['releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx'] # configuration for releases extension releases_issue_uri = 'https://dev.gnuviech-server.de/gva/ticket/%s' -releases_release_uri = 'https://dev.gnuviech-server.de/gva/milestone/%s' +releases_release_uri = 'https://dev.gnuviech-server.de/gva/browser/?rev=%s' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py index 60f195b..df34db3 100644 --- a/gnuviechadmin/gnuviechadmin/__init__.py +++ b/gnuviechadmin/gnuviechadmin/__init__.py @@ -1,4 +1,4 @@ # import celery_app to initialize it from gnuviechadmin.celery import app as celery_app # NOQA -__version__ = '0.12.dev1' +__version__ = '0.12.0.dev1' From a03a00e61b303eefba8fa692dd892a5f49b5f17e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 16:52:14 +0100 Subject: [PATCH 065/111] Add private network for inter VM communication --- Vagrantfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Vagrantfile b/Vagrantfile index cb2e67a..2be1b37 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -31,6 +31,7 @@ Vagrant.configure(2) do |config| # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" + config.vm.network "private_network", ip: "172.16.3.2" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on From f1f0e35ea14ced93f693016de776bd41ee7019a7 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 17:26:23 +0100 Subject: [PATCH 066/111] Update documentation copyright years --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 5968c1e..9fa7d82 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ master_doc = 'index' # General information about the project. project = u'gnuviechadmin' -copyright = u'2014, 2015 Jan Dittberner' +copyright = u'2014, 2015, 2016 Jan Dittberner' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 6147a9006689f7f58b3b4d2afd226d31d26ca8e4 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 29 Jan 2016 18:34:40 +0100 Subject: [PATCH 067/111] Improve salt setup This commit improves the salt setup of the Vagrant box: - Salt output is reduced to log level warning - Hosts entries are created for the internal IPs of all planned gva component VMs - .bashrc and a .bash_functions sourced from it are now managed for the vagrant user - the VM name has been changed to gva.local - recent salt versions do not depend on m2crypto anymore, therefore it is now installed before x509certificate functions are called - the rabbitmq_vhost for gva is now setup before any users are created because the previous implementation was broken with recent salt versions - the gnuviechadmin-locale-data-compile step has been simplified because Django 1.9's compilemessages takes care of recursive .mo file compilation - pillar data has been separated by role (especially queue permissions and credentials) - salt configuration is now unified with gvaldap --- Vagrantfile | 5 +- salt/bootstrap.sh | 1 + salt/pillar/gnuviechadmin/database.sls | 9 +- salt/pillar/gnuviechadmin/database/common.sls | 9 +- salt/pillar/gnuviechadmin/gvaldap.sls | 8 + salt/pillar/gnuviechadmin/init.sls | 7 +- salt/pillar/gnuviechadmin/queues.sls | 139 ++++++++++-------- salt/pillar/gnuviechadmin/queues/cli.sls | 7 + salt/pillar/gnuviechadmin/queues/common.sls | 3 + salt/pillar/gnuviechadmin/queues/gva.sls | 5 + salt/pillar/gnuviechadmin/queues/gvafile.sls | 5 + salt/pillar/gnuviechadmin/queues/gvaldap.sls | 5 + salt/pillar/gnuviechadmin/queues/gvamysql.sls | 5 + salt/pillar/gnuviechadmin/queues/gvapgsql.sls | 5 + salt/pillar/gnuviechadmin/queues/gvaweb.sls | 5 + salt/pillar/gnuviechadmin/webinterface.sls | 9 ++ salt/pillar/top.sls | 5 + salt/roots/base/bash_functions | 25 ++++ salt/roots/base/bashrc | 117 +++++++++++++++ salt/roots/base/init.sls | 13 ++ salt/roots/gnuviechadmin/base.sls | 98 ++++++++++++ salt/roots/gnuviechadmin/bash_functions | 25 ++++ salt/roots/gnuviechadmin/celery.sls | 13 ++ salt/roots/gnuviechadmin/database.sls | 16 +- .../{ => gva}/gnuviechadmin.nginx | 0 .../{gvasettings.sh => gva/settings.sh} | 10 +- salt/roots/gnuviechadmin/gvaldap.sls | 11 ++ .../roots/gnuviechadmin/gvaldap/run_celery.sh | 7 + salt/roots/gnuviechadmin/gvaldap/settings.sh | 14 ++ salt/roots/gnuviechadmin/queues.sls | 35 ++--- salt/roots/gnuviechadmin/vars.sls | 7 + salt/roots/gnuviechadmin/webinterface.sls | 83 ++--------- salt/roots/{base/nginx.sls => nginx/init.sls} | 2 +- salt/roots/{base => nginx}/nginx.conf | 0 salt/roots/webserver/init.sls | 2 +- salt/roots/webserver/sslcert.macros.sls | 1 + 36 files changed, 523 insertions(+), 188 deletions(-) create mode 100644 salt/pillar/gnuviechadmin/gvaldap.sls create mode 100644 salt/pillar/gnuviechadmin/queues/cli.sls create mode 100644 salt/pillar/gnuviechadmin/queues/common.sls create mode 100644 salt/pillar/gnuviechadmin/queues/gva.sls create mode 100644 salt/pillar/gnuviechadmin/queues/gvafile.sls create mode 100644 salt/pillar/gnuviechadmin/queues/gvaldap.sls create mode 100644 salt/pillar/gnuviechadmin/queues/gvamysql.sls create mode 100644 salt/pillar/gnuviechadmin/queues/gvapgsql.sls create mode 100644 salt/pillar/gnuviechadmin/queues/gvaweb.sls create mode 100644 salt/pillar/gnuviechadmin/webinterface.sls create mode 100644 salt/roots/base/bash_functions create mode 100644 salt/roots/base/bashrc create mode 100644 salt/roots/gnuviechadmin/base.sls create mode 100644 salt/roots/gnuviechadmin/bash_functions create mode 100644 salt/roots/gnuviechadmin/celery.sls rename salt/roots/gnuviechadmin/{ => gva}/gnuviechadmin.nginx (100%) rename salt/roots/gnuviechadmin/{gvasettings.sh => gva/settings.sh} (79%) create mode 100644 salt/roots/gnuviechadmin/gvaldap.sls create mode 100644 salt/roots/gnuviechadmin/gvaldap/run_celery.sh create mode 100644 salt/roots/gnuviechadmin/gvaldap/settings.sh create mode 100644 salt/roots/gnuviechadmin/vars.sls rename salt/roots/{base/nginx.sls => nginx/init.sls} (95%) rename salt/roots/{base => nginx}/nginx.conf (100%) diff --git a/Vagrantfile b/Vagrantfile index 2be1b37..4a14dda 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -14,7 +14,7 @@ Vagrant.configure(2) do |config| # boxes at https://atlas.hashicorp.com/search. config.vm.box = "debian/jessie64" - config.vm.hostname = "gva-dev" + config.vm.hostname = "gva.local" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs @@ -60,10 +60,11 @@ Vagrant.configure(2) do |config| config.vm.provision :salt do |salt| salt.bootstrap_script = "salt/bootstrap.sh" - salt.minion_id = "gvadev" + salt.minion_id = "gva.local" salt.masterless = true salt.run_highstate = true salt.verbose = true salt.colorize = true + salt.log_level = "warning" end end diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index 4e85da5..a9921e8 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -27,6 +27,7 @@ EOF cat >/etc/salt/grains <&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + #alias grep='grep --color=auto' + #alias fgrep='fgrep --color=auto' + #alias egrep='egrep --color=auto' +fi + +# colored GCC warnings and errors +#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' + +# some more ls aliases +#alias ll='ls -l' +#alias la='ls -A' +#alias l='ls -CF' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi + +if [ -f ~/.bash_functions ]; then + . ~/.bash_functions +fi diff --git a/salt/roots/base/init.sls b/salt/roots/base/init.sls index f8edda5..3b896aa 100644 --- a/salt/roots/base/init.sls +++ b/salt/roots/base/init.sls @@ -15,3 +15,16 @@ base-packages: update-system: pkg.uptodate: - refresh: True + +/home/vagrant/bin: + file.directory: + - user: vagrant + - group: vagrant + - mode: 0750 + +/home/vagrant/.bashrc: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0644 + - source: salt://base/bashrc diff --git a/salt/roots/gnuviechadmin/base.sls b/salt/roots/gnuviechadmin/base.sls new file mode 100644 index 0000000..b0e8ccb --- /dev/null +++ b/salt/roots/gnuviechadmin/base.sls @@ -0,0 +1,98 @@ +{% from 'gnuviechadmin/vars.sls' import home, gva_component, gva_amqp_user, checkout, appdir, venv %} + +gva.local: + host.present: + - ip: 172.16.3.2 + - names: + - mq + - gva.local + +gvaldap.local: + host.present: + - ip: 172.16.3.3 + +gvafile.local: + host.present: + - ip: 172.16.3.4 + +gvaweb.local: + host.present: + - ip: 172.16.3.5 + +gvamysql.local: + host.present: + - ip: 172.16.3.6 + +gvapgsql.local: + host.present: + - ip: 172.16.3.7 + +gnuviechadmin-packages: + pkg.installed: + - pkgs: + - libyaml-dev + - python-virtualenv + - python-dev + - python-pip + - gettext + +{{ home }}/gvasettings.sh: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0640 + - source: salt://gnuviechadmin/{{ gva_component }}/settings.sh + - template: jinja + - context: + broker_url: {{ 'amqp://%s:%s@mq/%s' % (gva_amqp_user, salt['pillar.get']('gnuviechadmin:queues:users:%s:password' % gva_amqp_user), salt['pillar.get']('gnuviechadmin:queues:vhost')) }} + +gnuviechadmin-venv: + cmd.run: + - name: virtualenv {{ venv }} + - user: vagrant + - group: vagrant + - unless: test -f {{ venv }}/bin/pip + +gnuviechadmin-requires: + cmd.run: + - name: {{ venv }}/bin/pip install -U -r requirements/local.txt && touch {{ venv }}/lastinstall + - user: vagrant + - group: vagrant + - cwd: {{ checkout }} + - require: + - cmd: gnuviechadmin-venv + - pkg: gnuviechadmin-packages + - unless: test -e {{ venv }}/lastinstall && test {{ checkout }}/requirements/local.txt -ot {{ venv }}/lastinstall && test {{ checkout }}/requirements/base.txt -ot {{ venv }}/lastinstall + +gnuviechadmin-dbschema: + cmd.wait: + - name: . {{ home }}/gvasettings.sh ; unset LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME ; {{ venv }}/bin/python manage.py migrate --noinput + - user: vagrant + - group: vagrant + - cwd: {{ appdir }} + - watch: + - cmd: gnuviechadmin-requires + - file: {{ home }}/gvasettings.sh + +gnuviechadmin-locale-data-compile: + cmd.wait: + - name: . {{ home }}/gvasettings.sh ; {{ venv }}/bin/python {{ appdir }}/manage.py compilemessages + - user: vagrant + - group: vagrant + - cwd: {{ appdir }} + - require: + - pkg: gnuviechadmin-packages + - file: {{ home }}/gvasettings.sh + - cmd: gnuviechadmin-venv + +/home/vagrant/.bash_functions: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0644 + - source: salt://base/bash_functions + - template: jinja + - context: + home: {{ home }} + venv: {{ venv }} + appdir: {{ appdir }} diff --git a/salt/roots/gnuviechadmin/bash_functions b/salt/roots/gnuviechadmin/bash_functions new file mode 100644 index 0000000..2c7fd41 --- /dev/null +++ b/salt/roots/gnuviechadmin/bash_functions @@ -0,0 +1,25 @@ +#!/bin/bash + +function devenv +{ + . $HOME/gvasettings.sh + . $HOME/gva-venv/bin/activate + cd /vagrant/gnuviechadmin +} + +function testenv +{ + devenv + export DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE%%.local}.test +} + +function settitle +{ + if [ -n "$STY" ] ; then # We are in a screen session + echo "Setting screen titles to $@" + printf "\033k%s\033\\" "$@" + screen -X eval "at \\# title $@" "shelltitle $@" + else + printf "\033]0;%s\007" "$@" + fi +} diff --git a/salt/roots/gnuviechadmin/celery.sls b/salt/roots/gnuviechadmin/celery.sls new file mode 100644 index 0000000..b27b504 --- /dev/null +++ b/salt/roots/gnuviechadmin/celery.sls @@ -0,0 +1,13 @@ +{% from 'gnuviechadmin/vars.sls' import home, gva_component, venv, appdir %} + +{{ home }}/bin/run_celery.sh: + file.managed: + - user: vagrant + - group: vagrant + - mode: 0750 + - source: salt://gnuviechadmin/{{ gva_component }}/run_celery.sh + - template: jinja + - context: + home: {{ home }} + virtualenv: {{ venv }} + appdir: {{ appdir }} diff --git a/salt/roots/gnuviechadmin/database.sls b/salt/roots/gnuviechadmin/database.sls index 5e9c963..ab57c2a 100644 --- a/salt/roots/gnuviechadmin/database.sls +++ b/salt/roots/gnuviechadmin/database.sls @@ -3,9 +3,9 @@ include: gnuviechadmin-database: postgres_user.present: - - name: {{ salt['pillar.get']('gnuviechadmin-database:owner:user') }} + - name: {{ salt['pillar.get']('gnuviechadmin:database:owner:user') }} - user: postgres - - password: {{ salt['pillar.get']('gnuviechadmin-database:owner:password') }} + - password: {{ salt['pillar.get']('gnuviechadmin:database:owner:password') }} - login: True - createdb: {% if salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') == 'local' %}True {%- else %}False @@ -13,20 +13,20 @@ gnuviechadmin-database: - require: - service: postgresql postgres_database.present: - - name: {{ salt['pillar.get']('gnuviechadmin-database:database') }} + - name: {{ salt['pillar.get']('gnuviechadmin:database:name') }} - user: postgres - - owner: {{ salt['pillar.get']('gnuviechadmin-database:owner:user') }} + - owner: {{ salt['pillar.get']('gnuviechadmin:database:owner:user') }} - encoding: UTF8 - template: template0 - require: - service: postgresql - - postgres_user: {{ salt['pillar.get']('gnuviechadmin-database:owner:user') }} + - postgres_user: {{ salt['pillar.get']('gnuviechadmin:database:owner:user') }} -{% for gnuviechadmin_db_role in salt['pillar.get']('gnuviechadmin-database:users') %} +{% for gnuviechadmin_db_role in salt['pillar.get']('gnuviechadmin:database:users') %} gnuviechadmin-dbuser-{{ gnuviechadmin_db_role }}: postgres_user.present: - - name: {{ salt['pillar.get']('gnuviechadmin-database:users:' + gnuviechadmin_db_role + ':user') }} - - password: {{ salt['pillar.get']('gnuviechadmin-database:users:' + gnuviechadmin_db_role + ':password') }} + - name: {{ salt['pillar.get']('gnuviechadmin:database:users:%s:user' % gnuviechadmin_db_role) }} + - password: {{ salt['pillar.get']('gnuviechadmin:database:users:%s:password' % gnuviechadmin_db_role) }} - login: True - require: - service: postgresql diff --git a/salt/roots/gnuviechadmin/gnuviechadmin.nginx b/salt/roots/gnuviechadmin/gva/gnuviechadmin.nginx similarity index 100% rename from salt/roots/gnuviechadmin/gnuviechadmin.nginx rename to salt/roots/gnuviechadmin/gva/gnuviechadmin.nginx diff --git a/salt/roots/gnuviechadmin/gvasettings.sh b/salt/roots/gnuviechadmin/gva/settings.sh similarity index 79% rename from salt/roots/gnuviechadmin/gvasettings.sh rename to salt/roots/gnuviechadmin/gva/settings.sh index 94b5aab..60bbb98 100644 --- a/salt/roots/gnuviechadmin/gvasettings.sh +++ b/salt/roots/gnuviechadmin/gva/settings.sh @@ -3,11 +3,11 @@ export DJANGO_SETTINGS_MODULE="gnuviechadmin.settings.{{ salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') }}" export GVA_ADMIN_NAME="Jan Dittberner" export GVA_ADMIN_EMAIL="{{ salt['pillar.get']('gnuviechadmin:adminemail') }}" -export GVA_PGSQL_DATABASE="{{ salt['pillar.get']('gnuviechadmin-database:database') }}" -export GVA_PGSQL_USER="{{ salt['pillar.get']('gnuviechadmin-database:owner:user') }}" -export GVA_PGSQL_PASSWORD="{{ salt['pillar.get']('gnuviechadmin-database:owner:password') }}" -export GVA_PGSQL_HOSTNAME="{{ salt['pillar.get']('gnuviechadmin-database:hostname') }}" -export GVA_PGSQL_PORT={{ salt['pillar.get']('gnuviechadmin-database:port') }} +export GVA_PGSQL_DATABASE="{{ salt['pillar.get']('gnuviechadmin:database:name') }}" +export GVA_PGSQL_USER="{{ salt['pillar.get']('gnuviechadmin:database:owner:user') }}" +export GVA_PGSQL_PASSWORD="{{ salt['pillar.get']('gnuviechadmin:database:owner:password') }}" +export GVA_PGSQL_HOSTNAME="{{ salt['pillar.get']('gnuviechadmin:database:host') }}" +export GVA_PGSQL_PORT={{ salt['pillar.get']('gnuviechadmin:database:port') }} export GVA_DOMAIN_NAME="{{ salt['pillar.get']('gnuviechadmin:domainname') }}" export GVA_SITE_NAME="{{ salt['pillar.get']('gnuviechadmin:sitename') }}" export GVA_SITE_SECRET="{{ salt['grains.get_or_set_hash']('gnuviechadmin:SECRET_KEY', 50) }}" diff --git a/salt/roots/gnuviechadmin/gvaldap.sls b/salt/roots/gnuviechadmin/gvaldap.sls new file mode 100644 index 0000000..f600b9f --- /dev/null +++ b/salt/roots/gnuviechadmin/gvaldap.sls @@ -0,0 +1,11 @@ +include: + - gnuviechadmin.base + - gnuviechadmin.celery + +gvaldap-packages: + pkg.installed: + - pkgs: + - libldap2-dev + - libsasl2-dev + - require_in: + - pkg: gnuviechadmin-packages diff --git a/salt/roots/gnuviechadmin/gvaldap/run_celery.sh b/salt/roots/gnuviechadmin/gvaldap/run_celery.sh new file mode 100644 index 0000000..eac31c8 --- /dev/null +++ b/salt/roots/gnuviechadmin/gvaldap/run_celery.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -ex + +. {{ home }}/gvasettings.sh +cd {{ appdir }} +{{ virtualenv }}/bin/celery worker -A gvaldap -Q ldap --loglevel=INFO diff --git a/salt/roots/gnuviechadmin/gvaldap/settings.sh b/salt/roots/gnuviechadmin/gvaldap/settings.sh new file mode 100644 index 0000000..e99308c --- /dev/null +++ b/salt/roots/gnuviechadmin/gvaldap/settings.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +export DJANGO_SETTINGS_MODULE="gvaldap.settings.{{ salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') }}" +export GVALDAP_ADMIN_NAME="Jan Dittberner" +export GVALDAP_ADMIN_EMAIL="{{ salt['pillar.get']('gnuviechadmin-gvaldap:admin_email') }}" +export GVALDAP_LDAP_URL="{{ salt['pillar.get']('gnuviechadmin-gvaldap:ldap_url') }}" +export GVALDAP_LDAP_USER="{{ salt['pillar.get']('gnuviechadmin-gvaldap:ldap_user') }}" +export GVALDAP_LDAP_PASSWORD="{{ salt['pillar.get']('gnuviechadmin-gvaldap:ldap_password' ) }}" +export GVALDAP_BASEDN_GROUP="{{ salt['pillar.get']('gnuviechadmin-gvaldap:basedn_group') }}" +export GVALDAP_BASEDN_USER="{{ salt['pillar.get']('gnuviechadmin-gvaldap:basedn_user') }}" +export GVALDAP_SECRETKEY="{{ salt['grains.get_or_set_hash']('gnuviechadmin-gvaldap:SECRET_KEY', 50) }}" +export GVALDAP_BROKER_URL="{{ broker_url }}" +export GVALDAP_ALLOWED_HOSTS="{{ salt['pillar.get']('gnuviechadmin-gvaldap:allowed_hosts') }}" +export GVALDAP_SERVER_EMAIL="{{ salt['pillar.get']('gnuviechadmin-gvaldap:server_email') }}" diff --git a/salt/roots/gnuviechadmin/queues.sls b/salt/roots/gnuviechadmin/queues.sls index e529dda..5462fb7 100644 --- a/salt/roots/gnuviechadmin/queues.sls +++ b/salt/roots/gnuviechadmin/queues.sls @@ -1,37 +1,30 @@ include: - rabbitmq-server -gnuviechadmin-queues: - rabbitmq_user.present: - - name: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} - - password: {{ salt['pillar.get']('gnuviechadmin-queues:owner:password') }} - - tags: - - administrator - - perms: - - {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }}: - - '.*' - - '.*' - - '.*' +gnuviechadmin-queue-vhost: rabbitmq_vhost.present: - - name: {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} - - owner: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} - - require: - - rabbitmq_user: {{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }} + - name: {{ salt['pillar.get']('gnuviechadmin:queues:vhost') }} -{% for user in salt['pillar.get']('gnuviechadmin-queues:users') %} +{% for user in salt['pillar.get']('gnuviechadmin:queues:users') %} gnuviechadmin-queue-user-{{ user }}: rabbitmq_user.present: - name: {{ user }} - - password: {{ salt['pillar.get']('gnuviechadmin-queues:users:%s:password' % user) }} -{% if salt['pillar.get']('gnuviechadmin-queues:users:%s:perms' % user) %} + - password: {{ salt['pillar.get']('gnuviechadmin:queues:users:%s:password' % user) }} +{% if salt['pillar.get']('gnuviechadmin:queues:users:%s:perms' % user) %} - perms: -{% for vhost, perms in salt['pillar.get']('gnuviechadmin-queues:users:%s:perms' % user).iteritems() %} +{% for vhost, perms in salt['pillar.get']('gnuviechadmin:queues:users:%s:perms' % user).iteritems() %} - {{ vhost }}: - {{ perms[0] }} - {{ perms[1] }} - {{ perms[2] }} {% endfor %} {% endif %} - - require: - - rabbitmq_vhost: {{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} +{% if salt['pillar.get']('gnuviechadmin:queues:users:%s:tags' % user) %} + - tags: +{% for tag in salt['pillar.get']('gnuviechadmin:queues:users:%s:tags' % user) %} + - {{ tag }} +{% endfor %} +{% endif %} + - require: + - rabbitmq_vhost: {{ salt['pillar.get']('gnuviechadmin:queues:vhost') }} {% endfor %} diff --git a/salt/roots/gnuviechadmin/vars.sls b/salt/roots/gnuviechadmin/vars.sls new file mode 100644 index 0000000..6ad1aa2 --- /dev/null +++ b/salt/roots/gnuviechadmin/vars.sls @@ -0,0 +1,7 @@ +{% set home = '/home/vagrant' %} +{% set venv = home + '/gva-venv' %} +{% set checkout = '/vagrant' %} +{% set gva_component = salt['pillar.get']('gnuviechadmin:component:name') %} +{% set gva_amqp_user = salt['pillar.get']('gnuviechadmin:component:amqp_user') %} +{% set python_module = salt['pillar.get']('gnuviechadmin:component:python_module', gva_component) %} +{% set appdir = checkout + '/' + python_module %} diff --git a/salt/roots/gnuviechadmin/webinterface.sls b/salt/roots/gnuviechadmin/webinterface.sls index 0c2d5be..f94c060 100644 --- a/salt/roots/gnuviechadmin/webinterface.sls +++ b/salt/roots/gnuviechadmin/webinterface.sls @@ -1,92 +1,27 @@ include: + - gnuviechadmin.base - webserver -mq: - host.present: - - ip: 127.0.0.1 - -gnuviechadmin-packages: +libpq-dev: pkg.installed: - - names: - - libpq-dev - - libyaml-dev - - python-virtualenv - - python-dev - - python-pip - - gettext + - require_in: + - pkg: gnuviechadmin-packages + +python-m2crypto: + pkg.installed: + - reload_modules: true {% import "webserver/sslcert.macros.sls" as sslcert %} -{% set venv = salt['pillar.get']('gnuviechadmin:virtualenv') %} -{% set checkout = '/vagrant' %} -{% set home = '/home/vagrant' %} -{% set appdir = checkout + '/gnuviechadmin' %} {% set domainname = salt['pillar.get']('gnuviechadmin:domainname') %} {{ sslcert.key_cert(domainname) }} -{{ venv }}: - file.directory: - - user: vagrant - - group: vagrant - - require: - - cmd: gnuviechadmin-venv - -{{ home }}/gvasettings.sh: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0640 - - source: salt://gnuviechadmin/gvasettings.sh - - template: jinja - - context: - broker_url: amqp://{{ salt['pillar.get']('gnuviechadmin-queues:owner:user') }}:{{ salt['pillar.get']('gnuviechadmin-queues:owner:password') }}@mq/{{ salt['pillar.get']('gnuviechadmin-queues:vhost') }} - -gnuviechadmin-venv: - cmd.run: - - name: virtualenv {{ venv }} - - user: vagrant - - group: vagrant - - unless: test -f {{ venv }}/bin/pip - -gnuviechadmin-requires: - cmd.run: - - name: {{ venv }}/bin/pip install -U -r requirements/local.txt && touch {{ venv }}/lastinstall - - user: vagrant - - group: vagrant - - cwd: {{ checkout }} - - require: - - file: {{ venv }} - - pkg: python-dev - - pkg: libpq-dev - - unless: test -e {{ venv }}/lastinstall && test {{ checkout }}/requirements/local.txt -ot {{ venv }}/lastinstall && test {{ checkout }}/requirements/base.txt -ot {{ venv }}/lastinstall - -gnuviechadmin-dbschema: - cmd.wait: - - name: . {{ home }}/gvasettings.sh ; unset LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME ; {{ venv }}/bin/python manage.py migrate --noinput - - user: vagrant - - group: vagrant - - cwd: {{ appdir }} - - watch: - - cmd: gnuviechadmin-requires - - file: {{ home }}/gvasettings.sh - -gnuviechadmin-locale-data-compile: - cmd.wait: - - name: . {{ home }}/gvasettings.sh ; find {{ appdir }} -type d -name 'locale' | while read dir; do cd $(dirname "$dir") ; {{ venv }}/bin/python {{ appdir }}/manage.py compilemessages ; done - - user: vagrant - - group: vagrant - - cwd: {{ appdir }} - - require: - - pkg: gettext - - file: {{ home }}/gvasettings.sh - - file: {{ venv }} - /etc/nginx/sites-available/{{ domainname }}: file.managed: - user: root - group: root - mode: 0640 - - source: salt://gnuviechadmin/gnuviechadmin.nginx + - source: salt://gnuviechadmin/gva/gnuviechadmin.nginx - template: jinja - context: domainname: {{ domainname }} diff --git a/salt/roots/base/nginx.sls b/salt/roots/nginx/init.sls similarity index 95% rename from salt/roots/base/nginx.sls rename to salt/roots/nginx/init.sls index 21afb3a..cd47736 100644 --- a/salt/roots/base/nginx.sls +++ b/salt/roots/nginx/init.sls @@ -11,7 +11,7 @@ nginx-common: /etc/nginx/nginx.conf: file.managed: - - source: salt://base/nginx.conf + - source: salt://nginx/nginx.conf - user: root - group: root - mode: 0644 diff --git a/salt/roots/base/nginx.conf b/salt/roots/nginx/nginx.conf similarity index 100% rename from salt/roots/base/nginx.conf rename to salt/roots/nginx/nginx.conf diff --git a/salt/roots/webserver/init.sls b/salt/roots/webserver/init.sls index 0fc0155..59fad3b 100644 --- a/salt/roots/webserver/init.sls +++ b/salt/roots/webserver/init.sls @@ -1,5 +1,5 @@ include: - - base.nginx + - nginx /etc/nginx/conf.d/logformat.conf: file.managed: diff --git a/salt/roots/webserver/sslcert.macros.sls b/salt/roots/webserver/sslcert.macros.sls index 528e65d..e3bf201 100644 --- a/salt/roots/webserver/sslcert.macros.sls +++ b/salt/roots/webserver/sslcert.macros.sls @@ -23,6 +23,7 @@ - require: - file: {{ nginx_ssl_certdir }} - cmd: {{ certfile }} + - pkg: python-m2crypto - require_in: - file: /etc/nginx/sites-available/{{ domain_name }} - service: nginx From e0449148a72a5c7b21377c045f4c138c39a5787c Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 10:42:45 +0000 Subject: [PATCH 068/111] Move salt states and pillar data to separate repository --- Vagrantfile | 4 +- salt/grains | 0 salt/minion | 11 -- salt/pillar/gnuviechadmin/database.sls | 8 -- salt/pillar/gnuviechadmin/database/common.sls | 5 - salt/pillar/gnuviechadmin/gvaldap.sls | 8 -- salt/pillar/gnuviechadmin/init.sls | 16 --- salt/pillar/gnuviechadmin/queues.sls | 77 ------------ salt/pillar/gnuviechadmin/queues/cli.sls | 7 -- salt/pillar/gnuviechadmin/queues/common.sls | 3 - salt/pillar/gnuviechadmin/queues/gva.sls | 5 - salt/pillar/gnuviechadmin/queues/gvafile.sls | 5 - salt/pillar/gnuviechadmin/queues/gvaldap.sls | 5 - salt/pillar/gnuviechadmin/queues/gvamysql.sls | 5 - salt/pillar/gnuviechadmin/queues/gvapgsql.sls | 5 - salt/pillar/gnuviechadmin/queues/gvaweb.sls | 5 - salt/pillar/gnuviechadmin/webinterface.sls | 9 -- salt/pillar/top.sls | 8 -- salt/roots/_states/rsa_key.py | 117 ------------------ salt/roots/_states/x509_certificate.py | 61 --------- salt/roots/base/bash_functions | 25 ---- salt/roots/base/bashrc | 117 ------------------ salt/roots/base/init.sls | 30 ----- salt/roots/base/screenrc | 14 --- salt/roots/gnuviechadmin/base.sls | 98 --------------- salt/roots/gnuviechadmin/bash_functions | 25 ---- salt/roots/gnuviechadmin/celery.sls | 13 -- salt/roots/gnuviechadmin/database.sls | 33 ----- .../gnuviechadmin/gva/gnuviechadmin.nginx | 27 ---- salt/roots/gnuviechadmin/gva/settings.sh | 24 ---- salt/roots/gnuviechadmin/gvaldap.sls | 11 -- .../roots/gnuviechadmin/gvaldap/run_celery.sh | 7 -- salt/roots/gnuviechadmin/gvaldap/settings.sh | 14 --- salt/roots/gnuviechadmin/queues.sls | 30 ----- salt/roots/gnuviechadmin/vars.sls | 7 -- salt/roots/gnuviechadmin/webinterface.sls | 39 ------ salt/roots/nginx/init.sls | 38 ------ salt/roots/nginx/nginx.conf | 49 -------- salt/roots/postgresql-server/init.sls | 9 -- salt/roots/rabbitmq-server/init.sls | 17 --- salt/roots/top.sls | 11 -- salt/roots/vim/init.sls | 15 --- salt/roots/vim/vimrc | 34 ----- salt/roots/webserver/init.sls | 50 -------- salt/roots/webserver/nginx-logformat.conf | 4 - salt/roots/webserver/nginx-security.conf | 19 --- salt/roots/webserver/nginx-ssl.conf | 15 --- salt/roots/webserver/sslcert.macros.sls | 30 ----- 48 files changed, 2 insertions(+), 1167 deletions(-) delete mode 100644 salt/grains delete mode 100644 salt/minion delete mode 100644 salt/pillar/gnuviechadmin/database.sls delete mode 100644 salt/pillar/gnuviechadmin/database/common.sls delete mode 100644 salt/pillar/gnuviechadmin/gvaldap.sls delete mode 100644 salt/pillar/gnuviechadmin/init.sls delete mode 100644 salt/pillar/gnuviechadmin/queues.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/cli.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/common.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/gva.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/gvafile.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/gvaldap.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/gvamysql.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/gvapgsql.sls delete mode 100644 salt/pillar/gnuviechadmin/queues/gvaweb.sls delete mode 100644 salt/pillar/gnuviechadmin/webinterface.sls delete mode 100644 salt/pillar/top.sls delete mode 100644 salt/roots/_states/rsa_key.py delete mode 100644 salt/roots/_states/x509_certificate.py delete mode 100644 salt/roots/base/bash_functions delete mode 100644 salt/roots/base/bashrc delete mode 100644 salt/roots/base/init.sls delete mode 100644 salt/roots/base/screenrc delete mode 100644 salt/roots/gnuviechadmin/base.sls delete mode 100644 salt/roots/gnuviechadmin/bash_functions delete mode 100644 salt/roots/gnuviechadmin/celery.sls delete mode 100644 salt/roots/gnuviechadmin/database.sls delete mode 100644 salt/roots/gnuviechadmin/gva/gnuviechadmin.nginx delete mode 100644 salt/roots/gnuviechadmin/gva/settings.sh delete mode 100644 salt/roots/gnuviechadmin/gvaldap.sls delete mode 100644 salt/roots/gnuviechadmin/gvaldap/run_celery.sh delete mode 100644 salt/roots/gnuviechadmin/gvaldap/settings.sh delete mode 100644 salt/roots/gnuviechadmin/queues.sls delete mode 100644 salt/roots/gnuviechadmin/vars.sls delete mode 100644 salt/roots/gnuviechadmin/webinterface.sls delete mode 100644 salt/roots/nginx/init.sls delete mode 100644 salt/roots/nginx/nginx.conf delete mode 100644 salt/roots/postgresql-server/init.sls delete mode 100644 salt/roots/rabbitmq-server/init.sls delete mode 100644 salt/roots/top.sls delete mode 100644 salt/roots/vim/init.sls delete mode 100644 salt/roots/vim/vimrc delete mode 100644 salt/roots/webserver/init.sls delete mode 100644 salt/roots/webserver/nginx-logformat.conf delete mode 100644 salt/roots/webserver/nginx-security.conf delete mode 100644 salt/roots/webserver/nginx-ssl.conf delete mode 100644 salt/roots/webserver/sslcert.macros.sls diff --git a/Vagrantfile b/Vagrantfile index 4a14dda..2e0add6 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -43,8 +43,8 @@ Vagrant.configure(2) do |config| # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. # config.vm.synced_folder "../data", "/vagrant_data" - config.vm.synced_folder "salt/roots/", "/srv/salt/" - config.vm.synced_folder "salt/pillar/", "/srv/pillar/" + config.vm.synced_folder "../gvasalt/states/", "/srv/salt/" + config.vm.synced_folder "../gvasalt/pillar/", "/srv/pillar/" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. diff --git a/salt/grains b/salt/grains deleted file mode 100644 index e69de29..0000000 diff --git a/salt/minion b/salt/minion deleted file mode 100644 index 834f627..0000000 --- a/salt/minion +++ /dev/null @@ -1,11 +0,0 @@ -file_client: local - -file_roots: - base: - - /srv/salt/ - -pillar_roots: - base: - - /srv/pillar - -log_file: file:///dev/log diff --git a/salt/pillar/gnuviechadmin/database.sls b/salt/pillar/gnuviechadmin/database.sls deleted file mode 100644 index 0732ece..0000000 --- a/salt/pillar/gnuviechadmin/database.sls +++ /dev/null @@ -1,8 +0,0 @@ -include: - - gnuviechadmin.database.common - -gnuviechadmin: - database: - owner: - user: gnuviechadmin - password: k4TG0oWeJ08urz697GVfavjK diff --git a/salt/pillar/gnuviechadmin/database/common.sls b/salt/pillar/gnuviechadmin/database/common.sls deleted file mode 100644 index 5a3bbee..0000000 --- a/salt/pillar/gnuviechadmin/database/common.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - database: - name: gnuviechadmin - host: localhost - port: 5432 diff --git a/salt/pillar/gnuviechadmin/gvaldap.sls b/salt/pillar/gnuviechadmin/gvaldap.sls deleted file mode 100644 index 45665fd..0000000 --- a/salt/pillar/gnuviechadmin/gvaldap.sls +++ /dev/null @@ -1,8 +0,0 @@ -include: - - gnuviechadmin.queues.common - - gnuviechadmin.queues.gvaldap - -gnuviechadmin: - component: - name: gvaldap - amqp_user: ldap diff --git a/salt/pillar/gnuviechadmin/init.sls b/salt/pillar/gnuviechadmin/init.sls deleted file mode 100644 index d98250c..0000000 --- a/salt/pillar/gnuviechadmin/init.sls +++ /dev/null @@ -1,16 +0,0 @@ -gnuviechadmin: - deploymenttype: local - mailfrom: admin@gnuviech-server.de - adminemail: admin@gnuviech-server.de - sitename: Gnuviech Customer Self Service - domainname: localhost - devinstance: True - minosuid: 10000 - minosgid: 10000 - osuserprefix: usr - osuserhomedirbase: /home - osuserdefaultshell: /usr/bin/rssh - uploadserver: gvafile.local - webmail_url: https://webmail.example.com/ - phpmyadmin_url: https://phpmyadmin.example.com/ - phppgadmin_url: https://phppgadmin.example.com/ diff --git a/salt/pillar/gnuviechadmin/queues.sls b/salt/pillar/gnuviechadmin/queues.sls deleted file mode 100644 index 881ed57..0000000 --- a/salt/pillar/gnuviechadmin/queues.sls +++ /dev/null @@ -1,77 +0,0 @@ -include: - - gnuviechadmin.queues.common - - gnuviechadmin.queues.gvaldap - - gnuviechadmin.queues.gvafile - - gnuviechadmin.queues.cli - - gnuviechadmin.queues.gva - - gnuviechadmin.queues.gvamysql - - gnuviechadmin.queues.gvapgsql - - gnuviechadmin.queues.gvaweb - -gnuviechadmin: - queues: - users: - ldap: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - file: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - gva: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - mysql: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - pgsql: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - web: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - cli: - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - quotajob: - perms: - '/gnuviechadmin': - - '^quotatool$' - - '^quotatool$' - - '^quotatool|amq.default$' - tags: - admin: - password: MmE3Iwylj8Sgy46Z - perms: - '/gnuviechadmin': - - '.*' - - '.*' - - '.*' - tags: - - administrator diff --git a/salt/pillar/gnuviechadmin/queues/cli.sls b/salt/pillar/gnuviechadmin/queues/cli.sls deleted file mode 100644 index d1eb2b3..0000000 --- a/salt/pillar/gnuviechadmin/queues/cli.sls +++ /dev/null @@ -1,7 +0,0 @@ -gnuviechadmin: - queues: - users: - cli: - password: bUQ4QEB8yQEfsB0i - quotajob: - password: TaNoj2H3ZNDIz1rt diff --git a/salt/pillar/gnuviechadmin/queues/common.sls b/salt/pillar/gnuviechadmin/queues/common.sls deleted file mode 100644 index db48e00..0000000 --- a/salt/pillar/gnuviechadmin/queues/common.sls +++ /dev/null @@ -1,3 +0,0 @@ -gnuviechadmin: - queues: - vhost: /gnuviechadmin diff --git a/salt/pillar/gnuviechadmin/queues/gva.sls b/salt/pillar/gnuviechadmin/queues/gva.sls deleted file mode 100644 index e34986f..0000000 --- a/salt/pillar/gnuviechadmin/queues/gva.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - queues: - users: - gva: - password: Y5KmkIou7o8J9jV5 diff --git a/salt/pillar/gnuviechadmin/queues/gvafile.sls b/salt/pillar/gnuviechadmin/queues/gvafile.sls deleted file mode 100644 index 79404a1..0000000 --- a/salt/pillar/gnuviechadmin/queues/gvafile.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - queues: - users: - file: - password: StR6EgMjLyNGP1F8 diff --git a/salt/pillar/gnuviechadmin/queues/gvaldap.sls b/salt/pillar/gnuviechadmin/queues/gvaldap.sls deleted file mode 100644 index 8fdc619..0000000 --- a/salt/pillar/gnuviechadmin/queues/gvaldap.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - queues: - users: - ldap: - password: tl0ALc4aQBAl0W2e diff --git a/salt/pillar/gnuviechadmin/queues/gvamysql.sls b/salt/pillar/gnuviechadmin/queues/gvamysql.sls deleted file mode 100644 index c99c363..0000000 --- a/salt/pillar/gnuviechadmin/queues/gvamysql.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - queues: - users: - mysql: - password: Bhruvz8Oe9rXxRc7 diff --git a/salt/pillar/gnuviechadmin/queues/gvapgsql.sls b/salt/pillar/gnuviechadmin/queues/gvapgsql.sls deleted file mode 100644 index 7dae40f..0000000 --- a/salt/pillar/gnuviechadmin/queues/gvapgsql.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - queues: - users: - pgsql: - password: rWOawAtb7MEmGZo3 diff --git a/salt/pillar/gnuviechadmin/queues/gvaweb.sls b/salt/pillar/gnuviechadmin/queues/gvaweb.sls deleted file mode 100644 index cd17ae1..0000000 --- a/salt/pillar/gnuviechadmin/queues/gvaweb.sls +++ /dev/null @@ -1,5 +0,0 @@ -gnuviechadmin: - queues: - users: - web: - password: 1fBXqCu175rU7SWA diff --git a/salt/pillar/gnuviechadmin/webinterface.sls b/salt/pillar/gnuviechadmin/webinterface.sls deleted file mode 100644 index 8eabd28..0000000 --- a/salt/pillar/gnuviechadmin/webinterface.sls +++ /dev/null @@ -1,9 +0,0 @@ -include: - - gnuviechadmin.queues.common - - gnuviechadmin.queues.gva - -gnuviechadmin: - component: - name: gva - amqp_user: gva - python_module: gnuviechadmin diff --git a/salt/pillar/top.sls b/salt/pillar/top.sls deleted file mode 100644 index 7159918..0000000 --- a/salt/pillar/top.sls +++ /dev/null @@ -1,8 +0,0 @@ -base: - '*': - - gnuviechadmin -{% for role in ('database', 'queues', 'webinterface', 'gvaldap', 'gvafile', 'gvamysql', 'gvapgsql', 'gvaweb') %} - 'roles:gnuviechadmin.{{ role }}': - - match: grain - - gnuviechadmin.{{ role }} -{% endfor %} \ No newline at end of file diff --git a/salt/roots/_states/rsa_key.py b/salt/roots/_states/rsa_key.py deleted file mode 100644 index 96ebda8..0000000 --- a/salt/roots/_states/rsa_key.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# -# some internal functions are copied from salt.states.file - -from Crypto.PublicKey import RSA -import os - - -def _check_user(user, group): - ''' - Checks if the named user and group are present on the minion - ''' - err = '' - if user: - uid = __salt__['file.user_to_uid'](user) - if uid == '': - err += 'User {0} is not available '.format(user) - if group: - gid = __salt__['file.group_to_gid'](group) - if gid == '': - err += 'Group {0} is not available'.format(group) - return err - - -def _error(ret, err_msg): - ret['result'] = False - ret['comment'] = err_msg - return ret - - -def _calculate_umask(mode): - mode = str(mode).lstrip('0') - if not mode: - mode = '0' - modeint = int(mode, 8) - return modeint ^ 0777 - - -def valid_key(name, bits=2048, user=None, group=None, mode='0700'): - """ - Make sure that the given key file exists and contains a valid RSA key. - - name - The name of the key file to check - - bits - Minimum bits for the RSA key - - user - The user to own the file, this defaults to the user salt is running as - on the minion - - group - The group ownership set for the file, this defaults to the group salt - is running on the minion - - mode - The permissions set on the file, this defaults to 0600 - """ - - mode = __salt__['config.manage_mode'](mode) - - ret = { - 'name': name, - 'changes': {}, - 'result': None, - 'comment': ''} - if not os.path.isfile(name) and __opts__['test']: - ret['comment'] = 'would create RSA key in file {0}'.format(name) - return ret - - u_check = _check_user(user, group) - if u_check: - return _error(ret, u_check) - if not os.path.isabs(name): - return _error( - ret, 'Specified file {0} is not an absolute path'.format(name)) - if os.path.isdir(name): - return _error( - ret, 'Specified target {0} is a directory'.format(name)) - if os.path.exists(name): - ret, perms = __salt__['file.check_perms']( - name, ret, user, group, mode) - if __opts__['test']: - ret['comment'] = 'File {0} not updated'.format(name) - return ret - - if not os.path.isfile(name): - rsa = RSA.generate(bits) - oldumask = os.umask(_calculate_umask(mode)) - with open(name, 'w') as rsafile: - rsafile.write(rsa.exportKey()) - os.umask(oldumask) - ret['comment'] = 'created new RSA key and saved PEM file {0}'.format( - name) - ret['changes']['created'] = name - ret['result'] = True - return ret - try: - with open(name, 'r') as rsafile: - rsa = RSA.importKey(rsafile.read()) - except Exception as e: - ret['comment'] = 'error loading RSA key from file {0}: {1}'.format( - name, e) - ret['result'] = False - return ret - keysize = rsa.size() + 1 - if keysize < bits: - ret['comment'] = ( - 'RSA key in {0} is only {1} bits, which is less than the ' - 'required {2} bits'.format(name, keysize, bits)) - ret['result'] = False - else: - ret['comment'] = 'RSA key in file {0} is ok ({1} bits)'.format( - name, keysize) - ret['result'] = True - return ret diff --git a/salt/roots/_states/x509_certificate.py b/salt/roots/_states/x509_certificate.py deleted file mode 100644 index ac1afb4..0000000 --- a/salt/roots/_states/x509_certificate.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf8 -*- -''' -Manage X.509 certificate life cycle -=================================== - -This state is useful for managing X.509 certificates' life cycles. - -Copyright (c) 2014 Jan Dittberner -''' - -from M2Crypto import X509 -from datetime import datetime -import os - - -def _error(ret, err_msg): - ret['result'] = False - ret['comment'] = err_msg - return ret - - -def valid_certificate( - name, mindays=14, keyfile=None, - checkchain=False, trustedcerts=None): - ''' - Checks whether the given certificate file is valid. - - name - The name of the certificate file to check - mindays - Mark the certificate as invalid if it is valid for less then this many - days - ''' - ret = { - 'name': name, - 'changes': {}, - 'result': None, - 'comment': ''} - if not os.path.isfile(name): - return _error( - ret, 'certificate file {0} does not exist'.format(name)) - try: - cert = X509.load_cert(name) - except Exception as e: - return _error( - ret, - 'error loading certificate {0}: {1}'.format(name, e)) - notafter = cert.get_not_after().get_datetime() - delta = notafter - datetime.now(notafter.tzinfo) - if delta.days < mindays: - return _error( - ret, - 'certificate {0} is only valid for {1} more day(s)'.format( - name, delta.days)) - # TODO: check keyfile match - # TODO: check trust chain - ret['comment'] = ( - 'certificate {0} is ok and still valid for {1} days'.format( - name, delta.days)) - ret['result'] = True - return ret diff --git a/salt/roots/base/bash_functions b/salt/roots/base/bash_functions deleted file mode 100644 index 00658d6..0000000 --- a/salt/roots/base/bash_functions +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -function devenv -{ - . $HOME/gvasettings.sh - . {{ venv }}/bin/activate - cd {{ appdir }} -} - -function testenv -{ - devenv - export DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE%%.local}.test -} - -function settitle -{ - if [ -n "$STY" ] ; then # We are in a screen session - echo "Setting screen titles to $@" - printf "\033k%s\033\\" "$@" - screen -X eval "at \\# title $@" "shelltitle $@" - else - printf "\033]0;%s\007" "$@" - fi -} diff --git a/salt/roots/base/bashrc b/salt/roots/base/bashrc deleted file mode 100644 index 4bc2a9f..0000000 --- a/salt/roots/base/bashrc +++ /dev/null @@ -1,117 +0,0 @@ -# ~/.bashrc: executed by bash(1) for non-login shells. -# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) -# for examples - -# If not running interactively, don't do anything -case $- in - *i*) ;; - *) return;; -esac - -# don't put duplicate lines or lines starting with space in the history. -# See bash(1) for more options -HISTCONTROL=ignoreboth - -# append to the history file, don't overwrite it -shopt -s histappend - -# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) -HISTSIZE=1000 -HISTFILESIZE=2000 - -# check the window size after each command and, if necessary, -# update the values of LINES and COLUMNS. -shopt -s checkwinsize - -# If set, the pattern "**" used in a pathname expansion context will -# match all files and zero or more directories and subdirectories. -#shopt -s globstar - -# make less more friendly for non-text input files, see lesspipe(1) -#[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" - -# set variable identifying the chroot you work in (used in the prompt below) -if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then - debian_chroot=$(cat /etc/debian_chroot) -fi - -# set a fancy prompt (non-color, unless we know we "want" color) -case "$TERM" in - xterm-color) color_prompt=yes;; -esac - -# uncomment for a colored prompt, if the terminal has the capability; turned -# off by default to not distract the user: the focus in a terminal window -# should be on the output of commands, not on the prompt -#force_color_prompt=yes - -if [ -n "$force_color_prompt" ]; then - if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then - # We have color support; assume it's compliant with Ecma-48 - # (ISO/IEC-6429). (Lack of such support is extremely rare, and such - # a case would tend to support setf rather than setaf.) - color_prompt=yes - else - color_prompt= - fi -fi - -if [ "$color_prompt" = yes ]; then - PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' -else - PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' -fi -unset color_prompt force_color_prompt - -# If this is an xterm set the title to user@host:dir -case "$TERM" in -xterm*|rxvt*) - PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" - ;; -*) - ;; -esac - -# enable color support of ls and also add handy aliases -if [ -x /usr/bin/dircolors ]; then - test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" - alias ls='ls --color=auto' - #alias dir='dir --color=auto' - #alias vdir='vdir --color=auto' - - #alias grep='grep --color=auto' - #alias fgrep='fgrep --color=auto' - #alias egrep='egrep --color=auto' -fi - -# colored GCC warnings and errors -#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' - -# some more ls aliases -#alias ll='ls -l' -#alias la='ls -A' -#alias l='ls -CF' - -# Alias definitions. -# You may want to put all your additions into a separate file like -# ~/.bash_aliases, instead of adding them here directly. -# See /usr/share/doc/bash-doc/examples in the bash-doc package. - -if [ -f ~/.bash_aliases ]; then - . ~/.bash_aliases -fi - -# enable programmable completion features (you don't need to enable -# this, if it's already enabled in /etc/bash.bashrc and /etc/profile -# sources /etc/bash.bashrc). -if ! shopt -oq posix; then - if [ -f /usr/share/bash-completion/bash_completion ]; then - . /usr/share/bash-completion/bash_completion - elif [ -f /etc/bash_completion ]; then - . /etc/bash_completion - fi -fi - -if [ -f ~/.bash_functions ]; then - . ~/.bash_functions -fi diff --git a/salt/roots/base/init.sls b/salt/roots/base/init.sls deleted file mode 100644 index 3b896aa..0000000 --- a/salt/roots/base/init.sls +++ /dev/null @@ -1,30 +0,0 @@ -base-packages: - pkg.installed: - - pkgs: - - screen - - htop - - git - -/home/vagrant/.screenrc: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0644 - - source: salt://base/screenrc - -update-system: - pkg.uptodate: - - refresh: True - -/home/vagrant/bin: - file.directory: - - user: vagrant - - group: vagrant - - mode: 0750 - -/home/vagrant/.bashrc: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0644 - - source: salt://base/bashrc diff --git a/salt/roots/base/screenrc b/salt/roots/base/screenrc deleted file mode 100644 index a555c1e..0000000 --- a/salt/roots/base/screenrc +++ /dev/null @@ -1,14 +0,0 @@ -# vim: syntax=screen - -hardstatus on -hardstatus alwayslastline -hardstatus string "%{= r}[ %{G}%H%{= r} ] %= %{=b b}%-w%{=rb db}%>%n %t%{-}%+w %=%{= r} [ %{G}%c %{M}%D %m-%d %{r}]" - -startup_message off - -defscrollback 10240 - -bind f eval "caption splitonly" "hardstatus ignore" -bind F eval "caption always" "hardstatus alwayslastline" - -defbce "on" diff --git a/salt/roots/gnuviechadmin/base.sls b/salt/roots/gnuviechadmin/base.sls deleted file mode 100644 index b0e8ccb..0000000 --- a/salt/roots/gnuviechadmin/base.sls +++ /dev/null @@ -1,98 +0,0 @@ -{% from 'gnuviechadmin/vars.sls' import home, gva_component, gva_amqp_user, checkout, appdir, venv %} - -gva.local: - host.present: - - ip: 172.16.3.2 - - names: - - mq - - gva.local - -gvaldap.local: - host.present: - - ip: 172.16.3.3 - -gvafile.local: - host.present: - - ip: 172.16.3.4 - -gvaweb.local: - host.present: - - ip: 172.16.3.5 - -gvamysql.local: - host.present: - - ip: 172.16.3.6 - -gvapgsql.local: - host.present: - - ip: 172.16.3.7 - -gnuviechadmin-packages: - pkg.installed: - - pkgs: - - libyaml-dev - - python-virtualenv - - python-dev - - python-pip - - gettext - -{{ home }}/gvasettings.sh: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0640 - - source: salt://gnuviechadmin/{{ gva_component }}/settings.sh - - template: jinja - - context: - broker_url: {{ 'amqp://%s:%s@mq/%s' % (gva_amqp_user, salt['pillar.get']('gnuviechadmin:queues:users:%s:password' % gva_amqp_user), salt['pillar.get']('gnuviechadmin:queues:vhost')) }} - -gnuviechadmin-venv: - cmd.run: - - name: virtualenv {{ venv }} - - user: vagrant - - group: vagrant - - unless: test -f {{ venv }}/bin/pip - -gnuviechadmin-requires: - cmd.run: - - name: {{ venv }}/bin/pip install -U -r requirements/local.txt && touch {{ venv }}/lastinstall - - user: vagrant - - group: vagrant - - cwd: {{ checkout }} - - require: - - cmd: gnuviechadmin-venv - - pkg: gnuviechadmin-packages - - unless: test -e {{ venv }}/lastinstall && test {{ checkout }}/requirements/local.txt -ot {{ venv }}/lastinstall && test {{ checkout }}/requirements/base.txt -ot {{ venv }}/lastinstall - -gnuviechadmin-dbschema: - cmd.wait: - - name: . {{ home }}/gvasettings.sh ; unset LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME ; {{ venv }}/bin/python manage.py migrate --noinput - - user: vagrant - - group: vagrant - - cwd: {{ appdir }} - - watch: - - cmd: gnuviechadmin-requires - - file: {{ home }}/gvasettings.sh - -gnuviechadmin-locale-data-compile: - cmd.wait: - - name: . {{ home }}/gvasettings.sh ; {{ venv }}/bin/python {{ appdir }}/manage.py compilemessages - - user: vagrant - - group: vagrant - - cwd: {{ appdir }} - - require: - - pkg: gnuviechadmin-packages - - file: {{ home }}/gvasettings.sh - - cmd: gnuviechadmin-venv - -/home/vagrant/.bash_functions: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0644 - - source: salt://base/bash_functions - - template: jinja - - context: - home: {{ home }} - venv: {{ venv }} - appdir: {{ appdir }} diff --git a/salt/roots/gnuviechadmin/bash_functions b/salt/roots/gnuviechadmin/bash_functions deleted file mode 100644 index 2c7fd41..0000000 --- a/salt/roots/gnuviechadmin/bash_functions +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -function devenv -{ - . $HOME/gvasettings.sh - . $HOME/gva-venv/bin/activate - cd /vagrant/gnuviechadmin -} - -function testenv -{ - devenv - export DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE%%.local}.test -} - -function settitle -{ - if [ -n "$STY" ] ; then # We are in a screen session - echo "Setting screen titles to $@" - printf "\033k%s\033\\" "$@" - screen -X eval "at \\# title $@" "shelltitle $@" - else - printf "\033]0;%s\007" "$@" - fi -} diff --git a/salt/roots/gnuviechadmin/celery.sls b/salt/roots/gnuviechadmin/celery.sls deleted file mode 100644 index b27b504..0000000 --- a/salt/roots/gnuviechadmin/celery.sls +++ /dev/null @@ -1,13 +0,0 @@ -{% from 'gnuviechadmin/vars.sls' import home, gva_component, venv, appdir %} - -{{ home }}/bin/run_celery.sh: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0750 - - source: salt://gnuviechadmin/{{ gva_component }}/run_celery.sh - - template: jinja - - context: - home: {{ home }} - virtualenv: {{ venv }} - appdir: {{ appdir }} diff --git a/salt/roots/gnuviechadmin/database.sls b/salt/roots/gnuviechadmin/database.sls deleted file mode 100644 index ab57c2a..0000000 --- a/salt/roots/gnuviechadmin/database.sls +++ /dev/null @@ -1,33 +0,0 @@ -include: - - postgresql-server - -gnuviechadmin-database: - postgres_user.present: - - name: {{ salt['pillar.get']('gnuviechadmin:database:owner:user') }} - - user: postgres - - password: {{ salt['pillar.get']('gnuviechadmin:database:owner:password') }} - - login: True - - createdb: {% if salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') == 'local' %}True -{%- else %}False -{%- endif %} - - require: - - service: postgresql - postgres_database.present: - - name: {{ salt['pillar.get']('gnuviechadmin:database:name') }} - - user: postgres - - owner: {{ salt['pillar.get']('gnuviechadmin:database:owner:user') }} - - encoding: UTF8 - - template: template0 - - require: - - service: postgresql - - postgres_user: {{ salt['pillar.get']('gnuviechadmin:database:owner:user') }} - -{% for gnuviechadmin_db_role in salt['pillar.get']('gnuviechadmin:database:users') %} -gnuviechadmin-dbuser-{{ gnuviechadmin_db_role }}: - postgres_user.present: - - name: {{ salt['pillar.get']('gnuviechadmin:database:users:%s:user' % gnuviechadmin_db_role) }} - - password: {{ salt['pillar.get']('gnuviechadmin:database:users:%s:password' % gnuviechadmin_db_role) }} - - login: True - - require: - - service: postgresql -{% endfor %} diff --git a/salt/roots/gnuviechadmin/gva/gnuviechadmin.nginx b/salt/roots/gnuviechadmin/gva/gnuviechadmin.nginx deleted file mode 100644 index d5768bd..0000000 --- a/salt/roots/gnuviechadmin/gva/gnuviechadmin.nginx +++ /dev/null @@ -1,27 +0,0 @@ -server { - server_name www.{{ domainname }}; - listen 443 ssl; - - ssl_certificate {{ ssl_certdir }}/{{ domainname }}.crt.pem; - ssl_certificate_key {{ ssl_keydir }}/{{ domainname }}.key.pem; - - if ( $host != '{{ domainname }}') { - return 301 https://{{ domainname }}$request_uri; - } - - client_max_body_size 1M; - gzip on; - gzip_types text/javascript application/x-javascript text/css; - - location /media { - alias /vagrant/gnuviechadmin/media; - } - - location /static { - alias /vagrant/gnuviechadmin/assets; - } - - location / { - proxy_pass http://localhost:8000; - } -} diff --git a/salt/roots/gnuviechadmin/gva/settings.sh b/salt/roots/gnuviechadmin/gva/settings.sh deleted file mode 100644 index 60bbb98..0000000 --- a/salt/roots/gnuviechadmin/gva/settings.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -export DJANGO_SETTINGS_MODULE="gnuviechadmin.settings.{{ salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') }}" -export GVA_ADMIN_NAME="Jan Dittberner" -export GVA_ADMIN_EMAIL="{{ salt['pillar.get']('gnuviechadmin:adminemail') }}" -export GVA_PGSQL_DATABASE="{{ salt['pillar.get']('gnuviechadmin:database:name') }}" -export GVA_PGSQL_USER="{{ salt['pillar.get']('gnuviechadmin:database:owner:user') }}" -export GVA_PGSQL_PASSWORD="{{ salt['pillar.get']('gnuviechadmin:database:owner:password') }}" -export GVA_PGSQL_HOSTNAME="{{ salt['pillar.get']('gnuviechadmin:database:host') }}" -export GVA_PGSQL_PORT={{ salt['pillar.get']('gnuviechadmin:database:port') }} -export GVA_DOMAIN_NAME="{{ salt['pillar.get']('gnuviechadmin:domainname') }}" -export GVA_SITE_NAME="{{ salt['pillar.get']('gnuviechadmin:sitename') }}" -export GVA_SITE_SECRET="{{ salt['grains.get_or_set_hash']('gnuviechadmin:SECRET_KEY', 50) }}" -export GVA_SITE_ADMINMAIL="{{ salt['pillar.get']('gnuviechadmin:adminemail') }}" -export GVA_MIN_OS_UID={{ salt['pillar.get']('gnuviechadmin:minosuid') }} -export GVA_MIN_OS_GID={{ salt['pillar.get']('gnuviechadmin:minosgid') }} -export GVA_OSUSER_PREFIX="{{ salt['pillar.get']('gnuviechadmin:osuserprefix') }}" -export GVA_OSUSER_HOME_BASEPATH="{{ salt['pillar.get']('gnuviechadmin:osuserhomedirbase') }}" -export GVA_OSUSER_DEFAULT_SHELL="{{ salt['pillar.get']('gnuviechadmin:osuserdefaultshell') }}" -export GVA_BROKER_URL="{{ broker_url }}" -export GVA_OSUSER_UPLOADSERVER="{{ salt['pillar.get']('gnuviechadmin:uploadserver') }}" -export GVA_WEBMAIL_URL="{{ salt['pillar.get']('gnuviechadmin:webmail_url') }}" -export GVA_PHPMYADMIN_URL="{{ salt['pillar.get']('gnuviechadmin:phpmyadmin_url') }}" -export GVA_PHPPGADMIN_URL="{{ salt['pillar.get']('gnuviechadmin:phppgadmin_url') }}" diff --git a/salt/roots/gnuviechadmin/gvaldap.sls b/salt/roots/gnuviechadmin/gvaldap.sls deleted file mode 100644 index f600b9f..0000000 --- a/salt/roots/gnuviechadmin/gvaldap.sls +++ /dev/null @@ -1,11 +0,0 @@ -include: - - gnuviechadmin.base - - gnuviechadmin.celery - -gvaldap-packages: - pkg.installed: - - pkgs: - - libldap2-dev - - libsasl2-dev - - require_in: - - pkg: gnuviechadmin-packages diff --git a/salt/roots/gnuviechadmin/gvaldap/run_celery.sh b/salt/roots/gnuviechadmin/gvaldap/run_celery.sh deleted file mode 100644 index eac31c8..0000000 --- a/salt/roots/gnuviechadmin/gvaldap/run_celery.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -ex - -. {{ home }}/gvasettings.sh -cd {{ appdir }} -{{ virtualenv }}/bin/celery worker -A gvaldap -Q ldap --loglevel=INFO diff --git a/salt/roots/gnuviechadmin/gvaldap/settings.sh b/salt/roots/gnuviechadmin/gvaldap/settings.sh deleted file mode 100644 index e99308c..0000000 --- a/salt/roots/gnuviechadmin/gvaldap/settings.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -export DJANGO_SETTINGS_MODULE="gvaldap.settings.{{ salt['pillar.get']('gnuviechadmin:deploymenttype', 'production') }}" -export GVALDAP_ADMIN_NAME="Jan Dittberner" -export GVALDAP_ADMIN_EMAIL="{{ salt['pillar.get']('gnuviechadmin-gvaldap:admin_email') }}" -export GVALDAP_LDAP_URL="{{ salt['pillar.get']('gnuviechadmin-gvaldap:ldap_url') }}" -export GVALDAP_LDAP_USER="{{ salt['pillar.get']('gnuviechadmin-gvaldap:ldap_user') }}" -export GVALDAP_LDAP_PASSWORD="{{ salt['pillar.get']('gnuviechadmin-gvaldap:ldap_password' ) }}" -export GVALDAP_BASEDN_GROUP="{{ salt['pillar.get']('gnuviechadmin-gvaldap:basedn_group') }}" -export GVALDAP_BASEDN_USER="{{ salt['pillar.get']('gnuviechadmin-gvaldap:basedn_user') }}" -export GVALDAP_SECRETKEY="{{ salt['grains.get_or_set_hash']('gnuviechadmin-gvaldap:SECRET_KEY', 50) }}" -export GVALDAP_BROKER_URL="{{ broker_url }}" -export GVALDAP_ALLOWED_HOSTS="{{ salt['pillar.get']('gnuviechadmin-gvaldap:allowed_hosts') }}" -export GVALDAP_SERVER_EMAIL="{{ salt['pillar.get']('gnuviechadmin-gvaldap:server_email') }}" diff --git a/salt/roots/gnuviechadmin/queues.sls b/salt/roots/gnuviechadmin/queues.sls deleted file mode 100644 index 5462fb7..0000000 --- a/salt/roots/gnuviechadmin/queues.sls +++ /dev/null @@ -1,30 +0,0 @@ -include: - - rabbitmq-server - -gnuviechadmin-queue-vhost: - rabbitmq_vhost.present: - - name: {{ salt['pillar.get']('gnuviechadmin:queues:vhost') }} - -{% for user in salt['pillar.get']('gnuviechadmin:queues:users') %} -gnuviechadmin-queue-user-{{ user }}: - rabbitmq_user.present: - - name: {{ user }} - - password: {{ salt['pillar.get']('gnuviechadmin:queues:users:%s:password' % user) }} -{% if salt['pillar.get']('gnuviechadmin:queues:users:%s:perms' % user) %} - - perms: -{% for vhost, perms in salt['pillar.get']('gnuviechadmin:queues:users:%s:perms' % user).iteritems() %} - - {{ vhost }}: - - {{ perms[0] }} - - {{ perms[1] }} - - {{ perms[2] }} -{% endfor %} -{% endif %} -{% if salt['pillar.get']('gnuviechadmin:queues:users:%s:tags' % user) %} - - tags: -{% for tag in salt['pillar.get']('gnuviechadmin:queues:users:%s:tags' % user) %} - - {{ tag }} -{% endfor %} -{% endif %} - - require: - - rabbitmq_vhost: {{ salt['pillar.get']('gnuviechadmin:queues:vhost') }} -{% endfor %} diff --git a/salt/roots/gnuviechadmin/vars.sls b/salt/roots/gnuviechadmin/vars.sls deleted file mode 100644 index 6ad1aa2..0000000 --- a/salt/roots/gnuviechadmin/vars.sls +++ /dev/null @@ -1,7 +0,0 @@ -{% set home = '/home/vagrant' %} -{% set venv = home + '/gva-venv' %} -{% set checkout = '/vagrant' %} -{% set gva_component = salt['pillar.get']('gnuviechadmin:component:name') %} -{% set gva_amqp_user = salt['pillar.get']('gnuviechadmin:component:amqp_user') %} -{% set python_module = salt['pillar.get']('gnuviechadmin:component:python_module', gva_component) %} -{% set appdir = checkout + '/' + python_module %} diff --git a/salt/roots/gnuviechadmin/webinterface.sls b/salt/roots/gnuviechadmin/webinterface.sls deleted file mode 100644 index f94c060..0000000 --- a/salt/roots/gnuviechadmin/webinterface.sls +++ /dev/null @@ -1,39 +0,0 @@ -include: - - gnuviechadmin.base - - webserver - -libpq-dev: - pkg.installed: - - require_in: - - pkg: gnuviechadmin-packages - -python-m2crypto: - pkg.installed: - - reload_modules: true - -{% import "webserver/sslcert.macros.sls" as sslcert %} - -{% set domainname = salt['pillar.get']('gnuviechadmin:domainname') %} -{{ sslcert.key_cert(domainname) }} - -/etc/nginx/sites-available/{{ domainname }}: - file.managed: - - user: root - - group: root - - mode: 0640 - - source: salt://gnuviechadmin/gva/gnuviechadmin.nginx - - template: jinja - - context: - domainname: {{ domainname }} - ssl_keydir: {{ salt['pillar.get']('nginx:sslkeydir', '/etc/nginx/ssl/private') }} - ssl_certdir: {{ salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') }} - - require: - - pkg: nginx - -/etc/nginx/sites-enabled/{{ domainname }}: - file.symlink: - - target: /etc/nginx/sites-available/{{ domainname }} - - require: - - file: /etc/nginx/sites-available/{{ domainname }} - - watch_in: - - service: nginx diff --git a/salt/roots/nginx/init.sls b/salt/roots/nginx/init.sls deleted file mode 100644 index cd47736..0000000 --- a/salt/roots/nginx/init.sls +++ /dev/null @@ -1,38 +0,0 @@ -nginx: - pkg: - - installed - service.running: - - enable: True - - require: - - pkg: nginx - -nginx-common: - pkg.installed - -/etc/nginx/nginx.conf: - file.managed: - - source: salt://nginx/nginx.conf - - user: root - - group: root - - mode: 0644 - - require: - - pkg: nginx-common - - watch_in: - - service: nginx - -{% set nginx_ssl_keydir = salt['pillar.get']('nginx:sslkeydir', '/etc/nginx/ssl/private') %} -{% set nginx_ssl_certdir = salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') %} - -{{ nginx_ssl_certdir }}: - file.directory: - - user: root - - group: root - - mode: 0755 - - makedirs: True - -{{ nginx_ssl_keydir }}: - file.directory: - - user: root - - group: root - - mode: 0750 - - makedirs: True diff --git a/salt/roots/nginx/nginx.conf b/salt/roots/nginx/nginx.conf deleted file mode 100644 index dfeb36d..0000000 --- a/salt/roots/nginx/nginx.conf +++ /dev/null @@ -1,49 +0,0 @@ -user www-data; -worker_processes 4; -pid /run/nginx.pid; - -events { - worker_connections 768; - # multi_accept on; -} - -http { - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - # server_tokens off; - - server_names_hash_bucket_size 64; - # server_name_in_redirect off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Logging Settings - ## - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - ## - # Gzip Settings - ## - - gzip on; - gzip_disable "msie6"; - - ## - # Virtual Host Configs - ## - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} diff --git a/salt/roots/postgresql-server/init.sls b/salt/roots/postgresql-server/init.sls deleted file mode 100644 index f08ace7..0000000 --- a/salt/roots/postgresql-server/init.sls +++ /dev/null @@ -1,9 +0,0 @@ -locales-all: - pkg.installed - -postgresql: - pkg: - - installed - service.running: - - require: - - pkg: postgresql diff --git a/salt/roots/rabbitmq-server/init.sls b/salt/roots/rabbitmq-server/init.sls deleted file mode 100644 index 6c3c7f6..0000000 --- a/salt/roots/rabbitmq-server/init.sls +++ /dev/null @@ -1,17 +0,0 @@ -rabbitmq-server: - pkg: - - installed - service: - - running - - requires: - - pkg: rabbitmq-server - -guest: - rabbitmq_user: - - absent - -rabbitmq_management: - rabbitmq_plugin: - - enabled - - watch_in: - - service: rabbitmq-server diff --git a/salt/roots/top.sls b/salt/roots/top.sls deleted file mode 100644 index 7075d53..0000000 --- a/salt/roots/top.sls +++ /dev/null @@ -1,11 +0,0 @@ -base: - '*': - - vim - - base -{% if 'roles' in grains %} -{% for role in grains['roles'] %} - 'roles:{{ role }}': - - match: grain - - {{ role }} -{% endfor %} -{% endif %} diff --git a/salt/roots/vim/init.sls b/salt/roots/vim/init.sls deleted file mode 100644 index fa234e3..0000000 --- a/salt/roots/vim/init.sls +++ /dev/null @@ -1,15 +0,0 @@ -vim-nox: - pkg.installed - -editor: - alternatives.set: - - path: /usr/bin/vim.nox - - require: - - pkg: vim-nox - -/home/vagrant/.vimrc: - file.managed: - - user: vagrant - - group: vagrant - - mode: 0644 - - source: salt://vim/vimrc diff --git a/salt/roots/vim/vimrc b/salt/roots/vim/vimrc deleted file mode 100644 index 931195b..0000000 --- a/salt/roots/vim/vimrc +++ /dev/null @@ -1,34 +0,0 @@ -syntax on - -set showcmd -set modeline -set modelines=3 -set expandtab -set shiftwidth=4 -set autoindent -set smarttab -set ruler -set list listchars=tab:▷⋅,trail:⋅,nbsp:⋅ -set cpoptions+=$ -set hlsearch -set virtualedit=all -set guioptions-=T -set guioptions-=m -set wildmenu -set complete=.,w,b,u,t -set number - -filetype plugin indent on - -autocmd BufNewFile,BufRead *.sls set filetype=yaml - -autocmd FileType make set noexpandtab -autocmd FileType python set tabstop=4 shiftwidth=4 autoindent smartindent textwidth=79 -autocmd FileType html set tabstop=2 shiftwidth=2 textwidth=200 smartindent autoindent -autocmd FileType htmldjango set tabstop=2 shiftwidth=2 textwidth=200 -autocmd FileType moin set tabstop=2 shiftwidth=2 -autocmd FileType rst set textwidth=79 -autocmd FileType yaml set tabstop=2 shiftwidth=2 - -set laststatus=2 -set statusline=%f%m%r%h%w\ [TYPE=%Y\ %{&ff}]\ \ [%c\ @\ %l/%L]\ (%p%%)\ [%b\ 0x%B] diff --git a/salt/roots/webserver/init.sls b/salt/roots/webserver/init.sls deleted file mode 100644 index 59fad3b..0000000 --- a/salt/roots/webserver/init.sls +++ /dev/null @@ -1,50 +0,0 @@ -include: - - nginx - -/etc/nginx/conf.d/logformat.conf: - file.managed: - - user: root - - group: root - - mode: 0644 - - source: salt://webserver/nginx-logformat.conf - - require: - - pkg: nginx - - watch_in: - - service: nginx - -{% set ssldir = salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') %} - -generate-dhparam-nginx: - cmd.run: - - name: openssl dhparam -out {{ ssldir }}/dhparams.pem 2048 - - umask: 022 - - user: root - - group: root - - creates: {{ ssldir }}/dhparams.pem - - require_in: - - file: /etc/nginx/conf.d/ssl.conf - - watch_in: - - service: nginx - -/etc/nginx/conf.d/ssl.conf: - file.managed: - - user: root - - group: root - - mode: 0644 - - source: salt://webserver/nginx-ssl.conf - - template: jinja - - require: - - pkg: nginx - - watch_in: - - service: nginx - -/etc/nginx/snippets/security.conf: - file.managed: - - user: root - - group: root - - mode: 0644 - - source: salt://webserver/nginx-security.conf - - require: - - pkg: nginx - - watch_in: - - service: nginx diff --git a/salt/roots/webserver/nginx-logformat.conf b/salt/roots/webserver/nginx-logformat.conf deleted file mode 100644 index bb26d9e..0000000 --- a/salt/roots/webserver/nginx-logformat.conf +++ /dev/null @@ -1,4 +0,0 @@ -log_format main '$remote_addr - $remote_user [$time_local] ' - '$server_name ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent"'; diff --git a/salt/roots/webserver/nginx-security.conf b/salt/roots/webserver/nginx-security.conf deleted file mode 100644 index 5585189..0000000 --- a/salt/roots/webserver/nginx-security.conf +++ /dev/null @@ -1,19 +0,0 @@ -# Security - Basic configuration - location = /favicon.ico { - log_not_found off; - access_log off; - expires max; - } - - location = /robots.txt { - allow all; - log_not_found off; - access_log off; - } - - # Deny access to hidden files - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } diff --git a/salt/roots/webserver/nginx-ssl.conf b/salt/roots/webserver/nginx-ssl.conf deleted file mode 100644 index 305f31d..0000000 --- a/salt/roots/webserver/nginx-ssl.conf +++ /dev/null @@ -1,15 +0,0 @@ -# Default TLS settings -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_ciphers kEECDH+AESGCM:kEECDH+AES:kEECDH:EDH+AESGCM:kEDH+AES:kEDH:AESGCM:ALL:!LOW:!EXP:!MD5:!aNULL:!eNULL:!RC4:!DSS; -ssl_prefer_server_ciphers on; -ssl_session_cache shared:SSL:10m; - -ssl_dhparam {{ salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') }}/dhparams.pem; - -# OCSP stapling -ssl_stapling on; -ssl_stapling_verify on; - -# use Google's DNS -resolver 8.8.8.8; -resolver_timeout 5s; diff --git a/salt/roots/webserver/sslcert.macros.sls b/salt/roots/webserver/sslcert.macros.sls deleted file mode 100644 index e3bf201..0000000 --- a/salt/roots/webserver/sslcert.macros.sls +++ /dev/null @@ -1,30 +0,0 @@ -{%- macro key_cert(domain_name) %} -{% set nginx_ssl_keydir = salt['pillar.get']('nginx:sslkeydir', '/etc/nginx/ssl/private') %} -{% set nginx_ssl_certdir = salt['pillar.get']('nginx:sslcertdir', '/etc/nginx/ssl/certs') %} -{% set keyfile = nginx_ssl_keydir + '/' + domain_name + '.key.pem' %} -{% set certfile = nginx_ssl_certdir + '/' + domain_name + '.crt.pem' %} - -{{ keyfile }}: - rsa_key.valid_key: - - bits: {{ salt['pillar.get']('nginx:keylength:' + domain_name, 2048) }} - - require: - - file: {{ nginx_ssl_keydir }} - - require_in: - - file: /etc/nginx/sites-available/{{ domain_name }} - - service: nginx - -{{ certfile }}: - cmd.run: - - name: openssl req -new -x509 -key {{ keyfile }} -subj '/CN={{ domain_name }}' -days 730 -out {{ certfile }} - - require: - - rsa_key: {{ keyfile }} - - creates: {{ certfile }} - x509_certificate.valid_certificate: - - require: - - file: {{ nginx_ssl_certdir }} - - cmd: {{ certfile }} - - pkg: python-m2crypto - - require_in: - - file: /etc/nginx/sites-available/{{ domain_name }} - - service: nginx -{% endmacro %} From eda20937dc041392bff3f44ca20ce6ec29ecfa4c Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 21:46:14 +0100 Subject: [PATCH 069/111] Use separate test vhost for celery queues --- docs/changelog.rst | 1 + gnuviechadmin/gnuviechadmin/settings/test.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5e0e720..0c7ac9a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :support:`-` use separate test vhost for celery queues * :support:`-` switch licensing to AGPLv3+ * :support:`-` add a Vagrant setup to ease development * :support:`-` add example provisioning defined as saltstack states diff --git a/gnuviechadmin/gnuviechadmin/settings/test.py b/gnuviechadmin/gnuviechadmin/settings/test.py index a079403..872b320 100644 --- a/gnuviechadmin/gnuviechadmin/settings/test.py +++ b/gnuviechadmin/gnuviechadmin/settings/test.py @@ -20,3 +20,5 @@ LOGGING['loggers'].update(dict( 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', 'userdbs', 'websites']])) +BROKER_URL = BROKER_URL + '_test' +CELERY_RESULT_PERSISTENT = False From 07b0eb6981232417aa260552ad4af4b1aa5871d8 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 10:45:51 +0000 Subject: [PATCH 070/111] Update celery dependency --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 3488e0c..3c7ab5d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,7 +7,7 @@ django-crispy-forms==1.6.0 logutils==0.3.3 psycopg2==2.6.1 passlib==1.6.5 -celery==3.1.19 +celery==3.1.20 billiard==3.3.0.22 kombu==3.0.33 pytz==2015.7 From f3b76a09b713a11a67777b95030c50d4041522d9 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 14:43:49 +0000 Subject: [PATCH 071/111] Add code quality requirements --- requirements/local.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requirements/local.txt b/requirements/local.txt index 1292b4f..4931134 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -5,3 +5,7 @@ sqlparse==0.1.18 Sphinx==1.3.3 snowballstemmer==1.2.1 releases==1.0.0 +flake8==2.5.2 +mccabe==0.4.0 +pep8==1.7.0 +pyflakes==1.0.0 From cd696ceb1f5e5f851bf90eeb2e36d4b71d80ea8d Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 16:35:26 +0100 Subject: [PATCH 072/111] Fix flake8 finding --- gnuviechadmin/osusers/tests/test_forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/osusers/tests/test_forms.py b/gnuviechadmin/osusers/tests/test_forms.py index a5dec33..c204497 100644 --- a/gnuviechadmin/osusers/tests/test_forms.py +++ b/gnuviechadmin/osusers/tests/test_forms.py @@ -22,10 +22,10 @@ from osusers.forms import ( INVALID_SSH_PUBLIC_KEY, ) -Customer = get_user_model() - from osusers.models import SshPublicKey, User +Customer = get_user_model() + class AddSshPublicKeyFormTest(TestCase): """ From 9d843c920a0e3137c10363106fab973d3457e92d Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 16:37:06 +0100 Subject: [PATCH 073/111] Add docstring to gnuviechadmin.settings.base --- gnuviechadmin/gnuviechadmin/settings/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 5e81e2c..1ee18c8 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -1,7 +1,9 @@ # -*- python -*- # pymode:lint_ignore=E501 -"""Common settings and globals.""" +""" +Common settings and globals. +""" from os import environ from os.path import abspath, basename, dirname, join, normpath @@ -18,7 +20,9 @@ def get_env_variable(var_name): Get a setting from an environment variable. :param str var_name: variable name - + :raises ImproperlyConfigured: if the environment setting is not defined + :return: environment setting value + :rtype: str """ try: return environ[var_name] From 3d95a9f61a8d76fce609af77fd2ea2fd97790ca5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 31 Jan 2016 20:26:47 +0100 Subject: [PATCH 074/111] Switch result backend to redis The AMQP result backend proved as impractical, this commit switches to redis instead. The redis server is setup on the webinterface host but can be configured on another host. --- gnuviechadmin/gnuviechadmin/settings/base.py | 3 +-- requirements/base.txt | 1 + salt/bootstrap.sh | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 1ee18c8..15b4128 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -349,8 +349,7 @@ WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME # ######### CELERY CONFIGURATION BROKER_URL = get_env_variable('GVA_BROKER_URL') -CELERY_RESULT_BACKEND = 'amqp' -CELERY_RESULT_PERSISTENT = True +CELERY_RESULT_BACKEND = get_env_variable('GVA_RESULTS_REDIS_URL') CELERY_TASK_RESULT_EXPIRES = None CELERY_ROUTES = ( 'gvacommon.celeryrouters.GvaRouter', diff --git a/requirements/base.txt b/requirements/base.txt index 3c7ab5d..d08a5dc 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -19,3 +19,4 @@ requests==2.9.1 requests-oauthlib==0.6.0 simplejson==3.8.1 -e git+https://git.gnuviech-server.de/gvacommon.git@0.2.1#egg=gvacommon +redis==2.10.5 diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index a9921e8..129addb 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -27,6 +27,7 @@ EOF cat >/etc/salt/grains < Date: Sun, 31 Jan 2016 23:22:07 +0100 Subject: [PATCH 075/111] Add docstrings from gvaldap settings --- gnuviechadmin/gnuviechadmin/settings/__init__.py | 3 +++ gnuviechadmin/gnuviechadmin/settings/local.py | 5 ++++- gnuviechadmin/gnuviechadmin/settings/production.py | 5 ++++- gnuviechadmin/gnuviechadmin/settings/test.py | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/gnuviechadmin/settings/__init__.py b/gnuviechadmin/gnuviechadmin/settings/__init__.py index e69de29..4f53a5e 100644 --- a/gnuviechadmin/gnuviechadmin/settings/__init__.py +++ b/gnuviechadmin/gnuviechadmin/settings/__init__.py @@ -0,0 +1,3 @@ +""" +This module contains settings for various environments. +""" diff --git a/gnuviechadmin/gnuviechadmin/settings/local.py b/gnuviechadmin/gnuviechadmin/settings/local.py index 7fa7460..42fda89 100644 --- a/gnuviechadmin/gnuviechadmin/settings/local.py +++ b/gnuviechadmin/gnuviechadmin/settings/local.py @@ -1,6 +1,9 @@ # -*- python -*- # pymode:lint_ignore=W0401,E501 -"""Development settings and globals.""" +""" +Development settings and globals based on :py:mod:`gvaldap.settings.base`. + +""" from __future__ import absolute_import diff --git a/gnuviechadmin/gnuviechadmin/settings/production.py b/gnuviechadmin/gnuviechadmin/settings/production.py index 6be4255..f4e8b7d 100644 --- a/gnuviechadmin/gnuviechadmin/settings/production.py +++ b/gnuviechadmin/gnuviechadmin/settings/production.py @@ -1,6 +1,9 @@ # -*- python -*- # pymode:lint_ignore=W0401,E501 -"""Production settings and globals.""" +""" +Production settings and globals based on :py:mod:`gvaldap.settings.base`. + +""" from __future__ import absolute_import diff --git a/gnuviechadmin/gnuviechadmin/settings/test.py b/gnuviechadmin/gnuviechadmin/settings/test.py index 872b320..623ede9 100644 --- a/gnuviechadmin/gnuviechadmin/settings/test.py +++ b/gnuviechadmin/gnuviechadmin/settings/test.py @@ -1,3 +1,8 @@ +# pymode:lint_ignore=W0401 +""" +Test settings based on :py:mod:`gvaldap.settings.base`. + +""" from __future__ import absolute_import # use import * to import all settings from base From 3c5d02776a1e80352c62008d1e500849b75aab0b Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 1 Feb 2016 22:27:05 +0000 Subject: [PATCH 076/111] Update Django and gvacommon dependencies This commit updates the Django dependency version to 1.9.2 and gvacommon to 0.3.0 that provides gvacommon.settings_utils.get_env_variable. The gnuviechadmin.settings.base module now uses this implementation instead of an own copy and the corresponding test has been removed too. --- gnuviechadmin/gnuviechadmin/settings/base.py | 20 +--------------- .../gnuviechadmin/tests/test_settings.py | 23 ------------------- requirements/base.txt | 4 ++-- 3 files changed, 3 insertions(+), 44 deletions(-) delete mode 100644 gnuviechadmin/gnuviechadmin/tests/test_settings.py diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 15b4128..d1ce425 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -5,30 +5,12 @@ Common settings and globals. """ -from os import environ from os.path import abspath, basename, dirname, join, normpath from sys import path -# Normally you should not import ANYTHING from Django directly -# into your settings, but ImproperlyConfigured is an exception. -from django.core.exceptions import ImproperlyConfigured from django.contrib.messages import constants as messages - -def get_env_variable(var_name): - """ - Get a setting from an environment variable. - - :param str var_name: variable name - :raises ImproperlyConfigured: if the environment setting is not defined - :return: environment setting value - :rtype: str - """ - try: - return environ[var_name] - except KeyError: - error_msg = "Set the %s environment variable" % var_name - raise ImproperlyConfigured(error_msg) +from gvacommon.settings_utils import get_env_variable # ######### PATH CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/tests/test_settings.py b/gnuviechadmin/gnuviechadmin/tests/test_settings.py deleted file mode 100644 index ed69779..0000000 --- a/gnuviechadmin/gnuviechadmin/tests/test_settings.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- python -*- -# -*- coding: utf-8 -*- -import os -from unittest import TestCase - -from django.core.exceptions import ImproperlyConfigured - -from gnuviechadmin.settings.base import get_env_variable - - -class GetEnvVariableTest(TestCase): - - def test_get_existing_env_variable(self): - os.environ['testvariable'] = 'myvalue' - self.assertEqual(get_env_variable('testvariable'), 'myvalue') - - def test_get_missing_env_variable(self): - if 'missingvariable' in os.environ: - del os.environ['missingvariable'] - with self.assertRaises(ImproperlyConfigured) as e: - get_env_variable('missingvariable') - self.assertEqual( - str(e.exception), 'Set the missingvariable environment variable') diff --git a/requirements/base.txt b/requirements/base.txt index d08a5dc..ab8fc2c 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django==1.9.1 +Django==1.9.2 curtsies==0.1.21 bpython==0.14.2 django-braces==1.8.1 @@ -18,5 +18,5 @@ python-openid==2.2.5 requests==2.9.1 requests-oauthlib==0.6.0 simplejson==3.8.1 --e git+https://git.gnuviech-server.de/gvacommon.git@0.2.1#egg=gvacommon +-e git+https://git.gnuviech-server.de/gvacommon.git@0.3.0#egg=gvacommon redis==2.10.5 From 09cfc6a373e57fcb718e06007304778e3fe9553e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 1 Feb 2016 22:31:34 +0000 Subject: [PATCH 077/111] Update Sphinx, bpython and cursies to latest versions --- requirements/base.txt | 4 ++-- requirements/local.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index ab8fc2c..39a2921 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,6 @@ Django==1.9.2 -curtsies==0.1.21 -bpython==0.14.2 +curtsies==0.2.6 +bpython==0.15.0 django-braces==1.8.1 django-model-utils==2.4 django-crispy-forms==1.6.0 diff --git a/requirements/local.txt b/requirements/local.txt index 4931134..bf0bc9e 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -2,7 +2,7 @@ -r test.txt django-debug-toolbar==1.4 sqlparse==0.1.18 -Sphinx==1.3.3 +Sphinx==1.3.5 snowballstemmer==1.2.1 releases==1.0.0 flake8==2.5.2 From 5dc3549896e3a66d9492fd52ab3657267487a03a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 24 Sep 2016 21:57:28 +0200 Subject: [PATCH 078/111] Improve documentation This commit adds a lot of documentation including block diagramms for message flows. --- docs/code/osusers.rst | 6 + docs/code/userdbs.rst | 6 + docs/conf.py | 7 +- docs/index.rst | 27 +- docs/task_flows.rst | 35 +++ gnuviechadmin/fileservertasks/tasks.py | 333 ++++++++++++++++++++++--- gnuviechadmin/ldaptasks/tasks.py | 81 ++++-- gnuviechadmin/osusers/admin.py | 28 +++ gnuviechadmin/osusers/signals.py | 275 ++++++++++++++++++-- gnuviechadmin/userdbs/signals.py | 141 +++++++++++ requirements/local.txt | 1 + 11 files changed, 853 insertions(+), 87 deletions(-) create mode 100644 docs/task_flows.rst diff --git a/docs/code/osusers.rst b/docs/code/osusers.rst index 3353239..29d62e3 100644 --- a/docs/code/osusers.rst +++ b/docs/code/osusers.rst @@ -31,6 +31,12 @@ .. automodule:: osusers.models :members: +:py:mod:`signals ` +----------------------------------- + +.. automodule:: osusers.signals + :members: + :py:mod:`urls ` ----------------------------- diff --git a/docs/code/userdbs.rst b/docs/code/userdbs.rst index c7af8a1..d5b0da2 100644 --- a/docs/code/userdbs.rst +++ b/docs/code/userdbs.rst @@ -30,6 +30,12 @@ .. automodule:: userdbs.models :members: +:py:mod:`signals ` +----------------------------------- + +.. automodule:: userdbs.signals + :members: + :py:mod:`templatetags ` --------------------------------------------- diff --git a/docs/conf.py b/docs/conf.py index 9fa7d82..11a3fa5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,12 +33,17 @@ django.setup() # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx'] +extensions = [ + 'releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx', + 'sphinxcontrib.blockdiag'] # configuration for releases extension releases_issue_uri = 'https://dev.gnuviech-server.de/gva/ticket/%s' releases_release_uri = 'https://dev.gnuviech-server.de/gva/browser/?rev=%s' +# configuration for blockdiag extension +blockdiag_fontpath = '/usr/share/fonts/truetype/dejavu/' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/index.rst b/docs/index.rst index c40633d..e5ae1d7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,20 @@ Welcome to gnuviechadmin's documentation! .. include:: ../README.rst +Contents +-------- + +.. toctree:: + :maxdepth: 3 + + install + deploy + tests + code + ideas + task_flows + changelog + License ------- @@ -20,19 +34,6 @@ later version. .. include:: ../COPYING :literal: -Contents --------- - -.. toctree:: - :maxdepth: 2 - - install - deploy - tests - code - ideas - changelog - Indices and tables ------------------ diff --git a/docs/task_flows.rst b/docs/task_flows.rst new file mode 100644 index 0000000..c75e878 --- /dev/null +++ b/docs/task_flows.rst @@ -0,0 +1,35 @@ +********** +Task Flows +********** + +gva uses Celery tasks to trigger actions on several servers, this chapter lists +the code parts that start tasks. See the code documentation for details on the +information flow. + +:py:mod:`osusers.admin` +======================= + + * :py:meth:`osusers.admin.SshPublicKeyAdmin.perform_delete_selected` + + +:py:mod:`osusers.signals` +========================= + + * :py:func:`osusers.signals.handle_group_created` + * :py:func:`osusers.signals.handle_group_deleted` + * :py:func:`osusers.signals.handle_ssh_keys_changed` + * :py:func:`osusers.signals.handle_user_added_to_group` + * :py:func:`osusers.signals.handle_user_created` + * :py:func:`osusers.signals.handle_user_deleted` + * :py:func:`osusers.signals.handle_user_password_set` + * :py:func:`osusers.signals.handle_user_removed_from_group` + + +:py:mod:`userdbs.signals` +========================= + + * :py:func:`userdbs.signals.handle_dbuser_created` + * :py:func:`userdbs.signals.handle_dbuser_deleted` + * :py:func:`userdbs.signals.handle_dbuser_deleted` + * :py:func:`userdbs.signals.handle_dbuser_password_set` + * :py:func:`userdbs.signals.handle_userdb_created` diff --git a/gnuviechadmin/fileservertasks/tasks.py b/gnuviechadmin/fileservertasks/tasks.py index 75ebad0..2de6d84 100644 --- a/gnuviechadmin/fileservertasks/tasks.py +++ b/gnuviechadmin/fileservertasks/tasks.py @@ -14,10 +14,38 @@ def setup_file_sftp_userdir(username, *args, **kwargs): This task creates the home directory for an SFTP user if it does not exist yet. - :param str username: the user name + :param str username: the username :raises Exception: if the SFTP directory of the user cannot be created - :return: the created directory name - :rtype: str + :return: a dictionary with the key :py:const:`username` set to the + username value and a new key :py:const:`sftp_directory` set to the + path of the created SFTP directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`fileservertasks.tasks.setup_file_sftp_userdir_chained` + at other positions in the task chain. + + """ + + +@shared_task +def setup_file_sftp_userdir_chained(previous_result, *args, **kwargs): + """ + This task creates the home directory for an SFTP user if it does not exist + yet. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` key + :raises Exception: if the SFTP directory of the user cannot be created + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`sftp_directory` key set to the path of the created SFTP + directory + :rtype: dict """ @@ -26,12 +54,40 @@ def setup_file_sftp_userdir(username, *args, **kwargs): def delete_file_sftp_userdir(username, *args, **kwargs): """ This task recursively deletes the home directory of an SFTP user if it - does not exist yet. + exists. - :param str username: the user name + :param str username: the username :raises Exception: if the SFTP directory of the user cannot be removed - :return: the removed directory name - :rtype: str + :return: a dictionary with the key :py:const:`username` set to the username + value and the new key :py:const:`sftp_directory` set to the path of the + deleted SFTP directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`fileservertasks.tasks.delete_file_sftp_userdir_chained` + at other positions in the task chain. + + """ + + +@shared_task +def delete_file_sftp_userdir_chained(previous_result, *args, **kwargs): + """ + This task recursively deletes the home directory of an SFTP user if it + exists. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` key + :raises Exception: if the SFTP directory of the user cannot be removed + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`sftp_directory` key set to the path of the removed SFTP + directory + :rtype: dict """ @@ -42,11 +98,40 @@ def setup_file_mail_userdir(username, *args, **kwargs): This task creates the mail base directory for a user if it does not exist yet. - :param str username: the user name + :param str username: the username :raises Exception: if the mail base directory for the user cannot be created - :return: the created directory name - :rtype: str + :return: a dictionary with the key :py:const:`username` set to the + username value and a new key :py:const:`mail_directory` set to the path + of the created mail directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`fileservertasks.tasks.setup_file_mail_userdir_chained` + at other positions in the task chain. + + """ + + +@shared_task +def setup_file_mail_userdir_chained(previous_result, *args, **kwargs): + """ + This task creates the mail base directory for a user if it does not exist + yet. + + :param dict previous_result: a dictionary containing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` key + :raises Exception: if the mail base directory for the user cannot be + created + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`mail_directory` key set to the path of the created mail + directory + :rtype: dict """ @@ -57,79 +142,255 @@ def delete_file_mail_userdir(username, *args, **kwargs): This task recursively deletes the mail base directory for a user if it does not exist yet. - :param str username: the user name - :raises Exception: if the mail base directory of the user cannot be removed - :return: the removed directory name + :param str username: the username + :raises Exception: if the mail base directory of the user cannot be deleted + :return: a dictionary with the key :py:const:`username` set to the + username value and a new key :py:const:`mail_directory` set to the path + of the deleted mail directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`fileservertasks.tasks.delete_file_mail_userdir_chained` + at other positions in the task chain. + + """ + + +@shared_task +def delete_file_mail_userdir_chained(previous_result, *args, **kwargs): + """ + This task recursively deletes the mail base directory for a user if it + does not exist yet. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` key + :raises Exception: if the mail base directory of the user cannot be deleted + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`mail_directory` key set to the path of the deleted mail + directory :rtype: str """ @shared_task -def create_file_mailbox(username, mailboxname): +def create_file_mailbox(username, mailboxname, *args, **kwargs): """ This task creates a new mailbox directory for the given user and mailbox name. - :param str username: the user name + :param str username: the username :param str mailboxname: the mailbox name :raises Exception: if the mailbox directory cannot be created - :return: the created mailbox directory name - :rtype: str + :return: a dictionary with the keys :py:const:`username` and + :py:const:`mailboxname` set to the values of username and mailboxname + and a new key :py:const:`mailbox_directory` set to the path of the + created mailbox directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`fileservertasks.tasks.create_file_mailbox_chained` at + other positions in the task chain. """ @shared_task -def delete_file_mailbox(username, mailboxname): +def create_file_mailbox_chained(previous_result, *args, **kwargs): + """ + This task creates a new mailbox directory for the given user and mailbox + name. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` and a :py:const:`mailboxname` key + :raises Exception: if the mailbox directory cannot be created + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`mailbox_directory` key set to the path of the created + mailbox directory + :rtype: dict + + """ + + +@shared_task +def delete_file_mailbox(username, mailboxname, *args, **kwargs): """ This task deletes the given mailbox of the given user. - :param str username: the user name + :param str username: the username :param str mailboxname: the mailbox name :raises Exception: if the mailbox directory cannot be deleted - :return: the deleted mailbox directory name - :rtype: str + :return: a dictionary with the keys :py:const:`username` and + :py:const:`mailboxname` set to the values of username and mailboxname + and a new key :py:const:`mailbox_directory` set to the path of the + deleted mailbox directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`fileservertasks.tasks.delete_file_mailbox_chained` for + other positions in the task chain. """ @shared_task -def create_file_website_hierarchy(username, sitename): +def delete_file_mailbox_chained(previous_result, *args, **kwargs): + """ + This task deletes the given mailbox of the given user. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` and a :py:const:`mailboxname` key + :raises Exception: if the mailbox directory cannot be deleted + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`mailbox_directory` key set to the path of the deleted + mailbox directory + :rtype: dict + + """ + + +@shared_task +def create_file_website_hierarchy(username, sitename, *args, **kwargs): """ This task creates the directory hierarchy for a website. - :param str username: the user name - :param str sitename: name of the website - :return: the directory name - :rtype: str + :param str username: the username + :param str sitename: the sitename + :raises Exception: if the website directory hierarchy directory cannot be + created + :return: a dictionary with the keys :py:const:`username` and + :py:const:`sitename` set to the values of username and sitename and a + new key :py:const:`website_directory` set to the path of the created + website directory + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use + :py:func:`fileservertasks.tasks.create_file_website_hierarchy_chained` + at other positions in the task chain """ @shared_task -def delete_file_website_hierarchy(username, sitename): +def create_file_website_hierarchy_chained(previous_result, *args, **kwargs): + """ + This task creates the directory hierarchy for a website. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` and a :py:const:`sitename` key + :raises Exception: if the website directory hierarchy directory cannot be + created + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`website_directory` key set to the path of the created + website directory + :rtype: dict + + """ + + +@shared_task +def delete_file_website_hierarchy(username, sitename, *args, **kwargs): + """ + This task deletes a website hierarchy recursively. + + :param str username: a username + :param str sitename: a site name + :return: a dictionary with the keys :py:const:`username` and + :py:const:`sitename` set to their original values and a new key + :py:const:`website_directory` set to the path of the deleted website + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use + :py:func:`fileservertasks.tasks.delete_file_website_hierarchy_chained` + at other positions in the task chain + + """ + + +@shared_task +def delete_file_website_hierarchy_chained(previous_result, *args, **kwargs): """ This task deletes the website hierarchy recursively. - :param str username: the user name - :param str sitename: name of the website - :return: the directory name - :rtype: str + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` and a :py:const:`sitename` key + :raises Exception: if the website directory hierarchy directory cannot be + deleted + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`website_directory` set to the path of the deleted website + directory + :rtype: dict """ @shared_task -def set_file_ssh_authorized_keys(username, ssh_keys): +def set_file_ssh_authorized_keys(username, ssh_keys, *args, **kwargs): + """ + This task set the authorized keys for ssh logins. + + :param str username: a username + :param list ssh_keys: a list of ssh keys + :raises Exception: if the update of the creation or update of ssh + authorized_keys failed + :return: a dictionary with the keys :py:const:`username` and + :py:const:`ssh_keys` set to their original values and a new key + :py:const:`ssh_authorized_keys` set to the path of the SSH + authorized_keys file + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use + :py:func:`fileservertasks.tasks.set_file_ssh_authorized_keys_chained` + at other positions in the task chain + + """ + + +@shared_task +def set_file_ssh_authorized_keys_chained(previous_result, *args, **kwargs): """ This task sets the authorized keys for ssh logins. - :param str username: the user name - :param list ssh_key: an ssh_key + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` and a :py:const:`ssh_keys` key :raises Exception: if the update of the creation or update of ssh authorized_keys failed - :return: the name of the authorized_keys file - :rtype: str + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`ssh_authorized_keys` set to the path of the SSH + authorized_keys file + :rtype: dict """ diff --git a/gnuviechadmin/ldaptasks/tasks.py b/gnuviechadmin/ldaptasks/tasks.py index 72acd8d..f47437c 100644 --- a/gnuviechadmin/ldaptasks/tasks.py +++ b/gnuviechadmin/ldaptasks/tasks.py @@ -9,7 +9,7 @@ from celery import shared_task @shared_task -def create_ldap_group(groupname, gid, descr): +def create_ldap_group(groupname, gid, description): """ This task creates an :py:class:`LDAP group ` if it does not exist yet. @@ -19,9 +19,12 @@ def create_ldap_group(groupname, gid, descr): :param str groupname: the group name :param int gid: the group id - :param str descr: description text for the group - :return: the distinguished name of the group - :rtype: str + :param str description: description text for the group + :return: dictionary containing groupname, gid, description and + :py:const:`group_dn` set to the distinguished name of the newly created + or existing LDAP group + :rtype: dict + """ @@ -45,8 +48,10 @@ def create_ldap_user(username, uid, gid, gecos, homedir, shell, password): is passed the password is not touched :raises celery.exceptions.Reject: if the specified primary group does not exist - :return: the distinguished name of the user - :rtype: str + :return: dictionary containing username, uid, gid, gecos, homedir, shell, + password and :py:const:`user_dn` set to the distinguished name of the + newly created or existing LDAP user + :rtype: dict """ @@ -59,8 +64,10 @@ def set_ldap_user_password(username, password): :param str username: the user name :param str password: teh clear text password - :return: :py:const:`True` if the password has been set, :py:const:`False` - if the user does not exist. + :return: dictionary containing the username and a flag + :py:const:`password_set` that is set to :py:const:`True` if the + password has been set, :py:const:`False` if the user does not exist. + :rtype: dict """ @@ -76,8 +83,10 @@ def add_ldap_user_to_group(username, groupname): :param str groupname: the group name :raises celery.exceptions.Retry: if the user does not exist yet, :py:func:`create_ldap_user` should be called before - :return: True if the user has been added to the group otherwise False - :rtype: boolean + :return: dictionary containing the username, groupname and a flag + :py:const`added` that is as a :py:const:`True` if the user has been + added to the group otherwise to :py:const:`False` + :rtype: dict """ @@ -89,20 +98,48 @@ def remove_ldap_user_from_group(username, groupname): :param str username: the user name :param str groupname: the group name - :return: True if the user has been removed, False otherwise - :rtype: boolean + :return: dictionary containing the input parameters and a flag + :py:const:`removed` that is set to :py:const:`True` if the user has + been removed, False otherwise + :rtype: dict """ @shared_task -def delete_ldap_user(username): +def delete_ldap_user(username, *args, **kwargs): """ This task deletes the given user. :param str username: the user name - :return: True if the user has been deleted, False otherwise - :rtype: boolean + :return: dictionary containing the username and a flag :py:const:`deleted` + that is set to :py:const:`True` if the user has been deleted and is set + to :py:const:`False` otherwise + :rtype: dict + + .. note:: + + This variant can only be used at the beginning of a Celery task chain + or as a standalone task. + + Use :py:func:`ldaptasks.tasks.delete_ldap_user_chained` at other + positions in the task chain + + """ + + +@shared_task +def delete_ldap_user_chained(previous_result, *args, **kwargs): + """ + This task deletes the given user. + + :param dict previous_result: a dictionary describing the result of the + previous step in the Celery task chain. This dictionary must contain a + :py:const:`username` key + :return: a copy of the :py:obj:`previous_result` dictionary with a new + :py:const:`deleted` key set to :py:const:`True` if the user has been + deleted and set to :py:const:`False` otherwise + :rtype: dict """ @@ -113,8 +150,10 @@ def delete_ldap_group_if_empty(groupname): This task deletes the given group if it is empty. :param str groupname: the group name - :return: True if the user has been deleted, False otherwise - :rtype: boolean + :return: dictionary that contains the groupname and a flag + :py:const:`deleted` that is set to :py:const:`True` if the group has + been deleted and is set to :py:const:`False` otherwise + :rtype: dict """ @@ -122,10 +161,12 @@ def delete_ldap_group_if_empty(groupname): @shared_task def delete_ldap_group(groupname): """ - This taks deletes the given group. + This task deletes the given group. :param str groupname: the group name - :return: True if the user has been deleted, False otherwise - :rtype: boolean + :return: dictionary that contains the groupname and a flag + :py:const:`deleted` that is set to :py:const:`True` if the group has + been deleted and is set to :py:const:`False` otherwise + :rtype: dict """ diff --git a/gnuviechadmin/osusers/admin.py b/gnuviechadmin/osusers/admin.py index addb70b..3b4fb2b 100644 --- a/gnuviechadmin/osusers/admin.py +++ b/gnuviechadmin/osusers/admin.py @@ -1,6 +1,10 @@ """ This module contains the Django admin classes of the :py:mod:`osusers` app. +The module starts Celery_ tasks. + +.. _Celery: http://www.celeryproject.org/ + """ from django import forms from django.utils.translation import ugettext_lazy as _ @@ -341,6 +345,30 @@ class SshPublicKeyAdmin(admin.ModelAdmin): :param request: the current HTTP request :param queryset: Django ORM queryset representing the selected ssh keys + This method starts a Celery_ task to update the list of authorized keys + for each affected user. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this method" + ]; + B [ label = "set file ssh authorized_keys", + description = ":py:func:`set_file_ssh_authorized_keys() + ` + called with username and a list of keys, returning the path + of the ssh authorized_keys file", + color = "LightGreen", + stacked + ]; + } + """ users = set([ item['user'] for item in diff --git a/gnuviechadmin/osusers/signals.py b/gnuviechadmin/osusers/signals.py index a74442b..e062cb9 100644 --- a/gnuviechadmin/osusers/signals.py +++ b/gnuviechadmin/osusers/signals.py @@ -1,6 +1,10 @@ """ This module contains the signal handlers of the :py:mod:`osusers` app. +The module starts Celery_ tasks. + +.. _Celery: http://www.celeryproject.org/ + """ from __future__ import absolute_import, unicode_literals @@ -14,17 +18,17 @@ from django.dispatch import receiver from fileservertasks.tasks import ( delete_file_mail_userdir, - delete_file_sftp_userdir, + delete_file_sftp_userdir_chained, set_file_ssh_authorized_keys, - setup_file_mail_userdir, - setup_file_sftp_userdir, + setup_file_mail_userdir_chained, + setup_file_sftp_userdir_chained, ) from ldaptasks.tasks import ( add_ldap_user_to_group, create_ldap_group, create_ldap_user, delete_ldap_group, - delete_ldap_user, + delete_ldap_user_chained, remove_ldap_user_from_group, set_ldap_user_password, ) @@ -44,6 +48,33 @@ _LOGGER = logging.getLogger(__name__) @receiver(password_set, sender=User) def handle_user_password_set(sender, instance, password, **kwargs): + """ + Handles password changes on :py:class:`User ` + instances. + + :param sender: sender of the signal + :param instance: User instance + :param str password: the new password + + This signal handler starts a Celery_ task. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "set ldap user password", color = "Wheat", + description = ":py:func:`set_ldap_user_password() + ` called with + username and password, returning :py:const:`True` if the + password has been set" ]; + } + """ taskresult = TaskResult.objects.create_task_result( 'handle_user_password_set', set_ldap_user_password.s(instance.username, password)) @@ -52,15 +83,35 @@ def handle_user_password_set(sender, instance, password, **kwargs): taskresult.task_id) -# @receiver(post_save) -# def handle_post_save(sender, **kwargs): -# _LOGGER.debug( -# 'handling post_save signal for %s with args %s', -# sender, kwargs) - - @receiver(post_save, sender=Group) def handle_group_created(sender, instance, created, **kwargs): + """ + Handles post creation actions on :py:class:`Group ` + instances. + + :param sender: sender of the signal + :param instance: Group instance + :param bool created: whether the instance has just been created + + This signal handler starts a Celery_ task. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "create ldap group", color = "Wheat", + description = ":py:func:`create_ldap_group() + ` called with groupname, + gid and description, returning group DN" ]; + } + + """ if created: taskresult = TaskResult.objects.create_task_result( 'handle_group_created', @@ -75,13 +126,52 @@ def handle_group_created(sender, instance, created, **kwargs): @receiver(post_save, sender=User) def handle_user_created(sender, instance, created, **kwargs): + """ + Handles post creation actions on :py:class:`User ` + instances. + + :param sender: sender of the signal + :param instance: User instance + :param bool created: whether the instance has just bean created + + This signal handler starts a chain of Celery_ tasks. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B -> C -> D; + B -> C [folded]; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "create ldap user", color = "Wheat", + description = ":py:func:`create_ldap_user() + ` called with username, uid, + gid, gecos, homeidr, shell, :py:const:`None`, returning + username" ]; + C [ label = "setup file sftp userdir", color = "LightGreen", + description = ":py:func:`setup_file_sftp_userdir_chained() + ` + called with the result of create ldap user task, returning a + dictionary containing username and sftp_directory"]; + D [ label = "setup file mail userdir", color = "LightGreen", + description = ":py:func:`setup_file_mail_userdir_chained() + ` called + with result of setup file sftp userdir task, returning + dictionary containing username, sftp_directory and + mail_directory" ]; + } + + """ if created: chain = create_ldap_user.s( instance.username, instance.uid, instance.group.gid, instance.gecos, instance.homedir, instance.shell, None - ) | setup_file_sftp_userdir.s( - instance.username - ) | setup_file_mail_userdir.s(instance.username) + ) | setup_file_sftp_userdir_chained.s() | ( + setup_file_mail_userdir_chained.s()) taskresult = TaskResult.objects.create_task_result( 'handle_user_created', chain) _LOGGER.info( @@ -93,6 +183,34 @@ def handle_user_created(sender, instance, created, **kwargs): @receiver(post_save, sender=AdditionalGroup) def handle_user_added_to_group(sender, instance, created, **kwargs): + """ + Handles post creation actions on :py:class:`AdditionalGroup + ` instances. + + :param sender: sender of the signal + :param instance: AdditionalGroup instance + :param bool created: whether the instance has just bean created + + This signal handler starts a Celery_ task. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "add ldap user to group", color = "Wheat", + description = ":py:func:`add_ldap_user_to_group() + ` called with username + and groupname, returning :py:const:`True` if the user has been + added to the group" ]; + } + + """ if created: taskresult = TaskResult.objects.create_task_result( 'handle_user_added_to_group', @@ -106,6 +224,33 @@ def handle_user_added_to_group(sender, instance, created, **kwargs): @receiver(post_save, sender=SshPublicKey) @receiver(post_delete, sender=SshPublicKey) def handle_ssh_keys_changed(sender, instance, **kwargs): + """ + Handles changes to :py:class:`SshPublicKey ` + instances related to a user. + + :param sender: sender of the signal + :param instance: SshPublicKey instance + + This signal handler starts a Celery_ task. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "set file ssh authorized_keys", color = "LightGreen", + description = ":py:func:`set_file_ssh_authorized_keys() + ` called + with username and the corresponding list of keys, returning the + path of the ssh_authorized_keys_file" ]; + } + + """ sig = set_file_ssh_authorized_keys.s( instance.user.username, [ str(key) for key in @@ -126,6 +271,33 @@ def handle_ssh_keys_changed(sender, instance, **kwargs): @receiver(post_delete, sender=Group) def handle_group_deleted(sender, instance, **kwargs): + """ + Handles cleanup actions to be done after deletion of a :py:class:`Group + ` instance. + + :param sender: sender of the signal + :param instance: Group instance + + This signal handler starts a Celery_ task. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this signal handler" + ]; + B [ label = "delete ldap group", color = "Wheat", + description = ":py:func:`delete_ldap_group() + ` called with groupname, + returning :py:const:`True` if the group has been deleted" ]; + } + + """ taskresult = TaskResult.objects.create_task_result( 'handle_group_deleted', delete_ldap_group.s(instance.groupname)) @@ -136,11 +308,49 @@ def handle_group_deleted(sender, instance, **kwargs): @receiver(post_delete, sender=User) def handle_user_deleted(sender, instance, **kwargs): + """ + Handles cleanup actions to be done after deletion of a :py:class:`User + ` instance. + + :param sender: sender of the signal + :param instance: User instance + + This signal handler starts a chain of Celery_ tasks. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B -> C -> D; + B -> C [folded]; + + A [ label = "", shape = beginpoint, + description = "this signal handler" + ]; + B [ label = "delete file mail userdir", color = "LightGreen", + description = ":py:func:`delete_file_mail_userdir() + ` called with + username, returning a dictionary containing the username and + the deleted mail_directory" ]; + C [ label = "delete file sftp userdir", color = "LightGreen", + description = ":py:func:`delete_file_sftp_userdir_chained() + ` + called with the result of delete mail userdir, returning + dictionary containing username, deleted mail_directory and + deleted sftp_directory" ]; + D [ label = "delete ldap user", color = "Wheat", + description = ":py:func:`delete_ldap_user_chained() + ` called with the + result of delete file sftp userdir and adding the deleted user + DN to the result" ]; + } + + """ chain = delete_file_mail_userdir.s( instance.username - ) | delete_file_sftp_userdir.s( - instance.username - ) | delete_ldap_user.s(instance.username) + ) | delete_file_sftp_userdir_chained.s() | delete_ldap_user_chained.s() _LOGGER.debug('chain signature %s', chain) taskresult = TaskResult.objects.create_task_result( 'handle_user_deleted', chain) @@ -151,6 +361,37 @@ def handle_user_deleted(sender, instance, **kwargs): @receiver(post_delete, sender=AdditionalGroup) def handle_user_removed_from_group(sender, instance, **kwargs): + """ + Handles cleanup actions to be done after removing a user from a group by + deleting the :py:class:`AdditionalGroup ` + instance. + + :param sender: sender of the signal + :param instance: AdditionalGroup instance + + This signal handler starts a Celery_ task. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + + A [ label = "", shape = beginpoint, + description = "this signal handler" + ]; + B [ label = "remove ldap user from group", color = "Wheat", + description = ":py:func:`remove_ldap_user_from_group() + ` called with + username and groupname, returning :py:const:`True` if the user + has been a member of the group and has been removed from the + group" + ]; + } + + """ taskresult = TaskResult.objects.create_task_result( 'handle_user_removed_from_group', remove_ldap_user_from_group.s( diff --git a/gnuviechadmin/userdbs/signals.py b/gnuviechadmin/userdbs/signals.py index fde20ac..4a5bc32 100644 --- a/gnuviechadmin/userdbs/signals.py +++ b/gnuviechadmin/userdbs/signals.py @@ -1,6 +1,10 @@ """ This module contains the signal handlers of the :py:mod:`userdbs` app. +The module starts Celery_ tasks. + +.. _Celery: http://www.celeryproject.org/ + """ from __future__ import unicode_literals @@ -29,6 +33,34 @@ def handle_dbuser_password_set(sender, instance, password, **kwargs): Signal handler triggered by password changes for :py:class:`userdbs.models.DatabaseUser` instances. + :param sender: the sender of the signal + :param instance: the Database user instance + :param str password: the new password for the database user + + This signal handler starts Celery_ tasks depending on the db_type value of + the database user. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + A -> C; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "set mysql userpassword", color = "PowderBlue", + description = ":py:func:`set_mysql_userpassword() + ` called with + database username and password" ]; + C [ label = "set pgsql userpassword", color = "DodgerBlue", + description = ":py:func:`set_pgsql_userpassword() + ` called with + database username and password" ]; + } + """ if instance.db_type == DB_TYPES.mysql: taskresult = TaskResult.objects.create_task_result( @@ -59,6 +91,35 @@ def handle_dbuser_created(sender, instance, created, **kwargs): Signal handler triggered after the creation of or updates to :py:class:`userdbs.models.DatabaseUser` instances. + :param sender: the sender of the signal + :param instance: the DatabaseUser instance + :param bool created: whether this signal handler is called for a newly + created instance + + This signal handler starts Celery_ tasks depending on the db_type value of + the database user. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + A -> C; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "create mysql user", color = "PowderBlue", + description = ":py:func:`create_mysql_user() + ` called with database + username and password" ]; + C [ label = "create pgsql user", color = "DodgerBlue", + description = ":py:func:`create_pgsql_user + ` called with database + username and password" ]; + } + """ if created: password = kwargs.get('password', generate_password()) @@ -95,6 +156,32 @@ def handle_dbuser_deleted(sender, instance, **kwargs): Signal handler triggered after the deletion of :py:class:`userdbs.models.DatabaseUser` instances. + :param sender: the sender of the signal + :param instance: the DatabaseUser instance + + This signal handler starts Celery_ tasks depending on the db_type value of + the database user. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + A -> C; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "delete mysql user", color = "PowderBlue", + description = ":py:func:`delete_mysql_user() + ` called with username + from instance.name" ]; + C [ label = "delete pgsql user", color = "DodgerBlue", + description = ":py:func:`delete_pgsql_user() + ` called with username + from instance.name" ]; + } """ if instance.db_type == DB_TYPES.mysql: taskresult = TaskResult.objects.create_task_result( @@ -126,6 +213,34 @@ def handle_userdb_created(sender, instance, created, **kwargs): Signal handler triggered after the creation of or updates to :py:class:`userdbs.models.UserDatabase` instances. + :param sender: the sender of the signal + :param instance: the UserDatabase instance + :param bool created: whether this signal handler has been called for a + newly created instance + + This signal handler starts Celery_ tasks depending on the db_type value of + the database user owning the UserDatabase instance. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + A -> C; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "create mysql database", color = "PowderBlue", + description = ":py:func:`create_mysql_database() + ` called with database + name and username" ]; + C [ label = "create pgsql database", color = "DodgerBlue", + description = ":py:func:`create_pgsql_database() + ` called with database + name and username" ]; + } """ if created: if instance.db_user.db_type == DB_TYPES.mysql: @@ -163,6 +278,32 @@ def handle_userdb_deleted(sender, instance, **kwargs): Signal handler triggered after the deletion of :py:class:`userdbs.models.UserDatabase` instances. + :param sender: the sender of the signal + :param instance: the UserDatabase instance + + This signal handler starts Celery_ tasks depending on the db_type value of + the database user owning the UserDatabase instance. + + .. blockdiag:: + :desctable: + + blockdiag { + node_width = 200; + + A -> B; + A -> C; + + A [ label = "", shape = beginpoint, + description = "this signal handler" ]; + B [ label = "delete mysql database", color = "PowderBlue", + description = ":py:func:`delete_mysql_user() + ` called with database + name and username" ]; + C [ label = "delete pgsql database", color = "DodgerBlue", + description = ":py:func:`delete_pgsql_user() + ` called with database + name" ]; + } """ if instance.db_user.db_type == DB_TYPES.mysql: taskresult = TaskResult.objects.create_task_result( diff --git a/requirements/local.txt b/requirements/local.txt index bf0bc9e..27428d9 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -9,3 +9,4 @@ flake8==2.5.2 mccabe==0.4.0 pep8==1.7.0 pyflakes==1.0.0 +sphinxcontrib-blockdiag==1.5.5 From 89011b155ab15a086e8c687ffccbbd10866f524e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 24 Sep 2016 23:54:57 +0200 Subject: [PATCH 079/111] Improve vagrant setup Install python-cryptography from jessie-backports and remove autogenerated comments from Vagrantfile. --- Vagrantfile | 45 +++------------------------------------------ salt/bootstrap.sh | 5 +++++ 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 2e0add6..ac15e99 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,60 +1,21 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. Vagrant.configure(2) do |config| - # The most common configuration options are documented and commented below. - # For a complete reference, please see the online documentation at - # https://docs.vagrantup.com. - - # Every Vagrant development environment requires a box. You can search for - # boxes at https://atlas.hashicorp.com/search. - config.vm.box = "debian/jessie64" + config.vm.box = "debian/contrib-jessie64" config.vm.hostname = "gva.local" + config.vm.network "private_network", ip: "172.16.3.2" - # Disable automatic box update checking. If you disable this, then - # boxes will only be checked for updates when the user runs - # `vagrant box outdated`. This is not recommended. - # config.vm.box_check_update = false - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. config.vm.network "forwarded_port", guest: 443, host: 8443 config.vm.network "forwarded_port", guest: 8000, host: 8000 config.vm.network "forwarded_port", guest: 15672, host: 15672 - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network "private_network", ip: "192.168.33.10" - config.vm.network "private_network", ip: "172.16.3.2" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network "public_network" - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - # config.vm.synced_folder "../data", "/vagrant_data" config.vm.synced_folder "../gvasalt/states/", "/srv/salt/" config.vm.synced_folder "../gvasalt/pillar/", "/srv/pillar/" - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # config.vm.provider "virtualbox" do |vb| - # # Display the VirtualBox GUI when booting the machine - # vb.gui = true - # - # # Customize the amount of memory on the VM: + # vb.gui = true vb.memory = "1024" end diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index 129addb..e0a3b07 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -1,5 +1,10 @@ #!/bin/sh - +echo "deb http://httpredir.debian.org/debian jessie-backports main" >/etc/apt/sources.list.d/backports.list + +apt-get update +apt-get install -y -t jessie-backports python-cryptography + # We just download the bootstrap script by default and execute that. if [ -x /usr/bin/fetch ]; then /usr/bin/fetch -o - https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@" From 150c9111ca5f36622661cb6004fbbe2989b755ca Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 25 Sep 2016 01:10:45 +0200 Subject: [PATCH 080/111] Ignore PyCharm files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2716883..6af40a7 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ _build/ .vagrant/ gnuviechadmin/assets/ coverage-report/ +.idea/ From 36d082006b7e5e6034b9bfa9b2a544aa8d16db3d Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 25 Sep 2016 16:36:31 +0200 Subject: [PATCH 081/111] Improve TaskResult admin view Provide a better presentation of TaskResult instances by specifying the list_display fields and allowing to filter finished and unfinished tasks. --- gnuviechadmin/taskresults/admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gnuviechadmin/taskresults/admin.py b/gnuviechadmin/taskresults/admin.py index cd47dc8..3647426 100644 --- a/gnuviechadmin/taskresults/admin.py +++ b/gnuviechadmin/taskresults/admin.py @@ -9,4 +9,7 @@ from django.contrib import admin from .models import TaskResult -admin.site.register(TaskResult) +@admin.register(TaskResult) +class TaskResultAdmin(admin.ModelAdmin): + list_display = ('task_id', 'creator', 'signature', 'finished', 'state') + list_filter = ('finished',) From 108f0e85bfd474565d73c80da560022c7793d9ea Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 25 Sep 2016 17:27:42 +0200 Subject: [PATCH 082/111] Protect /etc/salt/grains Make sure that the permissions of /etc/salt/grains only allow access for the root user. --- salt/bootstrap.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index e0a3b07..6e8835e 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -30,6 +30,7 @@ pillar_roots: log_file: file:///dev/log EOF +umask 077 cat >/etc/salt/grains < Date: Sun, 25 Sep 2016 17:32:25 +0200 Subject: [PATCH 083/111] Add hosting package data example --- fixtures/default_hostingpackages.json | 227 ++++++++++++++++++++++++++ fixtures/default_osuser_groups.json | 46 ++++++ 2 files changed, 273 insertions(+) create mode 100644 fixtures/default_hostingpackages.json create mode 100644 fixtures/default_osuser_groups.json diff --git a/fixtures/default_hostingpackages.json b/fixtures/default_hostingpackages.json new file mode 100644 index 0000000..359662a --- /dev/null +++ b/fixtures/default_hostingpackages.json @@ -0,0 +1,227 @@ +[ + { + "fields": { + "description": "", + "diskspace": 256, + "created": "2015-01-18T10:50:38.392Z", + "modified": "2015-01-18T10:50:38.409Z", + "diskspace_unit": 0, + "mailboxcount": 10, + "name": "Basispaket I" + }, + "model": "hostingpackages.hostingpackagetemplate", + "pk": 1 + }, + { + "fields": { + "description": "", + "diskspace": 512, + "created": "2015-01-18T10:50:55.413Z", + "modified": "2015-01-18T10:50:55.425Z", + "diskspace_unit": 0, + "mailboxcount": 25, + "name": "Basispaket II" + }, + "model": "hostingpackages.hostingpackagetemplate", + "pk": 2 + }, + { + "fields": { + "description": "", + "diskspace": 1, + "created": "2015-01-18T10:51:13.988Z", + "modified": "2015-01-18T10:51:13.999Z", + "diskspace_unit": 1, + "mailboxcount": 50, + "name": "Basispaket III" + }, + "model": "hostingpackages.hostingpackagetemplate", + "pk": 3 + }, + { + "fields": { + "description": "Hostingpaket, dass ausschlie\u00dflich Mailweiterleitungen und Web-Redirects enth\u00e4lt", + "diskspace": 0, + "created": "2015-01-28T20:45:20.598Z", + "modified": "2015-01-28T20:45:20.616Z", + "diskspace_unit": 0, + "mailboxcount": 0, + "name": "Weiterleitung an externe Services" + }, + "model": "hostingpackages.hostingpackagetemplate", + "pk": 4 + }, + { + "fields": { + "diskspace_unit": 0, + "diskspace": 256 + }, + "model": "hostingpackages.diskspaceoption", + "pk": 3 + }, + { + "fields": { + "diskspace_unit": 0, + "diskspace": 512 + }, + "model": "hostingpackages.diskspaceoption", + "pk": 4 + }, + { + "fields": { + "diskspace_unit": 1, + "diskspace": 1 + }, + "model": "hostingpackages.diskspaceoption", + "pk": 5 + }, + { + "fields": { + "diskspace_unit": 1, + "diskspace": 2 + }, + "model": "hostingpackages.diskspaceoption", + "pk": 6 + }, + { + "fields": { + "diskspace_unit": 1, + "diskspace": 5 + }, + "model": "hostingpackages.diskspaceoption", + "pk": 7 + }, + { + "fields": { + "diskspace_unit": 1, + "diskspace": 10 + }, + "model": "hostingpackages.diskspaceoption", + "pk": 8 + }, + { + "fields": { + "db_type": 0, + "number": 1 + }, + "model": "hostingpackages.userdatabaseoption", + "pk": 1 + }, + { + "fields": { + "db_type": 1, + "number": 1 + }, + "model": "hostingpackages.userdatabaseoption", + "pk": 2 + }, + { + "fields": { + "number": 10 + }, + "model": "hostingpackages.mailboxoption", + "pk": 9 + }, + { + "fields": { + "number": 25 + }, + "model": "hostingpackages.mailboxoption", + "pk": 10 + }, + { + "fields": { + "number": 50 + }, + "model": "hostingpackages.mailboxoption", + "pk": 11 + }, + { + "fields": { + "modified": "2015-01-18T10:51:38.298Z", + "created": "2015-01-18T10:51:38.286Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 1 + }, + { + "fields": { + "modified": "2015-01-18T10:51:41.804Z", + "created": "2015-01-18T10:51:41.792Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 2 + }, + { + "fields": { + "modified": "2015-01-18T10:52:03.193Z", + "created": "2015-01-18T10:52:03.181Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 3 + }, + { + "fields": { + "modified": "2015-01-18T10:52:08.430Z", + "created": "2015-01-18T10:52:08.418Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 4 + }, + { + "fields": { + "modified": "2015-01-18T10:52:14.153Z", + "created": "2015-01-18T10:52:14.134Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 5 + }, + { + "fields": { + "modified": "2015-01-18T10:52:19.151Z", + "created": "2015-01-18T10:52:19.138Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 6 + }, + { + "fields": { + "modified": "2015-01-18T10:52:24.461Z", + "created": "2015-01-18T10:52:24.448Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 7 + }, + { + "fields": { + "modified": "2015-01-18T10:52:30.821Z", + "created": "2015-01-18T10:52:30.807Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 8 + }, + { + "fields": { + "modified": "2015-01-18T10:52:53.657Z", + "created": "2015-01-18T10:52:53.646Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 9 + }, + { + "fields": { + "modified": "2015-01-18T10:52:56.079Z", + "created": "2015-01-18T10:52:56.064Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 10 + }, + { + "fields": { + "modified": "2015-01-18T10:53:01.634Z", + "created": "2015-01-18T10:53:01.622Z" + }, + "model": "hostingpackages.hostingoption", + "pk": 11 + } +] \ No newline at end of file diff --git a/fixtures/default_osuser_groups.json b/fixtures/default_osuser_groups.json new file mode 100644 index 0000000..f570511 --- /dev/null +++ b/fixtures/default_osuser_groups.json @@ -0,0 +1,46 @@ +[ + { + "fields": { + "created": "2014-06-03T10:13:59.796Z", + "descr": "SFTP users", + "groupname": "sftponly", + "modified": "2014-06-03T10:13:59.804Z", + "passwd": "" + }, + "model": "osusers.group", + "pk": 2000 + }, + { + "fields": { + "created": "2014-06-03T10:14:14.353Z", + "descr": "", + "groupname": "wwwusers", + "modified": "2014-06-03T10:14:14.360Z", + "passwd": "" + }, + "model": "osusers.group", + "pk": 2001 + }, + { + "fields": { + "created": "2014-06-03T10:14:31.853Z", + "descr": "", + "groupname": "webserver", + "modified": "2014-06-03T10:14:31.860Z", + "passwd": "" + }, + "model": "osusers.group", + "pk": 2002 + }, + { + "fields": { + "created": "2016-06-18T10:32:26.490Z", + "descr": "Group for git only access", + "groupname": "gitonly", + "modified": "2016-06-18T10:32:26.498Z", + "passwd": "" + }, + "model": "osusers.group", + "pk": 2003 + } +] \ No newline at end of file From c676415c97fe071d1eaaf02021004491082da64c Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 10 Aug 2018 11:57:00 +0200 Subject: [PATCH 084/111] Update vagrant setup to stretch64 with LXC --- Vagrantfile | 9 ++------- salt/bootstrap.sh | 4 +--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index ac15e99..63b11ce 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -2,10 +2,10 @@ # vi: set ft=ruby : Vagrant.configure(2) do |config| - config.vm.box = "debian/contrib-jessie64" + config.vm.box = "debian/stretch64" config.vm.hostname = "gva.local" - config.vm.network "private_network", ip: "172.16.3.2" + config.vm.network "private_network", ip: "172.16.3.2", lxc__bridge_name: 'vlxcbr1' config.vm.network "forwarded_port", guest: 443, host: 8443 config.vm.network "forwarded_port", guest: 8000, host: 8000 @@ -14,11 +14,6 @@ Vagrant.configure(2) do |config| config.vm.synced_folder "../gvasalt/states/", "/srv/salt/" config.vm.synced_folder "../gvasalt/pillar/", "/srv/pillar/" - config.vm.provider "virtualbox" do |vb| - # vb.gui = true - vb.memory = "1024" - end - config.vm.provision :salt do |salt| salt.bootstrap_script = "salt/bootstrap.sh" salt.minion_id = "gva.local" diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh index 6e8835e..7479d80 100755 --- a/salt/bootstrap.sh +++ b/salt/bootstrap.sh @@ -1,9 +1,7 @@ #!/bin/sh - -echo "deb http://httpredir.debian.org/debian jessie-backports main" >/etc/apt/sources.list.d/backports.list - apt-get update -apt-get install -y -t jessie-backports python-cryptography +apt-get install -y python-cryptography # We just download the bootstrap script by default and execute that. if [ -x /usr/bin/fetch ]; then From adc57657dd2d612a3dafa495af40f6fc36278b47 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 17 Nov 2018 20:07:29 +0100 Subject: [PATCH 085/111] Use pipenv for requirements --- Pipfile | 26 ++ Pipfile.lock | 543 ++++++++++++++++++++++++++++++++++++ requirements.txt | 3 - requirements/base.txt | 22 -- requirements/local.txt | 12 - requirements/production.txt | 3 - requirements/test.txt | 4 - 7 files changed, 569 insertions(+), 44 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 requirements.txt delete mode 100644 requirements/base.txt delete mode 100644 requirements/local.txt delete mode 100644 requirements/production.txt delete mode 100644 requirements/test.txt diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..c1ab7b4 --- /dev/null +++ b/Pipfile @@ -0,0 +1,26 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +django = "*" +django-model-utils = "*" +"psycopg2" = "*" +passlib = "*" +celery = "*" +redis = "*" +gvacommon = {ref = "0.4.0", git = "https://git.dittberner.info/gnuviech/gvacommon.git"} +requests-oauthlib = "*" +django-allauth = "*" + +[dev-packages] +coverage = "*" +django-debug-toolbar = "*" +sphinx = "*" +releases = "*" +sphinxcontrib-blockdiag = "*" +pylama = "*" + +[requires] +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..6255b38 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,543 @@ +{ + "_meta": { + "hash": { + "sha256": "ed0a23d288cc817ebfbf92a4acd140eee9e083d6331eaa5f06bdbc0d37c4767f" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "amqp": { + "hashes": [ + "sha256:073dd02fdd73041bffc913b767866015147b61f2a9bc104daef172fc1a0066eb", + "sha256:eed41946890cd43e8dee44a316b85cf6fee5a1a34bb4a562b660a358eb529e1b" + ], + "version": "==2.3.2" + }, + "billiard": { + "hashes": [ + "sha256:ed65448da5877b5558f19d2f7f11f8355ea76b3e63e1c0a6059f47cfae5f1c84" + ], + "version": "==3.5.0.4" + }, + "celery": { + "hashes": [ + "sha256:77dab4677e24dc654d42dfbdfed65fa760455b6bb563a0877ecc35f4cfcfc678", + "sha256:ad7a7411772b80a4d6c64f2f7f723200e39fb66cf614a7fdfab76d345acc7b13" + ], + "index": "pypi", + "version": "==4.2.1" + }, + "certifi": { + "hashes": [ + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + ], + "version": "==2018.10.15" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "defusedxml": { + "hashes": [ + "sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4", + "sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20" + ], + "version": "==0.5.0" + }, + "django": { + "hashes": [ + "sha256:1ffab268ada3d5684c05ba7ce776eaeedef360712358d6a6b340ae9f16486916", + "sha256:dd46d87af4c1bf54f4c926c3cfa41dc2b5c15782f15e4329752ce65f5dad1c37" + ], + "index": "pypi", + "version": "==2.1.3" + }, + "django-allauth": { + "hashes": [ + "sha256:45661b6fe308466154adf54461679fab6f0c6850b10c56dc322fbbb9d47e03d9" + ], + "index": "pypi", + "version": "==0.38.0" + }, + "django-model-utils": { + "hashes": [ + "sha256:2c057f3bf0859aba27f04389f0cedd2d48f8c9b3848acb86fd9970794e58f477", + "sha256:8cd377744aa45f9f131d652ec460c57d1aaa88d3e9b586c8e27eb709341b9084" + ], + "index": "pypi", + "version": "==3.1.2" + }, + "gvacommon": { + "git": "https://git.dittberner.info/gnuviech/gvacommon.git", + "ref": "8ba50c4fef85199ae3b48fc38b25081d10ce22c6" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "kombu": { + "hashes": [ + "sha256:86adec6c60f63124e2082ea8481bbe4ebe04fde8ebed32c177c7f0cd2c1c9082", + "sha256:b274db3a4eacc4789aeb24e1de3e460586db7c4fc8610f7adcc7a3a1709a60af" + ], + "version": "==4.2.1" + }, + "oauthlib": { + "hashes": [ + "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162", + "sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b" + ], + "version": "==2.1.0" + }, + "passlib": { + "hashes": [ + "sha256:3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", + "sha256:43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280" + ], + "index": "pypi", + "version": "==1.7.1" + }, + "psycopg2": { + "hashes": [ + "sha256:10e391687b171878181e71736d0effe3772314a339d9ae30995ec8171a0c834e", + "sha256:1283f9d45e458c2dcb15ba89367923563f90ef636fe78ee22df75183484a0237", + "sha256:1a9c32e4d140bea225f9821d993b2e53c913e717ea97b851246aa9b300095d8f", + "sha256:1be6f2438d2b71fec7b07c3c0949dd321b04349c382907ea76b36120edec8300", + "sha256:20ca6f29e118b8dd7133e8708b3fba2881e70a4e0841f874ed23985b7201a076", + "sha256:227c115b3c1f65d61385e51ac690b91b584640aefb45bffacd4bd33d02ed7221", + "sha256:27959abe64ca1fc6d8cd11a71a1f421d8287831a3262bd4cacd43bbf43cc3c82", + "sha256:2b2daf1fe30a58300542aea679fd87d1e1c2afd36e7644837b7954fa2dbacb92", + "sha256:36e51a51f295fdf67bcf05e7b1877011a6b39e6622b0013fe31c5025241873a3", + "sha256:3992b9b914f2eb77dc07e8045d2ca979e491612808bc5c7cd68f307469acf9f6", + "sha256:39a11de2335ad45ececed43ab851d36a4c52843d756471b940804f301792781e", + "sha256:3c2afe9ef0d1649005e3ccf93c1aaccd6f8ee379530e763d3b3b77f406b7c0ae", + "sha256:3fb18e0e52807fe3a300dc1b5421aa492d5e759550918f597d61863419482535", + "sha256:55eab94de96ee9702f23283e9c8b03cfdb0001e2b14d5d2e1bd5ff8114b96b9f", + "sha256:7e95c0ab7e7e6e452586f35d4d8966b1e924c8dd2c23977e3ea4968770ff1d26", + "sha256:7f47514dbddf604f196fcfe5da955537f04691bef8124aff5632316a78d992b7", + "sha256:8345370356bb4bddf93acbcfd0357163dd6b09471937adcfb38a2fbb49bdce53", + "sha256:8bc6ecb220c0b88d3742042013129c817c44459795c97e9ce1bca70a3f37a53b", + "sha256:8df623f248be15d1725faf5f333791678775047f12f17a90d29b5d22573f5cdc", + "sha256:9645f1305e4268cc0fc88c823cd6c91de27c003e183c233a6a230e5e963039ee", + "sha256:a68719ed5be8373dd72c9e45d55f7a202285e05a2e392eaa8872a67ea47d7d20", + "sha256:aca0edf062ec09e954fdf0cc93d3a872362701210983a1442549e703aedec25d", + "sha256:b0dd2114d93d8f424bb8ae76e0dc540f104b70ca9163172c05e7700b1459d4c9", + "sha256:b2c09359d6802279efb9efb3f91a9c94567151baee95175f9b637ea628f35244", + "sha256:ca7bc37b1efb7cc25271bf10f398462ed975d95259af1406d38fcb268466e34f", + "sha256:e64235d9013ebf6319cb9654e08f5066112c34d8c4cc41186254ab9c3d6d5b9b", + "sha256:ec9be679c0065667503851141c31fa699e1cc69ded3ba8e5d3673dd5a6eb1370", + "sha256:eca00d0f91fcb44d88b12f1fd16ad138e38fa07debb79587e2b7ff1fe80d72b9", + "sha256:f256e807b8b2b45b6af60d7f2bb5194aab2f4acc861241c4d8ef942a55f5030d", + "sha256:fce7612a3bd6a7ba95799f88285653bf130bd7ca066b52674d5f850108b2aec0" + ], + "index": "pypi", + "version": "==2.7.6.1" + }, + "python3-openid": { + "hashes": [ + "sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa", + "sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502" + ], + "version": "==3.1.0" + }, + "pytz": { + "hashes": [ + "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", + "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" + ], + "version": "==2018.7" + }, + "redis": { + "hashes": [ + "sha256:2100750629beff143b6a200a2ea8e719fcf26420adabb81402895e144c5083cf", + "sha256:8e0bdd2de02e829b6225b25646f9fb9daffea99a252610d040409a6738541f0a" + ], + "index": "pypi", + "version": "==3.0.1" + }, + "requests": { + "hashes": [ + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + ], + "version": "==2.20.1" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:8886bfec5ad7afb391ed5443b1f697c6f4ae98d0e5620839d8b4499c032ada3f", + "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8" + ], + "index": "pypi", + "version": "==1.0.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + }, + "vine": { + "hashes": [ + "sha256:52116d59bc45392af9fdd3b75ed98ae48a93e822cee21e5fda249105c59a7a72", + "sha256:6849544be74ec3638e84d90bc1cf2e1e9224cc10d96cd4383ec3f69e9bce077b" + ], + "version": "==1.1.4" + } + }, + "develop": { + "alabaster": { + "hashes": [ + "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", + "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" + ], + "version": "==0.7.12" + }, + "babel": { + "hashes": [ + "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", + "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + ], + "version": "==2.6.0" + }, + "blockdiag": { + "hashes": [ + "sha256:8dd6570a2ac41b3c0dfe5706de20913cdbebe1bbd2e6dea9ebc13db79df8c151", + "sha256:929125db1cb59145e09dc561021389c7ca71108ef4e4c51a12728eea5b75fccc" + ], + "version": "==1.5.4" + }, + "certifi": { + "hashes": [ + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + ], + "version": "==2018.10.15" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "coverage": { + "hashes": [ + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "index": "pypi", + "version": "==4.5.2" + }, + "django": { + "hashes": [ + "sha256:1ffab268ada3d5684c05ba7ce776eaeedef360712358d6a6b340ae9f16486916", + "sha256:dd46d87af4c1bf54f4c926c3cfa41dc2b5c15782f15e4329752ce65f5dad1c37" + ], + "index": "pypi", + "version": "==2.1.3" + }, + "django-debug-toolbar": { + "hashes": [ + "sha256:08e0e43f6c1fd9820af4cbdcd54b5fb80bf83a2e08b2cc952547a671174999b8", + "sha256:1dcae28d430522debafde2602b3450eb784410b78e16c29a00448032df2a4c90" + ], + "index": "pypi", + "version": "==1.10.1" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "funcparserlib": { + "hashes": [ + "sha256:b7992eac1a3eb97b3d91faa342bfda0729e990bd8a43774c1592c091e563c91d" + ], + "version": "==0.3.6" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "imagesize": { + "hashes": [ + "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", + "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + ], + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", + "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", + "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", + "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", + "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", + "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", + "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", + "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", + "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", + "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", + "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", + "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", + "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", + "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", + "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", + "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", + "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", + "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", + "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", + "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", + "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", + "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", + "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", + "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", + "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", + "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", + "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", + "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + ], + "version": "==1.1.0" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "packaging": { + "hashes": [ + "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", + "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9" + ], + "version": "==18.0" + }, + "pillow": { + "hashes": [ + "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", + "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", + "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", + "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", + "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", + "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", + "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", + "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", + "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", + "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", + "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", + "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", + "sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace", + "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", + "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", + "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", + "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", + "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", + "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", + "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", + "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", + "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", + "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", + "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", + "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", + "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", + "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", + "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", + "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", + "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888" + ], + "version": "==5.3.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + ], + "version": "==2.4.0" + }, + "pydocstyle": { + "hashes": [ + "sha256:2258f9b0df68b97bf3a6c29003edc5238ff8879f1efb6f1999988d934e432bd8", + "sha256:5741c85e408f9e0ddf873611085e819b809fca90b619f5fd7f34bd4959da3dd4", + "sha256:ed79d4ec5e92655eccc21eb0c6cf512e69512b4a97d215ace46d17e4990f2039" + ], + "version": "==3.0.0" + }, + "pyflakes": { + "hashes": [ + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + ], + "version": "==2.0.0" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "version": "==2.2.0" + }, + "pylama": { + "hashes": [ + "sha256:7e0327ee9b2a350ed73fe54c240894e534e2bccfb23a59ed5ce89f5a5689ee94", + "sha256:f81bf3bbd15db802b620903df491e5cd6469dcd542424ce6718425037dcc4d10" + ], + "index": "pypi", + "version": "==7.6.6" + }, + "pyparsing": { + "hashes": [ + "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b", + "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592" + ], + "version": "==2.3.0" + }, + "pytz": { + "hashes": [ + "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", + "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" + ], + "version": "==2018.7" + }, + "releases": { + "hashes": [ + "sha256:16cc20a59bd00854a8c6070b5b5eb410b093cd59c69bc8feb0cbaadd4ae8bc23", + "sha256:7a1b3163ab062d41dc4e61d8f51df055756d146f1fb6e18ee5cf55d91c9cda54" + ], + "index": "pypi", + "version": "==1.6.1" + }, + "requests": { + "hashes": [ + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + ], + "version": "==2.20.1" + }, + "semantic-version": { + "hashes": [ + "sha256:2a4328680073e9b243667b201119772aefc5fc63ae32398d6afafff07c4f54c0", + "sha256:2d06ab7372034bcb8b54f2205370f4aa0643c133b7e6dbd129c5200b83ab394b" + ], + "version": "==2.6.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", + "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + ], + "version": "==1.2.1" + }, + "sphinx": { + "hashes": [ + "sha256:217a7705adcb573da5bbe1e0f5cab4fa0bd89fd9342c9159121746f593c2d5a4", + "sha256:a602513f385f1d5785ff1ca420d9c7eb1a1b63381733b2f0ea8188a391314a86" + ], + "index": "pypi", + "version": "==1.7.9" + }, + "sphinxcontrib-blockdiag": { + "hashes": [ + "sha256:2d2ccde16bafb061ae8d2008f9524726e8ccd2a8502651b76a1e7f07a4ffd8eb", + "sha256:7cdff966d8f372b9536374954314a6cf4280e0e48bc2321a4f25cc7f2114f8f0" + ], + "index": "pypi", + "version": "==1.5.5" + }, + "sphinxcontrib-websupport": { + "hashes": [ + "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", + "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + ], + "version": "==1.1.0" + }, + "sqlparse": { + "hashes": [ + "sha256:ce028444cfab83be538752a2ffdb56bc417b7784ff35bb9a3062413717807dec", + "sha256:d9cf190f51cbb26da0412247dfe4fb5f4098edb73db84e02f9fc21fdca31fed4" + ], + "version": "==0.2.4" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + }, + "webcolors": { + "hashes": [ + "sha256:030562f624467a9901f0b455fef05486a88cfb5daa1e356bd4aacea043850b59", + "sha256:b3b88e5ef2b35fa9e01e3fabe99dddf49da074459c44774c59f3ccab3be4f121" + ], + "version": "==1.8.1" + } + } +} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d119713..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# This file is here because many Platforms as a Service look for -# requirements.txt in the root directory of a project. --r requirements/production.txt diff --git a/requirements/base.txt b/requirements/base.txt deleted file mode 100644 index 39a2921..0000000 --- a/requirements/base.txt +++ /dev/null @@ -1,22 +0,0 @@ -Django==1.9.2 -curtsies==0.2.6 -bpython==0.15.0 -django-braces==1.8.1 -django-model-utils==2.4 -django-crispy-forms==1.6.0 -logutils==0.3.3 -psycopg2==2.6.1 -passlib==1.6.5 -celery==3.1.20 -billiard==3.3.0.22 -kombu==3.0.33 -pytz==2015.7 -pyaml==15.8.2 -django-allauth==0.24.1 -oauthlib==1.0.3 -python-openid==2.2.5 -requests==2.9.1 -requests-oauthlib==0.6.0 -simplejson==3.8.1 --e git+https://git.gnuviech-server.de/gvacommon.git@0.3.0#egg=gvacommon -redis==2.10.5 diff --git a/requirements/local.txt b/requirements/local.txt deleted file mode 100644 index 27428d9..0000000 --- a/requirements/local.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Local development dependencies go here --r test.txt -django-debug-toolbar==1.4 -sqlparse==0.1.18 -Sphinx==1.3.5 -snowballstemmer==1.2.1 -releases==1.0.0 -flake8==2.5.2 -mccabe==0.4.0 -pep8==1.7.0 -pyflakes==1.0.0 -sphinxcontrib-blockdiag==1.5.5 diff --git a/requirements/production.txt b/requirements/production.txt deleted file mode 100644 index 696dc7c..0000000 --- a/requirements/production.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Pro-tip: Try not to put anything here. There should be no dependency in -# production that isn't in development. --r base.txt diff --git a/requirements/test.txt b/requirements/test.txt deleted file mode 100644 index 67a8663..0000000 --- a/requirements/test.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Test dependencies go here. --r base.txt -coverage==4.0.3 -mock==1.3.0 From 6cebd80c89281d1228ae73b20e3672eb417b8f4f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 19 Nov 2018 23:28:40 +0100 Subject: [PATCH 086/111] Started port to Django 2.1, Python 3, Docker This commit is a rough port to Django 2.1, Python 3 and a Docker based local development setup. Tests fail/error but migrations and the web frontend are already runnable. Task queue functionality is untested and translations seem to have trouble. --- .gitignore | 2 + Dockerfile | 31 +++ Pipfile | 4 +- Pipfile.lock | 20 +- Vagrantfile | 26 -- docker-compose.yml | 43 ++++ gnuviechadmin.sh | 8 + gnuviechadmin/contact_form/forms.py | 2 +- gnuviechadmin/contact_form/views.py | 4 +- gnuviechadmin/domains/forms.py | 2 +- .../migrations/0002_auto_20150124_1909.py | 40 ++- .../migrations/0003_auto_20151105_2133.py | 96 +++++-- gnuviechadmin/domains/models.py | 19 +- gnuviechadmin/domains/views.py | 7 +- .../{settings/base.py => settings.py} | 167 ++++++++++-- .../gnuviechadmin/settings/__init__.py | 3 - gnuviechadmin/gnuviechadmin/settings/local.py | 68 ----- .../gnuviechadmin/settings/production.py | 40 --- gnuviechadmin/gnuviechadmin/settings/test.py | 29 --- gnuviechadmin/gnuviechadmin/urls.py | 9 +- gnuviechadmin/hostingpackages/forms.py | 2 +- .../migrations/0001_initial.py | 208 +++++++++++---- .../0001_squashed_0005_auto_20150118_1303.py | 241 +++++++++++++----- .../migrations/0002_auto_20150118_1149.py | 31 ++- .../migrations/0003_auto_20150118_1407.py | 10 +- .../0004_customerhostingpackage_osuser.py | 7 +- .../0004_customerhostingpackagedomain.py | 24 +- .../migrations/0005_auto_20150118_1303.py | 7 +- gnuviechadmin/hostingpackages/models.py | 56 ++-- gnuviechadmin/managemails/forms.py | 2 +- .../managemails/migrations/0001_initial.py | 69 +++-- .../migrations/0003_auto_20150124_2029.py | 11 +- .../migrations/0004_auto_20150125_1825.py | 7 +- gnuviechadmin/managemails/models.py | 13 +- gnuviechadmin/osusers/forms.py | 4 +- .../osusers/migrations/0001_initial.py | 158 ++++++++---- .../osusers/migrations/0003_user_customer.py | 5 +- .../migrations/0004_auto_20150104_1751.py | 7 +- .../migrations/0005_auto_20150131_2009.py | 31 ++- gnuviechadmin/osusers/models.py | 17 +- gnuviechadmin/osusers/views.py | 2 +- gnuviechadmin/userdbs/forms.py | 2 +- .../userdbs/migrations/0001_initial.py | 48 ++-- gnuviechadmin/userdbs/models.py | 6 +- gnuviechadmin/websites/forms.py | 2 +- .../websites/migrations/0001_initial.py | 23 +- gnuviechadmin/websites/models.py | 5 +- salt/bootstrap.sh | 39 --- 48 files changed, 1081 insertions(+), 576 deletions(-) create mode 100644 Dockerfile delete mode 100644 Vagrantfile create mode 100644 docker-compose.yml create mode 100755 gnuviechadmin.sh rename gnuviechadmin/gnuviechadmin/{settings/base.py => settings.py} (62%) delete mode 100644 gnuviechadmin/gnuviechadmin/settings/__init__.py delete mode 100644 gnuviechadmin/gnuviechadmin/settings/local.py delete mode 100644 gnuviechadmin/gnuviechadmin/settings/production.py delete mode 100644 gnuviechadmin/gnuviechadmin/settings/test.py delete mode 100755 salt/bootstrap.sh diff --git a/.gitignore b/.gitignore index 6af40a7..bc825fe 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ _build/ gnuviechadmin/assets/ coverage-report/ .idea/ + +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e268ca1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM debian:stretch +LABEL maintainer "Jan Dittberner " + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + dumb-init \ + gettext \ + git \ + libpq-dev \ + python3-dev \ + python3-pip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*.* + +VOLUME /srv/gnuviechadmin/media /srv/gnuviechadmin/static +WORKDIR /srv/gnuviechadmin + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN python3 -m pip install -U pip && pip3 install pipenv + +COPY gnuviechadmin.sh /srv/ +COPY Pipfile Pipfile.lock /srv/gnuviechadmin/ + +RUN pipenv install --system --deploy --ignore-pipfile --dev + +EXPOSE 8000 +VOLUME /srv/gnuviechadmin + +ENTRYPOINT ["dumb-init", "/srv/gnuviechadmin.sh"] diff --git a/Pipfile b/Pipfile index c1ab7b4..9b6175e 100644 --- a/Pipfile +++ b/Pipfile @@ -13,6 +13,8 @@ redis = "*" gvacommon = {ref = "0.4.0", git = "https://git.dittberner.info/gnuviech/gvacommon.git"} requests-oauthlib = "*" django-allauth = "*" +django-crispy-forms = "*" +django-braces = "*" [dev-packages] coverage = "*" @@ -23,4 +25,4 @@ sphinxcontrib-blockdiag = "*" pylama = "*" [requires] -python_version = "3.6" +python_version = "3.5" diff --git a/Pipfile.lock b/Pipfile.lock index 6255b38..a713609 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "ed0a23d288cc817ebfbf92a4acd140eee9e083d6331eaa5f06bdbc0d37c4767f" + "sha256": "3b941559bcaf1c164285aef53553496fbf1414f362da1433b705ac7c796e33e6" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3.5" }, "sources": [ { @@ -73,6 +73,22 @@ "index": "pypi", "version": "==0.38.0" }, + "django-braces": { + "hashes": [ + "sha256:a457d74ea29478123c0c4652272681b3cea0bf1232187fd9f9b6f1d97d32a890", + "sha256:ba68e98b817c6f01d71d10849f359979617b3fe4cefb7f289adefddced092ddc" + ], + "index": "pypi", + "version": "==1.13.0" + }, + "django-crispy-forms": { + "hashes": [ + "sha256:5952bab971110d0b86c278132dae0aa095beee8f723e625c3d3fa28888f1675f", + "sha256:705ededc554ad8736157c666681165fe22ead2dec0d5446d65fc9dd976a5a876" + ], + "index": "pypi", + "version": "==1.7.2" + }, "django-model-utils": { "hashes": [ "sha256:2c057f3bf0859aba27f04389f0cedd2d48f8c9b3848acb86fd9970794e58f477", diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 63b11ce..0000000 --- a/Vagrantfile +++ /dev/null @@ -1,26 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -Vagrant.configure(2) do |config| - config.vm.box = "debian/stretch64" - - config.vm.hostname = "gva.local" - config.vm.network "private_network", ip: "172.16.3.2", lxc__bridge_name: 'vlxcbr1' - - config.vm.network "forwarded_port", guest: 443, host: 8443 - config.vm.network "forwarded_port", guest: 8000, host: 8000 - config.vm.network "forwarded_port", guest: 15672, host: 15672 - - config.vm.synced_folder "../gvasalt/states/", "/srv/salt/" - config.vm.synced_folder "../gvasalt/pillar/", "/srv/pillar/" - - config.vm.provision :salt do |salt| - salt.bootstrap_script = "salt/bootstrap.sh" - salt.minion_id = "gva.local" - salt.masterless = true - salt.run_highstate = true - salt.verbose = true - salt.colorize = true - salt.log_level = "warning" - end -end diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ed08e19 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +version: "3" +services: + db: + image: gnuviech/pgsql:stretch + ports: + - "15432:5432" + env_file: .env + volumes: + - "pg_data:/var/lib/postgresql/9.6/main" + mq: + image: gnuviech/mq:stretch + env_file: .env + volumes: + - "mq_data:/var/lib/rabbitmq/mnesia" + redis: + image: gnuviech/redis:stretch + env_file: .env + volumes: + - "redis_data:/var/lib/redis" + gva: + build: + context: . + ports: + - "8000:8000" + depends_on: + - db + - mq + - redis + env_file: .env + environment: + DJANGO_SETTINGS_MODULE: gnuviechadmin.settings + GVA_DOMAIN_NAME: localhost + GVA_SITE_NAME: localhost + volumes: + - "django_media:/srv/gnuviechadmin/media" + - "django_static:/srv/gnuviechadmin/static" + - "./gnuviechadmin:/srv/gnuviechadmin" +volumes: + django_media: + django_static: + pg_data: + redis_data: + mq_data: diff --git a/gnuviechadmin.sh b/gnuviechadmin.sh new file mode 100755 index 0000000..f5d74c2 --- /dev/null +++ b/gnuviechadmin.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +python3 manage.py compilemessages +python3 manage.py collectstatic --noinput +python3 manage.py migrate --noinput +python3 manage.py runserver 0.0.0.0:8000 diff --git a/gnuviechadmin/contact_form/forms.py b/gnuviechadmin/contact_form/forms.py index e531b93..4427eb6 100644 --- a/gnuviechadmin/contact_form/forms.py +++ b/gnuviechadmin/contact_form/forms.py @@ -7,9 +7,9 @@ from __future__ import absolute_import, unicode_literals from django import forms from django.conf import settings from django.core.mail import send_mail -from django.core.urlresolvers import reverse from django.template import RequestContext from django.template import loader +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from django.contrib.sites.models import Site diff --git a/gnuviechadmin/contact_form/views.py b/gnuviechadmin/contact_form/views.py index 0e27417..a1f3aba 100644 --- a/gnuviechadmin/contact_form/views.py +++ b/gnuviechadmin/contact_form/views.py @@ -5,7 +5,7 @@ This module defines the views of the contact_form app. from __future__ import absolute_import, unicode_literals from django.shortcuts import redirect -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.views.generic import ( FormView, TemplateView, @@ -31,7 +31,7 @@ class ContactFormView(FormView): def get_initial(self): initial = super(ContactFormView, self).get_initial() currentuser = self.request.user - if currentuser.is_authenticated(): + if currentuser.is_authenticated: initial['name'] = ( currentuser.get_full_name() or currentuser.username) initial['email'] = currentuser.email diff --git a/gnuviechadmin/domains/forms.py b/gnuviechadmin/domains/forms.py index 5ad0532..451e108 100644 --- a/gnuviechadmin/domains/forms.py +++ b/gnuviechadmin/domains/forms.py @@ -7,7 +7,7 @@ from __future__ import absolute_import, unicode_literals import re from django import forms -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.translation import ugettext as _ from crispy_forms.helper import FormHelper diff --git a/gnuviechadmin/domains/migrations/0002_auto_20150124_1909.py b/gnuviechadmin/domains/migrations/0002_auto_20150124_1909.py index 22ba714..27743bf 100644 --- a/gnuviechadmin/domains/migrations/0002_auto_20150124_1909.py +++ b/gnuviechadmin/domains/migrations/0002_auto_20150124_1909.py @@ -8,7 +8,6 @@ import model_utils.fields class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('domains', '0001_initial'), @@ -18,12 +17,31 @@ class Migration(migrations.Migration): migrations.CreateModel( name='HostingDomain', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('domain', models.CharField(unique=True, max_length=128, verbose_name='domain name')), - ('customer', models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('maildomain', models.OneToOneField(null=True, to='domains.MailDomain', blank=True, help_text='assigned mail domain for this domain', verbose_name='mail domain')), + ('id', + models.AutoField(verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), + ('created', + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('domain', + models.CharField( + unique=True, max_length=128, verbose_name='domain name')), + ('customer', + models.ForeignKey( + verbose_name='customer', blank=True, + to=settings.AUTH_USER_MODEL, null=True, + on_delete=models.CASCADE)), + ('maildomain', + models.OneToOneField( + null=True, to='domains.MailDomain', blank=True, + help_text='assigned mail domain for this domain', + verbose_name='mail domain', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'Hosting domain', @@ -34,13 +52,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name='maildomain', name='customer', - field=models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True), + field=models.ForeignKey( + verbose_name='customer', blank=True, + to=settings.AUTH_USER_MODEL, null=True, + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name='maildomain', name='domain', - field=models.CharField(unique=True, max_length=128, verbose_name='domain name'), + field=models.CharField( + unique=True, max_length=128, verbose_name='domain name'), preserve_default=True, ), ] diff --git a/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py b/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py index caf2e72..5891a27 100644 --- a/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py +++ b/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py @@ -8,7 +8,6 @@ import model_utils.fields class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('domains', '0002_auto_20150124_1909'), @@ -18,22 +17,29 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DNSComment', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), ('name', models.CharField(max_length=255)), - ('commenttype', models.CharField(max_length=10, db_column='type')), + ('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)), + ('customer', models.ForeignKey( + verbose_name='customer', + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), migrations.RunSQL( '''ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name - CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' ), migrations.CreateModel( name='DNSCryptoKey', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), ('flags', models.IntegerField()), ('active', models.BooleanField(default=True)), ('content', models.TextField()), @@ -42,15 +48,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DNSDomain', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('domain', models.CharField(unique=True, max_length=255, verbose_name='domain name')), - ('master', models.CharField(max_length=128, null=True, blank=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('domain', models.CharField( + unique=True, max_length=255, verbose_name='domain name')), + ('master', + models.CharField(max_length=128, null=True, blank=True)), ('last_check', models.IntegerField(null=True)), - ('domaintype', models.CharField(max_length=6, db_column='type', choices=[('MASTER', 'Master'), ('SLAVE', 'Slave'), ('NATIVE', 'Native')])), + ('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)), + ('customer', models.ForeignKey( + verbose_name='customer', blank=True, + to=settings.AUTH_USER_MODEL, null=True, + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'DNS domain', @@ -64,26 +85,35 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DNSDomainMetadata', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), ('kind', models.CharField(max_length=32)), ('content', models.TextField()), - ('domain', models.ForeignKey(to='domains.DNSDomain')), + ('domain', models.ForeignKey( + to='domains.DNSDomain', on_delete=models.CASCADE)), ], ), migrations.CreateModel( name='DNSRecord', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(db_index=True, max_length=255, null=True, blank=True)), - ('recordtype', models.CharField(max_length=10, null=True, db_column='type', blank=True)), - ('content', models.CharField(max_length=65535, null=True, blank=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), + ('name', models.CharField( + db_index=True, max_length=255, null=True, blank=True)), + ('recordtype', models.CharField( + max_length=10, null=True, db_column='type', blank=True)), + ('content', models.CharField( + max_length=65535, null=True, blank=True)), ('ttl', models.IntegerField(null=True)), ('prio', models.IntegerField(null=True)), ('change_date', models.IntegerField(null=True)), ('disabled', models.BooleanField(default=False)), ('ordername', models.CharField(max_length=255)), ('auth', models.BooleanField(default=True)), - ('domain', models.ForeignKey(to='domains.DNSDomain')), + ('domain', models.ForeignKey( + to='domains.DNSDomain', on_delete=models.CASCADE)), ], options={ 'verbose_name': 'DNS record', @@ -101,16 +131,22 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DNSSupermaster', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), ('ip', models.GenericIPAddressField()), ('nameserver', models.CharField(max_length=255)), - ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ('customer', models.ForeignKey( + verbose_name='customer', to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE)), ], ), migrations.CreateModel( name='DNSTSIGKey', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), ('name', models.CharField(max_length=255)), ('algorithm', models.CharField(max_length=50)), ('secret', models.CharField(max_length=255)), @@ -123,22 +159,26 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='hostingdomain', name='domain', - field=models.CharField(unique=True, max_length=255, verbose_name='domain name'), + field=models.CharField( + unique=True, max_length=255, verbose_name='domain name'), ), migrations.AlterField( model_name='maildomain', name='domain', - field=models.CharField(unique=True, max_length=255, verbose_name='domain name'), + field=models.CharField( + unique=True, max_length=255, verbose_name='domain name'), ), migrations.AddField( model_name='dnscryptokey', name='domain', - field=models.ForeignKey(to='domains.DNSDomain'), + field=models.ForeignKey( + to='domains.DNSDomain', on_delete=models.CASCADE), ), migrations.AddField( model_name='dnscomment', name='domain', - field=models.ForeignKey(to='domains.DNSDomain'), + field=models.ForeignKey( + to='domains.DNSDomain', on_delete=models.CASCADE), ), migrations.AlterUniqueTogether( name='dnssupermaster', @@ -154,6 +194,6 @@ class Migration(migrations.Migration): ), migrations.AlterIndexTogether( name='dnscomment', - index_together=set([('name', 'commenttype'), ('domain', 'modified_at')]), + index_together={('name', 'commenttype'), ('domain', 'modified_at')}, ), ] diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index 8e80329..4fff880 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -58,7 +58,7 @@ class DomainBase(TimeStampedModel): domain = models.CharField(_('domain name'), max_length=255, unique=True) customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True, - null=True) + null=True, on_delete=models.CASCADE) class Meta: abstract = True @@ -119,7 +119,8 @@ class HostingDomain(DomainBase): """ maildomain = models.OneToOneField( MailDomain, verbose_name=_('mail domain'), blank=True, null=True, - help_text=_('assigned mail domain for this domain') + help_text=_('assigned mail domain for this domain'), + on_delete=models.CASCADE, ) objects = HostingDomainManager() @@ -208,7 +209,7 @@ class DNSRecord(models.Model): domain_id, ordername text_pattern_ops); """ - domain = models.ForeignKey('DNSDomain') + domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) name = models.CharField( max_length=255, blank=True, null=True, db_index=True) recordtype = models.CharField( @@ -255,7 +256,8 @@ class DNSSupermaster(models.Model): nameserver = models.CharField(max_length=255) # account is replaced by customer customer = models.ForeignKey( - settings.AUTH_USER_MODEL, verbose_name=_('customer')) + settings.AUTH_USER_MODEL, verbose_name=_('customer'), + on_delete=models.CASCADE) class Meta: verbose_name = _('DNS supermaster') @@ -299,13 +301,14 @@ class DNSComment(models.Model): CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); """ - domain = models.ForeignKey('DNSDomain') + 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')) + 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 @@ -343,7 +346,7 @@ class DNSDomainMetadata(models.Model): CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); """ - domain = models.ForeignKey('DNSDomain') + domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS) content = models.TextField() @@ -376,7 +379,7 @@ class DNSCryptoKey(models.Model): CREATE INDEX domainidindex ON cryptokeys(domain_id); """ - domain = models.ForeignKey('DNSDomain') + domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) flags = models.IntegerField() active = models.BooleanField(default=True) content = models.TextField() diff --git a/gnuviechadmin/domains/views.py b/gnuviechadmin/domains/views.py index 2d16751..6ab3b78 100644 --- a/gnuviechadmin/domains/views.py +++ b/gnuviechadmin/domains/views.py @@ -4,16 +4,13 @@ This module defines views related to domains. """ from __future__ import absolute_import, unicode_literals +from braces.views import StaffuserRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect, get_object_or_404 from django.views.generic.edit import CreateView from django.utils.translation import ugettext as _ from django.contrib import messages -from braces.views import ( - LoginRequiredMixin, - StaffuserRequiredMixin, -) - from hostingpackages.models import CustomerHostingPackage from .forms import CreateHostingDomainForm diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings.py similarity index 62% rename from gnuviechadmin/gnuviechadmin/settings/base.py rename to gnuviechadmin/gnuviechadmin/settings.py index d1ce425..0b0ba92 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -38,7 +38,8 @@ DEBUG = False # ######### MANAGER CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins ADMINS = ( - (get_env_variable('GVA_ADMIN_NAME'), get_env_variable('GVA_ADMIN_EMAIL')), + (get_env_variable('GVA_ADMIN_NAME', default='Admin'), + get_env_variable('GVA_ADMIN_EMAIL', default='admin@example.org')), ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#managers @@ -50,12 +51,12 @@ MANAGERS = ADMINS # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': get_env_variable('GVA_PGSQL_DATABASE'), - 'USER': get_env_variable('GVA_PGSQL_USER'), + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': get_env_variable('GVA_PGSQL_DATABASE', default='gnuviechadmin'), + 'USER': get_env_variable('GVA_PGSQL_USER', default='gnuviechadmin'), 'PASSWORD': get_env_variable('GVA_PGSQL_PASSWORD'), - 'HOST': get_env_variable('GVA_PGSQL_HOSTNAME'), - 'PORT': get_env_variable('GVA_PGSQL_PORT'), + 'HOST': get_env_variable('GVA_PGSQL_HOSTNAME', default='db'), + 'PORT': get_env_variable('GVA_PGSQL_PORT', int, default=5432), } } # ######### END DATABASE CONFIGURATION @@ -98,16 +99,13 @@ MEDIA_URL = '/media/' # ######### END MEDIA CONFIGURATION -# ######### STATIC FILE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = normpath(join(SITE_ROOT, 'assets')) # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url STATIC_URL = '/static/' # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # noqa STATICFILES_DIRS = ( - normpath(join(SITE_ROOT, 'static')), + normpath(join(SITE_ROOT, 'gnuviechadmin', 'assets')), ) # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa @@ -146,7 +144,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ - normpath(join(SITE_ROOT, 'templates')), + normpath(join(DJANGO_ROOT, 'templates')), ], 'APP_DIRS': True, 'OPTIONS': { @@ -171,7 +169,7 @@ TEMPLATES = [ # ######### MIDDLEWARE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ # Default Django middleware. 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -181,7 +179,7 @@ MIDDLEWARE_CLASSES = ( # uncomment next line to enable translation to browser locale 'django.middleware.locale.LocaleMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) +] # ######### END MIDDLEWARE CONFIGURATION @@ -330,8 +328,12 @@ WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME # ######### CELERY CONFIGURATION -BROKER_URL = get_env_variable('GVA_BROKER_URL') -CELERY_RESULT_BACKEND = get_env_variable('GVA_RESULTS_REDIS_URL') +BROKER_URL = get_env_variable( + 'GVA_BROKER_URL', + default='amqp://gnuviechadmin:gnuviechadmin@mq/gnuviechadmin') +CELERY_RESULT_BACKEND = get_env_variable( + 'GVA_RESULTS_REDIS_URL', + default='redis://:gnuviechadmin@redis:6379/0') CELERY_TASK_RESULT_EXPIRES = None CELERY_ROUTES = ( 'gvacommon.celeryrouters.GvaRouter', @@ -345,17 +347,134 @@ CELERY_RESULT_SERIALIZER = 'json' # ######### CUSTOM APP CONFIGURATION -OSUSER_MINUID = int(get_env_variable('GVA_MIN_OS_UID')) -OSUSER_MINGID = int(get_env_variable('GVA_MIN_OS_GID')) -OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX') -OSUSER_HOME_BASEPATH = get_env_variable('GVA_OSUSER_HOME_BASEPATH') -OSUSER_DEFAULT_SHELL = get_env_variable('GVA_OSUSER_DEFAULT_SHELL') +OSUSER_MINUID = get_env_variable('GVA_MIN_OS_UID', int, default=10000) +OSUSER_MINGID = get_env_variable('GVA_MIN_OS_GID', int, default=10000) +OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX', default='usr') +OSUSER_HOME_BASEPATH = get_env_variable( + 'GVA_OSUSER_HOME_BASEPATH', default='/home') +OSUSER_DEFAULT_SHELL = get_env_variable( + 'GVA_OSUSER_DEFAULT_SHELL', default='/usr/bin/rssh') OSUSER_SFTP_GROUP = 'sftponly' OSUSER_SSH_GROUP = 'sshusers' OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP] -OSUSER_UPLOAD_SERVER = get_env_variable('GVA_OSUSER_UPLOADSERVER') +OSUSER_UPLOAD_SERVER = get_env_variable( + 'GVA_OSUSER_UPLOADSERVER', default='file') -GVA_LINK_WEBMAIL = get_env_variable('GVA_WEBMAIL_URL') -GVA_LINK_PHPMYADMIN = get_env_variable('GVA_PHPMYADMIN_URL') -GVA_LINK_PHPPGADMIN = get_env_variable('GVA_PHPPGADMIN_URL') +GVA_LINK_WEBMAIL = get_env_variable( + 'GVA_WEBMAIL_URL', default='https://webmail.example.org/') +GVA_LINK_PHPMYADMIN = get_env_variable( + 'GVA_PHPMYADMIN_URL', default='https://phpmyadmin.example.org/') +GVA_LINK_PHPPGADMIN = get_env_variable( + 'GVA_PHPPGADMIN_URL', default='https://phppgadmin.example.org/') # ######### END CUSTOM APP CONFIGURATION + +GVA_ENVIRONMENT = get_env_variable('GVA_ENVIRONMENT', default='prod') + +# ######### STATIC FILE CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root +STATIC_ROOT = '/srv/gnuviechadmin/static/' + +if GVA_ENVIRONMENT == 'local': + # ######### DEBUG CONFIGURATION + # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug + DEBUG = True + + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug + TEMPLATES[0]['OPTIONS']['debug'] = DEBUG + # ######### END DEBUG CONFIGURATION + + # ######### EMAIL CONFIGURATION + # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + # ######### END EMAIL CONFIGURATION + + # ######### CACHE CONFIGURATION + # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } + } + # ######### END CACHE CONFIGURATION + + # ######### TOOLBAR CONFIGURATION + # See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa + INSTALLED_APPS += ( + 'debug_toolbar', + ) + + MIDDLEWARE += [ + 'debug_toolbar.middleware.DebugToolbarMiddleware', + ] + + LOGGING['handlers'].update({ + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + } + }) + LOGGING['loggers'].update(dict( + [(key, {'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }) + for key in [ + 'dashboard', 'domains', 'fileservertasks', 'gvacommon', + 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', + 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', + 'userdbs', 'websites']])) + + DEBUG_TOOLBAR_PATCH_SETTINGS = False + + # http://django-debug-toolbar.readthedocs.org/en/latest/installation.html + INTERNAL_IPS = ('127.0.0.1', '10.0.2.2') + # ######### END TOOLBAR CONFIGURATION +elif GVA_ENVIRONMENT == 'test': + PASSWORD_HASHERS = ( + 'django.contrib.auth.hashers.MD5PasswordHasher', + ) + LOGGING['handlers'].update({ + 'console': { + 'level': 'ERROR', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + } + }) + LOGGING['loggers'].update(dict( + [(key, {'handlers': ['console'], 'level': 'ERROR', 'propagate': True, }) + for key in [ + 'dashboard', 'domains', 'fileservertasks', 'gvacommon', + 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', + 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', + 'userdbs', 'websites']])) + BROKER_URL = BROKER_URL + '_test' + CELERY_RESULT_PERSISTENT = False +else: + # ######### HOST CONFIGURATION + # See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production # noqa + ALLOWED_HOSTS = [SITES_DOMAIN_NAME] + # ######### END HOST CONFIGURATION + + # ######### EMAIL CONFIGURATION + # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + + # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix + EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME + + # See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email + DEFAULT_FROM_EMAIL = get_env_variable( + 'GVA_SITE_ADMINMAIL', default='admin@example.org') + + # See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email + SERVER_EMAIL = get_env_variable( + 'GVA_SITE_ADMINMAIL', default='admin@example.org') + # ######### END EMAIL CONFIGURATION + + # ######### CACHE CONFIGURATION + # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches + # CACHES = {} + # ######### END CACHE CONFIGURATION + + # ######### ALLAUTH PRODUCTION CONFIGURATION + ACCOUNT_EMAIL_SUBJECT_PREFIX = '[Jan Dittberner IT-Consulting & -Solutions] ' + ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https' + # ######### END ALLAUTH PRODUCTION CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/__init__.py b/gnuviechadmin/gnuviechadmin/settings/__init__.py deleted file mode 100644 index 4f53a5e..0000000 --- a/gnuviechadmin/gnuviechadmin/settings/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This module contains settings for various environments. -""" diff --git a/gnuviechadmin/gnuviechadmin/settings/local.py b/gnuviechadmin/gnuviechadmin/settings/local.py deleted file mode 100644 index 42fda89..0000000 --- a/gnuviechadmin/gnuviechadmin/settings/local.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- python -*- -# pymode:lint_ignore=W0401,E501 -""" -Development settings and globals based on :py:mod:`gvaldap.settings.base`. - -""" - -from __future__ import absolute_import - -# use import * to import all settings from base -from .base import * # NOQA - - -# ######### DEBUG CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug -DEBUG = True - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug -TEMPLATES[0]['OPTIONS']['debug'] = DEBUG -# ######### END DEBUG CONFIGURATION - - -# ######### EMAIL CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -# ######### END EMAIL CONFIGURATION - - -# ######### CACHE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - } -} -# ######### END CACHE CONFIGURATION - - -# ######### TOOLBAR CONFIGURATION -# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa -INSTALLED_APPS += ( - 'debug_toolbar', -) - -MIDDLEWARE_CLASSES += ( - 'debug_toolbar.middleware.DebugToolbarMiddleware', -) - -LOGGING['handlers'].update({ - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - } -}) -LOGGING['loggers'].update(dict( - [(key, {'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }) - for key in [ - 'dashboard', 'domains', 'fileservertasks', 'gvacommon', - 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', - 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', - 'userdbs', 'websites']])) - -DEBUG_TOOLBAR_PATCH_SETTINGS = False - -# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html -INTERNAL_IPS = ('127.0.0.1', '10.0.2.2') -# ######### END TOOLBAR CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/production.py b/gnuviechadmin/gnuviechadmin/settings/production.py deleted file mode 100644 index f4e8b7d..0000000 --- a/gnuviechadmin/gnuviechadmin/settings/production.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- python -*- -# pymode:lint_ignore=W0401,E501 -""" -Production settings and globals based on :py:mod:`gvaldap.settings.base`. - -""" - -from __future__ import absolute_import - -# use import * to import all settings from base -from .base import * # NOQA - -# ######### HOST CONFIGURATION -# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production # noqa -ALLOWED_HOSTS = [SITES_DOMAIN_NAME] -# ######### END HOST CONFIGURATION - -# ######### EMAIL CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix -EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email -DEFAULT_FROM_EMAIL = get_env_variable('GVA_SITE_ADMINMAIL') - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email -SERVER_EMAIL = get_env_variable('GVA_SITE_ADMINMAIL') -# ######### END EMAIL CONFIGURATION - -# ######### CACHE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches -# CACHES = {} -# ######### END CACHE CONFIGURATION - -# ######### ALLAUTH PRODUCTION CONFIGURATION -ACCOUNT_EMAIL_SUBJECT_PREFIX = '[Jan Dittberner IT-Consulting & -Solutions] ' -ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https' -# ######### END ALLAUTH PRODUCTION CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/settings/test.py b/gnuviechadmin/gnuviechadmin/settings/test.py deleted file mode 100644 index 623ede9..0000000 --- a/gnuviechadmin/gnuviechadmin/settings/test.py +++ /dev/null @@ -1,29 +0,0 @@ -# pymode:lint_ignore=W0401 -""" -Test settings based on :py:mod:`gvaldap.settings.base`. - -""" -from __future__ import absolute_import - -# use import * to import all settings from base -from .base import * # NOQA - -PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.MD5PasswordHasher', -) -LOGGING['handlers'].update({ - 'console': { - 'level': 'ERROR', - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - } -}) -LOGGING['loggers'].update(dict( - [(key, {'handlers': ['console'], 'level': 'ERROR', 'propagate': True, }) - for key in [ - 'dashboard', 'domains', 'fileservertasks', 'gvacommon', - 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', - 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', - 'userdbs', 'websites']])) -BROKER_URL = BROKER_URL + '_test' -CELERY_RESULT_PERSISTENT = False diff --git a/gnuviechadmin/gnuviechadmin/urls.py b/gnuviechadmin/gnuviechadmin/urls.py index cc214b8..2b6d0b1 100644 --- a/gnuviechadmin/gnuviechadmin/urls.py +++ b/gnuviechadmin/gnuviechadmin/urls.py @@ -5,6 +5,8 @@ from django.conf import settings from django.contrib import admin from django.contrib.flatpages import views +from django.contrib.staticfiles.urls import staticfiles_urlpatterns + admin.autodiscover() urlpatterns = [ @@ -28,6 +30,7 @@ urlpatterns = [ if settings.DEBUG: # pragma: no cover import debug_toolbar - urlpatterns += [ - url(r'^__debug__/', debug_toolbar.urls), - ] + + urlpatterns = [ + url(r'^__debug__/', include(debug_toolbar.urls)), + ] + staticfiles_urlpatterns() + urlpatterns diff --git a/gnuviechadmin/hostingpackages/forms.py b/gnuviechadmin/hostingpackages/forms.py index b424ec7..ddf5f6b 100644 --- a/gnuviechadmin/hostingpackages/forms.py +++ b/gnuviechadmin/hostingpackages/forms.py @@ -5,7 +5,7 @@ This module contains the form classes related to hosting packages. from __future__ import absolute_import, unicode_literals from django import forms -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.translation import ugettext as _ from crispy_forms.helper import FormHelper diff --git a/gnuviechadmin/hostingpackages/migrations/0001_initial.py b/gnuviechadmin/hostingpackages/migrations/0001_initial.py index b70934d..51ec210 100644 --- a/gnuviechadmin/hostingpackages/migrations/0001_initial.py +++ b/gnuviechadmin/hostingpackages/migrations/0001_initial.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone -from django.conf import settings import model_utils.fields +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -17,15 +16,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerHostingPackage', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('name', models.CharField(unique=True, max_length=128, verbose_name='name')), - ('description', models.TextField(verbose_name='description', blank=True)), - ('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')), - ('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), - ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('name', models.CharField( + unique=True, max_length=128, verbose_name='name')), + ('description', models.TextField( + verbose_name='description', blank=True)), + ('mailboxcount', models.PositiveIntegerField( + verbose_name='mailbox count')), + ('diskspace', models.PositiveIntegerField( + help_text='disk space for the hosting package', + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('customer', models.ForeignKey( + verbose_name='customer', to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'customer hosting package', @@ -36,9 +50,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerHostingPackageOption', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ], options={ 'verbose_name': 'customer hosting option', @@ -49,9 +69,16 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerDiskSpaceOption', fields=[ - ('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')), - ('diskspace', models.PositiveIntegerField(verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('customerhostingpackageoption_ptr', models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, + to='hostingpackages.CustomerHostingPackageOption', + on_delete=models.CASCADE)), + ('diskspace', models.PositiveIntegerField( + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), ], options={ 'ordering': ['diskspace_unit', 'diskspace'], @@ -59,13 +86,19 @@ class Migration(migrations.Migration): 'verbose_name': 'Disk space option', 'verbose_name_plural': 'Disk space options', }, - bases=('hostingpackages.customerhostingpackageoption', models.Model), + bases=( + 'hostingpackages.customerhostingpackageoption', models.Model), ), migrations.CreateModel( name='CustomerMailboxOption', fields=[ - ('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')), - ('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')), + ('customerhostingpackageoption_ptr', models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, + to='hostingpackages.CustomerHostingPackageOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + unique=True, verbose_name='number of mailboxes')), ], options={ 'ordering': ['number'], @@ -73,14 +106,22 @@ class Migration(migrations.Migration): 'verbose_name': 'Mailbox option', 'verbose_name_plural': 'Mailbox options', }, - bases=('hostingpackages.customerhostingpackageoption', models.Model), + bases=( + 'hostingpackages.customerhostingpackageoption', models.Model), ), migrations.CreateModel( name='CustomerUserDatabaseOption', fields=[ - ('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')), - ('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')), - ('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), + ('customerhostingpackageoption_ptr', models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, + to='hostingpackages.CustomerHostingPackageOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + default=1, verbose_name='number of databases')), + ('db_type', models.PositiveSmallIntegerField( + verbose_name='database type', + choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), ], options={ 'ordering': ['db_type', 'number'], @@ -88,14 +129,21 @@ class Migration(migrations.Migration): 'verbose_name': 'Database option', 'verbose_name_plural': 'Database options', }, - bases=('hostingpackages.customerhostingpackageoption', models.Model), + bases=( + 'hostingpackages.customerhostingpackageoption', models.Model), ), migrations.CreateModel( name='HostingOption', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ], options={ 'verbose_name': 'Hosting option', @@ -106,9 +154,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DiskSpaceOption', fields=[ - ('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')), - ('diskspace', models.PositiveIntegerField(verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('hostingoption_ptr', models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, to='hostingpackages.HostingOption', + on_delete=models.CASCADE)), + ('diskspace', models.PositiveIntegerField( + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), ], options={ 'ordering': ['diskspace_unit', 'diskspace'], @@ -121,14 +175,27 @@ class Migration(migrations.Migration): migrations.CreateModel( name='HostingPackageTemplate', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('name', models.CharField(unique=True, max_length=128, verbose_name='name')), - ('description', models.TextField(verbose_name='description', blank=True)), - ('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')), - ('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('name', models.CharField( + unique=True, max_length=128, verbose_name='name')), + ('description', models.TextField( + verbose_name='description', blank=True)), + ('mailboxcount', models.PositiveIntegerField( + verbose_name='mailbox count')), + ('diskspace', models.PositiveIntegerField( + help_text='disk space for the hosting package', + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), ], options={ 'verbose_name': 'Hosting package', @@ -139,8 +206,12 @@ class Migration(migrations.Migration): migrations.CreateModel( name='MailboxOption', fields=[ - ('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')), - ('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')), + ('hostingoption_ptr', models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, to='hostingpackages.HostingOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + unique=True, verbose_name='number of mailboxes')), ], options={ 'ordering': ['number'], @@ -153,9 +224,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserDatabaseOption', fields=[ - ('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')), - ('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')), - ('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), + ('hostingoption_ptr', models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, to='hostingpackages.HostingOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + default=1, verbose_name='number of databases')), + ('db_type', models.PositiveSmallIntegerField( + verbose_name='database type', + choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), ], options={ 'ordering': ['db_type', 'number'], @@ -167,48 +244,71 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userdatabaseoption', - unique_together=set([('number', 'db_type')]), + unique_together={('number', 'db_type')}, ), migrations.AlterUniqueTogether( name='diskspaceoption', - unique_together=set([('diskspace', 'diskspace_unit')]), + unique_together={('diskspace', 'diskspace_unit')}, ), migrations.AddField( model_name='customeruserdatabaseoption', name='template', - field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this hosting option is based on'), + field=models.ForeignKey( + verbose_name='user database option template', + to='hostingpackages.UserDatabaseOption', + help_text='The user database option template that this ' + 'hosting option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='customeruserdatabaseoption', - unique_together=set([('number', 'db_type')]), + unique_together={('number', 'db_type')}, ), migrations.AddField( model_name='customermailboxoption', name='template', - field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this hosting option is based on'), + field=models.ForeignKey( + verbose_name='mailbox option template', + to='hostingpackages.UserDatabaseOption', + help_text='The mailbox option template that this hosting ' + 'option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='customerhostingpackageoption', name='hosting_package', - field=models.ForeignKey(verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage'), + field=models.ForeignKey( + verbose_name='hosting package', + to='hostingpackages.CustomerHostingPackage', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='customerhostingpackage', name='template', - field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on.'), + field=models.ForeignKey( + verbose_name='hosting package template', + to='hostingpackages.HostingPackageTemplate', + help_text='The hosting package template that this hosting ' + 'package is based on.', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='customerdiskspaceoption', name='template', - field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this hosting option is based on'), + field=models.ForeignKey( + verbose_name='disk space option template', + to='hostingpackages.DiskSpaceOption', + help_text='The disk space option template that this hosting ' + 'option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='customerdiskspaceoption', - unique_together=set([('diskspace', 'diskspace_unit')]), + unique_together={('diskspace', 'diskspace_unit')}, ), ] diff --git a/gnuviechadmin/hostingpackages/migrations/0001_squashed_0005_auto_20150118_1303.py b/gnuviechadmin/hostingpackages/migrations/0001_squashed_0005_auto_20150118_1303.py index 09ca357..1bf5616 100644 --- a/gnuviechadmin/hostingpackages/migrations/0001_squashed_0005_auto_20150118_1303.py +++ b/gnuviechadmin/hostingpackages/migrations/0001_squashed_0005_auto_20150118_1303.py @@ -1,15 +1,18 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone -from django.conf import settings import model_utils.fields +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): - - replaces = [('hostingpackages', '0001_initial'), ('hostingpackages', '0002_auto_20150118_1149'), ('hostingpackages', '0003_auto_20150118_1221'), ('hostingpackages', '0004_customerhostingpackage_osuser'), ('hostingpackages', '0005_auto_20150118_1303')] + replaces = [('hostingpackages', '0001_initial'), + ('hostingpackages', '0002_auto_20150118_1149'), + ('hostingpackages', '0003_auto_20150118_1221'), + ('hostingpackages', '0004_customerhostingpackage_osuser'), + ('hostingpackages', '0005_auto_20150118_1303')] dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), @@ -20,15 +23,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerHostingPackage', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('name', models.CharField(unique=True, max_length=128, verbose_name='name')), - ('description', models.TextField(verbose_name='description', blank=True)), - ('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')), - ('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), - ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('name', models.CharField( + unique=True, max_length=128, verbose_name='name')), + ('description', models.TextField( + verbose_name='description', blank=True)), + ('mailboxcount', models.PositiveIntegerField( + verbose_name='mailbox count')), + ('diskspace', models.PositiveIntegerField( + help_text='disk space for the hosting package', + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('customer', models.ForeignKey( + verbose_name='customer', + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ 'verbose_name': 'customer hosting package', @@ -39,9 +57,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerHostingPackageOption', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ], options={ 'verbose_name': 'customer hosting option', @@ -52,9 +76,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerDiskSpaceOption', fields=[ - ('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')), - ('diskspace', models.PositiveIntegerField(verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('customerhostingpackageoption_ptr', + models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, + to='hostingpackages.CustomerHostingPackageOption', + on_delete=models.CASCADE)), + ('diskspace', models.PositiveIntegerField( + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), ], options={ 'ordering': ['diskspace_unit', 'diskspace'], @@ -62,13 +94,20 @@ class Migration(migrations.Migration): 'verbose_name': 'Disk space option', 'verbose_name_plural': 'Disk space options', }, - bases=('hostingpackages.customerhostingpackageoption', models.Model), + bases=( + 'hostingpackages.customerhostingpackageoption', models.Model), ), migrations.CreateModel( name='CustomerMailboxOption', fields=[ - ('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')), - ('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')), + ('customerhostingpackageoption_ptr', + models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, + to='hostingpackages.CustomerHostingPackageOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + unique=True, verbose_name='number of mailboxes')), ], options={ 'ordering': ['number'], @@ -76,14 +115,23 @@ class Migration(migrations.Migration): 'verbose_name': 'Mailbox option', 'verbose_name_plural': 'Mailbox options', }, - bases=('hostingpackages.customerhostingpackageoption', models.Model), + bases=( + 'hostingpackages.customerhostingpackageoption', models.Model), ), migrations.CreateModel( name='CustomerUserDatabaseOption', fields=[ - ('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')), - ('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')), - ('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), + ('customerhostingpackageoption_ptr', + models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, + to='hostingpackages.CustomerHostingPackageOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + default=1, verbose_name='number of databases')), + ('db_type', models.PositiveSmallIntegerField( + verbose_name='database type', + choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), ], options={ 'ordering': ['db_type', 'number'], @@ -91,14 +139,21 @@ class Migration(migrations.Migration): 'verbose_name': 'Database option', 'verbose_name_plural': 'Database options', }, - bases=('hostingpackages.customerhostingpackageoption', models.Model), + bases=( + 'hostingpackages.customerhostingpackageoption', models.Model), ), migrations.CreateModel( name='HostingOption', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ], options={ 'verbose_name': 'Hosting option', @@ -109,9 +164,16 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DiskSpaceOption', fields=[ - ('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')), - ('diskspace', models.PositiveIntegerField(verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('hostingoption_ptr', + models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, to='hostingpackages.HostingOption', + on_delete=models.CASCADE)), + ('diskspace', models.PositiveIntegerField( + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), ], options={ 'ordering': ['diskspace_unit', 'diskspace'], @@ -124,14 +186,27 @@ class Migration(migrations.Migration): migrations.CreateModel( name='HostingPackageTemplate', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('name', models.CharField(unique=True, max_length=128, verbose_name='name')), - ('description', models.TextField(verbose_name='description', blank=True)), - ('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')), - ('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')), - ('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('name', models.CharField( + unique=True, max_length=128, verbose_name='name')), + ('description', models.TextField( + verbose_name='description', blank=True)), + ('mailboxcount', models.PositiveIntegerField( + verbose_name='mailbox count')), + ('diskspace', models.PositiveIntegerField( + help_text='disk space for the hosting package', + verbose_name='disk space')), + ('diskspace_unit', models.PositiveSmallIntegerField( + verbose_name='unit of disk space', + choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])), ], options={ 'verbose_name': 'Hosting package', @@ -142,8 +217,13 @@ class Migration(migrations.Migration): migrations.CreateModel( name='MailboxOption', fields=[ - ('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')), - ('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')), + ('hostingoption_ptr', + models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, to='hostingpackages.HostingOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + unique=True, verbose_name='number of mailboxes')), ], options={ 'ordering': ['number'], @@ -156,9 +236,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserDatabaseOption', fields=[ - ('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')), - ('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')), - ('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), + ('hostingoption_ptr', + models.OneToOneField( + parent_link=True, auto_created=True, primary_key=True, + serialize=False, to='hostingpackages.HostingOption', + on_delete=models.CASCADE)), + ('number', models.PositiveIntegerField( + default=1, verbose_name='number of databases')), + ('db_type', + models.PositiveSmallIntegerField( + verbose_name='database type', + choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), ], options={ 'ordering': ['db_type', 'number'], @@ -170,60 +258,93 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userdatabaseoption', - unique_together=set([('number', 'db_type')]), + unique_together={('number', 'db_type')}, ), migrations.AlterUniqueTogether( name='diskspaceoption', - unique_together=set([('diskspace', 'diskspace_unit')]), + unique_together={('diskspace', 'diskspace_unit')}, ), migrations.AddField( model_name='customeruserdatabaseoption', name='template', - field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this hosting option is based on'), + field=models.ForeignKey( + verbose_name='user database option template', + to='hostingpackages.UserDatabaseOption', + help_text='The user database option template that this ' + 'hosting option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='customeruserdatabaseoption', - unique_together=set([('number', 'db_type')]), + unique_together={('number', 'db_type')}, ), migrations.AddField( model_name='customermailboxoption', name='template', - field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this mailbox option is based on'), + field=models.ForeignKey( + verbose_name='mailbox option template', + to='hostingpackages.UserDatabaseOption', + help_text='The mailbox option template that this mailbox ' + 'option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='customerhostingpackageoption', name='hosting_package', - field=models.ForeignKey(verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage'), + field=models.ForeignKey( + verbose_name='hosting package', + to='hostingpackages.CustomerHostingPackage', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='customerhostingpackage', name='template', - field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on'), + field=models.ForeignKey( + verbose_name='hosting package template', + to='hostingpackages.HostingPackageTemplate', + help_text='The hosting package template that this hosting ' + 'package is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='customerdiskspaceoption', name='template', - field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this hosting option is based on'), + field=models.ForeignKey( + verbose_name='disk space option template', + to='hostingpackages.DiskSpaceOption', + help_text='The disk space option template that this hosting ' + 'option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='customerdiskspaceoption', - unique_together=set([('diskspace', 'diskspace_unit')]), + unique_together={('diskspace', 'diskspace_unit')}, ), migrations.AlterField( model_name='customerdiskspaceoption', name='template', - field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this disk space option is based on'), + field=models.ForeignKey( + verbose_name='disk space option template', + to='hostingpackages.DiskSpaceOption', + help_text='The disk space option template that this disk ' + 'space option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name='customeruserdatabaseoption', name='template', - field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this database option is based on'), + field=models.ForeignKey( + verbose_name='user database option template', + to='hostingpackages.UserDatabaseOption', + help_text='The user database option template that this ' + 'database option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( @@ -234,12 +355,14 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='customerhostingpackage', - unique_together=set([('customer', 'name')]), + unique_together={('customer', 'name')}, ), migrations.AddField( model_name='customerhostingpackage', name='osuser', - field=models.OneToOneField(null=True, blank=True, to='osusers.User', verbose_name='Operating system user'), + field=models.OneToOneField( + null=True, blank=True, to='osusers.User', + verbose_name='Operating system user', on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/hostingpackages/migrations/0002_auto_20150118_1149.py b/gnuviechadmin/hostingpackages/migrations/0002_auto_20150118_1149.py index 61103ed..515829f 100644 --- a/gnuviechadmin/hostingpackages/migrations/0002_auto_20150118_1149.py +++ b/gnuviechadmin/hostingpackages/migrations/0002_auto_20150118_1149.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('hostingpackages', '0001_initial'), ] @@ -14,25 +13,45 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customerdiskspaceoption', name='template', - field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this disk space option is based on'), + field=models.ForeignKey( + verbose_name='disk space option template', + to='hostingpackages.DiskSpaceOption', + help_text='The disk space option template that this disk ' + 'space option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name='customerhostingpackage', name='template', - field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on'), + field=models.ForeignKey( + verbose_name='hosting package template', + to='hostingpackages.HostingPackageTemplate', + help_text='The hosting package template that this hosting ' + 'package is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name='customermailboxoption', name='template', - field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this mailbox option is based on'), + field=models.ForeignKey( + verbose_name='mailbox option template', + to='hostingpackages.UserDatabaseOption', + help_text='The mailbox option template that this mailbox ' + 'option is based on', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name='customeruserdatabaseoption', name='template', - field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this database option is based on'), + field=models.ForeignKey( + verbose_name='user database option template', + to='hostingpackages.UserDatabaseOption', + help_text='The user database option template that this ' + 'database option is based on', + on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/hostingpackages/migrations/0003_auto_20150118_1407.py b/gnuviechadmin/hostingpackages/migrations/0003_auto_20150118_1407.py index 8d7e82d..98979c5 100644 --- a/gnuviechadmin/hostingpackages/migrations/0003_auto_20150118_1407.py +++ b/gnuviechadmin/hostingpackages/migrations/0003_auto_20150118_1407.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('hostingpackages', '0002_auto_20150118_1319'), ] @@ -14,7 +13,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customermailboxoption', name='template', - field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.MailboxOption', help_text='The mailbox option template that this mailbox option is based on'), + field=models.ForeignKey( + verbose_name='mailbox option template', + to='hostingpackages.MailboxOption', + help_text='The mailbox option template that this mailbox ' + 'option is based on', + on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackage_osuser.py b/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackage_osuser.py index c5b47e3..616d13f 100644 --- a/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackage_osuser.py +++ b/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackage_osuser.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('osusers', '0004_auto_20150104_1751'), ('hostingpackages', '0003_auto_20150118_1221'), @@ -15,7 +14,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='customerhostingpackage', name='osuser', - field=models.ForeignKey(verbose_name='Operating system user', blank=True, to='osusers.User', null=True), + field=models.ForeignKey( + verbose_name='Operating system user', blank=True, + to='osusers.User', null=True, on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackagedomain.py b/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackagedomain.py index 9a5ee1d..446c730 100644 --- a/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackagedomain.py +++ b/gnuviechadmin/hostingpackages/migrations/0004_customerhostingpackagedomain.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('domains', '0002_auto_20150124_1909'), ('hostingpackages', '0003_auto_20150118_1407'), @@ -17,11 +16,22 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CustomerHostingPackageDomain', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('domain', models.OneToOneField(verbose_name='hosting domain', to='domains.HostingDomain')), - ('hosting_package', models.ForeignKey(related_name='domains', verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage')), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('domain', models.OneToOneField( + verbose_name='hosting domain', to='domains.HostingDomain', + on_delete=models.CASCADE)), + ('hosting_package', models.ForeignKey( + related_name='domains', verbose_name='hosting package', + to='hostingpackages.CustomerHostingPackage', + on_delete=models.CASCADE)), ], options={ 'abstract': False, diff --git a/gnuviechadmin/hostingpackages/migrations/0005_auto_20150118_1303.py b/gnuviechadmin/hostingpackages/migrations/0005_auto_20150118_1303.py index b66739e..9034ae0 100644 --- a/gnuviechadmin/hostingpackages/migrations/0005_auto_20150118_1303.py +++ b/gnuviechadmin/hostingpackages/migrations/0005_auto_20150118_1303.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('hostingpackages', '0004_customerhostingpackage_osuser'), ] @@ -14,7 +13,9 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customerhostingpackage', name='osuser', - field=models.OneToOneField(null=True, blank=True, to='osusers.User', verbose_name='Operating system user'), + field=models.OneToOneField( + null=True, blank=True, to='osusers.User', + verbose_name='Operating system user', on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/hostingpackages/models.py b/gnuviechadmin/hostingpackages/models.py index f1d8735..71c648f 100644 --- a/gnuviechadmin/hostingpackages/models.py +++ b/gnuviechadmin/hostingpackages/models.py @@ -5,9 +5,9 @@ This module contains the hosting package models. from __future__ import absolute_import, unicode_literals from django.conf import settings -from django.core.urlresolvers import reverse from django.db import transaction from django.db import models +from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _, ungettext @@ -26,7 +26,6 @@ from userdbs.models import ( UserDatabase, ) - DISK_SPACE_UNITS = Choices( (0, 'M', _('MiB')), (1, 'G', _('GiB')), @@ -94,6 +93,7 @@ class DiskSpaceOption(DiskSpaceOptionBase, HostingOption): existing hosting packages. """ + class Meta: unique_together = ['diskspace', 'diskspace_unit'] @@ -127,6 +127,7 @@ class UserDatabaseOption(UserDatabaseOptionBase, HostingOption): hosting packages. """ + class Meta: unique_together = ['number', 'db_type'] @@ -203,17 +204,19 @@ class CustomerHostingPackage(HostingPackageBase): """ customer = models.ForeignKey( - settings.AUTH_USER_MODEL, verbose_name=_('customer')) + settings.AUTH_USER_MODEL, verbose_name=_('customer'), + on_delete=models.CASCADE) template = models.ForeignKey( HostingPackageTemplate, verbose_name=_('hosting package template'), help_text=_( 'The hosting package template that this hosting package is based' ' on' - )) + ), + on_delete=models.CASCADE) name = models.CharField(_('name'), max_length=128) osuser = models.OneToOneField( OsUser, verbose_name=_('Operating system user'), - blank=True, null=True) + blank=True, null=True, on_delete=models.CASCADE) objects = CustomerHostingPackageManager() @@ -250,6 +253,7 @@ class CustomerHostingPackage(HostingPackageBase): ]: opts.extend(opt_type.objects.filter(hosting_package=self)) return opts + hostingoptions = property(get_hostingoptions) def get_disk_space(self, unit=None): @@ -272,15 +276,16 @@ class CustomerHostingPackage(HostingPackageBase): diskspace += option.diskspace elif option.diskspace_unit > min_unit: diskspace += ( - DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] * - option.diskspace) + DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] * + option.diskspace) else: diskspace = ( - DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] * - diskspace) + option.diskspace + DISK_SPACE_FACTORS[min_unit][ + option.diskspace_unit] * + diskspace) + option.diskspace min_unit = option.diskspace_unit if unit is None: - return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024**2 + return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024 ** 2 if unit > min_unit: return DISK_SPACE_FACTORS[unit][min_unit] * diskspace return DISK_SPACE_FACTORS[min_unit][unit] * diskspace @@ -298,7 +303,7 @@ class CustomerHostingPackage(HostingPackageBase): """ if unit is None: return (DISK_SPACE_FACTORS[self.diskspace_unit][0] * - self.diskspace * 1024**2) + self.diskspace * 1024 ** 2) if unit > self.diskspace_unit: return (DISK_SPACE_FACTORS[unit][self.diskspace_unit] * self.diskspace) @@ -312,6 +317,7 @@ class CustomerHostingPackage(HostingPackageBase): def get_mailboxes(self): if self.osuser: return Mailbox.objects.filter(osuser=self.osuser).all() + mailboxes = property(get_mailboxes) def get_used_mailbox_count(self): @@ -322,6 +328,7 @@ class CustomerHostingPackage(HostingPackageBase): if self.osuser: return Mailbox.objects.filter(osuser=self.osuser).count() return 0 + used_mailbox_count = property(get_used_mailbox_count) def get_mailbox_count(self): @@ -336,6 +343,7 @@ class CustomerHostingPackage(HostingPackageBase): mailbox_sum=models.Sum('number') ) return self.mailboxcount + (result['mailbox_sum'] or 0) + mailbox_count = property(get_mailbox_count) def may_add_mailbox(self): @@ -357,14 +365,15 @@ class CustomerHostingPackage(HostingPackageBase): if self.osuser: return UserDatabase.objects.filter( db_user__osuser=self.osuser).all() + databases = property(get_databases_flat) def may_add_database(self): return ( - CustomerUserDatabaseOption.objects.filter( - hosting_package=self).count() > - UserDatabase.objects.filter( - db_user__osuser=self.osuser).count() + CustomerUserDatabaseOption.objects.filter( + hosting_package=self).count() > + UserDatabase.objects.filter( + db_user__osuser=self.osuser).count() ) @transaction.atomic @@ -402,9 +411,10 @@ class CustomerHostingPackageDomain(TimeStampedModel): """ hosting_package = models.ForeignKey( CustomerHostingPackage, verbose_name=_('hosting package'), - related_name='domains') + related_name='domains', on_delete=models.CASCADE) domain = models.OneToOneField( - HostingDomain, verbose_name=_('hosting domain')) + HostingDomain, verbose_name=_('hosting domain'), + on_delete=models.CASCADE) def __str__(self): return self.domain.domain @@ -423,7 +433,8 @@ class CustomerHostingPackageOption(TimeStampedModel): """ hosting_package = models.ForeignKey( - CustomerHostingPackage, verbose_name=_('hosting package')) + CustomerHostingPackage, verbose_name=_('hosting package'), + on_delete=models.CASCADE) class Meta: verbose_name = _('customer hosting option') @@ -443,7 +454,8 @@ class CustomerDiskSpaceOption(DiskSpaceOptionBase, help_text=_( 'The disk space option template that this disk space option is' ' based on' - )) + ), + on_delete=models.CASCADE) class CustomerUserDatabaseOption(UserDatabaseOptionBase, @@ -459,7 +471,8 @@ class CustomerUserDatabaseOption(UserDatabaseOptionBase, help_text=_( 'The user database option template that this database option is' ' based on' - )) + ), + on_delete=models.CASCADE) class CustomerMailboxOption(MailboxOptionBase, @@ -474,4 +487,5 @@ class CustomerMailboxOption(MailboxOptionBase, verbose_name=_('mailbox option template'), help_text=_( 'The mailbox option template that this mailbox option is based on' - )) + ), + on_delete=models.CASCADE) diff --git a/gnuviechadmin/managemails/forms.py b/gnuviechadmin/managemails/forms.py index c35214f..dc1032a 100644 --- a/gnuviechadmin/managemails/forms.py +++ b/gnuviechadmin/managemails/forms.py @@ -5,8 +5,8 @@ This module defines form classes for mailbox and mail address editing. from __future__ import absolute_import, unicode_literals from django import forms -from django.core.urlresolvers import reverse from django.core.validators import validate_email +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from crispy_forms.helper import FormHelper diff --git a/gnuviechadmin/managemails/migrations/0001_initial.py b/gnuviechadmin/managemails/migrations/0001_initial.py index 293f59d..7b7b71f 100644 --- a/gnuviechadmin/managemails/migrations/0001_initial.py +++ b/gnuviechadmin/managemails/migrations/0001_initial.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('domains', '0001_initial'), ('osusers', '0001_initial'), @@ -17,9 +16,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='MailAddress', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ('active', models.BooleanField(default=True)), ('localpart', models.CharField(max_length=128)), ], @@ -32,9 +37,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='MailAddressForward', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ('target', models.EmailField(max_length=254)), ], options={ @@ -44,9 +55,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='MailAddressMailbox', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('mailaddress', models.OneToOneField(primary_key=True, serialize=False, to='managemails.MailAddress')), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('mailaddress', models.OneToOneField( + primary_key=True, serialize=False, + to='managemails.MailAddress', on_delete=models.CASCADE)), ], options={ }, @@ -55,13 +72,20 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Mailbox', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ('active', models.BooleanField(default=True)), ('username', models.CharField(unique=True, max_length=128)), ('password', models.CharField(max_length=255)), - ('osuser', models.ForeignKey(to='osusers.User')), + ('osuser', models.ForeignKey( + to='osusers.User', on_delete=models.CASCADE)), ], options={ 'verbose_name': 'Mailbox', @@ -72,31 +96,34 @@ class Migration(migrations.Migration): migrations.AddField( model_name='mailaddressmailbox', name='mailbox', - field=models.ForeignKey(to='managemails.Mailbox'), + field=models.ForeignKey( + to='managemails.Mailbox', on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='mailaddressmailbox', - unique_together=set([('mailaddress', 'mailbox')]), + unique_together={('mailaddress', 'mailbox')}, ), migrations.AddField( model_name='mailaddressforward', name='mailaddress', - field=models.ForeignKey(to='managemails.MailAddress'), + field=models.ForeignKey( + to='managemails.MailAddress', on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='mailaddressforward', - unique_together=set([('mailaddress', 'target')]), + unique_together={('mailaddress', 'target')}, ), migrations.AddField( model_name='mailaddress', name='domain', - field=models.ForeignKey(to='domains.MailDomain'), + field=models.ForeignKey( + to='domains.MailDomain', on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='mailaddress', - unique_together=set([('localpart', 'domain')]), + unique_together={('localpart', 'domain')}, ), ] diff --git a/gnuviechadmin/managemails/migrations/0003_auto_20150124_2029.py b/gnuviechadmin/managemails/migrations/0003_auto_20150124_2029.py index d9c31a0..7863dbb 100644 --- a/gnuviechadmin/managemails/migrations/0003_auto_20150124_2029.py +++ b/gnuviechadmin/managemails/migrations/0003_auto_20150124_2029.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('managemails', '0002_auto_20150117_1238'), ] @@ -14,13 +13,17 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='mailaddressmailbox', name='mailaddress', - field=models.OneToOneField(primary_key=True, serialize=False, to='managemails.MailAddress', verbose_name='mailaddress'), + field=models.OneToOneField( + primary_key=True, serialize=False, to='managemails.MailAddress', + verbose_name='mailaddress', on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name='mailaddressmailbox', name='mailbox', - field=models.ForeignKey(verbose_name='mailbox', to='managemails.Mailbox'), + field=models.ForeignKey( + verbose_name='mailbox', to='managemails.Mailbox', + on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/managemails/migrations/0004_auto_20150125_1825.py b/gnuviechadmin/managemails/migrations/0004_auto_20150125_1825.py index 893ca63..d1afccf 100644 --- a/gnuviechadmin/managemails/migrations/0004_auto_20150125_1825.py +++ b/gnuviechadmin/managemails/migrations/0004_auto_20150125_1825.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('managemails', '0003_auto_20150124_2029'), ] @@ -14,7 +13,9 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='mailaddress', name='domain', - field=models.ForeignKey(verbose_name='domain', to='domains.MailDomain'), + field=models.ForeignKey( + verbose_name='domain', to='domains.MailDomain', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( diff --git a/gnuviechadmin/managemails/models.py b/gnuviechadmin/managemails/models.py index 760551c..5d30164 100644 --- a/gnuviechadmin/managemails/models.py +++ b/gnuviechadmin/managemails/models.py @@ -109,7 +109,7 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel): This is the model class for a mailbox. """ - osuser = models.ForeignKey(OsUser) + osuser = models.ForeignKey(OsUser, on_delete=models.CASCADE) username = models.CharField(max_length=128, unique=True) password = models.CharField(max_length=255) @@ -162,7 +162,8 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model): """ localpart = models.CharField(_('local part'), max_length=128) - domain = models.ForeignKey(MailDomain, verbose_name=_('domain')) + domain = models.ForeignKey( + MailDomain, verbose_name=_('domain'), on_delete=models.CASCADE) class Meta: ordering = ['domain', 'localpart'] @@ -250,8 +251,10 @@ class MailAddressMailbox(TimeStampedModel, models.Model): """ mailaddress = models.OneToOneField( - MailAddress, verbose_name=_('mailaddress'), primary_key=True) - mailbox = models.ForeignKey(Mailbox, verbose_name=_('mailbox')) + MailAddress, verbose_name=_('mailaddress'), primary_key=True, + on_delete=models.CASCADE) + mailbox = models.ForeignKey( + Mailbox, verbose_name=_('mailbox'), on_delete=models.CASCADE) class Meta: unique_together = ('mailaddress', 'mailbox') @@ -265,7 +268,7 @@ class MailAddressForward(TimeStampedModel, models.Model): This is a model class to map mail addresses to forwarding addresses. """ - mailaddress = models.ForeignKey(MailAddress) + mailaddress = models.ForeignKey(MailAddress, on_delete=models.CASCADE) target = models.EmailField(max_length=254) class Meta: diff --git a/gnuviechadmin/osusers/forms.py b/gnuviechadmin/osusers/forms.py index 975f102..be0e57b 100644 --- a/gnuviechadmin/osusers/forms.py +++ b/gnuviechadmin/osusers/forms.py @@ -5,7 +5,7 @@ This module defines operating system user related forms. from __future__ import unicode_literals from django import forms -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from crispy_forms.helper import FormHelper @@ -79,7 +79,7 @@ class AddSshPublicKeyForm(forms.ModelForm): keytext = self.cleaned_data.get('publickeytext') try: SshPublicKey.objects.parse_keytext(keytext) - except: + except ValueError: raise forms.ValidationError(INVALID_SSH_PUBLIC_KEY) return keytext diff --git a/gnuviechadmin/osusers/migrations/0001_initial.py b/gnuviechadmin/osusers/migrations/0001_initial.py index 7c3d023..ddc97e8 100644 --- a/gnuviechadmin/osusers/migrations/0001_initial.py +++ b/gnuviechadmin/osusers/migrations/0001_initial.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ] @@ -15,9 +14,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AdditionalGroup', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), ], options={ 'verbose_name': 'Additional group', @@ -28,9 +33,14 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DeleteTaskResult', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('task_uuid', models.CharField(max_length=64, serialize=False, 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)), + ('task_uuid', models.CharField( + max_length=64, serialize=False, primary_key=True)), ('task_name', models.CharField(max_length=255, db_index=True)), ('is_finished', models.BooleanField(default=False)), ('is_success', models.BooleanField(default=False)), @@ -47,12 +57,21 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Group', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('groupname', models.CharField(unique=True, max_length=16, verbose_name='Group name')), - ('gid', models.PositiveSmallIntegerField(unique=True, serialize=False, verbose_name='Group ID', primary_key=True)), - ('descr', models.TextField(verbose_name='Description', blank=True)), - ('passwd', models.CharField(max_length=128, verbose_name='Group password', blank=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)), + ('groupname', models.CharField( + unique=True, max_length=16, verbose_name='Group name')), + ('gid', models.PositiveSmallIntegerField( + unique=True, serialize=False, verbose_name='Group ID', + primary_key=True)), + ('descr', models.TextField( + verbose_name='Description', blank=True)), + ('passwd', models.CharField( + max_length=128, verbose_name='Group password', blank=True)), ], options={ 'verbose_name': 'Group', @@ -63,15 +82,21 @@ class Migration(migrations.Migration): migrations.CreateModel( name='GroupTaskResult', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('task_uuid', models.CharField(max_length=64, serialize=False, 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)), + ('task_uuid', models.CharField( + max_length=64, serialize=False, primary_key=True)), ('task_name', models.CharField(max_length=255, db_index=True)), ('is_finished', models.BooleanField(default=False)), ('is_success', models.BooleanField(default=False)), ('state', models.CharField(max_length=10)), ('result_body', models.TextField(blank=True)), - ('group', models.ForeignKey(to='osusers.Group')), + ('group', models.ForeignKey( + to='osusers.Group', on_delete=models.CASCADE)), ], options={ 'abstract': False, @@ -81,13 +106,23 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('username', models.CharField(unique=True, max_length=64, verbose_name='User name')), - ('uid', models.PositiveSmallIntegerField(unique=True, serialize=False, verbose_name='User ID', primary_key=True)), - ('gecos', models.CharField(max_length=128, verbose_name='Gecos field', blank=True)), - ('homedir', models.CharField(max_length=256, verbose_name='Home directory')), - ('shell', models.CharField(max_length=64, verbose_name='Login shell')), + ('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)), + ('username', models.CharField( + unique=True, max_length=64, verbose_name='User name')), + ('uid', models.PositiveSmallIntegerField( + unique=True, serialize=False, verbose_name='User ID', + primary_key=True)), + ('gecos', models.CharField( + max_length=128, verbose_name='Gecos field', blank=True)), + ('homedir', models.CharField( + max_length=256, verbose_name='Home directory')), + ('shell', models.CharField( + max_length=64, verbose_name='Login shell')), ], options={ 'verbose_name': 'Benutzer', @@ -98,16 +133,43 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Shadow', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('user', models.OneToOneField(primary_key=True, serialize=False, to='osusers.User', verbose_name='Benutzer')), - ('passwd', models.CharField(max_length=128, verbose_name='Encrypted password')), - ('changedays', models.PositiveSmallIntegerField(help_text='This is expressed in days since Jan 1, 1970', null=True, verbose_name='Date of last change', blank=True)), - ('minage', models.PositiveSmallIntegerField(help_text='Minimum number of days before the password can be changed', null=True, verbose_name='Minimum age', blank=True)), - ('maxage', models.PositiveSmallIntegerField(help_text='Maximum number of days after which the password has to be changed', null=True, verbose_name='Maximum age', blank=True)), - ('gracedays', models.PositiveSmallIntegerField(help_text='The number of days before the password is going to expire', null=True, verbose_name='Grace period', blank=True)), - ('inactdays', models.PositiveSmallIntegerField(help_text='The number of days after the password has expired during which the password should still be accepted', null=True, verbose_name='Inactivity period', blank=True)), - ('expiredays', models.PositiveSmallIntegerField(default=None, help_text='The date of expiration of the account, expressed as number of days since Jan 1, 1970', null=True, verbose_name='Account expiration date', blank=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)), + ('user', models.OneToOneField( + primary_key=True, serialize=False, to='osusers.User', + verbose_name='Benutzer', on_delete=models.CASCADE)), + ('passwd', models.CharField( + max_length=128, verbose_name='Encrypted password')), + ('changedays', models.PositiveSmallIntegerField( + help_text='This is expressed in days since Jan 1, 1970', + null=True, verbose_name='Date of last change', blank=True)), + ('minage', models.PositiveSmallIntegerField( + help_text='Minimum number of days before the password can ' + 'be changed', + null=True, verbose_name='Minimum age', blank=True)), + ('maxage', models.PositiveSmallIntegerField( + help_text='Maximum number of days after which the ' + 'password has to be changed', + null=True, verbose_name='Maximum age', blank=True)), + ('gracedays', models.PositiveSmallIntegerField( + help_text='The number of days before the password is ' + 'going to expire', + null=True, verbose_name='Grace period', blank=True)), + ('inactdays', models.PositiveSmallIntegerField( + help_text='The number of days after the password has ' + 'expired during which the password should still ' + 'be accepted', + null=True, verbose_name='Inactivity period', blank=True)), + ('expiredays', models.PositiveSmallIntegerField( + default=None, + help_text='The date of expiration of the account, ' + 'expressed as number of days since Jan 1, 1970', + null=True, verbose_name='Account expiration date', + blank=True)), ], options={ 'verbose_name': 'Shadow password', @@ -118,15 +180,21 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserTaskResult', fields=[ - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('task_uuid', models.CharField(max_length=64, serialize=False, 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)), + ('task_uuid', models.CharField( + max_length=64, serialize=False, primary_key=True)), ('task_name', models.CharField(max_length=255, db_index=True)), ('is_finished', models.BooleanField(default=False)), ('is_success', models.BooleanField(default=False)), ('state', models.CharField(max_length=10)), ('result_body', models.TextField(blank=True)), - ('user', models.ForeignKey(to='osusers.User')), + ('user', models.ForeignKey( + to='osusers.User', on_delete=models.CASCADE)), ], options={ 'abstract': False, @@ -136,23 +204,27 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='group', - field=models.ForeignKey(verbose_name='Group', to='osusers.Group'), + field=models.ForeignKey( + verbose_name='Group', to='osusers.Group', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='additionalgroup', name='group', - field=models.ForeignKey(to='osusers.Group'), + field=models.ForeignKey( + to='osusers.Group', on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='additionalgroup', name='user', - field=models.ForeignKey(to='osusers.User'), + field=models.ForeignKey( + to='osusers.User', on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( name='additionalgroup', - unique_together=set([('user', 'group')]), + unique_together={('user', 'group')}, ), ] diff --git a/gnuviechadmin/osusers/migrations/0003_user_customer.py b/gnuviechadmin/osusers/migrations/0003_user_customer.py index b8043ef..6226187 100644 --- a/gnuviechadmin/osusers/migrations/0003_user_customer.py +++ b/gnuviechadmin/osusers/migrations/0003_user_customer.py @@ -6,7 +6,6 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('osusers', '0002_auto_20141226_1456'), @@ -16,7 +15,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='customer', - field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL), + field=models.ForeignKey( + default=1, to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE), preserve_default=False, ), ] diff --git a/gnuviechadmin/osusers/migrations/0004_auto_20150104_1751.py b/gnuviechadmin/osusers/migrations/0004_auto_20150104_1751.py index b1dcd60..288db36 100644 --- a/gnuviechadmin/osusers/migrations/0004_auto_20150104_1751.py +++ b/gnuviechadmin/osusers/migrations/0004_auto_20150104_1751.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('osusers', '0003_user_customer'), ] @@ -18,7 +17,9 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='shadow', name='user', - field=models.OneToOneField(primary_key=True, serialize=False, to='osusers.User', verbose_name='User'), + field=models.OneToOneField( + primary_key=True, serialize=False, to='osusers.User', + verbose_name='User', on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/gnuviechadmin/osusers/migrations/0005_auto_20150131_2009.py b/gnuviechadmin/osusers/migrations/0005_auto_20150131_2009.py index 809f1a2..d930c57 100644 --- a/gnuviechadmin/osusers/migrations/0005_auto_20150131_2009.py +++ b/gnuviechadmin/osusers/migrations/0005_auto_20150131_2009.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('osusers', '0004_auto_20150104_1751'), ] @@ -16,13 +15,25 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SshPublicKey', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('algorithm', models.CharField(max_length=20, verbose_name='Algorithm')), - ('data', models.TextField(help_text='Base64 encoded key bytes', verbose_name='Key bytes')), - ('comment', models.TextField(verbose_name='Comment', blank=True)), - ('user', models.ForeignKey(verbose_name='User', to='osusers.User')), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('algorithm', models.CharField( + max_length=20, verbose_name='Algorithm')), + ('data', models.TextField( + help_text='Base64 encoded key bytes', + verbose_name='Key bytes')), + ('comment', models.TextField( + verbose_name='Comment', blank=True)), + ('user', models.ForeignKey( + verbose_name='User', to='osusers.User', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'SSH public key', @@ -32,6 +43,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='sshpublickey', - unique_together=set([('user', 'algorithm', 'data')]), + unique_together={('user', 'algorithm', 'data')}, ), ] diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 68167cb..197c669 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -198,11 +198,13 @@ class User(TimeStampedModel, models.Model): _('User name'), max_length=64, unique=True) uid = models.PositiveSmallIntegerField( _('User ID'), unique=True, primary_key=True) - group = models.ForeignKey(Group, verbose_name=_('Group')) + group = models.ForeignKey( + Group, verbose_name=_('Group'), on_delete=models.CASCADE) gecos = models.CharField(_('Gecos field'), max_length=128, blank=True) homedir = models.CharField(_('Home directory'), max_length=256) shell = models.CharField(_('Login shell'), max_length=64) - customer = models.ForeignKey(settings.AUTH_USER_MODEL) + customer = models.ForeignKey( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE) objects = UserManager() @@ -307,7 +309,9 @@ class Shadow(TimeStampedModel, models.Model): entry. """ - user = models.OneToOneField(User, primary_key=True, verbose_name=_('User')) + user = models.OneToOneField( + User, primary_key=True, verbose_name=_('User'), + on_delete=models.CASCADE) passwd = models.CharField(_('Encrypted password'), max_length=128) changedays = models.PositiveSmallIntegerField( _('Date of last change'), @@ -364,8 +368,8 @@ class AdditionalGroup(TimeStampedModel, models.Model): :py:class:`operating system user `. """ - user = models.ForeignKey(User) - group = models.ForeignKey(Group) + user = models.ForeignKey(User, on_delete=models.CASCADE) + group = models.ForeignKey(Group, on_delete=models.CASCADE) class Meta: unique_together = ('user', 'group') @@ -498,7 +502,8 @@ class SshPublicKey(TimeStampedModel): system user `. """ - user = models.ForeignKey(User, verbose_name=_('User')) + user = models.ForeignKey( + User, verbose_name=_('User'), on_delete=models.CASCADE) algorithm = models.CharField(_('Algorithm'), max_length=20) data = models.TextField(_('Key bytes'), help_text=_('Base64 encoded key bytes')) diff --git a/gnuviechadmin/osusers/views.py b/gnuviechadmin/osusers/views.py index 2c4e81d..3a0a938 100644 --- a/gnuviechadmin/osusers/views.py +++ b/gnuviechadmin/osusers/views.py @@ -4,8 +4,8 @@ This module defines the views for gnuviechadmin operating system user handling. """ from __future__ import unicode_literals, absolute_import -from django.core.urlresolvers import reverse from django.shortcuts import redirect +from django.urls import reverse from django.views.generic import ( CreateView, DeleteView, diff --git a/gnuviechadmin/userdbs/forms.py b/gnuviechadmin/userdbs/forms.py index 2c24743..bf9a099 100644 --- a/gnuviechadmin/userdbs/forms.py +++ b/gnuviechadmin/userdbs/forms.py @@ -5,7 +5,7 @@ This module defines form classes for user database editing. from __future__ import absolute_import, unicode_literals from django import forms -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from crispy_forms.helper import FormHelper diff --git a/gnuviechadmin/userdbs/migrations/0001_initial.py b/gnuviechadmin/userdbs/migrations/0001_initial.py index 7a81c3c..54015a3 100644 --- a/gnuviechadmin/userdbs/migrations/0001_initial.py +++ b/gnuviechadmin/userdbs/migrations/0001_initial.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations import django.utils.timezone import model_utils.fields +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('osusers', '0004_auto_20150104_1751'), ] @@ -16,12 +15,22 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DatabaseUser', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('name', models.CharField(max_length=63, verbose_name='username')), - ('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), - ('osuser', models.ForeignKey(to='osusers.User')), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('name', models.CharField( + max_length=63, verbose_name='username')), + ('db_type', models.PositiveSmallIntegerField( + verbose_name='database type', + choices=[(0, 'PostgreSQL'), (1, 'MySQL')])), + ('osuser', models.ForeignKey( + to='osusers.User', on_delete=models.CASCADE)), ], options={ 'verbose_name': 'database user', @@ -32,11 +41,20 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserDatabase', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('db_name', models.CharField(max_length=63, verbose_name='database name')), - ('db_user', models.ForeignKey(verbose_name='database user', to='userdbs.DatabaseUser')), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('created', model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, verbose_name='created', + editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, verbose_name='modified', + editable=False)), + ('db_name', models.CharField( + max_length=63, verbose_name='database name')), + ('db_user', models.ForeignKey( + verbose_name='database user', to='userdbs.DatabaseUser', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'user database', @@ -46,10 +64,10 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userdatabase', - unique_together=set([('db_name', 'db_user')]), + unique_together={('db_name', 'db_user')}, ), migrations.AlterUniqueTogether( name='databaseuser', - unique_together=set([('name', 'db_type')]), + unique_together={('name', 'db_type')}, ), ] diff --git a/gnuviechadmin/userdbs/models.py b/gnuviechadmin/userdbs/models.py index 78f9ea7..c9eafb5 100644 --- a/gnuviechadmin/userdbs/models.py +++ b/gnuviechadmin/userdbs/models.py @@ -83,7 +83,7 @@ class DatabaseUserManager(models.Manager): @python_2_unicode_compatible class DatabaseUser(TimeStampedModel, models.Model): - osuser = models.ForeignKey(OsUser) + osuser = models.ForeignKey(OsUser, on_delete=models.CASCADE) name = models.CharField( _('username'), max_length=63) db_type = models.PositiveSmallIntegerField( @@ -203,7 +203,9 @@ class UserDatabase(TimeStampedModel, models.Model): # MySQL limits to 64, PostgreSQL to 63 characters db_name = models.CharField( _('database name'), max_length=63) - db_user = models.ForeignKey(DatabaseUser, verbose_name=_('database user')) + db_user = models.ForeignKey( + DatabaseUser, verbose_name=_('database user'), + on_delete=models.CASCADE) objects = UserDatabaseManager() diff --git a/gnuviechadmin/websites/forms.py b/gnuviechadmin/websites/forms.py index fa63538..94e52d1 100644 --- a/gnuviechadmin/websites/forms.py +++ b/gnuviechadmin/websites/forms.py @@ -5,7 +5,7 @@ This module defines form classes for website editing. from __future__ import absolute_import, unicode_literals from django import forms -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.translation import ugettext as _ from crispy_forms.bootstrap import AppendedText diff --git a/gnuviechadmin/websites/migrations/0001_initial.py b/gnuviechadmin/websites/migrations/0001_initial.py index bfbb1a5..51dbee9 100644 --- a/gnuviechadmin/websites/migrations/0001_initial.py +++ b/gnuviechadmin/websites/migrations/0001_initial.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('osusers', '0004_auto_20150104_1751'), ('domains', '0002_auto_20150124_1909'), @@ -15,11 +14,19 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Website', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('subdomain', models.CharField(max_length=64, verbose_name='sub domain')), - ('wildcard', models.BooleanField(default=False, verbose_name='wildcard')), - ('domain', models.ForeignKey(verbose_name='domain', to='domains.HostingDomain')), - ('osuser', models.ForeignKey(verbose_name='operating system user', to='osusers.User')), + ('id', models.AutoField( + verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('subdomain', models.CharField( + max_length=64, verbose_name='sub domain')), + ('wildcard', models.BooleanField( + default=False, verbose_name='wildcard')), + ('domain', models.ForeignKey( + verbose_name='domain', to='domains.HostingDomain', + on_delete=models.CASCADE)), + ('osuser', models.ForeignKey( + verbose_name='operating system user', to='osusers.User', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'website', @@ -29,6 +36,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='website', - unique_together=set([('domain', 'subdomain')]), + unique_together={('domain', 'subdomain')}, ), ] diff --git a/gnuviechadmin/websites/models.py b/gnuviechadmin/websites/models.py index c58e904..a0b2168 100644 --- a/gnuviechadmin/websites/models.py +++ b/gnuviechadmin/websites/models.py @@ -34,9 +34,10 @@ class Website(models.Model): subdomain = models.CharField( _('sub domain'), max_length=64) osuser = models.ForeignKey( - OsUser, verbose_name=_('operating system user')) + OsUser, verbose_name=_('operating system user'), + on_delete=models.CASCADE) domain = models.ForeignKey( - HostingDomain, verbose_name=_('domain')) + HostingDomain, models.CASCADE, verbose_name=_('domain')) wildcard = models.BooleanField(_('wildcard'), default=False) class Meta: diff --git a/salt/bootstrap.sh b/salt/bootstrap.sh deleted file mode 100755 index 7479d80..0000000 --- a/salt/bootstrap.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh - - -apt-get update -apt-get install -y python-cryptography - -# We just download the bootstrap script by default and execute that. -if [ -x /usr/bin/fetch ]; then - /usr/bin/fetch -o - https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@" -elif [ -x /usr/bin/curl ]; then - /usr/bin/curl -L https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@" -else - python \ - -c 'import urllib; print urllib.urlopen("https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh").read()' \ - | sh -s -- "$@" -fi - -cat >/etc/salt/minion </etc/salt/grains < Date: Wed, 26 Dec 2018 13:33:36 +0100 Subject: [PATCH 087/111] Set version number --- gnuviechadmin/gnuviechadmin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py index df34db3..5bf2a31 100644 --- a/gnuviechadmin/gnuviechadmin/__init__.py +++ b/gnuviechadmin/gnuviechadmin/__init__.py @@ -1,4 +1,4 @@ # import celery_app to initialize it from gnuviechadmin.celery import app as celery_app # NOQA -__version__ = '0.12.0.dev1' +__version__ = '0.11.5' From 6f5c0a1b7c5d7868170ac379e8ea95b558bda280 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 26 Dec 2018 14:49:56 +0100 Subject: [PATCH 088/111] Use custom SHOW_TOOLBAR_CALLBACK --- gnuviechadmin/gnuviechadmin/settings.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py index cf5fa9a..60db07c 100644 --- a/gnuviechadmin/gnuviechadmin/settings.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -373,6 +373,10 @@ GVA_ENVIRONMENT = get_env_variable('GVA_ENVIRONMENT', default='prod') # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root STATIC_ROOT = '/srv/gnuviechadmin/static/' +def show_debug_toolbar(request): + return DEBUG == True and GVA_ENVIRONMENT == 'local' + + if GVA_ENVIRONMENT == 'local': # ######### DEBUG CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug @@ -422,9 +426,10 @@ if GVA_ENVIRONMENT == 'local': 'userdbs', 'websites']])) DEBUG_TOOLBAR_PATCH_SETTINGS = False + DEBUG_TOOLBAR_CONFIG = { + 'SHOW_TOOLBAR_CALLBACK': 'gnuviechadmin.settings.show_debug_toolbar', + } - # http://django-debug-toolbar.readthedocs.org/en/latest/installation.html - INTERNAL_IPS = ('127.0.0.1', '10.0.2.2') # ######### END TOOLBAR CONFIGURATION elif GVA_ENVIRONMENT == 'test': PASSWORD_HASHERS = ( From c3e05061332806b340ff93aeb46e227390157c08 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 26 Dec 2018 14:50:20 +0100 Subject: [PATCH 089/111] Update dependencies --- Pipfile.lock | 76 ++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index a713609..b0942fc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -25,9 +25,9 @@ }, "billiard": { "hashes": [ - "sha256:ed65448da5877b5558f19d2f7f11f8355ea76b3e63e1c0a6059f47cfae5f1c84" + "sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e" ], - "version": "==3.5.0.4" + "version": "==3.5.0.5" }, "celery": { "hashes": [ @@ -39,10 +39,10 @@ }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" }, "chardet": { "hashes": [ @@ -60,11 +60,11 @@ }, "django": { "hashes": [ - "sha256:1ffab268ada3d5684c05ba7ce776eaeedef360712358d6a6b340ae9f16486916", - "sha256:dd46d87af4c1bf54f4c926c3cfa41dc2b5c15782f15e4329752ce65f5dad1c37" + "sha256:068d51054083d06ceb32ce02b7203f1854256047a0d58682677dd4f81bceabd7", + "sha256:55409a056b27e6d1246f19ede41c6c610e4cab549c005b62cbeefabc6433356b" ], "index": "pypi", - "version": "==2.1.3" + "version": "==2.1.4" }, "django-allauth": { "hashes": [ @@ -103,17 +103,17 @@ }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "kombu": { "hashes": [ - "sha256:86adec6c60f63124e2082ea8481bbe4ebe04fde8ebed32c177c7f0cd2c1c9082", - "sha256:b274db3a4eacc4789aeb24e1de3e460586db7c4fc8610f7adcc7a3a1709a60af" + "sha256:52763f41077e25fe7e2f17b8319d8a7b7ab953a888c49d9e4e0464fceb716896", + "sha256:9bf7d37b93249b76a03afb7bbcf7149a358b6079ca2431e725414b1caa10922c" ], - "version": "==4.2.1" + "version": "==4.2.2" }, "oauthlib": { "hashes": [ @@ -190,10 +190,10 @@ }, "requests": { "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], - "version": "==2.20.1" + "version": "==2.21.0" }, "requests-oauthlib": { "hashes": [ @@ -242,10 +242,10 @@ }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" }, "chardet": { "hashes": [ @@ -293,19 +293,19 @@ }, "django": { "hashes": [ - "sha256:1ffab268ada3d5684c05ba7ce776eaeedef360712358d6a6b340ae9f16486916", - "sha256:dd46d87af4c1bf54f4c926c3cfa41dc2b5c15782f15e4329752ce65f5dad1c37" + "sha256:068d51054083d06ceb32ce02b7203f1854256047a0d58682677dd4f81bceabd7", + "sha256:55409a056b27e6d1246f19ede41c6c610e4cab549c005b62cbeefabc6433356b" ], "index": "pypi", - "version": "==2.1.3" + "version": "==2.1.4" }, "django-debug-toolbar": { "hashes": [ - "sha256:08e0e43f6c1fd9820af4cbdcd54b5fb80bf83a2e08b2cc952547a671174999b8", - "sha256:1dcae28d430522debafde2602b3450eb784410b78e16c29a00448032df2a4c90" + "sha256:89d75b60c65db363fb24688d977e5fbf0e73386c67acf562d278402a10fc3736", + "sha256:c2b0134119a624f4ac9398b44f8e28a01c7686ac350a12a74793f3dd57a9eea0" ], "index": "pypi", - "version": "==1.10.1" + "version": "==1.11" }, "docutils": { "hashes": [ @@ -323,10 +323,10 @@ }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "imagesize": { "hashes": [ @@ -448,10 +448,10 @@ }, "pygments": { "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", + "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" ], - "version": "==2.2.0" + "version": "==2.3.1" }, "pylama": { "hashes": [ @@ -485,10 +485,10 @@ }, "requests": { "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], - "version": "==2.20.1" + "version": "==2.21.0" }, "semantic-version": { "hashes": [ @@ -499,10 +499,10 @@ }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "snowballstemmer": { "hashes": [ From 24009bff3ec7abe8d458387129735c91de1dfa61 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 26 Dec 2018 14:51:03 +0100 Subject: [PATCH 090/111] Set version to 0.12.0-alpha --- gnuviechadmin/gnuviechadmin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py index 5bf2a31..f02ae64 100644 --- a/gnuviechadmin/gnuviechadmin/__init__.py +++ b/gnuviechadmin/gnuviechadmin/__init__.py @@ -1,4 +1,4 @@ # import celery_app to initialize it from gnuviechadmin.celery import app as celery_app # NOQA -__version__ = '0.11.5' +__version__ = '0.12.0-alpha' From 10a83d36f7daca1e9c44e220c66af61b4bd011ae Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 26 Dec 2018 14:51:34 +0100 Subject: [PATCH 091/111] Fix locale support --- gnuviechadmin/gnuviechadmin/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py index 60db07c..169683e 100644 --- a/gnuviechadmin/gnuviechadmin/settings.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -86,7 +86,7 @@ USE_TZ = True LOCALE_PATHS = ( - normpath(join(SITE_ROOT, 'locale')), + normpath(join(SITE_ROOT, 'gnuviechadmin', 'locale')), ) @@ -175,9 +175,9 @@ MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # uncomment next line to enable translation to browser locale - 'django.middleware.locale.LocaleMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # ######### END MIDDLEWARE CONFIGURATION From 96a8e0e995049d04c6d4211131394d4ce691895f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 30 Jan 2019 21:07:37 +0100 Subject: [PATCH 092/111] Update dependencies, use Python 3.6 --- Pipfile | 2 +- Pipfile.lock | 212 +++++++++++++++++++++++++-------------------------- 2 files changed, 107 insertions(+), 107 deletions(-) diff --git a/Pipfile b/Pipfile index 9b6175e..383f7b9 100644 --- a/Pipfile +++ b/Pipfile @@ -25,4 +25,4 @@ sphinxcontrib-blockdiag = "*" pylama = "*" [requires] -python_version = "3.5" +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index b0942fc..725c3a6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "3b941559bcaf1c164285aef53553496fbf1414f362da1433b705ac7c796e33e6" + "sha256": "8a774f3325746a2eb48dfd94b9af22cbb8668b33e267391c64e8e295cee63b72" }, "pipfile-spec": 6, "requires": { - "python_version": "3.5" + "python_version": "3.6" }, "sources": [ { @@ -18,10 +18,10 @@ "default": { "amqp": { "hashes": [ - "sha256:073dd02fdd73041bffc913b767866015147b61f2a9bc104daef172fc1a0066eb", - "sha256:eed41946890cd43e8dee44a316b85cf6fee5a1a34bb4a562b660a358eb529e1b" + "sha256:9f181e4aef6562e6f9f45660578fc1556150ca06e836ecb9e733e6ea10b48464", + "sha256:c3d7126bfbc640d076a01f1f4f6e609c0e4348508150c1f61336b0d83c738d2b" ], - "version": "==2.3.2" + "version": "==2.4.0" }, "billiard": { "hashes": [ @@ -60,11 +60,11 @@ }, "django": { "hashes": [ - "sha256:068d51054083d06ceb32ce02b7203f1854256047a0d58682677dd4f81bceabd7", - "sha256:55409a056b27e6d1246f19ede41c6c610e4cab549c005b62cbeefabc6433356b" + "sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8", + "sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3" ], "index": "pypi", - "version": "==2.1.4" + "version": "==2.1.5" }, "django-allauth": { "hashes": [ @@ -110,17 +110,17 @@ }, "kombu": { "hashes": [ - "sha256:52763f41077e25fe7e2f17b8319d8a7b7ab953a888c49d9e4e0464fceb716896", - "sha256:9bf7d37b93249b76a03afb7bbcf7149a358b6079ca2431e725414b1caa10922c" + "sha256:1ef049243aa05f29e988ab33444ec7f514375540eaa8e0b2e1f5255e81c5e56d", + "sha256:3c9dca2338c5d893f30c151f5d29bfb81196748ab426d33c362ab51f1e8dbf78" ], - "version": "==4.2.2" + "version": "==4.2.2.post1" }, "oauthlib": { "hashes": [ - "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162", - "sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b" + "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", + "sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e" ], - "version": "==2.1.0" + "version": "==3.0.1" }, "passlib": { "hashes": [ @@ -132,39 +132,39 @@ }, "psycopg2": { "hashes": [ - "sha256:10e391687b171878181e71736d0effe3772314a339d9ae30995ec8171a0c834e", - "sha256:1283f9d45e458c2dcb15ba89367923563f90ef636fe78ee22df75183484a0237", - "sha256:1a9c32e4d140bea225f9821d993b2e53c913e717ea97b851246aa9b300095d8f", - "sha256:1be6f2438d2b71fec7b07c3c0949dd321b04349c382907ea76b36120edec8300", - "sha256:20ca6f29e118b8dd7133e8708b3fba2881e70a4e0841f874ed23985b7201a076", - "sha256:227c115b3c1f65d61385e51ac690b91b584640aefb45bffacd4bd33d02ed7221", - "sha256:27959abe64ca1fc6d8cd11a71a1f421d8287831a3262bd4cacd43bbf43cc3c82", - "sha256:2b2daf1fe30a58300542aea679fd87d1e1c2afd36e7644837b7954fa2dbacb92", - "sha256:36e51a51f295fdf67bcf05e7b1877011a6b39e6622b0013fe31c5025241873a3", - "sha256:3992b9b914f2eb77dc07e8045d2ca979e491612808bc5c7cd68f307469acf9f6", - "sha256:39a11de2335ad45ececed43ab851d36a4c52843d756471b940804f301792781e", - "sha256:3c2afe9ef0d1649005e3ccf93c1aaccd6f8ee379530e763d3b3b77f406b7c0ae", - "sha256:3fb18e0e52807fe3a300dc1b5421aa492d5e759550918f597d61863419482535", - "sha256:55eab94de96ee9702f23283e9c8b03cfdb0001e2b14d5d2e1bd5ff8114b96b9f", - "sha256:7e95c0ab7e7e6e452586f35d4d8966b1e924c8dd2c23977e3ea4968770ff1d26", - "sha256:7f47514dbddf604f196fcfe5da955537f04691bef8124aff5632316a78d992b7", - "sha256:8345370356bb4bddf93acbcfd0357163dd6b09471937adcfb38a2fbb49bdce53", - "sha256:8bc6ecb220c0b88d3742042013129c817c44459795c97e9ce1bca70a3f37a53b", - "sha256:8df623f248be15d1725faf5f333791678775047f12f17a90d29b5d22573f5cdc", - "sha256:9645f1305e4268cc0fc88c823cd6c91de27c003e183c233a6a230e5e963039ee", - "sha256:a68719ed5be8373dd72c9e45d55f7a202285e05a2e392eaa8872a67ea47d7d20", - "sha256:aca0edf062ec09e954fdf0cc93d3a872362701210983a1442549e703aedec25d", - "sha256:b0dd2114d93d8f424bb8ae76e0dc540f104b70ca9163172c05e7700b1459d4c9", - "sha256:b2c09359d6802279efb9efb3f91a9c94567151baee95175f9b637ea628f35244", - "sha256:ca7bc37b1efb7cc25271bf10f398462ed975d95259af1406d38fcb268466e34f", - "sha256:e64235d9013ebf6319cb9654e08f5066112c34d8c4cc41186254ab9c3d6d5b9b", - "sha256:ec9be679c0065667503851141c31fa699e1cc69ded3ba8e5d3673dd5a6eb1370", - "sha256:eca00d0f91fcb44d88b12f1fd16ad138e38fa07debb79587e2b7ff1fe80d72b9", - "sha256:f256e807b8b2b45b6af60d7f2bb5194aab2f4acc861241c4d8ef942a55f5030d", - "sha256:fce7612a3bd6a7ba95799f88285653bf130bd7ca066b52674d5f850108b2aec0" + "sha256:02445ebbb3a11a3fe8202c413d5e6faf38bb75b4e336203ee144ca2c46529f94", + "sha256:0e9873e60f98f0c52339abf8f0339d1e22bfe5aae0bcf7aabd40c055175035ec", + "sha256:1148a5eb29073280bf9057c7fc45468592c1bb75a28f6df1591adb93c8cb63d0", + "sha256:259a8324e109d4922b0fcd046e223e289830e2568d6f4132a3702439e5fd532b", + "sha256:28dffa9ed4595429e61bacac41d3f9671bb613d1442ff43bcbec63d4f73ed5e8", + "sha256:314a74302d4737a3865d40ea50e430ce1543c921ba10f39d562e807cfe2edf2a", + "sha256:36b60201b6d215d7658a71493fdf6bd5e60ad9a0cffed39906627ff9f4f3afd3", + "sha256:3f9d532bce54c4234161176ff3b8688ff337575ca441ea27597e112dfcd0ee0c", + "sha256:5d222983847b40af989ad96c07fc3f07e47925e463baa5de716be8f805b41d9b", + "sha256:6757a6d2fc58f7d8f5d471ad180a0bd7b4dd3c7d681f051504fbea7ae29c8d6f", + "sha256:6a0e0f1e74edb0ab57d89680e59e7bfefad2bfbdf7c80eb38304d897d43674bb", + "sha256:6ca703ccdf734e886a1cf53eb702261110f6a8b0ed74bcad15f1399f74d3f189", + "sha256:8513b953d8f443c446aa79a4cc8a898bd415fc5e29349054f03a7d696d495542", + "sha256:9262a5ce2038570cb81b4d6413720484cb1bc52c064b2f36228d735b1f98b794", + "sha256:97441f851d862a0c844d981cbee7ee62566c322ebb3d68f86d66aa99d483985b", + "sha256:a07feade155eb8e69b54dd6774cf6acf2d936660c61d8123b8b6b1f9247b67d6", + "sha256:a9b9c02c91b1e3ec1f1886b2d0a90a0ea07cc529cb7e6e472b556bc20ce658f3", + "sha256:ae88216f94728d691b945983140bf40d51a1ff6c7fe57def93949bf9339ed54a", + "sha256:b360ffd17659491f1a6ad7c928350e229c7b7bd83a2b922b6ee541245c7a776f", + "sha256:b4221957ceccf14b2abdabef42d806e791350be10e21b260d7c9ce49012cc19e", + "sha256:b90758e49d5e6b152a460d10b92f8a6ccf318fcc0ee814dcf53f3a6fc5328789", + "sha256:c669ea986190ed05fb289d0c100cc88064351f2b85177cbfd3564c4f4847d18c", + "sha256:d1b61999d15c79cf7f4f7cc9021477aef35277fc52452cf50fd13b713c84424d", + "sha256:de7bb043d1adaaf46e38d47e7a5f703bb3dab01376111e522b07d25e1a79c1e1", + "sha256:e393568e288d884b94d263f2669215197840d097c7e5b0acd1a51c1ea7d1aba8", + "sha256:ed7e0849337bd37d89f2c2b0216a0de863399ee5d363d31b1e5330a99044737b", + "sha256:f153f71c3164665d269a5d03c7fa76ba675c7a8de9dc09a4e2c2cdc9936a7b41", + "sha256:f1fb5a8427af099beb7f65093cbdb52e021b8e6dbdfaf020402a623f4181baf5", + "sha256:f36b333e9f86a2fba960c72b90c34be6ca71819e300f7b1fc3d2b0f0b2c546cd", + "sha256:f4526d078aedd5187d0508aa5f9a01eae6a48a470ed678406da94b4cd6524b7e" ], "index": "pypi", - "version": "==2.7.6.1" + "version": "==2.7.7" }, "python3-openid": { "hashes": [ @@ -175,18 +175,18 @@ }, "pytz": { "hashes": [ - "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", - "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" + "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", + "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" ], - "version": "==2018.7" + "version": "==2018.9" }, "redis": { "hashes": [ - "sha256:2100750629beff143b6a200a2ea8e719fcf26420adabb81402895e144c5083cf", - "sha256:8e0bdd2de02e829b6225b25646f9fb9daffea99a252610d040409a6738541f0a" + "sha256:74c892041cba46078ae1ef845241548baa3bd3634f9a6f0f952f006eb1619c71", + "sha256:7ba8612bbfd966dea8c62322543fed0095da2834dbd5a7c124afbc617a156aa7" ], "index": "pypi", - "version": "==3.0.1" + "version": "==3.1.0" }, "requests": { "hashes": [ @@ -197,11 +197,11 @@ }, "requests-oauthlib": { "hashes": [ - "sha256:8886bfec5ad7afb391ed5443b1f697c6f4ae98d0e5620839d8b4499c032ada3f", - "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8" + "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57", + "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140" ], "index": "pypi", - "version": "==1.0.0" + "version": "==1.2.0" }, "urllib3": { "hashes": [ @@ -212,10 +212,10 @@ }, "vine": { "hashes": [ - "sha256:52116d59bc45392af9fdd3b75ed98ae48a93e822cee21e5fda249105c59a7a72", - "sha256:6849544be74ec3638e84d90bc1cf2e1e9224cc10d96cd4383ec3f69e9bce077b" + "sha256:3cd505dcf980223cfaf13423d371f2e7ff99247e38d5985a01ec8264e4f2aca1", + "sha256:ee4813e915d0e1a54e5c1963fde0855337f82655678540a6bc5996bca4165f76" ], - "version": "==1.1.4" + "version": "==1.2.0" } }, "develop": { @@ -293,11 +293,11 @@ }, "django": { "hashes": [ - "sha256:068d51054083d06ceb32ce02b7203f1854256047a0d58682677dd4f81bceabd7", - "sha256:55409a056b27e6d1246f19ede41c6c610e4cab549c005b62cbeefabc6433356b" + "sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8", + "sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3" ], "index": "pypi", - "version": "==2.1.4" + "version": "==2.1.5" }, "django-debug-toolbar": { "hashes": [ @@ -384,52 +384,52 @@ }, "packaging": { "hashes": [ - "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", - "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9" + "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", + "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" ], - "version": "==18.0" + "version": "==19.0" }, "pillow": { "hashes": [ - "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", - "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", - "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", - "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", - "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", - "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", - "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", - "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", - "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", - "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", - "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", - "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", - "sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace", - "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", - "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", - "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", - "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", - "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", - "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", - "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", - "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", - "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", - "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", - "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", - "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", - "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", - "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", - "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", - "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", - "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888" + "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", + "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", + "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", + "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", + "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", + "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", + "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", + "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", + "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", + "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", + "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", + "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", + "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", + "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", + "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", + "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", + "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", + "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", + "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", + "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", + "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", + "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", + "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", + "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", + "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", + "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", + "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", + "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", + "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", + "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" ], - "version": "==5.3.0" + "version": "==5.4.1" }, "pycodestyle": { "hashes": [ - "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", - "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" ], - "version": "==2.4.0" + "version": "==2.5.0" }, "pydocstyle": { "hashes": [ @@ -441,10 +441,10 @@ }, "pyflakes": { "hashes": [ - "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", - "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", + "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "pygments": { "hashes": [ @@ -463,17 +463,17 @@ }, "pyparsing": { "hashes": [ - "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b", - "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592" + "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", + "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3" ], - "version": "==2.3.0" + "version": "==2.3.1" }, "pytz": { "hashes": [ - "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", - "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" + "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", + "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" ], - "version": "==2018.7" + "version": "==2018.9" }, "releases": { "hashes": [ From ddec6b41841aace5f3d8dfa51325247fa6479ccc Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 30 Jan 2019 21:08:14 +0100 Subject: [PATCH 093/111] Use gnuviechadmin.settings Specific settings have been removed, they are now triggerd by the GVA_ENVIRONMENT variable. --- docs/code/gnuviechadmin.rst | 24 ------------------------ gnuviechadmin/manage.py | 2 +- gnuviechadmin/setup.cfg | 2 +- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/docs/code/gnuviechadmin.rst b/docs/code/gnuviechadmin.rst index 57fabec..8625299 100644 --- a/docs/code/gnuviechadmin.rst +++ b/docs/code/gnuviechadmin.rst @@ -35,28 +35,4 @@ The project module :py:mod:`gnuviechadmin` ------------------------------------------- .. automodule:: gnuviechadmin.settings - - -:py:mod:`base ` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: gnuviechadmin.settings.base :members: - - -:py:mod:`local ` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: gnuviechadmin.settings.local - - -:py:mod:`production ` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: gnuviechadmin.settings.production - - -:py:mod:`test ` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: gnuviechadmin.settings.test diff --git a/gnuviechadmin/manage.py b/gnuviechadmin/manage.py index c6003db..d28aed8 100755 --- a/gnuviechadmin/manage.py +++ b/gnuviechadmin/manage.py @@ -4,7 +4,7 @@ import sys if __name__ == "__main__": os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings.local") + "DJANGO_SETTINGS_MODULE", "gnuviechadmin.settings") from django.core.management import execute_from_command_line diff --git a/gnuviechadmin/setup.cfg b/gnuviechadmin/setup.cfg index e83e084..f2d9645 100644 --- a/gnuviechadmin/setup.cfg +++ b/gnuviechadmin/setup.cfg @@ -9,7 +9,7 @@ source = gnuviechadmin,contact_form,dashboard,domains,gvawebcore,managemails,osu branch = True [coverage:report] -omit = */migrations/*,*/tests/*.py,*/tests.py,gnuviechadmin/settings/local.py,gnuviechadmin/settings/production.py +omit = */migrations/*,*/tests/*.py,*/tests.py,gnuviechadmin/settings.py show_missing = True [coverage:html] From 3d18392b673e66fe04b5da91e7aed67658fdc082 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 30 Jan 2019 21:27:25 +0100 Subject: [PATCH 094/111] Fix tests for Python 3 - drop Python 2 __future__ imports - fix tests to handle new Django and Python 3 module names - reformat changed files with black --- .../contact_form/tests/test_forms.py | 64 +- .../contact_form/tests/test_views.py | 124 ++- gnuviechadmin/dashboard/tests/test_views.py | 59 +- gnuviechadmin/domains/tests/test_admin.py | 4 +- gnuviechadmin/domains/tests/test_forms.py | 74 +- gnuviechadmin/domains/tests/test_models.py | 86 +- gnuviechadmin/domains/tests/test_views.py | 137 ++-- gnuviechadmin/domains/views.py | 33 +- gnuviechadmin/gnuviechadmin/settings.py | 443 +++++----- .../tests/test_contextprocessors.py | 80 +- gnuviechadmin/gvawebcore/tests/test_forms.py | 23 +- gnuviechadmin/gvawebcore/tests/test_views.py | 22 +- gnuviechadmin/hostingpackages/models.py | 254 +++--- .../hostingpackages/tests/test_models.py | 7 +- gnuviechadmin/managemails/tests/test_admin.py | 112 +-- gnuviechadmin/managemails/tests/test_forms.py | 482 ++++++----- .../managemails/tests/test_models.py | 262 +++--- gnuviechadmin/managemails/tests/test_views.py | 766 ++++++++++-------- gnuviechadmin/osusers/admin.py | 155 ++-- gnuviechadmin/osusers/forms.py | 63 +- gnuviechadmin/osusers/models.py | 278 ++++--- gnuviechadmin/osusers/tests/test_admin.py | 206 ++--- gnuviechadmin/osusers/tests/test_forms.py | 160 ++-- gnuviechadmin/osusers/tests/test_models.py | 410 ++++++---- gnuviechadmin/osusers/tests/test_views.py | 287 +++---- .../commands/test_fetch_taskresults.py | 37 +- .../taskresults/tests/test_models.py | 34 +- gnuviechadmin/userdbs/tests/test_admin.py | 118 ++- gnuviechadmin/userdbs/tests/test_forms.py | 115 ++- gnuviechadmin/userdbs/tests/test_models.py | 209 +++-- gnuviechadmin/userdbs/tests/test_signals.py | 53 +- gnuviechadmin/userdbs/tests/test_views.py | 225 ++--- 32 files changed, 2707 insertions(+), 2675 deletions(-) diff --git a/gnuviechadmin/contact_form/tests/test_forms.py b/gnuviechadmin/contact_form/tests/test_forms.py index 61eb115..b546657 100644 --- a/gnuviechadmin/contact_form/tests/test_forms.py +++ b/gnuviechadmin/contact_form/tests/test_forms.py @@ -2,26 +2,18 @@ Tests for :py:mod:`contact_form.forms`. """ -from __future__ import absolute_import, unicode_literals +from unittest.mock import MagicMock, Mock, patch -import mock -from mock import MagicMock, Mock - -from django.core.urlresolvers import reverse -from django.test import TestCase from django.contrib.sites.models import Site +from django.test import TestCase +from django.urls import reverse from contact_form.forms import ContactForm -TEST_DATA = { - 'name': 'Test User', - 'email': 'test@example.org', - 'body': 'Test message' -} +TEST_DATA = {"name": "Test User", "email": "test@example.org", "body": "Test message"} class ContactFormTest(TestCase): - def test_constructor_needs_request(self): with self.assertRaises(KeyError): ContactForm() @@ -29,63 +21,63 @@ class ContactFormTest(TestCase): def test_constructor(self): request = MagicMock() form = ContactForm(request=request) - self.assertTrue(hasattr(form, 'request')) + self.assertTrue(hasattr(form, "request")) self.assertEqual(form.request, request) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse('contact_form')) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual(form.helper.form_action, reverse("contact_form")) self.assertEqual(len(form.helper.inputs), 1) - self.assertEqual(form.helper.inputs[0].name, 'submit') + self.assertEqual(form.helper.inputs[0].name, "submit") def test_constructor_fields(self): request = MagicMock() form = ContactForm(request=request) self.assertEqual(len(form.fields), 3) - self.assertIn('email', form.fields) - self.assertIn('name', form.fields) - self.assertIn('body', form.fields) + self.assertIn("email", form.fields) + self.assertIn("name", form.fields) + self.assertIn("body", form.fields) self.assertEqual(len(form.data), 0) def test_get_context_invalid(self): request = MagicMock() form = ContactForm(request=request) with self.assertRaisesMessage( - ValueError, - 'Cannot generate context from invalid contact form'): + ValueError, "Cannot generate context from invalid contact form" + ): form.get_context() def test_get_context_valid_site_installed(self): request = MagicMock() form = ContactForm(request=request, data=TEST_DATA) context = form.get_context() - self.assertIn('site', context) - self.assertIn('name', context) - self.assertIn('email', context) - self.assertIn('body', context) + self.assertIn("site", context) + self.assertIn("name", context) + self.assertIn("email", context) + self.assertIn("body", context) def test_get_context_valid_site_not_installed(self): request = MagicMock() form = ContactForm(request=request, data=TEST_DATA) - with mock.patch('contact_form.forms.Site') as sitemock: + with patch("contact_form.forms.Site") as sitemock: sitemock._meta.installed = False context = form.get_context() - self.assertIn('site', context) - self.assertIn('name', context) - self.assertIn('email', context) - self.assertIn('body', context) + self.assertIn("site", context) + self.assertIn("name", context) + self.assertIn("email", context) + self.assertIn("body", context) def test_message(self): request = Mock() - request.META = {'REMOTE_ADDR': '127.0.0.1'} + request.META = {"REMOTE_ADDR": "127.0.0.1"} form = ContactForm(request=request, data=TEST_DATA) message = form.message() - self.assertIn(TEST_DATA['name'], message) - self.assertIn(TEST_DATA['email'], message) - self.assertIn(TEST_DATA['body'], message) - self.assertIn('127.0.0.1', message) + self.assertIn(TEST_DATA["name"], message) + self.assertIn(TEST_DATA["email"], message) + self.assertIn(TEST_DATA["body"], message) + self.assertIn("127.0.0.1", message) def test_subject(self): request = Mock() form = ContactForm(request=request, data=TEST_DATA) subject = form.subject() self.assertIn(Site.objects.get_current().name, subject) - self.assertIn(TEST_DATA['name'], subject) + self.assertIn(TEST_DATA["name"], subject) diff --git a/gnuviechadmin/contact_form/tests/test_views.py b/gnuviechadmin/contact_form/tests/test_views.py index 41d98b7..f7df680 100644 --- a/gnuviechadmin/contact_form/tests/test_views.py +++ b/gnuviechadmin/contact_form/tests/test_views.py @@ -2,126 +2,122 @@ Tests for :py:mod:`contact_form.views`. """ -from __future__ import absolute_import, unicode_literals - -from django.core import mail -from django.core.urlresolvers import reverse -from django.test import TestCase - from django.contrib.auth import get_user_model - +from django.core import mail +from django.test import TestCase +from django.urls import reverse User = get_user_model() -TEST_USER = 'test' -TEST_PASSWORD = 'secret' -TEST_EMAIL = 'test@example.org' -TEST_NAME = 'Example Tester'.split() -TEST_MESSAGE = ''' +TEST_USER = "test" +TEST_PASSWORD = "secret" +TEST_EMAIL = "test@example.org" +TEST_NAME = "Example Tester".split() +TEST_MESSAGE = """ This is a really unimportant test message. -''' +""" class ContactFormViewTest(TestCase): - def _setup_user(self, **kwargs): return User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD, - **kwargs) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD, **kwargs + ) def test_get_contact_form_template(self): - response = self.client.get(reverse('contact_form')) - self.assertTemplateUsed(response, 'contact_form/contact_form.html') + response = self.client.get(reverse("contact_form")) + self.assertTemplateUsed(response, "contact_form/contact_form.html") def test_get_contact_form_anonymous_status(self): - response = self.client.get(reverse('contact_form')) + response = self.client.get(reverse("contact_form")) self.assertEqual(response.status_code, 200) def test_get_contact_form_anonymous_has_empty_form(self): - response = self.client.get(reverse('contact_form')) - self.assertIn('form', response.context) - form = response.context['form'] + response = self.client.get(reverse("contact_form")) + self.assertIn("form", response.context) + form = response.context["form"] self.assertEqual(len(form.initial), 0) def test_get_contact_form_fields_anonymous(self): - response = self.client.get(reverse('contact_form')) - for name in ('name', 'email', 'body'): - self.assertIn(name, response.context['form'].fields) + response = self.client.get(reverse("contact_form")) + for name in ("name", "email", "body"): + self.assertIn(name, response.context["form"].fields) def test_post_empty_form_template(self): - response = self.client.post(reverse('contact_form'), {}) - self.assertTemplateUsed(response, 'contact_form/contact_form.html') + response = self.client.post(reverse("contact_form"), {}) + self.assertTemplateUsed(response, "contact_form/contact_form.html") def test_post_empty_form_status(self): - response = self.client.post(reverse('contact_form'), {}) + response = self.client.post(reverse("contact_form"), {}) self.assertEqual(response.status_code, 200) def test_post_empty_form_validation_errors(self): - response = self.client.post(reverse('contact_form'), {}) - self.assertIn('form', response.context) - form = response.context['form'] + response = self.client.post(reverse("contact_form"), {}) + self.assertIn("form", response.context) + form = response.context["form"] self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 3) def test_post_empty_form_no_mail(self): - self.client.post(reverse('contact_form'), {}) + self.client.post(reverse("contact_form"), {}) self.assertEqual(len(mail.outbox), 0) def test_get_contact_form_logged_in_no_fullname_initial(self): self._setup_user() self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('contact_form')) - self.assertIn('form', response.context) - form = response.context['form'] - self.assertEqual( - form.initial, {'name': TEST_USER, 'email': TEST_EMAIL}) + response = self.client.get(reverse("contact_form")) + self.assertIn("form", response.context) + form = response.context["form"] + self.assertEqual(form.initial, {"name": TEST_USER, "email": TEST_EMAIL}) def test_get_contact_form_logged_in_fullname_initial(self): - self._setup_user( - first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self._setup_user(first_name=TEST_NAME[0], last_name=TEST_NAME[1]) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.get(reverse('contact_form')) - self.assertIn('form', response.context) - form = response.context['form'] + response = self.client.get(reverse("contact_form")) + self.assertIn("form", response.context) + form = response.context["form"] self.assertEqual( - form.initial, - {'name': " ".join(TEST_NAME), 'email': TEST_EMAIL}) + form.initial, {"name": " ".join(TEST_NAME), "email": TEST_EMAIL} + ) def test_post_filled_form_anonymous_redirects(self): - response = self.client.post(reverse('contact_form'), { - 'name': TEST_USER, 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) - self.assertRedirects(response, reverse('contact_success')) + response = self.client.post( + reverse("contact_form"), + {"name": TEST_USER, "email": TEST_EMAIL, "body": TEST_MESSAGE}, + ) + self.assertRedirects(response, reverse("contact_success")) def test_post_filled_form_anonymous_mail(self): - self.client.post(reverse('contact_form'), { - 'name': TEST_USER, 'email': TEST_EMAIL, 'body': TEST_MESSAGE}) + self.client.post( + reverse("contact_form"), + {"name": TEST_USER, "email": TEST_EMAIL, "body": TEST_MESSAGE}, + ) self.assertEqual(len(mail.outbox), 1) def test_post_filled_form_logged_in_redirects(self): - self._setup_user( - first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self._setup_user(first_name=TEST_NAME[0], last_name=TEST_NAME[1]) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post(reverse('contact_form'), { - 'name': " ".join(TEST_NAME), 'email': TEST_EMAIL, - 'body': TEST_MESSAGE}) - self.assertRedirects(response, reverse('contact_success')) + response = self.client.post( + reverse("contact_form"), + {"name": " ".join(TEST_NAME), "email": TEST_EMAIL, "body": TEST_MESSAGE}, + ) + self.assertRedirects(response, reverse("contact_success")) def test_post_filled_form_logged_in_mail(self): - self._setup_user( - first_name=TEST_NAME[0], last_name=TEST_NAME[1]) + self._setup_user(first_name=TEST_NAME[0], last_name=TEST_NAME[1]) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - self.client.post(reverse('contact_form'), { - 'name': " ".join(TEST_NAME), 'email': TEST_EMAIL, - 'body': TEST_MESSAGE}) + self.client.post( + reverse("contact_form"), + {"name": " ".join(TEST_NAME), "email": TEST_EMAIL, "body": TEST_MESSAGE}, + ) self.assertEqual(len(mail.outbox), 1) class ContactSuccessViewTest(TestCase): - def test_get_template(self): - response = self.client.get(reverse('contact_success')) - self.assertTemplateUsed(response, 'contact_form/contact_success.html') + response = self.client.get(reverse("contact_success")) + self.assertTemplateUsed(response, "contact_form/contact_success.html") def test_get_status(self): - response = self.client.get(reverse('contact_success')) + response = self.client.get(reverse("contact_success")) self.assertEqual(response.status_code, 200) diff --git a/gnuviechadmin/dashboard/tests/test_views.py b/gnuviechadmin/dashboard/tests/test_views.py index dcf5e17..2ff835b 100644 --- a/gnuviechadmin/dashboard/tests/test_views.py +++ b/gnuviechadmin/dashboard/tests/test_views.py @@ -3,66 +3,65 @@ Tests for :py:mod:`dashboard.views`. """ -from django.core.urlresolvers import reverse -from django.test import TestCase from django.contrib.auth import get_user_model - +from django.test import TestCase +from django.urls import reverse User = get_user_model() -TEST_USER = 'test' -TEST_PASSWORD = 'secret' +TEST_USER = "test" +TEST_PASSWORD = "secret" class IndexViewTest(TestCase): - def test_index_view(self): - response = self.client.get(reverse('dashboard')) + response = self.client.get(reverse("dashboard")) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'dashboard/index.html') + self.assertTemplateUsed(response, "dashboard/index.html") class UserDashboardViewTest(TestCase): - def _create_test_user(self): self.user = User.objects.create(username=TEST_USER) self.user.set_password(TEST_PASSWORD) self.user.save() def test_user_dashboard_view_no_user(self): - response = self.client.get(reverse( - 'customer_dashboard', kwargs={'slug': TEST_USER})) + response = self.client.get( + reverse("customer_dashboard", kwargs={"slug": TEST_USER}) + ) self.assertEqual(response.status_code, 404) def test_user_dashboard_view_anonymous(self): User.objects.create(username=TEST_USER) - response = self.client.get(reverse( - 'customer_dashboard', kwargs={'slug': TEST_USER})) + response = self.client.get( + reverse("customer_dashboard", kwargs={"slug": TEST_USER}) + ) self.assertEqual(response.status_code, 403) def test_user_dashboard_view_logged_in_ok(self): self._create_test_user() - self.assertTrue( - self.client.login(username=TEST_USER, password=TEST_PASSWORD)) - response = self.client.get(reverse( - 'customer_dashboard', kwargs={'slug': TEST_USER})) + self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD)) + response = self.client.get( + reverse("customer_dashboard", kwargs={"slug": TEST_USER}) + ) self.assertEqual(response.status_code, 200) def test_user_dashboard_view_logged_in_template(self): self._create_test_user() - self.assertTrue( - self.client.login(username=TEST_USER, password=TEST_PASSWORD)) - response = self.client.get(reverse( - 'customer_dashboard', kwargs={'slug': TEST_USER})) - self.assertTemplateUsed(response, 'dashboard/user_dashboard.html') + self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD)) + response = self.client.get( + reverse("customer_dashboard", kwargs={"slug": TEST_USER}) + ) + self.assertTemplateUsed(response, "dashboard/user_dashboard.html") def test_user_dashboard_view_logged_in_context_fresh(self): self._create_test_user() - self.assertTrue( - self.client.login(username=TEST_USER, password=TEST_PASSWORD)) - response = self.client.get(reverse( - 'customer_dashboard', kwargs={'slug': TEST_USER})) - self.assertIn('dashboard_user', response.context) - self.assertEqual(self.user, response.context['dashboard_user']) - self.assertIn('hosting_packages', response.context) - self.assertEqual(len(response.context['hosting_packages']), 0) + self.assertTrue(self.client.login(username=TEST_USER, password=TEST_PASSWORD)) + response = self.client.get( + reverse("customer_dashboard", kwargs={"slug": TEST_USER}) + ) + self.assertIn("dashboard_user", response.context) + self.assertEqual(self.user, response.context["dashboard_user"]) + self.assertIn("hosting_packages", response.context) + self.assertEqual(len(response.context["hosting_packages"]), 0) diff --git a/gnuviechadmin/domains/tests/test_admin.py b/gnuviechadmin/domains/tests/test_admin.py index aa717a9..15bfaac 100644 --- a/gnuviechadmin/domains/tests/test_admin.py +++ b/gnuviechadmin/domains/tests/test_admin.py @@ -1,8 +1,8 @@ from django.test import TestCase -from django.core.urlresolvers import reverse +from django.urls import reverse class TestMailDomainAdmin(TestCase): def test_admin_for_maildomain(self): - admin_url = reverse('admin:domains_maildomain_changelist') + admin_url = reverse("admin:domains_maildomain_changelist") self.assertIsNotNone(admin_url) diff --git a/gnuviechadmin/domains/tests/test_forms.py b/gnuviechadmin/domains/tests/test_forms.py index 746432f..7bc1f64 100644 --- a/gnuviechadmin/domains/tests/test_forms.py +++ b/gnuviechadmin/domains/tests/test_forms.py @@ -2,51 +2,42 @@ Tests for :py:mod:`domains.forms`. """ -from __future__ import absolute_import, unicode_literals +from unittest.mock import MagicMock, Mock, patch -from mock import MagicMock, Mock, patch - -from django.core.urlresolvers import reverse from django.forms import ValidationError from django.test import TestCase +from django.urls import reverse from django.utils.translation import ugettext as _ from domains.forms import relative_domain_validator, CreateHostingDomainForm class RelativeDomainValidatorTest(TestCase): - def test_valid_domainname(self): - relative_domain_validator('example.org') + relative_domain_validator("example.org") def test_domain_name_too_long(self): - with self.assertRaisesMessage( - ValidationError, _('host name too long')): - relative_domain_validator('e' * 255) + with self.assertRaisesMessage(ValidationError, _("host name too long")): + relative_domain_validator("e" * 255) def test_domain_name_part_too_long(self): - with self.assertRaisesMessage( - ValidationError, _('invalid domain name')): - relative_domain_validator('a' * 64 + '.org') + with self.assertRaisesMessage(ValidationError, _("invalid domain name")): + relative_domain_validator("a" * 64 + ".org") def test_domain_name_illegal_characters(self): - with self.assertRaisesMessage( - ValidationError, _('invalid domain name')): - relative_domain_validator('eXampl3.org') + with self.assertRaisesMessage(ValidationError, _("invalid domain name")): + relative_domain_validator("eXampl3.org") def test_domain_name_starts_with_dash(self): - with self.assertRaisesMessage( - ValidationError, _('invalid domain name')): - relative_domain_validator('-example.org') + with self.assertRaisesMessage(ValidationError, _("invalid domain name")): + relative_domain_validator("-example.org") def test_domain_name_ends_with_dash(self): - with self.assertRaisesMessage( - ValidationError, _('invalid domain name')): - relative_domain_validator('example-.org') + with self.assertRaisesMessage(ValidationError, _("invalid domain name")): + relative_domain_validator("example-.org") class CreateHostingDomainFormTest(TestCase): - def test_constructor_needs_hostingpackage(self): instance = MagicMock() with self.assertRaises(KeyError): @@ -56,50 +47,53 @@ class CreateHostingDomainFormTest(TestCase): hostingpackage = Mock(id=42) instance = MagicMock() form = CreateHostingDomainForm(instance, hostingpackage=hostingpackage) - self.assertTrue(hasattr(form, 'hosting_package')) + self.assertTrue(hasattr(form, "hosting_package")) self.assertEqual(form.hosting_package, hostingpackage) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'create_hosting_domain', kwargs={'package': 42})) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("create_hosting_domain", kwargs={"package": 42}), + ) self.assertEqual(len(form.helper.layout.fields), 2) - self.assertEqual(form.helper.layout.fields[1].name, 'submit') + self.assertEqual(form.helper.layout.fields[1].name, "submit") def test_domain_field_has_relative_domain_validator(self): hostingpackage = Mock(id=42) instance = MagicMock() form = CreateHostingDomainForm(instance, hostingpackage=hostingpackage) - self.assertIn( - relative_domain_validator, form.fields['domain'].validators) + self.assertIn(relative_domain_validator, form.fields["domain"].validators) def test_clean(self): hostingpackage = Mock(id=42) instance = MagicMock() form = CreateHostingDomainForm( - instance, hostingpackage=hostingpackage, - data={'domain': 'example.org'}) + instance, hostingpackage=hostingpackage, data={"domain": "example.org"} + ) self.assertTrue(form.is_valid()) - self.assertIn('hosting_package', form.cleaned_data) - self.assertEqual(hostingpackage, form.cleaned_data['hosting_package']) + self.assertIn("hosting_package", form.cleaned_data) + self.assertEqual(hostingpackage, form.cleaned_data["hosting_package"]) def test_save(self): hostingpackage = Mock(id=42) instance = MagicMock() form = CreateHostingDomainForm( - instance, hostingpackage=hostingpackage, - data={'domain': 'example.org'}) + instance, hostingpackage=hostingpackage, data={"domain": "example.org"} + ) self.assertTrue(form.is_valid()) - with patch('domains.forms.HostingDomain') as domain: + with patch("domains.forms.HostingDomain") as domain: form.save() domain.objects.create_for_hosting_package.assert_called_with( - commit=True, **form.cleaned_data) + commit=True, **form.cleaned_data + ) form.save(commit=False) domain.objects.create_for_hosting_package.assert_called_with( - commit=False, **form.cleaned_data) + commit=False, **form.cleaned_data + ) def test_save_m2m(self): hostingpackage = Mock(id=42) instance = MagicMock() form = CreateHostingDomainForm( - instance, hostingpackage=hostingpackage, - data={'domain': 'example.org'}) + instance, hostingpackage=hostingpackage, data={"domain": "example.org"} + ) form.save_m2m() diff --git a/gnuviechadmin/domains/tests/test_models.py b/gnuviechadmin/domains/tests/test_models.py index abe4614..fd6d645 100644 --- a/gnuviechadmin/domains/tests/test_models.py +++ b/gnuviechadmin/domains/tests/test_models.py @@ -2,9 +2,7 @@ Tests for :py:mod:`domains.models`. """ -from __future__ import absolute_import, unicode_literals - -from mock import patch +from unittest.mock import patch from django.test import TestCase from django.contrib.auth import get_user_model @@ -20,41 +18,38 @@ from domains.models import ( HostingDomain, MailDomain, ) -from hostingpackages.models import ( - CustomerHostingPackage, - HostingPackageTemplate, -) +from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate User = get_user_model() -TEST_USER = 'test' +TEST_USER = "test" class MailDomainTest(TestCase): - def test___str__(self): - md = MailDomain.objects.create(domain='example.org') - self.assertEqual(str(md), 'example.org') + md = MailDomain.objects.create(domain="example.org") + self.assertEqual(str(md), "example.org") def test_get_mailaddresses(self): - md = MailDomain.objects.create(domain='example.org') + md = MailDomain.objects.create(domain="example.org") from managemails.models import MailAddress - addrmock = MailAddress.objects.create(localpart='info', domain=md) + + addrmock = MailAddress.objects.create(localpart="info", domain=md) self.assertIn(addrmock, md.get_mailaddresses()) self.assertIn(addrmock, md.mailaddresses) class HostingDomainManagerTest(TestCase): - def _setup_hosting_package(self): template = HostingPackageTemplate.objects.create( - name='testpackagetemplate', mailboxcount=0, diskspace=1, - diskspace_unit=0) + name="testpackagetemplate", mailboxcount=0, diskspace=1, diskspace_unit=0 + ) customer = User.objects.create_user(username=TEST_USER) package = CustomerHostingPackage.objects.create_from_template( - customer, template, 'testpackage') - with patch('hostingpackages.models.settings') as hmsettings: + customer, template, "testpackage" + ) + with patch("hostingpackages.models.settings") as hmsettings: hmsettings.OSUSER_DEFAULT_GROUPS = [] package.save() return package @@ -62,7 +57,8 @@ class HostingDomainManagerTest(TestCase): def test_create_for_hosting_package_with_commit(self): package = self._setup_hosting_package() hostingdomain = HostingDomain.objects.create_for_hosting_package( - package, 'example.org', True) + package, "example.org", True + ) self.assertIsNotNone(hostingdomain) self.assertTrue(hostingdomain.customer, package.customer) @@ -70,70 +66,60 @@ class HostingDomainManagerTest(TestCase): def test_create_for_hosting_package_no_commit(self): package = self._setup_hosting_package() hostingdomain = HostingDomain.objects.create_for_hosting_package( - package, 'example.org', False) + package, "example.org", False + ) self.assertIsNotNone(hostingdomain) self.assertTrue(hostingdomain.customer, package.customer) class HostingDomainTest(TestCase): - def test___str__(self): - hostingdomain = HostingDomain(domain='test') - self.assertEqual(str(hostingdomain), 'test') + hostingdomain = HostingDomain(domain="test") + self.assertEqual(str(hostingdomain), "test") class DNSDomainTest(TestCase): - def test___str__(self): - dnsdomain = DNSDomain(domain='test') - self.assertEqual(str(dnsdomain), 'test') + 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') + 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') + 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') + 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') + dnsdomain = DNSDomain(domain="test") dnsdomainmetadata = DNSDomainMetadata( - domain=dnsdomain, kind='SOA-EDIT', content='INCEPTION') - self.assertEqual(str(dnsdomainmetadata), 'test SOA-EDIT INCEPTION') + 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') + 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') + dnstsigkey = DNSTSIGKey(name="testkey", algorithm="hmac-md5", secret="dummykey") + self.assertEqual(str(dnstsigkey), "testkey hmac-md5 XXXX") diff --git a/gnuviechadmin/domains/tests/test_views.py b/gnuviechadmin/domains/tests/test_views.py index 567f227..cc4e099 100644 --- a/gnuviechadmin/domains/tests/test_views.py +++ b/gnuviechadmin/domains/tests/test_views.py @@ -2,149 +2,152 @@ Tests for :py:mod:`domains.views`. """ -from __future__ import absolute_import, unicode_literals - -from mock import patch, MagicMock - -from django.core.urlresolvers import reverse -from django.test import TestCase +from unittest.mock import MagicMock, patch from django.contrib.auth import get_user_model - -from hostingpackages.models import ( - CustomerHostingPackage, - HostingPackageTemplate, -) +from django.test import TestCase +from django.urls import reverse from domains.views import CreateHostingDomain - +from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate User = get_user_model() -TEST_USER = 'test' -TEST_PASSWORD = 'secret' -TEST_EMAIL = 'test@example.org' -TEST_NAME = 'Example Tester'.split() +TEST_USER = "test" +TEST_PASSWORD = "secret" +TEST_EMAIL = "test@example.org" +TEST_NAME = "Example Tester".split() class CreateHostingDomainTest(TestCase): - def _setup_hosting_package(self, customer): template = HostingPackageTemplate.objects.create( - name='testpackagetemplate', mailboxcount=0, diskspace=1, - diskspace_unit=0) + name="testpackagetemplate", mailboxcount=0, diskspace=1, diskspace_unit=0 + ) package = CustomerHostingPackage.objects.create_from_template( - customer, template, 'testpackage') - with patch('hostingpackages.models.settings') as hmsettings: + customer, template, "testpackage" + ) + with patch("hostingpackages.models.settings") as hmsettings: hmsettings.OSUSER_DEFAULT_GROUPS = [] package.save() return package def test_get_anonymous(self): response = self.client.get( - reverse('create_hosting_domain', kwargs={'package': 1})) + reverse("create_hosting_domain", kwargs={"package": 1}) + ) self.assertEqual(response.status_code, 403) def test_get_regular_user(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_hosting_domain', - kwargs={'package': package.id})) + reverse("create_hosting_domain", kwargs={"package": package.id}) + ) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_hosting_domain', - kwargs={'package': package.id})) + reverse("create_hosting_domain", kwargs={"package": package.id}) + ) self.assertEqual(response.status_code, 200) def test_get_template(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_hosting_domain', - kwargs={'package': package.id})) - self.assertTemplateUsed(response, 'domains/hostingdomain_create.html') + reverse("create_hosting_domain", kwargs={"package": package.id}) + ) + self.assertTemplateUsed(response, "domains/hostingdomain_create.html") def test_get_no_package_found(self): User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_hosting_domain', - kwargs={'package': 1})) + reverse("create_hosting_domain", kwargs={"package": 1}) + ) self.assertEqual(response.status_code, 404) def test_get_get_form_kwargs(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = CreateHostingDomain( - request=MagicMock(), kwargs={'package': str(package.id)}) + request=MagicMock(), kwargs={"package": str(package.id)} + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], package) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], package) def test_get_context_data_has_hosting_package(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_hosting_domain', - kwargs={'package': package.id})) - self.assertIn('hostingpackage', response.context) - self.assertEqual(response.context['hostingpackage'], package) + reverse("create_hosting_domain", kwargs={"package": package.id}) + ) + self.assertIn("hostingpackage", response.context) + self.assertEqual(response.context["hostingpackage"], package) def test_get_context_data_has_customer(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_hosting_domain', - kwargs={'package': package.id})) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], customer) + reverse("create_hosting_domain", kwargs={"package": package.id}) + ) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], customer) def test_form_valid_redirect(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('create_hosting_domain', - kwargs={'package': package.id}), - data={'domain': 'example.org'}) + reverse("create_hosting_domain", kwargs={"package": package.id}), + data={"domain": "example.org"}, + ) self.assertRedirects(response, package.get_absolute_url()) def test_form_valid_message(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('create_hosting_domain', - kwargs={'package': package.id}), follow=True, - data={'domain': 'example.org'}) - messages = list(response.context['messages']) + reverse("create_hosting_domain", kwargs={"package": package.id}), + follow=True, + data={"domain": "example.org"}, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - 'Successfully created domain example.org', str(messages[0])) + self.assertEqual("Successfully created domain example.org", str(messages[0])) diff --git a/gnuviechadmin/domains/views.py b/gnuviechadmin/domains/views.py index 6ab3b78..41ca975 100644 --- a/gnuviechadmin/domains/views.py +++ b/gnuviechadmin/domains/views.py @@ -5,54 +5,49 @@ This module defines views related to domains. from __future__ import absolute_import, unicode_literals from braces.views import StaffuserRequiredMixin -from django.contrib.auth.mixins import LoginRequiredMixin -from django.shortcuts import redirect, get_object_or_404 -from django.views.generic.edit import CreateView -from django.utils.translation import ugettext as _ from django.contrib import messages +from django.shortcuts import get_object_or_404, redirect +from django.utils.translation import ugettext as _ +from django.views.generic.edit import CreateView from hostingpackages.models import CustomerHostingPackage - from .forms import CreateHostingDomainForm from .models import HostingDomain -class CreateHostingDomain( - LoginRequiredMixin, StaffuserRequiredMixin, CreateView -): +class CreateHostingDomain(StaffuserRequiredMixin, CreateView): """ This view is used for creating a new HostingDomain instance for an existing hosting package. - """ + model = HostingDomain raise_exception = True - template_name_suffix = '_create' + template_name_suffix = "_create" form_class = CreateHostingDomainForm def _get_hosting_package(self): - return get_object_or_404( - CustomerHostingPackage, pk=int(self.kwargs['package'])) + return get_object_or_404(CustomerHostingPackage, pk=int(self.kwargs["package"])) def get_form_kwargs(self): kwargs = super(CreateHostingDomain, self).get_form_kwargs() - kwargs['hostingpackage'] = self._get_hosting_package() + kwargs["hostingpackage"] = self._get_hosting_package() return kwargs def get_context_data(self, **kwargs): context = super(CreateHostingDomain, self).get_context_data(**kwargs) hosting_package = self._get_hosting_package() - context.update({ - 'hostingpackage': hosting_package, - 'customer': hosting_package.customer, - }) + context.update( + {"hostingpackage": hosting_package, "customer": hosting_package.customer} + ) return context def form_valid(self, form): hostingdomain = form.save() messages.success( self.request, - _('Successfully created domain {domainname}').format( - domainname=hostingdomain.domain) + _("Successfully created domain {domainname}").format( + domainname=hostingdomain.domain + ), ) return redirect(self._get_hosting_package()) diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py index 169683e..26b5b9d 100644 --- a/gnuviechadmin/gnuviechadmin/settings.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -38,8 +38,10 @@ DEBUG = False # ######### MANAGER CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins ADMINS = ( - (get_env_variable('GVA_ADMIN_NAME', default='Admin'), - get_env_variable('GVA_ADMIN_EMAIL', default='admin@example.org')), + ( + get_env_variable("GVA_ADMIN_NAME", default="Admin"), + get_env_variable("GVA_ADMIN_EMAIL", default="admin@example.org"), + ), ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#managers @@ -50,13 +52,13 @@ MANAGERS = ADMINS # ######### DATABASE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': get_env_variable('GVA_PGSQL_DATABASE', default='gnuviechadmin'), - 'USER': get_env_variable('GVA_PGSQL_USER', default='gnuviechadmin'), - 'PASSWORD': get_env_variable('GVA_PGSQL_PASSWORD'), - 'HOST': get_env_variable('GVA_PGSQL_HOSTNAME', default='db'), - 'PORT': get_env_variable('GVA_PGSQL_PORT', int, default=5432), + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": get_env_variable("GVA_PGSQL_DATABASE", default="gnuviechadmin"), + "USER": get_env_variable("GVA_PGSQL_USER", default="gnuviechadmin"), + "PASSWORD": get_env_variable("GVA_PGSQL_PASSWORD"), + "HOST": get_env_variable("GVA_PGSQL_HOSTNAME", default="db"), + "PORT": get_env_variable("GVA_PGSQL_PORT", int, default=5432), } } # ######### END DATABASE CONFIGURATION @@ -64,15 +66,15 @@ DATABASES = { # ######### GENERAL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone -TIME_ZONE = 'Europe/Berlin' +TIME_ZONE = "Europe/Berlin" # See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" # See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id SITE_ID = 1 -SITES_DOMAIN_NAME = get_env_variable('GVA_DOMAIN_NAME') -SITES_SITE_NAME = get_env_variable('GVA_SITE_NAME') +SITES_DOMAIN_NAME = get_env_variable("GVA_DOMAIN_NAME") +SITES_SITE_NAME = get_env_variable("GVA_SITE_NAME") # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n USE_I18N = True @@ -85,33 +87,28 @@ USE_TZ = True # ######### END GENERAL CONFIGURATION -LOCALE_PATHS = ( - normpath(join(SITE_ROOT, 'gnuviechadmin', 'locale')), -) +LOCALE_PATHS = (normpath(join(SITE_ROOT, "gnuviechadmin", "locale")),) # ######### MEDIA CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root -MEDIA_ROOT = normpath(join(SITE_ROOT, 'media')) +MEDIA_ROOT = normpath(join(SITE_ROOT, "media")) # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url -MEDIA_URL = '/media/' +MEDIA_URL = "/media/" # ######### END MEDIA CONFIGURATION - # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url -STATIC_URL = '/static/' +STATIC_URL = "/static/" # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # noqa -STATICFILES_DIRS = ( - normpath(join(SITE_ROOT, 'gnuviechadmin', 'assets')), -) +STATICFILES_DIRS = (normpath(join(SITE_ROOT, "gnuviechadmin", "assets")),) # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ) # ######### END STATIC FILE CONFIGURATION @@ -119,7 +116,7 @@ STATICFILES_FINDERS = ( # ######### SECRET CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Note: This key should only be used for development and testing. -SECRET_KEY = get_env_variable('GVA_SITE_SECRET') +SECRET_KEY = get_env_variable("GVA_SITE_SECRET") # ######### END SECRET CONFIGURATION @@ -132,9 +129,7 @@ ALLOWED_HOSTS = [] # ######### FIXTURE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS # noqa -FIXTURE_DIRS = ( - normpath(join(SITE_ROOT, 'fixtures')), -) +FIXTURE_DIRS = (normpath(join(SITE_ROOT, "fixtures")),) # ######### END FIXTURE CONFIGURATION @@ -142,27 +137,25 @@ FIXTURE_DIRS = ( # See: https://docs.djangoproject.com/en/1.9/ref/settings/#std:setting-TEMPLATES # noqa TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - normpath(join(DJANGO_ROOT, 'templates')), - ], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.template.context_processors.request', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [normpath(join(DJANGO_ROOT, "templates"))], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "django.template.context_processors.request", # custom context processors - 'gnuviechadmin.context_processors.navigation', - 'gnuviechadmin.context_processors.version_info', - ], + "gnuviechadmin.context_processors.navigation", + "gnuviechadmin.context_processors.version_info", + ] }, - }, + } ] # ######### END TEMPLATE CONFIGURATION @@ -171,14 +164,14 @@ TEMPLATES = [ # See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes MIDDLEWARE = [ # Default Django middleware. - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", # uncomment next line to enable translation to browser locale - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] # ######### END MIDDLEWARE CONFIGURATION @@ -186,7 +179,6 @@ MIDDLEWARE = [ AUTHENTICATION_BACKENDS = ( # Needed to login by username in Django admin, regardless of `allauth` "django.contrib.auth.backends.ModelBackend", - # `allauth` specific authentication methods, such as login by e-mail "allauth.account.auth_backends.AuthenticationBackend", ) @@ -194,87 +186,83 @@ AUTHENTICATION_BACKENDS = ( # ######### URL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf -ROOT_URLCONF = '%s.urls' % SITE_NAME +ROOT_URLCONF = "%s.urls" % SITE_NAME # ######### END URL CONFIGURATION # ######### TEST RUNNER CONFIGURATION -TEST_RUNNER = 'django.test.runner.DiscoverRunner' +TEST_RUNNER = "django.test.runner.DiscoverRunner" # ######### END TEST RUNNER CONFIGURATION # ######### APP CONFIGURATION DJANGO_APPS = ( # Default Django apps: - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.staticfiles", # Useful template tags: - 'django.contrib.humanize', - + "django.contrib.humanize", # Admin panel and documentation: - 'django.contrib.admin', - + "django.contrib.admin", # Flatpages for about page - 'django.contrib.flatpages', - - 'crispy_forms', + "django.contrib.flatpages", + "crispy_forms", ) ALLAUTH_APPS = ( - 'allauth', - 'allauth.account', - 'allauth.socialaccount', - 'allauth.socialaccount.providers.google', - 'allauth.socialaccount.providers.linkedin_oauth2', - 'allauth.socialaccount.providers.twitter', + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.google", + "allauth.socialaccount.providers.linkedin_oauth2", + "allauth.socialaccount.providers.twitter", ) # Apps specific for this project go here. LOCAL_APPS = ( - 'dashboard', - 'taskresults', - 'ldaptasks', - 'mysqltasks', - 'pgsqltasks', - 'fileservertasks', - 'webtasks', - 'domains', - 'osusers', - 'managemails', - 'userdbs', - 'hostingpackages', - 'websites', - 'contact_form', + "dashboard", + "taskresults", + "ldaptasks", + "mysqltasks", + "pgsqltasks", + "fileservertasks", + "webtasks", + "domains", + "osusers", + "managemails", + "userdbs", + "hostingpackages", + "websites", + "contact_form", ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + ALLAUTH_APPS + LOCAL_APPS MESSAGE_TAGS = { - messages.DEBUG: '', - messages.ERROR: 'alert-danger', - messages.INFO: 'alert-info', - messages.SUCCESS: 'alert-success', - messages.WARNING: 'alert-warning', + messages.DEBUG: "", + messages.ERROR: "alert-danger", + messages.INFO: "alert-info", + messages.SUCCESS: "alert-success", + messages.WARNING: "alert-warning", } # ######### END APP CONFIGURATION # ######### ALLAUTH CONFIGURATION ACCOUNT_EMAIL_REQUIRED = True -ACCOUNT_EMAIL_VERIFICATION = 'mandatory' -LOGIN_REDIRECT_URL = '/' +ACCOUNT_EMAIL_VERIFICATION = "mandatory" +LOGIN_REDIRECT_URL = "/" SOCIALACCOUNT_QUERY_EMAIL = True # ######### END ALLAUTH CONFIGURATION # ######### CRISPY FORMS CONFIGURATION -CRISPY_TEMPLATE_PACK = 'bootstrap3' +CRISPY_TEMPLATE_PACK = "bootstrap3" # ######### END CRISPY_FORMS CONFIGURATION @@ -286,170 +274,197 @@ CRISPY_TEMPLATE_PACK = 'bootstrap3' # See http://docs.djangoproject.com/en/dev/topics/logging for # more details on how to customize your logging configuration. LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(name)s ' - '%(module)s:%(lineno)d %(process)d %(thread)d %(message)s', - }, - 'simple': { - 'format': '%(levelname)s %(name)s:%(lineno)d %(message)s', + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(name)s " + "%(module)s:%(lineno)d %(process)d %(thread)d %(message)s" }, + "simple": {"format": "%(levelname)s %(name)s:%(lineno)d %(message)s"}, }, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", } }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' + "loggers": { + "django.request": { + "handlers": ["mail_admins"], + "level": "ERROR", + "propagate": True, } }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } } # ######### END LOGGING CONFIGURATION # ######### WSGI CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application -WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME +WSGI_APPLICATION = "%s.wsgi.application" % SITE_NAME # ######### END WSGI CONFIGURATION # ######### CELERY CONFIGURATION BROKER_URL = get_env_variable( - 'GVA_BROKER_URL', - default='amqp://gnuviechadmin:gnuviechadmin@mq/gnuviechadmin') -CELERY_RESULT_BACKEND = get_env_variable( - 'GVA_RESULTS_REDIS_URL', - default='redis://:gnuviechadmin@redis:6379/0') -CELERY_TASK_RESULT_EXPIRES = None -CELERY_ROUTES = ( - 'gvacommon.celeryrouters.GvaRouter', + "GVA_BROKER_URL", default="amqp://gnuviechadmin:gnuviechadmin@mq/gnuviechadmin" ) -CELERY_TIMEZONE = 'Europe/Berlin' +BROKER_TRANSPORT_OPTIONS = { + "max_retries": 3, + "interval_start": 0, + "interval_step": 0.2, + "interval_max": 0.2, +} +CELERY_RESULT_BACKEND = get_env_variable( + "GVA_RESULTS_REDIS_URL", default="redis://:gnuviechadmin@redis:6379/0" +) +CELERY_TASK_RESULT_EXPIRES = None +CELERY_ROUTES = ("gvacommon.celeryrouters.GvaRouter",) +CELERY_TIMEZONE = "Europe/Berlin" CELERY_ENABLE_UTC = True -CELERY_ACCEPT_CONTENT = ['json'] -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_SERIALIZER = "json" # ######### END CELERY CONFIGURATION # ######### CUSTOM APP CONFIGURATION -OSUSER_MINUID = get_env_variable('GVA_MIN_OS_UID', int, default=10000) -OSUSER_MINGID = get_env_variable('GVA_MIN_OS_GID', int, default=10000) -OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX', default='usr') -OSUSER_HOME_BASEPATH = get_env_variable( - 'GVA_OSUSER_HOME_BASEPATH', default='/home') +OSUSER_MINUID = get_env_variable("GVA_MIN_OS_UID", int, default=10000) +OSUSER_MINGID = get_env_variable("GVA_MIN_OS_GID", int, default=10000) +OSUSER_USERNAME_PREFIX = get_env_variable("GVA_OSUSER_PREFIX", default="usr") +OSUSER_HOME_BASEPATH = get_env_variable("GVA_OSUSER_HOME_BASEPATH", default="/home") OSUSER_DEFAULT_SHELL = get_env_variable( - 'GVA_OSUSER_DEFAULT_SHELL', default='/usr/bin/rssh') -OSUSER_SFTP_GROUP = 'sftponly' -OSUSER_SSH_GROUP = 'sshusers' + "GVA_OSUSER_DEFAULT_SHELL", default="/usr/bin/rssh" +) +OSUSER_SFTP_GROUP = "sftponly" +OSUSER_SSH_GROUP = "sshusers" OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP] -OSUSER_UPLOAD_SERVER = get_env_variable( - 'GVA_OSUSER_UPLOADSERVER', default='file') +OSUSER_UPLOAD_SERVER = get_env_variable("GVA_OSUSER_UPLOADSERVER", default="file") GVA_LINK_WEBMAIL = get_env_variable( - 'GVA_WEBMAIL_URL', default='https://webmail.example.org/') + "GVA_WEBMAIL_URL", default="https://webmail.example.org/" +) GVA_LINK_PHPMYADMIN = get_env_variable( - 'GVA_PHPMYADMIN_URL', default='https://phpmyadmin.example.org/') + "GVA_PHPMYADMIN_URL", default="https://phpmyadmin.example.org/" +) GVA_LINK_PHPPGADMIN = get_env_variable( - 'GVA_PHPPGADMIN_URL', default='https://phppgadmin.example.org/') + "GVA_PHPPGADMIN_URL", default="https://phppgadmin.example.org/" +) # ######### END CUSTOM APP CONFIGURATION -GVA_ENVIRONMENT = get_env_variable('GVA_ENVIRONMENT', default='prod') +GVA_ENVIRONMENT = get_env_variable("GVA_ENVIRONMENT", default="prod") # ######### STATIC FILE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = '/srv/gnuviechadmin/static/' +STATIC_ROOT = "/srv/gnuviechadmin/static/" + def show_debug_toolbar(request): - return DEBUG == True and GVA_ENVIRONMENT == 'local' + return DEBUG == True and GVA_ENVIRONMENT == "local" -if GVA_ENVIRONMENT == 'local': +if GVA_ENVIRONMENT == "local": # ######### DEBUG CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug DEBUG = True - + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug - TEMPLATES[0]['OPTIONS']['debug'] = DEBUG + TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # ######### END DEBUG CONFIGURATION - + # ######### EMAIL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend - EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # ######### END EMAIL CONFIGURATION - + # ######### CACHE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - } - } + CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}} # ######### END CACHE CONFIGURATION - + # ######### TOOLBAR CONFIGURATION # See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup # noqa - INSTALLED_APPS += ( - 'debug_toolbar', - ) - - MIDDLEWARE += [ - 'debug_toolbar.middleware.DebugToolbarMiddleware', - ] - - LOGGING['handlers'].update({ - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple', + INSTALLED_APPS += ("debug_toolbar",) + + MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] + + LOGGING["handlers"].update( + { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "simple", + } } - }) - LOGGING['loggers'].update(dict( - [(key, {'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }) - for key in [ - 'dashboard', 'domains', 'fileservertasks', 'gvacommon', - 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', - 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', - 'userdbs', 'websites']])) - + ) + LOGGING["loggers"].update( + dict( + [ + (key, {"handlers": ["console"], "level": "DEBUG", "propagate": True}) + for key in [ + "dashboard", + "domains", + "fileservertasks", + "gvacommon", + "gvawebcore", + "hostingpackages", + "ldaptasks", + "managemails", + "mysqltasks", + "osusers", + "pgsqltasks", + "taskresults", + "userdbs", + "websites", + ] + ] + ) + ) + DEBUG_TOOLBAR_PATCH_SETTINGS = False DEBUG_TOOLBAR_CONFIG = { - 'SHOW_TOOLBAR_CALLBACK': 'gnuviechadmin.settings.show_debug_toolbar', + "SHOW_TOOLBAR_CALLBACK": "gnuviechadmin.settings.show_debug_toolbar" } - + # ######### END TOOLBAR CONFIGURATION -elif GVA_ENVIRONMENT == 'test': - PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.MD5PasswordHasher', - ) - LOGGING['handlers'].update({ - 'console': { - 'level': 'ERROR', - 'class': 'logging.StreamHandler', - 'formatter': 'simple', +elif GVA_ENVIRONMENT == "test": + PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) + LOGGING["handlers"].update( + { + "console": { + "level": "ERROR", + "class": "logging.StreamHandler", + "formatter": "simple", + } } - }) - LOGGING['loggers'].update(dict( - [(key, {'handlers': ['console'], 'level': 'ERROR', 'propagate': True, }) - for key in [ - 'dashboard', 'domains', 'fileservertasks', 'gvacommon', - 'gvawebcore', 'hostingpackages', 'ldaptasks', 'managemails', - 'mysqltasks', 'osusers', 'pgsqltasks', 'taskresults', - 'userdbs', 'websites']])) - BROKER_URL = BROKER_URL + '_test' + ) + LOGGING["loggers"].update( + dict( + [ + (key, {"handlers": ["console"], "level": "ERROR", "propagate": True}) + for key in [ + "dashboard", + "domains", + "fileservertasks", + "gvacommon", + "gvawebcore", + "hostingpackages", + "ldaptasks", + "managemails", + "mysqltasks", + "osusers", + "pgsqltasks", + "taskresults", + "userdbs", + "websites", + ] + ] + ) + ) + BROKER_URL = BROKER_URL + "_test" CELERY_RESULT_PERSISTENT = False else: # ######### HOST CONFIGURATION @@ -459,18 +474,18 @@ else: # ######### EMAIL CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend - EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix - EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME + EMAIL_SUBJECT_PREFIX = "[%s] " % SITE_NAME # See: https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email DEFAULT_FROM_EMAIL = get_env_variable( - 'GVA_SITE_ADMINMAIL', default='admin@example.org') + "GVA_SITE_ADMINMAIL", default="admin@example.org" + ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email - SERVER_EMAIL = get_env_variable( - 'GVA_SITE_ADMINMAIL', default='admin@example.org') + SERVER_EMAIL = get_env_variable("GVA_SITE_ADMINMAIL", default="admin@example.org") # ######### END EMAIL CONFIGURATION # ######### CACHE CONFIGURATION @@ -479,6 +494,6 @@ else: # ######### END CACHE CONFIGURATION # ######### ALLAUTH PRODUCTION CONFIGURATION - ACCOUNT_EMAIL_SUBJECT_PREFIX = '[Jan Dittberner IT-Consulting & -Solutions] ' - ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https' + ACCOUNT_EMAIL_SUBJECT_PREFIX = "[Jan Dittberner IT-Consulting & -Solutions] " + ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" # ######### END ALLAUTH PRODUCTION CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py b/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py index 9379ad3..664e8d2 100644 --- a/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py +++ b/gnuviechadmin/gnuviechadmin/tests/test_contextprocessors.py @@ -5,14 +5,13 @@ This module contains tests for :py:mod:`gnuviechadmin.context_processors`. """ -from mock import MagicMock +from unittest.mock import MagicMock from django.conf import settings -from django.core.urlresolvers import reverse +from django.contrib.auth import get_user_model from django.http import HttpRequest from django.test import TestCase - -from django.contrib.auth import get_user_model +from django.urls import reverse from gnuviechadmin import __version__ as gvaversion from gnuviechadmin.context_processors import navigation @@ -22,48 +21,40 @@ User = get_user_model() 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): - response = self.client.get( - '/', HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get("/", HTTP_X_REQUESTED_WITH="XMLHttpRequest") for item in self.EXPECTED_ITEMS: self.assertNotIn(item, response.context) def _check_static_urls(self, context): - self.assertEqual( - context['webmail_url'], settings.GVA_LINK_WEBMAIL) - self.assertEqual( - context['phpmyadmin_url'], settings.GVA_LINK_PHPMYADMIN) - self.assertEqual( - context['phppgadmin_url'], settings.GVA_LINK_PHPPGADMIN) + self.assertEqual(context["webmail_url"], settings.GVA_LINK_WEBMAIL) + self.assertEqual(context["phpmyadmin_url"], settings.GVA_LINK_PHPMYADMIN) + self.assertEqual(context["phppgadmin_url"], settings.GVA_LINK_PHPPGADMIN) def test_index_page_context(self): - response = self.client.get('/') + response = self.client.get("/") for item in self.EXPECTED_ITEMS: self.assertIn(item, response.context) self._check_static_urls(response.context) - self.assertEqual(response.context['active_item'], 'dashboard') + self.assertEqual(response.context["active_item"], "dashboard") def test_contact_page_context(self): - response = self.client.get(reverse('contact_form')) + response = self.client.get(reverse("contact_form")) for item in self.EXPECTED_ITEMS: self.assertIn(item, response.context) self._check_static_urls(response.context) - self.assertEqual(response.context['active_item'], 'contact') + 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'})) + 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') + self.assertEqual(response.context["active_item"], "hostingpackage") def _test_page_context_by_viewmodule(self, viewmodule, expecteditem): request = HttpRequest() @@ -73,57 +64,48 @@ class NavigationContextProcessorTest(TestCase): for item in self.EXPECTED_ITEMS: self.assertIn(item, context) self._check_static_urls(context) - self.assertEqual(context['active_item'], expecteditem) + self.assertEqual(context["active_item"], expecteditem) def test_osusers_page_context(self): - self._test_page_context_by_viewmodule( - 'osusers.views', 'hostingpackage') + self._test_page_context_by_viewmodule("osusers.views", "hostingpackage") def test_userdbs_page_context(self): - self._test_page_context_by_viewmodule( - 'userdbs.views', 'hostingpackage') + self._test_page_context_by_viewmodule("userdbs.views", "hostingpackage") def test_managemails_page_context(self): - self._test_page_context_by_viewmodule( - 'managemails.views', 'hostingpackage') + self._test_page_context_by_viewmodule("managemails.views", "hostingpackage") def test_websites_page_context(self): - self._test_page_context_by_viewmodule( - 'websites.views', 'hostingpackage') + self._test_page_context_by_viewmodule("websites.views", "hostingpackage") def test_domains_page_context(self): - self._test_page_context_by_viewmodule( - 'domains.views', 'hostingpackage') + self._test_page_context_by_viewmodule("domains.views", "hostingpackage") def test_allauth_account_page_context(self): - self._test_page_context_by_viewmodule( - 'allauth.account.views', 'account') + self._test_page_context_by_viewmodule("allauth.account.views", "account") def test_allauth_socialaccount_page_context(self): - self._test_page_context_by_viewmodule( - 'allauth.socialaccount.views', 'account') + self._test_page_context_by_viewmodule("allauth.socialaccount.views", "account") def test_imprint_page_context(self): - response = self.client.get(reverse('imprint')) + response = self.client.get(reverse("imprint")) for item in self.EXPECTED_ITEMS: self.assertIn(item, response.context) self._check_static_urls(response.context) - self.assertEqual(response.context['active_item'], 'imprint') + self.assertEqual(response.context["active_item"], "imprint") def test_no_resolver_match(self): request = HttpRequest() context = navigation(request) self._check_static_urls(context) - self.assertEqual(context['active_item'], 'dashboard') + self.assertEqual(context["active_item"], "dashboard") def test_admin_module(self): - self._test_page_context_by_viewmodule( - 'django.contrib.admin.foo', 'dashboard') + self._test_page_context_by_viewmodule("django.contrib.admin.foo", "dashboard") class VersionInfoContextProcessorTest(TestCase): - def test_version_info_in_context(self): - response = self.client.get('/') - self.assertIn('gnuviechadmin_version', response.context) - self.assertEqual(response.context['gnuviechadmin_version'], gvaversion) + response = self.client.get("/") + self.assertIn("gnuviechadmin_version", response.context) + self.assertEqual(response.context["gnuviechadmin_version"], gvaversion) diff --git a/gnuviechadmin/gvawebcore/tests/test_forms.py b/gnuviechadmin/gvawebcore/tests/test_forms.py index e657829..b4bb213 100644 --- a/gnuviechadmin/gvawebcore/tests/test_forms.py +++ b/gnuviechadmin/gvawebcore/tests/test_forms.py @@ -5,25 +5,26 @@ This module contains tests for :py:mod:`gvawebcore.forms`. from unittest import TestCase -from gvawebcore.forms import PasswordModelFormMixin, PASSWORD_MISMATCH_ERROR +from gvawebcore.forms import PASSWORD_MISMATCH_ERROR, PasswordModelFormMixin class PasswordModelFormMixinTest(TestCase): - def test_form_properties(self): form = PasswordModelFormMixin() - self.assertIn('password1', form.fields) - self.assertIn('password2', form.fields) + self.assertIn("password1", form.fields) + self.assertIn("password2", form.fields) def test_clean_password_same(self): - form = PasswordModelFormMixin(data={ - 'password1': 'secret', 'password2': 'secret'}) + form = PasswordModelFormMixin( + data={"password1": "secret", "password2": "secret"} + ) self.assertTrue(form.is_valid()) - self.assertEqual('secret', form.clean_password2()) + self.assertEqual("secret", form.clean_password2()) def test_clean_password_different(self): - form = PasswordModelFormMixin(data={ - 'password1': 'onesecret', 'password2': 'other'}) + form = PasswordModelFormMixin( + data={"password1": "onesecret", "password2": "other"} + ) self.assertFalse(form.is_valid()) - self.assertIn('password2', form.errors) - self.assertIn(PASSWORD_MISMATCH_ERROR, form.errors['password2']) + self.assertIn("password2", form.errors) + self.assertIn(PASSWORD_MISMATCH_ERROR, form.errors["password2"]) diff --git a/gnuviechadmin/gvawebcore/tests/test_views.py b/gnuviechadmin/gvawebcore/tests/test_views.py index 6c4ca4b..30cd39c 100644 --- a/gnuviechadmin/gvawebcore/tests/test_views.py +++ b/gnuviechadmin/gvawebcore/tests/test_views.py @@ -4,31 +4,29 @@ This model contains tests for :py:mod:`gvawebcore.views`. """ from unittest import TestCase - -from mock import patch, Mock +from unittest.mock import Mock, patch from gvawebcore.views import HostingPackageAndCustomerMixin class HostingPackageAndCustomerMixinTest(TestCase): - class TestView(HostingPackageAndCustomerMixin): - kwargs = {'package': '1'} + kwargs = {"package": "1"} - @patch('gvawebcore.views.get_object_or_404') + @patch("gvawebcore.views.get_object_or_404") def test_get_hosting_package(self, get_object_or_404): - get_object_or_404.return_value = 'A package' + get_object_or_404.return_value = "A package" view = self.TestView() - self.assertEqual('A package', view.get_hosting_package()) + self.assertEqual("A package", view.get_hosting_package()) def test_get_hosting_package_cached(self): view = self.TestView() - view.hostingpackage = 'Cached package' - self.assertEqual('Cached package', view.get_hosting_package()) + view.hostingpackage = "Cached package" + self.assertEqual("Cached package", view.get_hosting_package()) - @patch('gvawebcore.views.get_object_or_404') + @patch("gvawebcore.views.get_object_or_404") def test_get_customer_object(self, get_object_or_404): - get_object_or_404.return_value = Mock(customer='A customer') + get_object_or_404.return_value = Mock(customer="A customer") view = self.TestView() - self.assertEqual('A customer', view.get_customer_object()) + self.assertEqual("A customer", view.get_customer_object()) diff --git a/gnuviechadmin/hostingpackages/models.py b/gnuviechadmin/hostingpackages/models.py index 71c648f..c1a5267 100644 --- a/gnuviechadmin/hostingpackages/models.py +++ b/gnuviechadmin/hostingpackages/models.py @@ -16,37 +16,24 @@ from model_utils.models import TimeStampedModel from domains.models import HostingDomain from managemails.models import Mailbox -from osusers.models import ( - AdditionalGroup, - Group, - User as OsUser, -) -from userdbs.models import ( - DB_TYPES, - UserDatabase, -) +from osusers.models import AdditionalGroup, Group, User as OsUser +from userdbs.models import DB_TYPES, UserDatabase -DISK_SPACE_UNITS = Choices( - (0, 'M', _('MiB')), - (1, 'G', _('GiB')), - (2, 'T', _('TiB')), -) +DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("TiB"))) -DISK_SPACE_FACTORS = ( - (1, None, None), - (1024, 1, None), - (1024 * 1024, 1024, 1), -) +DISK_SPACE_FACTORS = ((1, None, None), (1024, 1, None), (1024 * 1024, 1024, 1)) @python_2_unicode_compatible class HostingPackageBase(TimeStampedModel): - description = models.TextField(_('description'), blank=True) - mailboxcount = models.PositiveIntegerField(_('mailbox count')) + description = models.TextField(_("description"), blank=True) + mailboxcount = models.PositiveIntegerField(_("mailbox count")) diskspace = models.PositiveIntegerField( - _('disk space'), help_text=_('disk space for the hosting package')) + _("disk space"), help_text=_("disk space for the hosting package") + ) diskspace_unit = models.PositiveSmallIntegerField( - _('unit of disk space'), choices=DISK_SPACE_UNITS) + _("unit of disk space"), choices=DISK_SPACE_UNITS + ) class Meta: abstract = True @@ -56,11 +43,11 @@ class HostingPackageBase(TimeStampedModel): class HostingPackageTemplate(HostingPackageBase): - name = models.CharField(_('name'), max_length=128, unique=True) + name = models.CharField(_("name"), max_length=128, unique=True) class Meta: - verbose_name = _('Hosting package') - verbose_name_plural = _('Hosting packages') + verbose_name = _("Hosting package") + verbose_name_plural = _("Hosting packages") class HostingOption(TimeStampedModel): @@ -72,19 +59,21 @@ class HostingOption(TimeStampedModel): @python_2_unicode_compatible class DiskSpaceOptionBase(models.Model): - diskspace = models.PositiveIntegerField(_('disk space')) + diskspace = models.PositiveIntegerField(_("disk space")) diskspace_unit = models.PositiveSmallIntegerField( - _('unit of disk space'), choices=DISK_SPACE_UNITS) + _("unit of disk space"), choices=DISK_SPACE_UNITS + ) class Meta: abstract = True - ordering = ['diskspace_unit', 'diskspace'] - verbose_name = _('Disk space option') - verbose_name_plural = _('Disk space options') + ordering = ["diskspace_unit", "diskspace"] + verbose_name = _("Disk space option") + verbose_name_plural = _("Disk space options") def __str__(self): return _("Additional disk space {space} {unit}").format( - space=self.diskspace, unit=self.get_diskspace_unit_display()) + space=self.diskspace, unit=self.get_diskspace_unit_display() + ) class DiskSpaceOption(DiskSpaceOptionBase, HostingOption): @@ -95,30 +84,24 @@ class DiskSpaceOption(DiskSpaceOptionBase, HostingOption): """ class Meta: - unique_together = ['diskspace', 'diskspace_unit'] + unique_together = ["diskspace", "diskspace_unit"] @python_2_unicode_compatible class UserDatabaseOptionBase(models.Model): - number = models.PositiveIntegerField( - _('number of databases'), default=1) - db_type = models.PositiveSmallIntegerField( - _('database type'), choices=DB_TYPES) + number = models.PositiveIntegerField(_("number of databases"), default=1) + db_type = models.PositiveSmallIntegerField(_("database type"), choices=DB_TYPES) class Meta: abstract = True - ordering = ['db_type', 'number'] - verbose_name = _('Database option') - verbose_name_plural = _('Database options') + ordering = ["db_type", "number"] + verbose_name = _("Database option") + verbose_name_plural = _("Database options") def __str__(self): return ungettext( - '{type} database', - '{count} {type} databases', - self.number - ).format( - type=self.get_db_type_display(), count=self.number - ) + "{type} database", "{count} {type} databases", self.number + ).format(type=self.get_db_type_display(), count=self.number) class UserDatabaseOption(UserDatabaseOptionBase, HostingOption): @@ -129,7 +112,7 @@ class UserDatabaseOption(UserDatabaseOptionBase, HostingOption): """ class Meta: - unique_together = ['number', 'db_type'] + unique_together = ["number", "db_type"] @python_2_unicode_compatible @@ -138,23 +121,19 @@ class MailboxOptionBase(models.Model): Base class for mailbox options. """ - number = models.PositiveIntegerField( - _('number of mailboxes'), unique=True) + + number = models.PositiveIntegerField(_("number of mailboxes"), unique=True) class Meta: abstract = True - ordering = ['number'] - verbose_name = _('Mailbox option') - verbose_name_plural = _('Mailbox options') + ordering = ["number"] + verbose_name = _("Mailbox option") + verbose_name_plural = _("Mailbox options") def __str__(self): return ungettext( - '{count} additional mailbox', - '{count} additional mailboxes', - self.number - ).format( - count=self.number - ) + "{count} additional mailbox", "{count} additional mailboxes", self.number + ).format(count=self.number) class MailboxOption(MailboxOptionBase, HostingOption): @@ -189,10 +168,11 @@ class CustomerHostingPackageManager(models.Manager): """ package = CustomerHostingPackage( - customer=customer, template=template, name=name) + customer=customer, template=template, name=name + ) package.description = template.description package.copy_template_attributes() - if 'commit' in kwargs and kwargs['commit'] is True: + if "commit" in kwargs and kwargs["commit"] is True: package.save(**kwargs) return package @@ -203,45 +183,49 @@ class CustomerHostingPackage(HostingPackageBase): This class defines customer specific hosting packages. """ + customer = models.ForeignKey( - settings.AUTH_USER_MODEL, verbose_name=_('customer'), - on_delete=models.CASCADE) + settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE + ) template = models.ForeignKey( - HostingPackageTemplate, verbose_name=_('hosting package template'), + HostingPackageTemplate, + verbose_name=_("hosting package template"), help_text=_( - 'The hosting package template that this hosting package is based' - ' on' + "The hosting package template that this hosting package is based" " on" ), - on_delete=models.CASCADE) - name = models.CharField(_('name'), max_length=128) + on_delete=models.CASCADE, + ) + name = models.CharField(_("name"), max_length=128) osuser = models.OneToOneField( - OsUser, verbose_name=_('Operating system user'), - blank=True, null=True, on_delete=models.CASCADE) + OsUser, + verbose_name=_("Operating system user"), + blank=True, + null=True, + on_delete=models.CASCADE, + ) objects = CustomerHostingPackageManager() class Meta: - unique_together = ['customer', 'name'] - verbose_name = _('customer hosting package') - verbose_name_plural = _('customer hosting packages') + unique_together = ["customer", "name"] + verbose_name = _("customer hosting package") + verbose_name_plural = _("customer hosting packages") def __str__(self): - return _("{name} for {customer}").format( - name=self.name, customer=self.customer - ) + return _("{name} for {customer}").format(name=self.name, customer=self.customer) def get_absolute_url(self): - return reverse('hosting_package_details', kwargs={ - 'user': self.customer.username, - 'pk': self.id, - }) + return reverse( + "hosting_package_details", + kwargs={"user": self.customer.username, "pk": self.id}, + ) def copy_template_attributes(self): """ Copy the attributes of the hosting package's template to the package. """ - for attrname in ('diskspace', 'diskspace_unit', 'mailboxcount'): + for attrname in ("diskspace", "diskspace_unit", "mailboxcount"): setattr(self, attrname, getattr(self.template, attrname)) def get_hostingoptions(self): @@ -249,7 +233,7 @@ class CustomerHostingPackage(HostingPackageBase): for opt_type in [ CustomerDiskSpaceOption, CustomerMailboxOption, - CustomerUserDatabaseOption + CustomerUserDatabaseOption, ]: opts.extend(opt_type.objects.filter(hosting_package=self)) return opts @@ -276,13 +260,13 @@ class CustomerHostingPackage(HostingPackageBase): diskspace += option.diskspace elif option.diskspace_unit > min_unit: diskspace += ( - DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] * - option.diskspace) + DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] + * option.diskspace + ) else: diskspace = ( - DISK_SPACE_FACTORS[min_unit][ - option.diskspace_unit] * - diskspace) + option.diskspace + DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] * diskspace + ) + option.diskspace min_unit = option.diskspace_unit if unit is None: return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024 ** 2 @@ -302,16 +286,16 @@ class CustomerHostingPackage(HostingPackageBase): """ if unit is None: - return (DISK_SPACE_FACTORS[self.diskspace_unit][0] * - self.diskspace * 1024 ** 2) + return ( + DISK_SPACE_FACTORS[self.diskspace_unit][0] * self.diskspace * 1024 ** 2 + ) if unit > self.diskspace_unit: - return (DISK_SPACE_FACTORS[unit][self.diskspace_unit] * - self.diskspace) + return DISK_SPACE_FACTORS[unit][self.diskspace_unit] * self.diskspace return DISK_SPACE_FACTORS[self.diskspace_unit][unit] * self.diskspace def get_quota(self): soft = 1024 * self.get_disk_space(DISK_SPACE_UNITS.M) - hard = soft * 105 / 100 + hard = soft * 105 // 100 return (soft, hard) def get_mailboxes(self): @@ -337,12 +321,10 @@ class CustomerHostingPackage(HostingPackageBase): of its mailbox options. """ - result = CustomerMailboxOption.objects.filter( - hosting_package=self - ).aggregate( - mailbox_sum=models.Sum('number') + result = CustomerMailboxOption.objects.filter(hosting_package=self).aggregate( + mailbox_sum=models.Sum("number") ) - return self.mailboxcount + (result['mailbox_sum'] or 0) + return self.mailboxcount + (result["mailbox_sum"] or 0) mailbox_count = property(get_mailbox_count) @@ -355,25 +337,23 @@ class CustomerHostingPackage(HostingPackageBase): options for this hosting package. """ - return CustomerUserDatabaseOption.objects.values( - 'db_type' - ).filter(hosting_package=self).annotate( - number=models.Sum('number') - ).all() + return ( + CustomerUserDatabaseOption.objects.values("db_type") + .filter(hosting_package=self) + .annotate(number=models.Sum("number")) + .all() + ) def get_databases_flat(self): if self.osuser: - return UserDatabase.objects.filter( - db_user__osuser=self.osuser).all() + return UserDatabase.objects.filter(db_user__osuser=self.osuser).all() databases = property(get_databases_flat) def may_add_database(self): return ( - CustomerUserDatabaseOption.objects.filter( - hosting_package=self).count() > - UserDatabase.objects.filter( - db_user__osuser=self.osuser).count() + CustomerUserDatabaseOption.objects.filter(hosting_package=self).count() + > UserDatabase.objects.filter(db_user__osuser=self.osuser).count() ) @transaction.atomic @@ -409,12 +389,16 @@ class CustomerHostingPackageDomain(TimeStampedModel): domain. """ + hosting_package = models.ForeignKey( - CustomerHostingPackage, verbose_name=_('hosting package'), - related_name='domains', on_delete=models.CASCADE) + CustomerHostingPackage, + verbose_name=_("hosting package"), + related_name="domains", + on_delete=models.CASCADE, + ) domain = models.OneToOneField( - HostingDomain, verbose_name=_('hosting domain'), - on_delete=models.CASCADE) + HostingDomain, verbose_name=_("hosting domain"), on_delete=models.CASCADE + ) def __str__(self): return self.domain.domain @@ -432,60 +416,62 @@ class CustomerHostingPackageOption(TimeStampedModel): This class defines options for customer hosting packages. """ + hosting_package = models.ForeignKey( - CustomerHostingPackage, verbose_name=_('hosting package'), - on_delete=models.CASCADE) + CustomerHostingPackage, + verbose_name=_("hosting package"), + on_delete=models.CASCADE, + ) class Meta: - verbose_name = _('customer hosting option') - verbose_name_plural = _('customer hosting options') + verbose_name = _("customer hosting option") + verbose_name_plural = _("customer hosting options") -class CustomerDiskSpaceOption(DiskSpaceOptionBase, - CustomerHostingPackageOption): +class CustomerDiskSpaceOption(DiskSpaceOptionBase, CustomerHostingPackageOption): """ This is a class for customer hosting package options adding additional disk space to existing customer hosting package. """ + template = models.ForeignKey( DiskSpaceOption, - verbose_name=_('disk space option template'), + verbose_name=_("disk space option template"), help_text=_( - 'The disk space option template that this disk space option is' - ' based on' + "The disk space option template that this disk space option is" " based on" ), - on_delete=models.CASCADE) + on_delete=models.CASCADE, + ) -class CustomerUserDatabaseOption(UserDatabaseOptionBase, - CustomerHostingPackageOption): +class CustomerUserDatabaseOption(UserDatabaseOptionBase, CustomerHostingPackageOption): """ This is a class for customer hosting package options adding user databases to existing customer hosting packages. """ + template = models.ForeignKey( UserDatabaseOption, - verbose_name=_('user database option template'), + verbose_name=_("user database option template"), help_text=_( - 'The user database option template that this database option is' - ' based on' + "The user database option template that this database option is" " based on" ), - on_delete=models.CASCADE) + on_delete=models.CASCADE, + ) -class CustomerMailboxOption(MailboxOptionBase, - CustomerHostingPackageOption): +class CustomerMailboxOption(MailboxOptionBase, CustomerHostingPackageOption): """ This is a class for customer hosting package options adding additional mailboxes to existing customer hosting packages. """ + template = models.ForeignKey( MailboxOption, - verbose_name=_('mailbox option template'), - help_text=_( - 'The mailbox option template that this mailbox option is based on' - ), - on_delete=models.CASCADE) + verbose_name=_("mailbox option template"), + help_text=_("The mailbox option template that this mailbox option is based on"), + on_delete=models.CASCADE, + ) diff --git a/gnuviechadmin/hostingpackages/tests/test_models.py b/gnuviechadmin/hostingpackages/tests/test_models.py index b762d05..66f579c 100644 --- a/gnuviechadmin/hostingpackages/tests/test_models.py +++ b/gnuviechadmin/hostingpackages/tests/test_models.py @@ -5,10 +5,7 @@ Test for models. from django.test import TestCase -from hostingpackages.models import ( - DISK_SPACE_UNITS, - CustomerHostingPackage, -) +from hostingpackages.models import DISK_SPACE_UNITS, CustomerHostingPackage class CustomerHostingPackageTest(TestCase): @@ -16,7 +13,7 @@ class CustomerHostingPackageTest(TestCase): package = CustomerHostingPackage( diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G ) - self.assertEqual(package.get_disk_space(), 10 * 1024 * 1024**2) + self.assertEqual(package.get_disk_space(), 10 * 1024 ** 3) def test_get_disk_space_mib(self): package = CustomerHostingPackage( diff --git a/gnuviechadmin/managemails/tests/test_admin.py b/gnuviechadmin/managemails/tests/test_admin.py index 67496d6..ddacf4b 100644 --- a/gnuviechadmin/managemails/tests/test_admin.py +++ b/gnuviechadmin/managemails/tests/test_admin.py @@ -1,14 +1,14 @@ from django import forms -from django.core.urlresolvers import reverse from django.test import TestCase from django.test.utils import override_settings +from django.urls import reverse from django.utils.html import format_html from django.utils.translation import ugettext as _ from django.contrib.admin import AdminSite from django.contrib.auth import get_user_model -from mock import Mock +from unittest.mock import Mock from osusers.models import User @@ -21,9 +21,7 @@ from managemails.admin import ( ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget, ) -from managemails.models import ( - Mailbox, -) +from managemails.models import Mailbox Customer = get_user_model() @@ -31,14 +29,14 @@ Customer = get_user_model() class ReadOnlyPasswordHashWidgetTest(TestCase): def test_render(self): widget = ReadOnlyPasswordHashWidget() - rendered = widget.render('password', 'secret', {'class': 'test'}) + rendered = widget.render("password", "secret", {"class": "test"}) self.assertEqual( rendered, format_html( '
    {0}
    ', - format_html('{0}: secret ', - _('Hash')) - )) + format_html("{0}: secret ", _("Hash")), + ), + ) class ReadOnlyPasswordHashFieldTest(TestCase): @@ -48,24 +46,24 @@ class ReadOnlyPasswordHashFieldTest(TestCase): def test_bound_data(self): field = ReadOnlyPasswordHashField() - self.assertEqual(field.bound_data('new', 'old'), 'old') + self.assertEqual(field.bound_data("new", "old"), "old") def test__has_changed(self): field = ReadOnlyPasswordHashField() - self.assertFalse(field.has_changed('new', 'old')) + self.assertFalse(field.has_changed("new", "old")) class CustomerTestCase(TestCase): def setUp(self): super(CustomerTestCase, self).setUp() - self.customer = Customer.objects.create(username='test') + self.customer = Customer.objects.create(username="test") class MailboxCreationFormTest(CustomerTestCase): def test_clean_password2_same(self): form = MailboxCreationForm() - form.cleaned_data = {'password1': 'secret', 'password2': 'secret'} - self.assertEqual(form.clean_password2(), 'secret') + form.cleaned_data = {"password1": "secret", "password2": "secret"} + self.assertEqual(form.clean_password2(), "secret") def test_clean_password2_empty(self): form = MailboxCreationForm() @@ -74,59 +72,47 @@ class MailboxCreationFormTest(CustomerTestCase): def test_clean_password2_mismatch(self): form = MailboxCreationForm() - form.cleaned_data = {'password1': 'secretx', 'password2': 'secrety'} + form.cleaned_data = {"password1": "secretx", "password2": "secrety"} with self.assertRaises(forms.ValidationError) as cm: form.clean_password2() self.assertEqual(cm.exception.message, PASSWORD_MISMATCH_ERROR) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_save_commit(self): user = User.objects.create_user(customer=self.customer) - form = MailboxCreationForm(data={ - 'osuser': user.uid, - 'password1': 'secret', - 'password2': 'secret', - }) + form = MailboxCreationForm( + data={"osuser": user.uid, "password1": "secret", "password2": "secret"} + ) mailbox = form.save() self.assertIsNotNone(mailbox) - self.assertEqual( - len(Mailbox.objects.filter(osuser=user)), 1) + self.assertEqual(len(Mailbox.objects.filter(osuser=user)), 1) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_save_no_commit(self): user = User.objects.create_user(customer=self.customer) - form = MailboxCreationForm(data={ - 'osuser': user.uid, - 'password1': 'secret', - 'password2': 'secret', - }) + form = MailboxCreationForm( + data={"osuser": user.uid, "password1": "secret", "password2": "secret"} + ) mailbox = form.save(commit=False) self.assertIsNotNone(mailbox) - self.assertEqual( - len(Mailbox.objects.filter(osuser=user)), 0) + self.assertEqual(len(Mailbox.objects.filter(osuser=user)), 0) class MailboxChangeFormTest(CustomerTestCase): @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_clean_password(self): mailbox = Mailbox( - username='test', - osuser=User.objects.create_user(customer=self.customer)) - mailbox.set_password('test') + username="test", osuser=User.objects.create_user(customer=self.customer) + ) + mailbox.set_password("test") mailbox.save() - form = MailboxChangeForm(instance=mailbox, data={'password': 'blub'}) + form = MailboxChangeForm(instance=mailbox, data={"password": "blub"}) self.assertEqual(form.clean_password(), mailbox.password) @@ -151,55 +137,43 @@ class MailBoxAdminTest(CustomerTestCase): self.mbadmin = MailboxAdmin(Mailbox, site) def test_get_fieldsets_without_object(self): - self.assertEqual( - self.mbadmin.get_fieldsets(Mock()), - self.mbadmin.add_fieldsets) + self.assertEqual(self.mbadmin.get_fieldsets(Mock()), self.mbadmin.add_fieldsets) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_get_fieldsets_with_object(self): mailbox = Mailbox( - username='test', - osuser=User.objects.create_user(customer=self.customer)) - mailbox.set_password('test') + username="test", osuser=User.objects.create_user(customer=self.customer) + ) + mailbox.set_password("test") mailbox.save() self.assertEqual( - self.mbadmin.get_fieldsets(Mock(), mailbox), - self.mbadmin.fieldsets) + self.mbadmin.get_fieldsets(Mock(), mailbox), self.mbadmin.fieldsets + ) def test_get_form_without_object(self): form = self.mbadmin.get_form(Mock) - self.assertEqual( - form.Meta.fields, - ['osuser', 'password1', 'password2'] - ) + self.assertEqual(form.Meta.fields, ["osuser", "password1", "password2"]) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_get_form_with_object(self): mailbox = Mailbox( - username='test', - osuser=User.objects.create_user(customer=self.customer)) - mailbox.set_password('test') + username="test", osuser=User.objects.create_user(customer=self.customer) + ) + mailbox.set_password("test") mailbox.save() form = self.mbadmin.get_form(Mock, mailbox) - self.assertEqual( - form.Meta.fields, - ['osuser', 'username', 'password', 'active'] - ) + self.assertEqual(form.Meta.fields, ["osuser", "username", "password", "active"]) def test_admin_for_mailbox(self): - admin_url = reverse('admin:managemails_mailaddress_changelist') + admin_url = reverse("admin:managemails_mailaddress_changelist") self.assertIsNotNone(admin_url) class MailAddressAdminTest(TestCase): def test_admin_for_mailaddress(self): - admin_url = reverse('admin:managemails_mailaddress_changelist') + admin_url = reverse("admin:managemails_mailaddress_changelist") self.assertIsNotNone(admin_url) diff --git a/gnuviechadmin/managemails/tests/test_forms.py b/gnuviechadmin/managemails/tests/test_forms.py index abcd144..be72a61 100644 --- a/gnuviechadmin/managemails/tests/test_forms.py +++ b/gnuviechadmin/managemails/tests/test_forms.py @@ -2,13 +2,11 @@ This module provides tests for :py:mod:`managemails.forms`. """ -from __future__ import absolute_import, unicode_literals +from unittest.mock import MagicMock, Mock, patch, ANY -from mock import MagicMock, Mock, patch, ANY - -from django.core.urlresolvers import reverse from django.forms import ValidationError from django.test import TestCase +from django.urls import reverse from managemails.forms import ( AddMailAddressForm, @@ -22,7 +20,6 @@ from managemails.forms import ( class CreateMailboxFormTest(TestCase): - def test_constructor_needs_hostingpackage(self): instance = MagicMock() with self.assertRaises(KeyError): @@ -32,33 +29,35 @@ class CreateMailboxFormTest(TestCase): hostingpackage = Mock(id=42) instance = MagicMock() form = CreateMailboxForm(instance, hostingpackage=hostingpackage) - self.assertTrue(hasattr(form, 'hosting_package')) + self.assertTrue(hasattr(form, "hosting_package")) self.assertEqual(form.hosting_package, hostingpackage) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'create_mailbox', kwargs={'package': 42})) - self.assertIn('password1', form.fields) - self.assertIn('password2', form.fields) - self.assertEqual(form.helper.inputs[0].name, 'submit') + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, reverse("create_mailbox", kwargs={"package": 42}) + ) + self.assertIn("password1", form.fields) + self.assertIn("password2", form.fields) + self.assertEqual(form.helper.inputs[0].name, "submit") - @patch('managemails.forms.Mailbox.objects') + @patch("managemails.forms.Mailbox.objects") def test_save(self, mailbox_objects): osuser = MagicMock() hostingpackage = Mock(id=42, osuser=osuser) instance = MagicMock() form = CreateMailboxForm( - instance=instance, hostingpackage=hostingpackage, - data={'password1': 'secret', 'password2': 'secret'}) - mailbox_objects.get_next_mailbox_name.return_value = 'mailbox23' + instance=instance, + hostingpackage=hostingpackage, + data={"password1": "secret", "password2": "secret"}, + ) + mailbox_objects.get_next_mailbox_name.return_value = "mailbox23" self.assertTrue(form.is_valid()) form.save(commit=False) self.assertEqual(osuser, form.instance.osuser) - self.assertEqual('mailbox23', form.instance.username) - instance.set_password.assert_called_with('secret') + self.assertEqual("mailbox23", form.instance.username) + instance.set_password.assert_called_with("secret") class ChangeMailboxPasswordFormTest(TestCase): - def test_constructor_needs_hostingpackage(self): instance = MagicMock() with self.assertRaises(KeyError): @@ -66,72 +65,75 @@ class ChangeMailboxPasswordFormTest(TestCase): def test_constructor(self): hostingpackage = Mock(id=42) - instance = MagicMock(username='testuser') + instance = MagicMock(username="testuser") form = ChangeMailboxPasswordForm( - instance=instance, hostingpackage=hostingpackage) - self.assertTrue(hasattr(form, 'hosting_package')) + instance=instance, hostingpackage=hostingpackage + ) + self.assertTrue(hasattr(form, "hosting_package")) self.assertEqual(form.hosting_package, hostingpackage) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'change_mailbox_password', kwargs={ - 'package': 42, 'slug': 'testuser'})) - self.assertIn('password1', form.fields) - self.assertIn('password2', form.fields) - self.assertEqual(form.helper.inputs[0].name, 'submit') + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse( + "change_mailbox_password", kwargs={"package": 42, "slug": "testuser"} + ), + ) + self.assertIn("password1", form.fields) + self.assertIn("password2", form.fields) + self.assertEqual(form.helper.inputs[0].name, "submit") def test_save(self): hostingpackage = Mock(id=42) - instance = MagicMock(username='testuser') + instance = MagicMock(username="testuser") form = ChangeMailboxPasswordForm( - instance=instance, hostingpackage=hostingpackage, - data={'password1': 'newsecret', 'password2': 'newsecret'}) + instance=instance, + hostingpackage=hostingpackage, + data={"password1": "newsecret", "password2": "newsecret"}, + ) self.assertTrue(form.is_valid()) form.save(commit=False) - instance.set_password.assert_called_with('newsecret') + instance.set_password.assert_called_with("newsecret") class MultipleEmailValidatorTest(TestCase): - def test_valid_single_address(self): self.assertEqual( - 'test@example.org', - multiple_email_validator('test@example.org')) + "test@example.org", multiple_email_validator("test@example.org") + ) def test_valid_multiple_addresses(self): self.assertEqual( - 'test1@example.org,test2@example.org', - multiple_email_validator('test1@example.org,test2@example.org')) + "test1@example.org,test2@example.org", + multiple_email_validator("test1@example.org,test2@example.org"), + ) def test_empty(self): - self.assertEqual( - '', multiple_email_validator('')) + self.assertEqual("", multiple_email_validator("")) def test_none(self): self.assertIsNone(multiple_email_validator(None)) def test_invalid_single_address(self): with self.assertRaises(ValidationError): - multiple_email_validator('no@ddress') + multiple_email_validator("no@ddress") def test_invalid_multiple_addresses(self): with self.assertRaises(ValidationError): - multiple_email_validator('test1@example.org,no@ddress') + multiple_email_validator("test1@example.org,no@ddress") class MailAddressFieldMixinTest(TestCase): - def test_fields_defined(self): form = MailAddressFieldMixin() - self.assertIn('mailbox_or_forwards', form.fields) - self.assertIn('mailbox', form.fields) - self.assertIn('forwards', form.fields) + self.assertIn("mailbox_or_forwards", form.fields) + self.assertIn("mailbox", form.fields) + self.assertIn("forwards", form.fields) class AddMailAddressFormTest(TestCase): - def setUp(self): - self.patcher1 = patch('managemails.forms.Mailbox.objects') - self.patcher2 = patch('managemails.forms.MailAddress.objects') + self.patcher1 = patch("managemails.forms.Mailbox.objects") + self.patcher2 = patch("managemails.forms.MailAddress.objects") self.mailbox_objects = self.patcher1.start() self.mailaddress_objects = self.patcher2.start() @@ -151,175 +153,192 @@ class AddMailAddressFormTest(TestCase): def test_constructor(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, - maildomain=maildomain) + instance=instance, hostingpackage=hostingpackage, maildomain=maildomain + ) self.mailbox_objects.unused.assert_called_with(osuser=osuser) - self.assertIn('mailbox_or_forwards', form.fields) - self.assertIn('mailbox', form.fields) - self.assertIn('forwards', form.fields) - self.assertTrue(hasattr(form, 'hosting_package')) + self.assertIn("mailbox_or_forwards", form.fields) + self.assertIn("mailbox", form.fields) + self.assertIn("forwards", form.fields) + self.assertTrue(hasattr(form, "hosting_package")) self.assertEqual(form.hosting_package, hostingpackage) - self.assertTrue(hasattr(form, 'maildomain')) + self.assertTrue(hasattr(form, "maildomain")) self.assertEqual(form.maildomain, maildomain) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'add_mailaddress', kwargs={ - 'package': 42, 'domain': 'example.org'})) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("add_mailaddress", kwargs={"package": 42, "domain": "example.org"}), + ) self.assertEqual(len(form.helper.layout), 2) - self.assertEqual(form.helper.layout[1].name, 'submit') + self.assertEqual(form.helper.layout[1].name, "submit") def test_clean_localpart_valid(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'forwards': 'test2@example.org' - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "forwards": "test2@example.org", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertTrue(form.is_valid()) - self.assertEqual('test', form.clean_localpart()) + self.assertEqual("test", form.clean_localpart()) def test_clean_localpart_duplicate(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'forwards': 'test2@example.org' - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "forwards": "test2@example.org", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = True self.assertFalse(form.is_valid()) - self.assertIn('localpart', form.errors) + self.assertIn("localpart", form.errors) def test_clean_no_mailbox_choice(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertFalse(form.is_valid()) - self.assertIn('mailbox', form.errors) + self.assertIn("mailbox", form.errors) def test_clean_no_forward_address_choice(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertFalse(form.is_valid()) - self.assertIn('forwards', form.errors) + self.assertIn("forwards", form.errors) def test_save_with_forwards_no_commit(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'forwards': 'test2@example.org,test3@example.org' - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "forwards": "test2@example.org,test3@example.org", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertTrue(form.is_valid()) - address1 = MagicMock(mailaddress='test2@example.org') - address2 = MagicMock(mailaddress='test3@example.org') + address1 = MagicMock(mailaddress="test2@example.org") + address2 = MagicMock(mailaddress="test3@example.org") instance.set_forward_addresses.return_value = [address1, address2] form.save(commit=False) self.assertEqual(maildomain, instance.domain) - instance.set_forward_addresses.assert_called_with([ - 'test2@example.org', 'test3@example.org'], commit=False) + instance.set_forward_addresses.assert_called_with( + ["test2@example.org", "test3@example.org"], commit=False + ) address1.save.assert_not_called() address2.save.assert_not_called() instance.save.assert_not_called() def test_save_with_forwards_commit(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'forwards': 'test2@example.org,test3@example.org' - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "forwards": "test2@example.org,test3@example.org", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertTrue(form.is_valid()) - address1 = MagicMock(mailaddress='test2@example.org') - address2 = MagicMock(mailaddress='test3@example.org') + address1 = MagicMock(mailaddress="test2@example.org") + address2 = MagicMock(mailaddress="test3@example.org") instance.set_forward_addresses.return_value = [address1, address2] form.save(commit=True) self.assertEqual(maildomain, instance.domain) - instance.set_forward_addresses.assert_called_with([ - 'test2@example.org', 'test3@example.org'], commit=False) + instance.set_forward_addresses.assert_called_with( + ["test2@example.org", "test3@example.org"], commit=False + ) address1.save.assert_called_with() address2.save.assert_called_with() instance.save.assert_called_with() def test_save_with_mailbox_no_commit(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - 'mailbox': 'mailbox23', - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + "mailbox": "mailbox23", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertTrue(form.is_valid()) - mailbox = MagicMock(osuser=osuser, username='testuserp01') + mailbox = MagicMock(osuser=osuser, username="testuserp01") instance.set_mailbox.return_value = mailbox form.save(commit=False) self.assertEqual(maildomain, instance.domain) @@ -329,22 +348,24 @@ class AddMailAddressFormTest(TestCase): def test_save_with_mailbox_commit(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - 'mailbox': 'mailbox23', - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + "mailbox": "mailbox23", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertTrue(form.is_valid()) - mailbox = MagicMock(osuser=osuser, username='testuserp01') + mailbox = MagicMock(osuser=osuser, username="testuserp01") instance.set_mailbox.return_value = mailbox form.save(commit=True) self.assertEqual(maildomain, instance.domain) @@ -355,26 +376,28 @@ class AddMailAddressFormTest(TestCase): def test_save_with_other_choice(self): instance = MagicMock() - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = AddMailAddressForm( - instance=instance, hostingpackage=hostingpackage, + instance=instance, + hostingpackage=hostingpackage, maildomain=maildomain, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - 'mailbox': 'mailbox23', - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + "mailbox": "mailbox23", + }, + ) self.mailaddress_objects.filter( - domain=maildomain, localpart='test' + domain=maildomain, localpart="test" ).exists.return_value = False self.assertTrue(form.is_valid()) - form.cleaned_data['mailbox_or_forwards'] = -1 - address1 = MagicMock(mailaddress='test2@example.org') - address2 = MagicMock(mailaddress='test3@example.org') + form.cleaned_data["mailbox_or_forwards"] = -1 + address1 = MagicMock(mailaddress="test2@example.org") + address2 = MagicMock(mailaddress="test3@example.org") instance.set_forward_addresses.return_value = [address1, address2] - mailbox = MagicMock(osuser=osuser, username='testuserp01') + mailbox = MagicMock(osuser=osuser, username="testuserp01") instance.set_mailbox.return_value = mailbox form.save(commit=True) instance.set_mailbox.assert_not_called() @@ -386,10 +409,9 @@ class AddMailAddressFormTest(TestCase): class EditMailAddressFormTest(TestCase): - def setUp(self): - self.patcher1 = patch('managemails.forms.Mailbox.objects') - self.patcher2 = patch('managemails.forms.MailAddress.objects') + self.patcher1 = patch("managemails.forms.Mailbox.objects") + self.patcher2 = patch("managemails.forms.MailAddress.objects") self.mailbox_objects = self.patcher1.start() self.mailaddress_objects = self.patcher2.start() @@ -409,115 +431,125 @@ class EditMailAddressFormTest(TestCase): def test_constructor(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, - hostingpackage=hostingpackage) + instance=instance, maildomain=maildomain, hostingpackage=hostingpackage + ) self.mailbox_objects.unused_or_own.assert_called_with(instance, osuser) - self.assertIn('mailbox_or_forwards', form.fields) - self.assertIn('mailbox', form.fields) - self.assertIn('forwards', form.fields) - self.assertTrue(hasattr(form, 'hosting_package')) + self.assertIn("mailbox_or_forwards", form.fields) + self.assertIn("mailbox", form.fields) + self.assertIn("forwards", form.fields) + self.assertTrue(hasattr(form, "hosting_package")) self.assertEqual(form.hosting_package, hostingpackage) - self.assertTrue(hasattr(form, 'maildomain')) + self.assertTrue(hasattr(form, "maildomain")) self.assertEqual(form.maildomain, maildomain) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'edit_mailaddress', kwargs={ - 'package': 42, - 'domain': 'example.org', - 'pk': 23})) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse( + "edit_mailaddress", + kwargs={"package": 42, "domain": "example.org", "pk": 23}, + ), + ) self.assertEqual(len(form.helper.layout), 2) - self.assertEqual(form.helper.layout[1].name, 'submit') + self.assertEqual(form.helper.layout[1].name, "submit") def test_clean_no_mailbox_choice(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, - data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - }) + data={"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox}, + ) self.assertFalse(form.is_valid()) - self.assertIn('mailbox', form.errors) + self.assertIn("mailbox", form.errors) def test_clean_no_forward_address_choice(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, - data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - }) + data={"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards}, + ) self.assertFalse(form.is_valid()) - self.assertIn('forwards', form.errors) + self.assertIn("forwards", form.errors) def test_save_with_forwards_no_commit(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'forwards': 'test2@example.org,test3@example.org' - }) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "forwards": "test2@example.org,test3@example.org", + }, + ) self.assertTrue(form.is_valid()) - address1 = MagicMock(mailaddress='test2@example.org') - address2 = MagicMock(mailaddress='test3@example.org') + address1 = MagicMock(mailaddress="test2@example.org") + address2 = MagicMock(mailaddress="test3@example.org") instance.set_forward_addresses.return_value = [address1, address2] form.save(commit=False) instance.set_forward_addresses.assert_called_with( - ['test2@example.org', 'test3@example.org'], False) + ["test2@example.org", "test3@example.org"], False + ) address1.save.assert_not_called() address2.save.assert_not_called() instance.save.assert_not_called() def test_save_with_forwards_commit(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'forwards': 'test2@example.org,test3@example.org' - }) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "forwards": "test2@example.org,test3@example.org", + }, + ) self.assertTrue(form.is_valid()) - address1 = MagicMock(mailaddress='test2@example.org') - address2 = MagicMock(mailaddress='test3@example.org') + address1 = MagicMock(mailaddress="test2@example.org") + address2 = MagicMock(mailaddress="test3@example.org") instance.set_forward_addresses.return_value = [address1, address2] form.save(commit=True) instance.set_forward_addresses.assert_called_with( - ['test2@example.org', 'test3@example.org'], True) + ["test2@example.org", "test3@example.org"], True + ) instance.save.assert_called_with() def test_save_with_mailbox_no_commit(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - 'mailbox': 'mailbox23', - }) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + "mailbox": "mailbox23", + }, + ) self.assertTrue(form.is_valid()) - mailbox = MagicMock(osuser=osuser, username='testuserp01') + mailbox = MagicMock(osuser=osuser, username="testuserp01") instance.set_mailbox.return_value = mailbox form.save(commit=False) instance.set_mailbox.assert_called_with(ANY, False) @@ -526,18 +558,20 @@ class EditMailAddressFormTest(TestCase): def test_save_with_mailbox_commit(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - 'mailbox': 'mailbox23', - }) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + "mailbox": "mailbox23", + }, + ) self.assertTrue(form.is_valid()) - mailbox = MagicMock(osuser=osuser, username='testuserp01') + mailbox = MagicMock(osuser=osuser, username="testuserp01") instance.set_mailbox.return_value = mailbox self.mailbox_objects.unused_or_own.get.return_value = mailbox form.save(commit=True) @@ -546,18 +580,20 @@ class EditMailAddressFormTest(TestCase): def test_save_with_other_choice(self): instance = MagicMock(id=23) - osuser = Mock(username='testuser') + osuser = Mock(username="testuser") hostingpackage = MagicMock(id=42, osuser=osuser) - maildomain = MagicMock(domain='example.org') + maildomain = MagicMock(domain="example.org") form = EditMailAddressForm( - instance=instance, maildomain=maildomain, + instance=instance, + maildomain=maildomain, hostingpackage=hostingpackage, data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.mailbox, - 'mailbox': 'mailbox23', - }) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox, + "mailbox": "mailbox23", + }, + ) self.assertTrue(form.is_valid()) - form.cleaned_data['mailbox_or_forwards'] = -1 + form.cleaned_data["mailbox_or_forwards"] = -1 form.save(commit=True) instance.set_mailbox.assert_not_called() instance.save.assert_called_with() diff --git a/gnuviechadmin/managemails/tests/test_models.py b/gnuviechadmin/managemails/tests/test_models.py index 16ac111..895a078 100644 --- a/gnuviechadmin/managemails/tests/test_models.py +++ b/gnuviechadmin/managemails/tests/test_models.py @@ -1,9 +1,7 @@ """ This module contains tests for :py:mod:`managemails.models` """ -from __future__ import unicode_literals - -from mock import patch +from unittest.mock import patch from django.test import TestCase, TransactionTestCase from django.test.utils import override_settings @@ -14,59 +12,51 @@ from passlib.hash import sha512_crypt from domains.models import MailDomain from osusers.models import User -from managemails.models import ( - MailAddress, - Mailbox, -) +from managemails.models import MailAddress, Mailbox Customer = get_user_model() @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class MailboxTest(TestCase): - def setUp(self): super(MailboxTest, self).setUp() - self.customer = Customer.objects.create_user('test') + self.customer = Customer.objects.create_user("test") def test_set_password(self): user = User.objects.create_user(self.customer) - mb = Mailbox.objects.create(username='test', osuser=user) - mb.set_password('test') - self.assertTrue(sha512_crypt.verify('test', mb.password)) + mb = Mailbox.objects.create(username="test", osuser=user) + mb.set_password("test") + self.assertTrue(sha512_crypt.verify("test", mb.password)) def test___str__(self): user = User.objects.create_user(self.customer) - mb = Mailbox.objects.create(username='test', osuser=user) - mb.set_password('test') - self.assertEqual(str(mb), 'test') + mb = Mailbox.objects.create(username="test", osuser=user) + mb.set_password("test") + self.assertEqual(str(mb), "test") - @patch('managemails.models.create_file_mailbox') + @patch("managemails.models.create_file_mailbox") def test_save(self, create_file_mailbox_task): user = User.objects.create_user(self.customer) mb = Mailbox.objects.create_mailbox(user) self.assertIsNotNone(mb.pk) - create_file_mailbox_task.delay.assert_called_with( - user.username, mb.username) + create_file_mailbox_task.delay.assert_called_with(user.username, mb.username) - @patch('managemails.models.delete_file_mailbox') + @patch("managemails.models.delete_file_mailbox") def test_delete(self, delete_file_mailbox_task): user = User.objects.create_user(self.customer) mb = Mailbox.objects.create_mailbox(user) mb.delete() self.assertIsNone(mb.pk) - delete_file_mailbox_task.delay.assert_called_with( - user.username, mb.username) + delete_file_mailbox_task.delay.assert_called_with(user.username, mb.username) def test_get_mailaddresses(self): user = User.objects.create_user(self.customer) mb = Mailbox.objects.create_mailbox(user) - md = MailDomain.objects.create(domain='example.org') - address = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + address = MailAddress.objects.create(localpart="test", domain=md) address.set_mailbox(mb) mailaddresses = mb.get_mailaddresses() self.assertEqual(len(mailaddresses), 1) @@ -74,31 +64,28 @@ class MailboxTest(TestCase): @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class MailAddressTest(TransactionTestCase): - def test__str__(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) - self.assertEqual(str(ma), 'test@example.org') + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) + self.assertEqual(str(ma), "test@example.org") def test_set_mailbox_fresh(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb) self.assertIn(ma, mb.get_mailaddresses()) def test_set_mailbox_reassing(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb) mb2 = Mailbox.objects.create_mailbox(user) @@ -107,168 +94,178 @@ class MailAddressTest(TransactionTestCase): self.assertNotIn(ma, mb.get_mailaddresses()) def test_set_mailbox_with_forwards(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) - ma.set_forward_addresses(['test2@example.org']) + ma.set_forward_addresses(["test2@example.org"]) ma.set_mailbox(mb) self.assertEqual(ma.mailaddressforward_set.count(), 0) self.assertIn(ma, mb.get_mailaddresses()) def test_set_mailbox_with_unsaved_address(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb) self.assertIn(ma, mb.get_mailaddresses()) def test_set_mailbox_fresh_no_commit(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb, commit=False) self.assertNotIn(ma, mb.get_mailaddresses()) def test_set_mailbox_with_unsaved_address_no_commit(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb, commit=False) self.assertNotIn(ma, mb.get_mailaddresses()) def test_set_forward_addresses_fresh(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) - ma.set_forward_addresses(['test2@example.org']) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) + ma.set_forward_addresses(["test2@example.org"]) + + def get_target(maf): + return maf.target + self.assertQuerysetEqual( - ma.mailaddressforward_set.all(), ['test2@example.org'], - lambda(maf): maf.target) + ma.mailaddressforward_set.all(), ["test2@example.org"], get_target + ) def test_set_forward_addresses_unsaved(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress(localpart='test', domain=md) - ma.set_forward_addresses(['test2@example.org']) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress(localpart="test", domain=md) + ma.set_forward_addresses(["test2@example.org"]) + + def get_target(maf): + return maf.target + self.assertQuerysetEqual( - ma.mailaddressforward_set.all(), ['test2@example.org'], - lambda(maf): maf.target) + ma.mailaddressforward_set.all(), ["test2@example.org"], get_target + ) def test_set_forward_addresses_replace_forwards(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) - ma.set_forward_addresses(['test2@example.org']) - ma.set_forward_addresses(['test3@example.org']) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) + ma.set_forward_addresses(["test2@example.org"]) + ma.set_forward_addresses(["test3@example.org"]) + + def get_target(maf): + return maf.target + self.assertQuerysetEqual( - ma.mailaddressforward_set.all(), ['test3@example.org'], - lambda(maf): maf.target) + ma.mailaddressforward_set.all(), ["test3@example.org"], get_target + ) def test_set_forward_addresses_add_forwards(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) - ma.set_forward_addresses(['test2@example.org']) - ma.set_forward_addresses(['test2@example.org', 'test3@example.org']) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) + ma.set_forward_addresses(["test2@example.org"]) + ma.set_forward_addresses(["test2@example.org", "test3@example.org"]) + + def get_target(maf): + return maf.target + self.assertQuerysetEqual( ma.mailaddressforward_set.all(), - ['test2@example.org', 'test3@example.org'], - lambda(maf): maf.target, - ordered=False) + ["test2@example.org", "test3@example.org"], + get_target, + ordered=False, + ) def test_set_forward_addresses_replace_mailbox(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb) - ma.set_forward_addresses(['test2@example.org']) + ma.set_forward_addresses(["test2@example.org"]) self.assertNotIn(ma, mb.get_mailaddresses()) + + def get_target(maf): + return maf.target + self.assertQuerysetEqual( - ma.mailaddressforward_set.all(), ['test2@example.org'], - lambda(maf): maf.target) + ma.mailaddressforward_set.all(), ["test2@example.org"], get_target + ) def test_set_forward_addresses_fresh_no_commit(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress.objects.create(localpart='test', domain=md) - mafwds = ma.set_forward_addresses(['test2@example.org'], commit=False) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress.objects.create(localpart="test", domain=md) + mafwds = ma.set_forward_addresses(["test2@example.org"], commit=False) self.assertEqual(ma.mailaddressforward_set.count(), 0) - self.assertEqual(mafwds[0].target, 'test2@example.org') + self.assertEqual(mafwds[0].target, "test2@example.org") def test_set_forward_address_unsaved_no_commit(self): - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress(localpart='test', domain=md) - mafwds = ma.set_forward_addresses(['test2@example.org'], commit=False) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress(localpart="test", domain=md) + mafwds = ma.set_forward_addresses(["test2@example.org"], commit=False) self.assertEqual(ma.mailaddressforward_set.count(), 0) - self.assertEqual(mafwds[0].target, 'test2@example.org') + self.assertEqual(mafwds[0].target, "test2@example.org") @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class MailboxManagerTest(TransactionTestCase): - def setUp(self): super(MailboxManagerTest, self).setUp() - self.customer = Customer.objects.create_user('test') + self.customer = Customer.objects.create_user("test") self.user = User.objects.create_user(self.customer) def test_get_next_mailbox_name_fresh(self): mailboxname = Mailbox.objects.get_next_mailbox_name(self.user) - self.assertEqual(mailboxname, '{}p01'.format(self.user.username)) + self.assertEqual(mailboxname, "{}p01".format(self.user.username)) def test_get_next_mailbox_name_second(self): Mailbox.objects.create_mailbox(self.user) mailboxname = Mailbox.objects.get_next_mailbox_name(self.user) - self.assertEqual(mailboxname, '{}p02'.format(self.user.username)) + self.assertEqual(mailboxname, "{}p02".format(self.user.username)) def test_get_next_mailbox_name_gap_detection(self): - mailboxes = [ - Mailbox.objects.create_mailbox(self.user) for i in range(3) - ] + mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(3)] mailboxes[1].delete() mailboxname = Mailbox.objects.get_next_mailbox_name(self.user) - self.assertEqual(mailboxname, '{}p02'.format(self.user.username)) + self.assertEqual(mailboxname, "{}p02".format(self.user.username)) def test_unused_or_own_fresh(self): - md = MailDomain.objects.create(domain='example.org') - address = MailAddress.objects.create(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + address = MailAddress.objects.create(localpart="test", domain=md) mailboxes = Mailbox.objects.unused_or_own(address, self.user) self.assertQuerysetEqual(mailboxes, []) def test_unused_or_own_unassigned(self): - md = MailDomain.objects.create(domain='example.org') - address = MailAddress.objects.create(localpart='test', domain=md) - mailboxes = [ - Mailbox.objects.create_mailbox(self.user) for i in range(2) - ] + md = MailDomain.objects.create(domain="example.org") + address = MailAddress.objects.create(localpart="test", domain=md) + mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)] assignable = Mailbox.objects.unused_or_own(address, self.user) self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes]) def test_unused_or_own_assigned(self): - md = MailDomain.objects.create(domain='example.org') - address = MailAddress.objects.create(localpart='test', domain=md) - mailboxes = [ - Mailbox.objects.create_mailbox(self.user) for i in range(2) - ] + md = MailDomain.objects.create(domain="example.org") + address = MailAddress.objects.create(localpart="test", domain=md) + mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)] address.set_mailbox(mailboxes[0]) assignable = Mailbox.objects.unused_or_own(address, self.user) self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes]) def test_unused_or_own_assigned_other(self): - md = MailDomain.objects.create(domain='example.org') - address = MailAddress.objects.create(localpart='test', domain=md) - address2 = MailAddress.objects.create(localpart='test2', domain=md) - mailboxes = [ - Mailbox.objects.create_mailbox(self.user) for i in range(2) - ] + md = MailDomain.objects.create(domain="example.org") + address = MailAddress.objects.create(localpart="test", domain=md) + address2 = MailAddress.objects.create(localpart="test2", domain=md) + mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)] address2.set_mailbox(mailboxes[0]) assignable = Mailbox.objects.unused_or_own(address, self.user) self.assertQuerysetEqual(assignable, [repr(mailboxes[1])]) @@ -283,11 +280,9 @@ class MailboxManagerTest(TransactionTestCase): self.assertQuerysetEqual(mailboxes, [repr(mailbox)]) def test_unused_assigned(self): - md = MailDomain.objects.create(domain='example.org') - address = MailAddress.objects.create(localpart='test', domain=md) - mailboxes = [ - Mailbox.objects.create_mailbox(self.user) for i in range(2) - ] + md = MailDomain.objects.create(domain="example.org") + address = MailAddress.objects.create(localpart="test", domain=md) + mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)] address.set_mailbox(mailboxes[0]) assignable = Mailbox.objects.unused(self.user) self.assertQuerysetEqual(assignable, [repr(mailboxes[1])]) @@ -295,31 +290,28 @@ class MailboxManagerTest(TransactionTestCase): def test_create_mailbox_no_password(self): mailbox = Mailbox.objects.create_mailbox(self.user) self.assertEqual(mailbox.osuser, self.user) - self.assertEqual(mailbox.username, '{}p01'.format(self.user.username)) - self.assertEqual(mailbox.password, '') + self.assertEqual(mailbox.username, "{}p01".format(self.user.username)) + self.assertEqual(mailbox.password, "") def test_create_mailbox_with_password(self): - mailbox = Mailbox.objects.create_mailbox(self.user, 'test') + mailbox = Mailbox.objects.create_mailbox(self.user, "test") self.assertEqual(mailbox.osuser, self.user) - self.assertEqual(mailbox.username, '{}p01'.format(self.user.username)) - self.assertTrue(sha512_crypt.verify('test', mailbox.password)) + self.assertEqual(mailbox.username, "{}p01".format(self.user.username)) + self.assertTrue(sha512_crypt.verify("test", mailbox.password)) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class MailAddressMailboxTest(TestCase): - def setUp(self): super(MailAddressMailboxTest, self).setUp() - self.customer = Customer.objects.create_user('test') + self.customer = Customer.objects.create_user("test") def test___str__(self): user = User.objects.create_user(self.customer) - md = MailDomain.objects.create(domain='example.org') - ma = MailAddress(localpart='test', domain=md) + md = MailDomain.objects.create(domain="example.org") + ma = MailAddress(localpart="test", domain=md) mb = Mailbox.objects.create_mailbox(user) ma.set_mailbox(mb) self.assertEqual(str(ma.mailaddressmailbox), mb.username) diff --git a/gnuviechadmin/managemails/tests/test_views.py b/gnuviechadmin/managemails/tests/test_views.py index dc0f6dd..ca8608f 100644 --- a/gnuviechadmin/managemails/tests/test_views.py +++ b/gnuviechadmin/managemails/tests/test_views.py @@ -2,23 +2,15 @@ Tests for :py:mod:`managemails.views`. """ -from __future__ import absolute_import, unicode_literals +from unittest.mock import patch, MagicMock -try: - from unittest.mock import patch, MagicMock -except ImportError: - from mock import patch, MagicMock - -from django.core.urlresolvers import reverse from django.test import TestCase from django.test.utils import override_settings from django.contrib.auth import get_user_model +from django.urls import reverse from domains.models import MailDomain -from hostingpackages.models import ( - CustomerHostingPackage, - HostingPackageTemplate, -) +from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate from managemails.forms import MAILBOX_OR_FORWARDS from managemails.models import MailAddress, Mailbox @@ -32,630 +24,764 @@ from managemails.views import ( User = get_user_model() -TEST_USER = 'test' -TEST_PASSWORD = 'secret' -TEST_EMAIL = 'test@example.org' -TEST_NAME = 'Example Tester'.split() +TEST_USER = "test" +TEST_PASSWORD = "secret" +TEST_EMAIL = "test@example.org" +TEST_NAME = "Example Tester".split() class HostingPackageAwareTestMixin(object): + # noinspection PyMethodMayBeStatic def _setup_hosting_package(self, customer, mailboxcount): template = HostingPackageTemplate.objects.create( - name='testpackagetemplate', mailboxcount=mailboxcount, - diskspace=1, diskspace_unit=0) + name="testpackagetemplate", + mailboxcount=mailboxcount, + diskspace=1, + diskspace_unit=0, + ) package = CustomerHostingPackage.objects.create_from_template( - customer, template, 'testpackage') - with patch('hostingpackages.models.settings') as hmsettings: + customer, template, "testpackage" + ) + with patch("hostingpackages.models.settings") as hmsettings: hmsettings.OSUSER_DEFAULT_GROUPS = [] package.save() return package @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class CreateMailboxTest(HostingPackageAwareTestMixin, TestCase): - def test_get_anonymous(self): - response = self.client.get( - reverse('create_mailbox', kwargs={'package': 1})) + response = self.client.get(reverse("create_mailbox", kwargs={"package": 1})) self.assertEqual(response.status_code, 404) def test_get_regular_user(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_mailbox', kwargs={'package': package.pk})) + reverse("create_mailbox", kwargs={"package": package.pk}) + ) self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) User.objects.create_user( - 'test2', email='test2@example.org', password=TEST_PASSWORD) + "test2", email="test2@example.org", password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) - self.client.login(username='test2', password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get( - reverse('create_mailbox', kwargs={'package': package.pk})) + reverse("create_mailbox", kwargs={"package": package.pk}) + ) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - customer = User.objects.create_user('customer') + customer = User.objects.create_user("customer") package = self._setup_hosting_package(customer, 1) User.objects.create_superuser( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_mailbox', kwargs={'package': package.pk})) + reverse("create_mailbox", kwargs={"package": package.pk}) + ) self.assertEqual(response.status_code, 200) def test_get_template(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_mailbox', kwargs={'package': package.pk})) - self.assertTemplateUsed(response, 'managemails/mailbox_create.html') + reverse("create_mailbox", kwargs={"package": package.pk}) + ) + self.assertTemplateUsed(response, "managemails/mailbox_create.html") def test_get_no_more_mailboxes(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 0) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_mailbox', kwargs={'package': package.pk})) + reverse("create_mailbox", kwargs={"package": package.pk}) + ) self.assertEqual(response.status_code, 403) self.assertEqual( response.content, - 'You are not allowed to add more mailboxes to this hosting' - ' package') + b"You are not allowed to add more mailboxes to this hosting" b" package", + ) def test_get_context_data(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('create_mailbox', kwargs={'package': package.pk})) - self.assertIn('hostingpackage', response.context) - self.assertEqual(response.context['hostingpackage'], package) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], customer) + reverse("create_mailbox", kwargs={"package": package.pk}) + ) + self.assertIn("hostingpackage", response.context) + self.assertEqual(response.context["hostingpackage"], package) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], customer) def test_get_form_kwargs(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) self.client.login(username=TEST_USER, password=TEST_PASSWORD) - view = CreateMailbox( - request=MagicMock(), kwargs={'package': str(package.pk)}) + view = CreateMailbox(request=MagicMock(), kwargs={"package": str(package.pk)}) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], package) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], package) def test_form_valid_redirect(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('create_mailbox', kwargs={'package': package.pk}), - data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + reverse("create_mailbox", kwargs={"package": package.pk}), + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) self.assertRedirects(response, package.get_absolute_url()) def test_form_valid_message(self): customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) package = self._setup_hosting_package(customer, 1) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('create_mailbox', kwargs={'package': package.pk}), + reverse("create_mailbox", kwargs={"package": package.pk}), follow=True, - data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) - messages = list(response.context['messages']) + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - 'Mailbox usr01p01 created successfully.', str(messages[0])) + self.assertEqual("Mailbox usr01p01 created successfully.", str(messages[0])) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class ChangeMailboxPasswordTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer, 1) self.mailbox = Mailbox.objects.create_mailbox( - self.package.osuser, TEST_PASSWORD) + self.package.osuser, TEST_PASSWORD + ) def test_get_anonymous(self): response = self.client.get( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username})) + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ) + ) self.assertEqual(response.status_code, 403) def test_get_regular_user(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username})) + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ) + ) self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): User.objects.create_user( - 'test2', email='test2@example.org', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + "test2", email="test2@example.org", password=TEST_PASSWORD + ) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username})) + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ) + ) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): User.objects.create_superuser( - 'admin', email='admin@example.org', password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + "admin", email="admin@example.org", password=TEST_PASSWORD + ) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username})) + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ) + ) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username})) - self.assertTemplateUsed( - response, 'managemails/mailbox_setpassword.html') + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ) + ) + self.assertTemplateUsed(response, "managemails/mailbox_setpassword.html") def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username})) - self.assertIn('hostingpackage', response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ) + ) + self.assertIn("hostingpackage", response.context) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) def test_get_form_kwargs(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) - view = ChangeMailboxPassword(request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'slug': self.mailbox.username}) + view = ChangeMailboxPassword( + request=MagicMock(), + kwargs={"package": str(self.package.pk), "slug": self.mailbox.username}, + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username}), - data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ), + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('change_mailbox_password', kwargs={ - 'package': self.package.pk, - 'slug': self.mailbox.username}), + reverse( + "change_mailbox_password", + kwargs={"package": self.package.pk, "slug": self.mailbox.username}, + ), follow=True, - data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) - messages = list(response.context['messages']) + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( - str(messages[0]), - 'Successfully set new password for mailbox usr01p01.') + str(messages[0]), "Successfully set new password for mailbox usr01p01." + ) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class AddMailAddressTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer, 1) self.maildomain = MailDomain.objects.create( - domain='example.org', customer=self.customer) + domain="example.org", customer=self.customer + ) def test_get_anonymous(self): response = self.client.get( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - })) + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ) + ) self.assertEqual(response.status_code, 403) def test_get_regular_user(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - })) + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ) + ) self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): User.objects.create_user( - 'test2', email='test2@example.org', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + "test2", email="test2@example.org", password=TEST_PASSWORD + ) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - })) + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ) + ) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): User.objects.create_superuser( - 'admin', email='admin@example.org', password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + "admin", email="admin@example.org", password=TEST_PASSWORD + ) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - })) + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ) + ) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - })) - self.assertTemplateUsed( - response, 'managemails/mailaddress_create.html') + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ) + ) + self.assertTemplateUsed(response, "managemails/mailaddress_create.html") def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - })) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ) + ) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) def test_get_form_kwargs(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) - view = AddMailAddress(request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'domain': self.maildomain.domain}) + view = AddMailAddress( + request=MagicMock(), + kwargs={"package": str(self.package.pk), "domain": self.maildomain.domain}, + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) - self.assertIn('maildomain', the_kwargs) - self.assertEqual(the_kwargs['maildomain'], self.maildomain) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) + self.assertIn("maildomain", the_kwargs) + self.assertEqual(the_kwargs["maildomain"], self.maildomain) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - }), + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ), data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'mailbox': '', - 'forwards': 'test2@example.org', - }) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "mailbox": "", + "forwards": "test2@example.org", + }, + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('add_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain - }), + reverse( + "add_mailaddress", + kwargs={"package": self.package.pk, "domain": self.maildomain.domain}, + ), follow=True, data={ - 'localpart': 'test', - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'mailbox': '', - 'forwards': 'test2@example.org', - }) - messages = list(response.context['messages']) + "localpart": "test", + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "mailbox": "", + "forwards": "test2@example.org", + }, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( - str(messages[0]), - 'Successfully added mail address test@example.org') + str(messages[0]), "Successfully added mail address test@example.org" + ) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class DeleteMailAddressTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer, 1) self.maildomain = MailDomain.objects.create( - domain='example.org', customer=self.customer) + domain="example.org", customer=self.customer + ) self.mailaddress = MailAddress.objects.create( - localpart='test', domain=self.maildomain) - self.mailaddress.set_forward_addresses(['test2@example.org']) + localpart="test", domain=self.maildomain + ) + self.mailaddress.set_forward_addresses(["test2@example.org"]) def test_get_anonymous(self): response = self.client.get( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 403) def test_get_regular_user(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): User.objects.create_user( - 'test2', email='test2@example.org', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + "test2", email="test2@example.org", password=TEST_PASSWORD + ) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): User.objects.create_superuser( - 'admin', email='admin@example.org', password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + "admin", email="admin@example.org", password=TEST_PASSWORD + ) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) - self.assertTemplateUsed( - response, 'managemails/mailaddress_confirm_delete.html') + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) + self.assertTemplateUsed(response, "managemails/mailaddress_confirm_delete.html") def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) - self.assertIn('hostingpackage', response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertIn('maildomain', response.context) - self.assertEqual(response.context['maildomain'], self.maildomain) + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) + self.assertIn("hostingpackage", response.context) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertIn("maildomain", response.context) + self.assertEqual(response.context["maildomain"], self.maildomain) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('delete_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk}), - data={}) + reverse( + "delete_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ), + data={}, + ) self.assertRedirects(response, self.package.get_absolute_url()) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class EditMailAddressTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD) + TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer, 1) self.maildomain = MailDomain.objects.create( - domain='example.org', customer=self.customer) + domain="example.org", customer=self.customer + ) def _set_mailaddress_with_forward(self): self.mailaddress = MailAddress.objects.create( - localpart='test', domain=self.maildomain) - self.mailaddress.set_forward_addresses(['test2@example.org']) + localpart="test", domain=self.maildomain + ) + self.mailaddress.set_forward_addresses(["test2@example.org"]) def _set_mailaddress_with_mailbox(self): self.mailaddress = MailAddress.objects.create( - localpart='test', domain=self.maildomain) + localpart="test", domain=self.maildomain + ) self.mailbox = Mailbox.objects.create_mailbox(self.package.osuser) self.mailaddress.set_mailbox(self.mailbox) def test_get_anonymous(self): self._set_mailaddress_with_forward() response = self.client.get( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 403) def test_get_regular_user(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): self._set_mailaddress_with_forward() User.objects.create_user( - 'test2', email='test2@example.org', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + "test2", email="test2@example.org", password=TEST_PASSWORD + ) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): self._set_mailaddress_with_forward() User.objects.create_superuser( - 'admin', email='admin@example.org', password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + "admin", email="admin@example.org", password=TEST_PASSWORD + ) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) self.assertEqual(response.status_code, 200) def test_get_template(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) - self.assertTemplateUsed(response, 'managemails/mailaddress_edit.html') + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) + self.assertTemplateUsed(response, "managemails/mailaddress_edit.html") def test_get_context_data(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk})) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ) + ) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) def test_get_form_kwargs(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = EditMailAddress( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'domain': self.maildomain.domain, - 'pk': str(self.mailaddress.pk)}) + request=MagicMock(), + kwargs={ + "package": str(self.package.pk), + "domain": self.maildomain.domain, + "pk": str(self.mailaddress.pk), + }, + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) - self.assertIn('maildomain', the_kwargs) - self.assertEqual(the_kwargs['maildomain'], self.maildomain) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) + self.assertIn("maildomain", the_kwargs) + self.assertEqual(the_kwargs["maildomain"], self.maildomain) def test_get_initial_with_forwards(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = EditMailAddress( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'domain': self.maildomain.domain, - 'pk': str(self.mailaddress.pk)}) + request=MagicMock(), + kwargs={ + "package": str(self.package.pk), + "domain": self.maildomain.domain, + "pk": str(self.mailaddress.pk), + }, + ) initial = view.get_initial() - self.assertIn('mailbox_or_forwards', initial) - self.assertEqual( - initial['mailbox_or_forwards'], MAILBOX_OR_FORWARDS.forwards) - self.assertIn('forwards', initial) - self.assertEqual(initial['forwards'], 'test2@example.org') - self.assertNotIn('mailbox', initial) + self.assertIn("mailbox_or_forwards", initial) + self.assertEqual(initial["mailbox_or_forwards"], MAILBOX_OR_FORWARDS.forwards) + self.assertIn("forwards", initial) + self.assertEqual(initial["forwards"], "test2@example.org") + self.assertNotIn("mailbox", initial) def test_get_initial_with_mailbox(self): self._set_mailaddress_with_mailbox() self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = EditMailAddress( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'domain': self.maildomain.domain, - 'pk': str(self.mailaddress.pk)}) + request=MagicMock(), + kwargs={ + "package": str(self.package.pk), + "domain": self.maildomain.domain, + "pk": str(self.mailaddress.pk), + }, + ) initial = view.get_initial() - self.assertIn('mailbox_or_forwards', initial) - self.assertEqual( - initial['mailbox_or_forwards'], MAILBOX_OR_FORWARDS.mailbox) - self.assertIn('mailbox', initial) - self.assertEqual(initial['mailbox'], self.mailbox) - self.assertNotIn('forwards', initial) + self.assertIn("mailbox_or_forwards", initial) + self.assertEqual(initial["mailbox_or_forwards"], MAILBOX_OR_FORWARDS.mailbox) + self.assertIn("mailbox", initial) + self.assertEqual(initial["mailbox"], self.mailbox) + self.assertNotIn("forwards", initial) def test_get_initial_with_unassigned_address(self): self.mailaddress = MailAddress.objects.create( - localpart='test', domain=self.maildomain) + localpart="test", domain=self.maildomain + ) view = EditMailAddress( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'domain': self.maildomain.domain, - 'pk': str(self.mailaddress.pk)}) + request=MagicMock(), + kwargs={ + "package": str(self.package.pk), + "domain": self.maildomain.domain, + "pk": str(self.mailaddress.pk), + }, + ) initial = view.get_initial() - self.assertNotIn('mailbox_or_forwards', initial) - self.assertNotIn('forwards', initial) - self.assertNotIn('mailbox', initial) + self.assertNotIn("mailbox_or_forwards", initial) + self.assertNotIn("forwards", initial) + self.assertNotIn("mailbox", initial) def test_form_valid_redirect(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk}), + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ), data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'mailbox': '', - 'forwards': 'test2@example.org,test3@example.org' - }) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "mailbox": "", + "forwards": "test2@example.org,test3@example.org", + }, + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): self._set_mailaddress_with_forward() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - reverse('edit_mailaddress', kwargs={ - 'package': self.package.pk, - 'domain': self.maildomain.domain, - 'pk': self.mailaddress.pk}), + reverse( + "edit_mailaddress", + kwargs={ + "package": self.package.pk, + "domain": self.maildomain.domain, + "pk": self.mailaddress.pk, + }, + ), follow=True, data={ - 'mailbox_or_forwards': MAILBOX_OR_FORWARDS.forwards, - 'mailbox': '', - 'forwards': 'test2@example.org,test3@example.org' - }) - messages = list(response.context['messages']) + "mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards, + "mailbox": "", + "forwards": "test2@example.org,test3@example.org", + }, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), - 'Successfully updated mail address test@example.org targets.') + "Successfully updated mail address test@example.org targets.", + ) diff --git a/gnuviechadmin/osusers/admin.py b/gnuviechadmin/osusers/admin.py index 3b4fb2b..b6688f6 100644 --- a/gnuviechadmin/osusers/admin.py +++ b/gnuviechadmin/osusers/admin.py @@ -7,26 +7,14 @@ The module starts Celery_ tasks. """ from django import forms -from django.utils.translation import ugettext_lazy as _ from django.contrib import admin +from django.utils.translation import ugettext_lazy as _ from fileservertasks.tasks import set_file_ssh_authorized_keys -from gvawebcore.forms import ( - PASSWORD_MISMATCH_ERROR -) +from gvawebcore.forms import PASSWORD_MISMATCH_ERROR from taskresults.models import TaskResult - -from .forms import ( - INVALID_SSH_PUBLIC_KEY, - DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, -) -from .models import ( - AdditionalGroup, - Group, - Shadow, - SshPublicKey, - User, -) +from .forms import DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, INVALID_SSH_PUBLIC_KEY +from .models import AdditionalGroup, Group, Shadow, SshPublicKey, User class AdditionalGroupInline(admin.TabularInline): @@ -34,6 +22,7 @@ class AdditionalGroupInline(admin.TabularInline): Inline for :py:class:`osusers.models.AdditionalGroup` instances. """ + model = AdditionalGroup @@ -42,8 +31,9 @@ class ShadowInline(admin.TabularInline): Inline for :py:class:`osusers.models.ShadowInline` instances. """ + model = Shadow - readonly_fields = ['passwd'] + readonly_fields = ["passwd"] can_delete = False @@ -53,18 +43,17 @@ class UserCreationForm(forms.ModelForm): `. """ + password1 = forms.CharField( - label=_('Password'), widget=forms.PasswordInput, - required=False, + label=_("Password"), widget=forms.PasswordInput, required=False ) password2 = forms.CharField( - label=_('Password (again)'), widget=forms.PasswordInput, - required=False, + label=_("Password (again)"), widget=forms.PasswordInput, required=False ) class Meta: model = User - fields = ['customer'] + fields = ["customer"] def clean_password2(self): """ @@ -74,8 +63,8 @@ class UserCreationForm(forms.ModelForm): :rtype: str or None """ - password1 = self.cleaned_data.get('password1') - password2 = self.cleaned_data.get('password2') + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: raise forms.ValidationError(PASSWORD_MISMATCH_ERROR) return password2 @@ -90,8 +79,10 @@ class UserCreationForm(forms.ModelForm): """ user = User.objects.create_user( - customer=self.cleaned_data['customer'], - password=self.cleaned_data['password1'], commit=commit) + customer=self.cleaned_data["customer"], + password=self.cleaned_data["password1"], + commit=commit, + ) return user def save_m2m(self): @@ -108,14 +99,16 @@ class UserAdmin(admin.ModelAdmin): `. """ - actions = ['perform_delete_selected'] + + actions = ["perform_delete_selected"] add_form = UserCreationForm inlines = [AdditionalGroupInline, ShadowInline] add_fieldsets = ( - (None, { - 'classes': ('wide',), - 'fields': ('customer', 'password1', 'password2')}), + ( + None, + {"classes": ("wide",), "fields": ("customer", "password1", "password2")}, + ), ) def get_form(self, request, obj=None, **kwargs): @@ -132,10 +125,12 @@ class UserAdmin(admin.ModelAdmin): """ defaults = {} if obj is None: - defaults.update({ - 'form': self.add_form, - 'fields': admin.options.flatten_fieldsets(self.add_fieldsets), - }) + defaults.update( + { + "form": self.add_form, + "fields": admin.options.flatten_fieldsets(self.add_fieldsets), + } + ) defaults.update(kwargs) return super(UserAdmin, self).get_form(request, obj, **defaults) @@ -151,7 +146,7 @@ class UserAdmin(admin.ModelAdmin): """ if obj: - return ['uid'] + return ["uid"] return [] def perform_delete_selected(self, request, queryset): @@ -167,7 +162,8 @@ class UserAdmin(admin.ModelAdmin): """ for user in queryset.all(): user.delete() - perform_delete_selected.short_description = _('Delete selected users') + + perform_delete_selected.short_description = _("Delete selected users") def get_actions(self, request): """ @@ -182,8 +178,8 @@ class UserAdmin(admin.ModelAdmin): """ actions = super(UserAdmin, self).get_actions(request) - if 'delete_selected' in actions: # pragma: no cover - del actions['delete_selected'] + if "delete_selected" in actions: # pragma: no cover + del actions["delete_selected"] return actions @@ -193,7 +189,8 @@ class GroupAdmin(admin.ModelAdmin): `. """ - actions = ['perform_delete_selected'] + + actions = ["perform_delete_selected"] def perform_delete_selected(self, request, queryset): """ @@ -208,7 +205,8 @@ class GroupAdmin(admin.ModelAdmin): """ for group in queryset.all(): group.delete() - perform_delete_selected.short_description = _('Delete selected groups') + + perform_delete_selected.short_description = _("Delete selected groups") def get_actions(self, request): """ @@ -223,8 +221,8 @@ class GroupAdmin(admin.ModelAdmin): """ actions = super(GroupAdmin, self).get_actions(request) - if 'delete_selected' in actions: # pragma: no cover - del actions['delete_selected'] + if "delete_selected" in actions: # pragma: no cover + del actions["delete_selected"] return actions @@ -234,33 +232,37 @@ class SshPublicKeyCreationForm(forms.ModelForm): `. """ + publickeytext = forms.CharField( - label=_('Key text'), widget=forms.Textarea, - help_text=_('A SSH public key in either OpenSSH or RFC 4716 format')) + label=_("Key text"), + widget=forms.Textarea, + help_text=_("A SSH public key in either OpenSSH or RFC 4716 format"), + ) class Meta: model = SshPublicKey - fields = ['user'] + fields = ["user"] def clean_publickeytext(self): - keytext = self.cleaned_data.get('publickeytext') + keytext = self.cleaned_data.get("publickeytext") try: - SshPublicKey.objects.parse_keytext(keytext) + SshPublicKey.objects.parse_key_text(keytext) except: raise forms.ValidationError(INVALID_SSH_PUBLIC_KEY) return keytext def clean(self): - user = self.cleaned_data.get('user') - keytext = self.cleaned_data.get('publickeytext') + user = self.cleaned_data.get("user") + keytext = self.cleaned_data.get("publickeytext") if user and keytext: - alg, data, comment = SshPublicKey.objects.parse_keytext(keytext) + alg, data, comment = SshPublicKey.objects.parse_key_text(keytext) if SshPublicKey.objects.filter( user=user, algorithm=alg, data=data ).exists(): self.add_error( - 'publickeytext', - forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER)) + "publickeytext", + forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER), + ) super(SshPublicKeyCreationForm, self).clean() def save(self, commit=True): @@ -272,8 +274,9 @@ class SshPublicKeyCreationForm(forms.ModelForm): :rtype: :py:class:`osusers.models.SshPublicKey` """ - algorithm, keydata, comment = SshPublicKey.objects.parse_keytext( - self.cleaned_data.get('publickeytext')) + algorithm, keydata, comment = SshPublicKey.objects.parse_key_text( + self.cleaned_data.get("publickeytext") + ) self.instance.algorithm = algorithm self.instance.data = keydata self.instance.comment = comment @@ -286,14 +289,13 @@ class SshPublicKeyAdmin(admin.ModelAdmin): `. """ - actions = ['perform_delete_selected'] + + actions = ["perform_delete_selected"] add_form = SshPublicKeyCreationForm - list_display = ['user', 'algorithm', 'comment'] + list_display = ["user", "algorithm", "comment"] add_fieldsets = ( - (None, { - 'classes': ('wide',), - 'fields': ('user', 'publickeytext')}), + (None, {"classes": ("wide",), "fields": ("user", "publickeytext")}), ) def get_form(self, request, obj=None, **kwargs): @@ -311,13 +313,14 @@ class SshPublicKeyAdmin(admin.ModelAdmin): """ defaults = {} if obj is None: - defaults.update({ - 'form': self.add_form, - 'fields': admin.options.flatten_fieldsets(self.add_fieldsets), - }) + defaults.update( + { + "form": self.add_form, + "fields": admin.options.flatten_fieldsets(self.add_fieldsets), + } + ) defaults.update(kwargs) - return super(SshPublicKeyAdmin, self).get_form( - request, obj, **defaults) + return super(SshPublicKeyAdmin, self).get_form(request, obj, **defaults) def get_readonly_fields(self, request, obj=None): """ @@ -332,7 +335,7 @@ class SshPublicKeyAdmin(admin.ModelAdmin): """ if obj: - return ['algorithm', 'data'] + return ["algorithm", "data"] return [] def perform_delete_selected(self, request, queryset): @@ -370,23 +373,19 @@ class SshPublicKeyAdmin(admin.ModelAdmin): } """ - users = set([ - item['user'] for item in - queryset.values('user').distinct() - ]) + users = set([item["user"] for item in queryset.values("user").distinct()]) queryset.delete() for user in users: # TODO: move to model/signal TaskResult.objects.create_task_result( - 'perform_delete_selected', + "perform_delete_selected", set_file_ssh_authorized_keys.s( User.objects.get(uid=user).username, - [str(key) for key in SshPublicKey.objects.filter( - user_id=user)] - ) + [str(key) for key in SshPublicKey.objects.filter(user_id=user)], + ), ) - perform_delete_selected.short_description = _( - 'Delete selected SSH public keys') + + perform_delete_selected.short_description = _("Delete selected SSH public keys") def get_actions(self, request): """ @@ -401,8 +400,8 @@ class SshPublicKeyAdmin(admin.ModelAdmin): """ actions = super(SshPublicKeyAdmin, self).get_actions(request) - if 'delete_selected' in actions: # pragma: no cover - del actions['delete_selected'] + if "delete_selected" in actions: # pragma: no cover + del actions["delete_selected"] return actions diff --git a/gnuviechadmin/osusers/forms.py b/gnuviechadmin/osusers/forms.py index be0e57b..6ca67c5 100644 --- a/gnuviechadmin/osusers/forms.py +++ b/gnuviechadmin/osusers/forms.py @@ -13,14 +13,12 @@ from crispy_forms.layout import Submit from gvawebcore.forms import PasswordModelFormMixin -from .models import ( - SshPublicKey, - User, -) +from .models import SshPublicKey, User -INVALID_SSH_PUBLIC_KEY = _('Invalid SSH public key data format.') +INVALID_SSH_PUBLIC_KEY = _("Invalid SSH public key data format.") DUPLICATE_SSH_PUBLIC_KEY_FOR_USER = _( - 'This SSH public key is already assigned to this user.') + "This SSH public key is already assigned to this user." +) class ChangeOsUserPasswordForm(PasswordModelFormMixin, forms.ModelForm): @@ -28,6 +26,7 @@ class ChangeOsUserPasswordForm(PasswordModelFormMixin, forms.ModelForm): A form for setting an OS user's password. """ + class Meta: model = User fields = [] @@ -36,8 +35,9 @@ class ChangeOsUserPasswordForm(PasswordModelFormMixin, forms.ModelForm): self.helper = FormHelper() super(ChangeOsUserPasswordForm, self).__init__(*args, **kwargs) self.helper.form_action = reverse( - 'set_osuser_password', kwargs={'slug': self.instance.username}) - self.helper.add_input(Submit('submit', _('Set password'))) + "set_osuser_password", kwargs={"slug": self.instance.username} + ) + self.helper.add_input(Submit("submit", _("Set password"))) def save(self, commit=True): """ @@ -48,7 +48,7 @@ class ChangeOsUserPasswordForm(PasswordModelFormMixin, forms.ModelForm): :rtype: :py:class:`osusers.models.User` """ - self.instance.set_password(self.cleaned_data['password1']) + self.instance.set_password(self.cleaned_data["password1"]) return super(ChangeOsUserPasswordForm, self).save(commit=commit) @@ -58,41 +58,45 @@ class AddSshPublicKeyForm(forms.ModelForm): `. """ + publickeytext = forms.CharField( - label=_('Key text'), widget=forms.Textarea, - help_text=_('A SSH public key in either OpenSSH or RFC 4716 format')) + label=_("Key text"), + widget=forms.Textarea, + help_text=_("A SSH public key in either OpenSSH or RFC 4716 format"), + ) class Meta: model = SshPublicKey fields = [] def __init__(self, *args, **kwargs): - hosting_package = kwargs.pop('hostingpackage') + hosting_package = kwargs.pop("hostingpackage") self.osuser = hosting_package.osuser super(AddSshPublicKeyForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_action = reverse( - 'add_ssh_key', kwargs={'package': hosting_package.id}) - self.helper.add_input(Submit('submit', _('Add SSH public key'))) + "add_ssh_key", kwargs={"package": hosting_package.id} + ) + self.helper.add_input(Submit("submit", _("Add SSH public key"))) def clean_publickeytext(self): - keytext = self.cleaned_data.get('publickeytext') + keytext = self.cleaned_data.get("publickeytext") try: - SshPublicKey.objects.parse_keytext(keytext) + SshPublicKey.objects.parse_key_text(keytext) except ValueError: raise forms.ValidationError(INVALID_SSH_PUBLIC_KEY) return keytext def clean(self): - keytext = self.cleaned_data.get('publickeytext') + keytext = self.cleaned_data.get("publickeytext") if keytext is not None: - alg, data, comment = SshPublicKey.objects.parse_keytext(keytext) + alg, data, comment = SshPublicKey.objects.parse_key_text(keytext) if SshPublicKey.objects.filter( user=self.osuser, algorithm=alg, data=data ).exists(): self.add_error( - 'publickeytext', - forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER) + "publickeytext", + forms.ValidationError(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER), ) def save(self, commit=True): @@ -104,8 +108,9 @@ class AddSshPublicKeyForm(forms.ModelForm): :rtype: :py:class:`osusers.models.SshPublicKey` """ - algorithm, keydata, comment = SshPublicKey.objects.parse_keytext( - self.cleaned_data.get('publickeytext')) + algorithm, keydata, comment = SshPublicKey.objects.parse_key_text( + self.cleaned_data.get("publickeytext") + ) self.instance.user = self.osuser self.instance.algorithm = algorithm self.instance.data = keydata @@ -119,17 +124,19 @@ class EditSshPublicKeyCommentForm(forms.ModelForm): ` comment fields. """ + class Meta: model = SshPublicKey - fields = ['comment'] + fields = ["comment"] def __init__(self, *args, **kwargs): - hosting_package = kwargs.pop('hostingpackage') + hosting_package = kwargs.pop("hostingpackage") self.osuser = hosting_package.osuser super(EditSshPublicKeyCommentForm, self).__init__(*args, **kwargs) - self.fields['comment'].widget = forms.TextInput() + self.fields["comment"].widget = forms.TextInput() self.helper = FormHelper() self.helper.form_action = reverse( - 'edit_ssh_key_comment', - kwargs={'package': hosting_package.id, 'pk': self.instance.id}) - self.helper.add_input(Submit('submit', _('Change Comment'))) + "edit_ssh_key_comment", + kwargs={"package": hosting_package.id, "pk": self.instance.id}, + ) + self.helper.add_input(Submit("submit", _("Change Comment"))) diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 197c669..14d36ff 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -2,20 +2,16 @@ This module defines the database models of operating system users. """ -from __future__ import unicode_literals - import base64 from datetime import date import logging import os -import six from django.db import models, transaction from django.conf import settings from django.core.exceptions import ValidationError from django.dispatch import Signal from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ from model_utils.models import TimeStampedModel @@ -27,11 +23,10 @@ from passlib.utils import generate_password _LOGGER = logging.getLogger(__name__) -password_set = Signal(providing_args=['instance', 'password']) +password_set = Signal(providing_args=["instance", "password"]) -CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _( - "You can not use a user's primary group.") +CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _("You can not use a user's primary group.") class GroupManager(models.Manager): @@ -48,34 +43,31 @@ class GroupManager(models.Manager): :rtype: int """ - q = self.aggregate(models.Max('gid')) - if q['gid__max'] is None: + q = self.aggregate(models.Max("gid")) + if q["gid__max"] is None: return settings.OSUSER_MINGID - return max(settings.OSUSER_MINGID, q['gid__max'] + 1) + return max(settings.OSUSER_MINGID, q["gid__max"] + 1) -@python_2_unicode_compatible class Group(TimeStampedModel, models.Model): """ This entity class corresponds to an operating system group. """ - groupname = models.CharField( - _('Group name'), max_length=16, unique=True) - gid = models.PositiveSmallIntegerField( - _('Group ID'), unique=True, primary_key=True) - descr = models.TextField(_('Description'), blank=True) - passwd = models.CharField( - _('Group password'), max_length=128, blank=True) + + groupname = models.CharField(_("Group name"), max_length=16, unique=True) + gid = models.PositiveSmallIntegerField(_("Group ID"), unique=True, primary_key=True) + descr = models.TextField(_("Description"), blank=True) + passwd = models.CharField(_("Group password"), max_length=128, blank=True) objects = GroupManager() class Meta: - verbose_name = _('Group') - verbose_name_plural = _('Groups') + verbose_name = _("Group") + verbose_name_plural = _("Groups") def __str__(self): - return '{0} ({1})'.format(self.groupname, self.gid) + return "{0} ({1})".format(self.groupname, self.gid) @transaction.atomic def save(self, *args, **kwargs): @@ -122,10 +114,10 @@ class UserManager(models.Manager): :rtype: int """ - q = self.aggregate(models.Max('uid')) - if q['uid__max'] is None: + q = self.aggregate(models.Max("uid")) + if q["uid__max"] is None: return settings.OSUSER_MINUID - return max(settings.OSUSER_MINUID, q['uid__max'] + 1) + return max(settings.OSUSER_MINUID, q["uid__max"] + 1) def get_next_username(self): """ @@ -137,23 +129,21 @@ class UserManager(models.Manager): """ count = 1 usernameformat = "{0}{1:02d}" - nextuser = usernameformat.format(settings.OSUSER_USERNAME_PREFIX, - count) - for user in self.values('username').filter( - username__startswith=settings.OSUSER_USERNAME_PREFIX - ).order_by('username'): - if user['username'] == nextuser: + nextuser = usernameformat.format(settings.OSUSER_USERNAME_PREFIX, count) + for user in ( + self.values("username") + .filter(username__startswith=settings.OSUSER_USERNAME_PREFIX) + .order_by("username") + ): + if user["username"] == nextuser: count += 1 - nextuser = usernameformat.format( - settings.OSUSER_USERNAME_PREFIX, count) + nextuser = usernameformat.format(settings.OSUSER_USERNAME_PREFIX, count) else: break return nextuser @transaction.atomic - def create_user( - self, customer, username=None, password=None, commit=False - ): + def create_user(self, customer, username=None, password=None, commit=False): """ Create a new user with a primary group named the same as the user and an initial password. @@ -179,41 +169,42 @@ class UserManager(models.Manager): password = generate_password() homedir = os.path.join(settings.OSUSER_HOME_BASEPATH, username) group = Group.objects.create(groupname=username, gid=gid) - user = self.create(username=username, group=group, uid=uid, - homedir=homedir, customer=customer, - shell=settings.OSUSER_DEFAULT_SHELL) + user = self.create( + username=username, + group=group, + uid=uid, + homedir=homedir, + customer=customer, + shell=settings.OSUSER_DEFAULT_SHELL, + ) user.set_password(password) if commit: user.save() return user -@python_2_unicode_compatible class User(TimeStampedModel, models.Model): """ This entity class corresponds to an operating system user. """ - username = models.CharField( - _('User name'), max_length=64, unique=True) - uid = models.PositiveSmallIntegerField( - _('User ID'), unique=True, primary_key=True) - group = models.ForeignKey( - Group, verbose_name=_('Group'), on_delete=models.CASCADE) - gecos = models.CharField(_('Gecos field'), max_length=128, blank=True) - homedir = models.CharField(_('Home directory'), max_length=256) - shell = models.CharField(_('Login shell'), max_length=64) - customer = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + + username = models.CharField(_("User name"), max_length=64, unique=True) + uid = models.PositiveSmallIntegerField(_("User ID"), unique=True, primary_key=True) + group = models.ForeignKey(Group, verbose_name=_("Group"), on_delete=models.CASCADE) + gecos = models.CharField(_("Gecos field"), max_length=128, blank=True) + homedir = models.CharField(_("Home directory"), max_length=256) + shell = models.CharField(_("Login shell"), max_length=64) + customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) objects = UserManager() class Meta: - verbose_name = _('User') - verbose_name_plural = _('Users') + verbose_name = _("User") + verbose_name_plural = _("Users") def __str__(self): - return '{0} ({1})'.format(self.username, self.uid) + return "{0} ({1})".format(self.username, self.uid) @transaction.atomic def set_password(self, password): @@ -226,17 +217,15 @@ class User(TimeStampedModel, models.Model): :param str password: the new password """ - if hasattr(self, 'shadow'): + if hasattr(self, "shadow"): self.shadow.set_password(password) else: - self.shadow = Shadow.objects.create_shadow( - user=self, password=password - ) - password_set.send( - sender=self.__class__, password=password, instance=self) + self.shadow = Shadow.objects.create_shadow(user=self, password=password) + password_set.send(sender=self.__class__, password=password, instance=self) return True def is_sftp_user(self): + # noinspection PyUnresolvedReferences return self.additionalgroup_set.filter( group__groupname=settings.OSUSER_SFTP_GROUP ).exists() @@ -269,6 +258,7 @@ class User(TimeStampedModel, models.Model): :py:meth:`django.db.Model.delete` """ + # noinspection PyUnresolvedReferences self.group.delete() super(User, self).delete(*args, **kwargs) @@ -293,64 +283,84 @@ class ShadowManager(models.Manager): """ changedays = (timezone.now().date() - date(1970, 1, 1)).days shadow = self.create( - user=user, changedays=changedays, - minage=0, maxage=None, gracedays=7, - inactdays=30, expiredays=None + user=user, + changedays=changedays, + minage=0, + maxage=None, + gracedays=7, + inactdays=30, + expiredays=None, ) shadow.set_password(password) shadow.save() return shadow -@python_2_unicode_compatible class Shadow(TimeStampedModel, models.Model): """ This entity class corresponds to an operating system user's shadow file entry. """ + user = models.OneToOneField( - User, primary_key=True, verbose_name=_('User'), - on_delete=models.CASCADE) - passwd = models.CharField(_('Encrypted password'), max_length=128) + User, primary_key=True, verbose_name=_("User"), on_delete=models.CASCADE + ) + passwd = models.CharField(_("Encrypted password"), max_length=128) changedays = models.PositiveSmallIntegerField( - _('Date of last change'), - help_text=_('This is expressed in days since Jan 1, 1970'), - blank=True, null=True) + _("Date of last change"), + help_text=_("This is expressed in days since Jan 1, 1970"), + blank=True, + null=True, + ) minage = models.PositiveSmallIntegerField( - _('Minimum age'), - help_text=_('Minimum number of days before the password can be' - ' changed'), - blank=True, null=True) + _("Minimum age"), + help_text=_("Minimum number of days before the password can be" " changed"), + blank=True, + null=True, + ) maxage = models.PositiveSmallIntegerField( - _('Maximum age'), - help_text=_('Maximum number of days after which the password has to' - ' be changed'), - blank=True, null=True) + _("Maximum age"), + help_text=_( + "Maximum number of days after which the password has to" " be changed" + ), + blank=True, + null=True, + ) gracedays = models.PositiveSmallIntegerField( - _('Grace period'), - help_text=_('The number of days before the password is going to' - ' expire'), - blank=True, null=True) + _("Grace period"), + help_text=_("The number of days before the password is going to" " expire"), + blank=True, + null=True, + ) inactdays = models.PositiveSmallIntegerField( - _('Inactivity period'), - help_text=_('The number of days after the password has expired during' - ' which the password should still be accepted'), - blank=True, null=True) + _("Inactivity period"), + help_text=_( + "The number of days after the password has expired during" + " which the password should still be accepted" + ), + blank=True, + null=True, + ) expiredays = models.PositiveSmallIntegerField( - _('Account expiration date'), - help_text=_('The date of expiration of the account, expressed as' - ' number of days since Jan 1, 1970'), - blank=True, null=True, default=None) + _("Account expiration date"), + help_text=_( + "The date of expiration of the account, expressed as" + " number of days since Jan 1, 1970" + ), + blank=True, + null=True, + default=None, + ) objects = ShadowManager() class Meta: - verbose_name = _('Shadow password') - verbose_name_plural = _('Shadow passwords') + verbose_name = _("Shadow password") + verbose_name_plural = _("Shadow passwords") def __str__(self): - return 'for user {0}'.format(self.user) + return "for user {0}".format(self.user) def set_password(self, password): """ @@ -361,23 +371,23 @@ class Shadow(TimeStampedModel, models.Model): self.passwd = sha512_crypt.encrypt(password) -@python_2_unicode_compatible class AdditionalGroup(TimeStampedModel, models.Model): """ This entity class corresponds to additional group assignments for an :py:class:`operating system user `. """ + user = models.ForeignKey(User, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) class Meta: - unique_together = ('user', 'group') - verbose_name = _('Additional group') - verbose_name_plural = _('Additional groups') + unique_together = ("user", "group") + verbose_name = _("Additional group") + verbose_name_plural = _("Additional groups") def __str__(self): - return '{0} in {1}'.format(self.user, self.group) + return "{0} in {1}".format(self.user, self.group) def clean(self): """ @@ -385,6 +395,7 @@ class AdditionalGroup(TimeStampedModel, models.Model): group. """ + # noinspection PyUnresolvedReferences if self.user.group == self.group: raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL) @@ -423,60 +434,62 @@ class SshPublicKeyManager(models.Manager): """ - def parse_keytext(self, keytext): + def parse_key_text(self, key_text: str): """ Parse a SSH public key in OpenSSH or :rfc:`4716` format into its components algorithm, key data and comment. - :param str keytext: key text + :param str key_text: key text :return: triple of algorithm name, key data and comment :rtype: triple of str """ - if keytext.startswith('---- BEGIN SSH2 PUBLIC KEY ----'): - comment = '' - data = '' - continued = '' + if key_text.startswith("---- BEGIN SSH2 PUBLIC KEY ----"): + comment = "" + data = "" + continued = "" headers = {} - for line in keytext.splitlines(): - if line == '---- BEGIN SSH2 PUBLIC KEY ----': + header_tag = None + for line in key_text.splitlines(): + if line == "---- BEGIN SSH2 PUBLIC KEY ----": continue - elif ':' in line: # a header line + elif ":" in line: # a header line header_tag, header_value = [ - item.strip() for item in line.split(':', 1)] - if header_value.endswith('\\'): + item.strip() for item in line.split(":", 1) + ] + if header_value.endswith("\\"): continued = header_value[:-1] else: headers[header_tag.lower()] = header_value elif continued: - if line.endswith('\\'): + if line.endswith("\\"): continued += line[:-1] continue header_value = continued + line headers[header_tag.lower()] = header_value - continued = '' - elif line == '---- END SSH2 PUBLIC KEY ----': + continued = "" + elif line == "---- END SSH2 PUBLIC KEY ----": break elif line: # ignore empty lines data += line - if 'comment' in headers: - comment = headers['comment'] + if "comment" in headers: + comment = headers["comment"] else: - parts = keytext.split(None, 2) + parts = key_text.split(None, 2) if len(parts) < 2: - raise ValueError('invalid SSH public key') + raise ValueError("invalid SSH public key") data = parts[1] comment = len(parts) == 3 and parts[2] or "" try: keybytes = base64.b64decode(data) except TypeError: - raise ValueError('invalid SSH public key') - parts = keybytes.split(b'\x00' * 3) + raise ValueError("invalid SSH public key") + parts = keybytes.split(b"\x00" * 3) if len(parts) < 2: - raise ValueError('invalid SSH public key') - alglength = six.byte2int(parts[1]) - algname = parts[1][1:1+alglength] - return algname, data, comment + raise ValueError("invalid SSH public key") + key_length = int.from_bytes(parts[1], byteorder="big") + key_algorithm = parts[1][1 : 1 + key_length].decode("utf-8") + return key_algorithm, data, comment def create_ssh_public_key(self, user, keytext): """ @@ -490,31 +503,28 @@ class SshPublicKeyManager(models.Manager): :retype: :py:class:`osusers.models.SshPublicKey` """ - algorithm, data, comment = self.parse_keytext(keytext) - return self.create( - user=user, algorithm=algorithm, data=data, comment=comment) + algorithm, data, comment = self.parse_key_text(keytext) + return self.create(user=user, algorithm=algorithm, data=data, comment=comment) -@python_2_unicode_compatible class SshPublicKey(TimeStampedModel): """ This entity class represents single SSH keys for an :py:class:`operating system user `. """ - user = models.ForeignKey( - User, verbose_name=_('User'), on_delete=models.CASCADE) - algorithm = models.CharField(_('Algorithm'), max_length=20) - data = models.TextField(_('Key bytes'), - help_text=_('Base64 encoded key bytes')) - comment = models.TextField(_('Comment'), blank=True) + + user = models.ForeignKey(User, verbose_name=_("User"), on_delete=models.CASCADE) + algorithm = models.CharField(_("Algorithm"), max_length=20) + data = models.TextField(_("Key bytes"), help_text=_("Base64 encoded key bytes")) + comment = models.TextField(_("Comment"), blank=True) objects = SshPublicKeyManager() class Meta: - verbose_name = _('SSH public key') - verbose_name_plural = _('SSH public keys') - unique_together = [('user', 'algorithm', 'data')] + verbose_name = _("SSH public key") + verbose_name_plural = _("SSH public keys") + unique_together = [("user", "algorithm", "data")] def __str__(self): return "{algorithm} {data} {comment}".format( diff --git a/gnuviechadmin/osusers/tests/test_admin.py b/gnuviechadmin/osusers/tests/test_admin.py index 3ab2be9..1f2e7f3 100644 --- a/gnuviechadmin/osusers/tests/test_admin.py +++ b/gnuviechadmin/osusers/tests/test_admin.py @@ -5,17 +5,10 @@ from django.test.utils import override_settings from django.contrib.auth import get_user_model -from mock import MagicMock, Mock, patch +from unittest.mock import MagicMock, Mock, patch -from osusers.forms import ( - INVALID_SSH_PUBLIC_KEY, - DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, -) -from osusers.models import ( - Group, - SshPublicKey, - User, -) +from osusers.forms import INVALID_SSH_PUBLIC_KEY, DUPLICATE_SSH_PUBLIC_KEY_FOR_USER +from osusers.models import Group, SshPublicKey, User from osusers.admin import ( GroupAdmin, PASSWORD_MISMATCH_ERROR, @@ -30,20 +23,19 @@ Customer = get_user_model() class CustomerTestCase(TestCase): def setUp(self): - self.customer = Customer.objects.create_user('test') + self.customer = Customer.objects.create_user("test") super(CustomerTestCase, self).setUp() class UserCreationFormTest(CustomerTestCase): - def test_clean_password2_same(self): form = UserCreationForm() form.cleaned_data = { - 'customer': self.customer, - 'password1': 'secret', - 'password2': 'secret' + "customer": self.customer, + "password1": "secret", + "password2": "secret", } - self.assertEqual(form.clean_password2(), 'secret') + self.assertEqual(form.clean_password2(), "secret") def test_clean_password2_empty(self): form = UserCreationForm() @@ -53,25 +45,23 @@ class UserCreationFormTest(CustomerTestCase): def test_clean_password2_mismatch(self): form = UserCreationForm() form.cleaned_data = { - 'customer': self.customer, - 'password1': 'secretx', - 'password2': 'secrety' + "customer": self.customer, + "password1": "secretx", + "password2": "secrety", } with self.assertRaises(forms.ValidationError) as cm: form.clean_password2() self.assertEqual(cm.exception.message, PASSWORD_MISMATCH_ERROR) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_save_commit(self): form = UserCreationForm() form.cleaned_data = { - 'customer': self.customer, - 'password1': 'secret', - 'password2': 'secret' + "customer": self.customer, + "password1": "secret", + "password2": "secret", } user = form.save() self.assertIsNotNone(user) @@ -89,121 +79,99 @@ class UserAdminTest(CustomerTestCase): super(UserAdminTest, self).setUp() def test_get_form_without_object(self): - form = self.uadmin.get_form(Mock(name='request')) - self.assertEqual( - form.Meta.fields, - ['customer', 'password1', 'password2'] - ) + form = self.uadmin.get_form(Mock(name="request")) + self.assertEqual(form.Meta.fields, ["customer", "password1", "password2"]) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_get_form_with_object(self): user = User.objects.create_user(customer=self.customer) - form = self.uadmin.get_form(Mock(name='request'), user) + form = self.uadmin.get_form(Mock(name="request"), user) self.assertEqual( form.Meta.fields, - ['username', 'group', 'gecos', 'homedir', 'shell', 'customer', - 'uid'] + ["username", "group", "gecos", "homedir", "shell", "customer", "uid"], ) def test_get_inline_instances_without_object(self): - inlines = self.uadmin.get_inline_instances(Mock(name='request')) + inlines = self.uadmin.get_inline_instances(Mock(name="request")) self.assertEqual(len(inlines), 2) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_get_inline_instances_with_object(self): user = User.objects.create_user(customer=self.customer) - inlines = self.uadmin.get_inline_instances( - Mock(name='request'), user) + inlines = self.uadmin.get_inline_instances(Mock(name="request"), user) self.assertEqual(len(inlines), len(UserAdmin.inlines)) for index in range(len(inlines)): self.assertIsInstance(inlines[index], UserAdmin.inlines[index]) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_perform_delete_selected(self): user = User.objects.create_user(customer=self.customer) self.uadmin.perform_delete_selected( - Mock(name='request'), User.objects.filter(uid=user.uid)) + Mock(name="request"), User.objects.filter(uid=user.uid) + ) self.assertEqual(User.objects.filter(uid=user.uid).count(), 0) def test_get_actions(self): - requestmock = MagicMock(name='request') - self.assertNotIn( - 'delete_selected', - self.uadmin.get_actions(requestmock)) - self.assertIn( - 'perform_delete_selected', - self.uadmin.get_actions(requestmock)) + requestmock = MagicMock(name="request") + self.assertNotIn("delete_selected", self.uadmin.get_actions(requestmock)) + self.assertIn("perform_delete_selected", self.uadmin.get_actions(requestmock)) class GroupAdminTest(TestCase): - def setUp(self): site = AdminSite() self.gadmin = GroupAdmin(Group, site) super(GroupAdminTest, self).setUp() def test_get_inline_instances_without_object(self): - inlines = self.gadmin.get_inline_instances(Mock(name='request')) + inlines = self.gadmin.get_inline_instances(Mock(name="request")) self.assertEqual(inlines, []) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) def test_get_inline_instances_with_object(self): - group = Group.objects.create(gid=1000, groupname='test') - inlines = self.gadmin.get_inline_instances( - Mock(name='request'), group) + group = Group.objects.create(gid=1000, groupname="test") + inlines = self.gadmin.get_inline_instances(Mock(name="request"), group) self.assertEqual(len(inlines), len(GroupAdmin.inlines)) for index in range(len(inlines)): self.assertIsInstance(inlines[index], GroupAdmin.inlines[index]) def test_perform_delete_selected(self): - group = Group.objects.create(gid=1000, groupname='test') + group = Group.objects.create(gid=1000, groupname="test") self.gadmin.perform_delete_selected( - Mock(name='request'), Group.objects.filter(gid=group.gid)) + Mock(name="request"), Group.objects.filter(gid=group.gid) + ) self.assertEqual(Group.objects.filter(gid=group.gid).count(), 0) def test_get_actions(self): - requestmock = MagicMock(name='request') - self.assertNotIn( - 'delete_selected', - self.gadmin.get_actions(requestmock)) - self.assertIn( - 'perform_delete_selected', - self.gadmin.get_actions(requestmock)) + requestmock = MagicMock(name="request") + self.assertNotIn("delete_selected", self.gadmin.get_actions(requestmock)) + self.assertIn("perform_delete_selected", self.gadmin.get_actions(requestmock)) class SshPublicKeyCreationFormTest(CustomerTestCase): - - @patch('osusers.admin.SshPublicKey.objects') + @patch("osusers.admin.SshPublicKey.objects") def test_clean_publickeytext_valid_key(self, sshpkmanager): form = SshPublicKeyCreationForm() - sshpkmanager.parse_keytext = MagicMock(side_effect=ValueError) - form.cleaned_data = {'publickeytext': 'wrongkey'} + sshpkmanager.parse_key_text = MagicMock(side_effect=ValueError) + form.cleaned_data = {"publickeytext": "wrongkey"} with self.assertRaises(forms.ValidationError) as ve: form.clean_publickeytext() self.assertEqual(ve.exception.message, INVALID_SSH_PUBLIC_KEY) - @patch('osusers.admin.SshPublicKey.objects') + @patch("osusers.admin.SshPublicKey.objects") def test_clean_publickeytext_invalid_key(self, sshpkmanager): form = SshPublicKeyCreationForm() - sshpkmanager.parse_keytext = MagicMock(return_value='goodkey') - form.cleaned_data = {'publickeytext': 'goodkey'} - self.assertEqual(form.clean_publickeytext(), 'goodkey') + sshpkmanager.parse_key_text = MagicMock(return_value="goodkey") + form.cleaned_data = {"publickeytext": "goodkey"} + self.assertEqual(form.clean_publickeytext(), "goodkey") def test_clean_missing_data(self): form = SshPublicKeyCreationForm() @@ -211,95 +179,83 @@ class SshPublicKeyCreationFormTest(CustomerTestCase): form.clean() self.assertEqual(len(form.errors), 0) - @patch('osusers.admin.SshPublicKey.objects.parse_keytext') - def test_clean_once(self, parse_keytext): - parse_keytext.return_value = ('good', 'key', 'comment') + @patch("osusers.admin.SshPublicKey.objects.parse_key_text") + def test_clean_once(self, parse_key_text): + parse_key_text.return_value = ("good", "key", "comment") user = User.objects.create_user(customer=self.customer) form = SshPublicKeyCreationForm() - form.cleaned_data = { - 'user': user, - 'publickeytext': 'good key comment' - } + form.cleaned_data = {"user": user, "publickeytext": "good key comment"} form.clean() self.assertEqual(len(form.errors), 0) - @patch('osusers.admin.SshPublicKey.objects.parse_keytext') - def test_clean_again(self, parse_keytext): - parse_keytext.return_value = ('good', 'key', 'comment') + @patch("osusers.admin.SshPublicKey.objects.parse_key_text") + def test_clean_again(self, parse_key_text): + parse_key_text.return_value = ("good", "key", "comment") user = User.objects.create_user(customer=self.customer) SshPublicKey.objects.create( - user=user, algorithm='good', data='key', comment='comment') + user=user, algorithm="good", data="key", comment="comment" + ) form = SshPublicKeyCreationForm() - form.cleaned_data = { - 'user': user, - 'publickeytext': 'good key comment' - } + form.cleaned_data = {"user": user, "publickeytext": "good key comment"} form.clean() - self.assertIn('publickeytext', form.errors) + self.assertIn("publickeytext", form.errors) self.assertEqual( - form.errors['publickeytext'], - [DUPLICATE_SSH_PUBLIC_KEY_FOR_USER]) + form.errors["publickeytext"], [DUPLICATE_SSH_PUBLIC_KEY_FOR_USER] + ) - @patch('osusers.admin.SshPublicKey.objects.parse_keytext') - def test_save(self, parse_keytext): - parse_keytext.return_value = ('good', 'key', 'comment') + @patch("osusers.admin.SshPublicKey.objects.parse_key_text") + def test_save(self, parse_key_text): + parse_key_text.return_value = ("good", "key", "comment") user = User.objects.create_user(customer=self.customer) form = SshPublicKeyCreationForm() - form.cleaned_data = { - 'user': user, - 'publickeytext': 'good key comment' - } + form.cleaned_data = {"user": user, "publickeytext": "good key comment"} form.instance.user = user form.save() self.assertTrue( - SshPublicKey.objects.filter( - user=user, algorithm='good', data='key')) + SshPublicKey.objects.filter(user=user, algorithm="good", data="key") + ) class SshPublicKeyAdminTest(CustomerTestCase): - def setUp(self): site = AdminSite() self.sadmin = SshPublicKeyAdmin(SshPublicKey, site) super(SshPublicKeyAdminTest, self).setUp() def test_get_form_no_instance(self): - form = self.sadmin.get_form(request=Mock(name='request')) + form = self.sadmin.get_form(request=Mock(name="request")) self.assertEqual(form.Meta.model, SshPublicKey) def test_get_form_with_instance(self): user = User.objects.create_user(customer=self.customer) key = SshPublicKey.objects.create( - user=user, algorithm='good', data='key', comment='comment') - form = self.sadmin.get_form(request=Mock(name='request'), obj=key) + user=user, algorithm="good", data="key", comment="comment" + ) + form = self.sadmin.get_form(request=Mock(name="request"), obj=key) self.assertEqual(form.Meta.model, SshPublicKey) - self.assertEqual( - form.Meta.fields, - ['user', 'comment', 'algorithm', 'data']) + self.assertEqual(form.Meta.fields, ["user", "comment", "algorithm", "data"]) def test_get_readonly_fields_no_instance(self): - readonly_fields = self.sadmin.get_readonly_fields( - request=Mock(name='request')) + readonly_fields = self.sadmin.get_readonly_fields(request=Mock(name="request")) self.assertEqual(readonly_fields, []) def test_get_readonly_fields_with_instance(self): readonly_fields = self.sadmin.get_readonly_fields( - request=Mock(name='request'), obj=Mock()) - self.assertEqual(readonly_fields, ['algorithm', 'data']) + request=Mock(name="request"), obj=Mock() + ) + self.assertEqual(readonly_fields, ["algorithm", "data"]) def test_perform_delete_selected(self): user = User.objects.create_user(customer=self.customer) key = SshPublicKey.objects.create( - user=user, algorithm='good', data='key', comment='comment') + user=user, algorithm="good", data="key", comment="comment" + ) self.sadmin.perform_delete_selected( - Mock(name='request'), SshPublicKey.objects.filter(id=key.id)) + Mock(name="request"), SshPublicKey.objects.filter(id=key.id) + ) self.assertFalse(SshPublicKey.objects.filter(id=key.id).exists()) def test_get_actions(self): - requestmock = MagicMock(name='request') - self.assertNotIn( - 'delete_selected', - self.sadmin.get_actions(requestmock)) - self.assertIn( - 'perform_delete_selected', - self.sadmin.get_actions(requestmock)) + requestmock = MagicMock(name="request") + self.assertNotIn("delete_selected", self.sadmin.get_actions(requestmock)) + self.assertIn("perform_delete_selected", self.sadmin.get_actions(requestmock)) diff --git a/gnuviechadmin/osusers/tests/test_forms.py b/gnuviechadmin/osusers/tests/test_forms.py index c204497..e3c5eb6 100644 --- a/gnuviechadmin/osusers/tests/test_forms.py +++ b/gnuviechadmin/osusers/tests/test_forms.py @@ -2,15 +2,13 @@ This module provides tests for :py:mod:`osusers.forms`. """ -from __future__ import absolute_import, unicode_literals - -from mock import MagicMock, Mock, patch +from unittest.mock import MagicMock, Mock, patch from django import forms -from django.core.urlresolvers import reverse from django.test import TestCase from django.contrib.auth import get_user_model +from django.urls import reverse from passlib.hash import sha512_crypt @@ -34,7 +32,7 @@ class AddSshPublicKeyFormTest(TestCase): """ def _setup_hostingpackage(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer=customer) self.hostingpackage = Mock(id=42, osuser=user) @@ -42,97 +40,92 @@ class AddSshPublicKeyFormTest(TestCase): instance = MagicMock() with self.assertRaises(KeyError) as ke: AddSshPublicKeyForm(instance) - self.assertEqual(ke.exception.args[0], 'hostingpackage') + self.assertEqual(ke.exception.args[0], "hostingpackage") def test_constructor(self): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) - self.assertTrue(hasattr(form, 'osuser')) + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) + self.assertTrue(hasattr(form, "osuser")) self.assertEqual(form.osuser, self.hostingpackage.osuser) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'add_ssh_key', kwargs={'package': self.hostingpackage.id})) - self.assertIn('publickeytext', form.fields) - self.assertEqual(form.helper.inputs[0].name, 'submit') + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("add_ssh_key", kwargs={"package": self.hostingpackage.id}), + ) + self.assertIn("publickeytext", form.fields) + self.assertEqual(form.helper.inputs[0].name, "submit") - @patch('osusers.forms.SshPublicKey.objects.parse_keytext') - def test_clean_publickeytext_invalid(self, parse_keytext): + @patch("osusers.forms.SshPublicKey.objects.parse_key_text") + def test_clean_publickeytext_invalid(self, parse_key_text): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) - form.cleaned_data = {'publickeytext': 'a bad key'} - parse_keytext.side_effect = ValueError + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {"publickeytext": "a bad key"} + parse_key_text.side_effect = ValueError with self.assertRaises(forms.ValidationError) as ve: form.clean_publickeytext() self.assertEqual(ve.exception.message, INVALID_SSH_PUBLIC_KEY) - @patch('osusers.forms.SshPublicKey.objects.parse_keytext') - def test_clean_publickeytext_valid(self, parse_keytext): + @patch("osusers.forms.SshPublicKey.objects.parse_key_text") + def test_clean_publickeytext_valid(self, _): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) - form.cleaned_data = {'publickeytext': 'good key comment'} + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {"publickeytext": "good key comment"} retval = form.clean_publickeytext() - self.assertEqual(retval, 'good key comment') + self.assertEqual(retval, "good key comment") def test_clean_none(self): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) - form.cleaned_data = {'publickeytext': None} + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) + form.cleaned_data = {"publickeytext": None} form.clean() - self.assertIsNone(form.cleaned_data['publickeytext']) + self.assertIsNone(form.cleaned_data["publickeytext"]) - @patch('osusers.forms.SshPublicKey.objects.parse_keytext') - def test_clean_fresh(self, parse_keytext): + @patch("osusers.forms.SshPublicKey.objects.parse_key_text") + def test_clean_fresh(self, parse_key_text): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) - sshpubkey = 'good key comment' - form.cleaned_data = {'publickeytext': sshpubkey} - parse_keytext.return_value = sshpubkey.split(' ') + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) + sshpubkey = "good key comment" + form.cleaned_data = {"publickeytext": sshpubkey} + parse_key_text.return_value = sshpubkey.split(" ") form.clean() - self.assertEqual( - form.cleaned_data['publickeytext'], 'good key comment') + self.assertEqual(form.cleaned_data["publickeytext"], "good key comment") - @patch('osusers.forms.SshPublicKey.objects.parse_keytext') - def test_clean_duplicate(self, parse_keytext): + @patch("osusers.forms.SshPublicKey.objects.parse_key_text") + def test_clean_duplicate(self, parse_key_text): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) SshPublicKey.objects.create( - user=self.hostingpackage.osuser, algorithm='good', data='key', - comment='comment') - sshpubkey = 'good key comment' - form.cleaned_data = {'publickeytext': sshpubkey} - parse_keytext.return_value = sshpubkey.split(' ') + user=self.hostingpackage.osuser, + algorithm="good", + data="key", + comment="comment", + ) + sshpubkey = "good key comment" + form.cleaned_data = {"publickeytext": sshpubkey} + parse_key_text.return_value = sshpubkey.split(" ") form.clean() - self.assertIn('publickeytext', form.errors) - self.assertIn( - DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, - form.errors['publickeytext']) + self.assertIn("publickeytext", form.errors) + self.assertIn(DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, form.errors["publickeytext"]) - @patch('osusers.admin.SshPublicKey.objects.parse_keytext') - def test_save(self, parse_keytext): + @patch("osusers.admin.SshPublicKey.objects.parse_key_text") + def test_save(self, parse_key_text): self._setup_hostingpackage() instance = MagicMock() - form = AddSshPublicKeyForm( - instance, hostingpackage=self.hostingpackage) - sshpubkey = 'good key comment' - form.cleaned_data = {'publickeytext': sshpubkey} - parse_keytext.return_value = sshpubkey.split(' ') + form = AddSshPublicKeyForm(instance, hostingpackage=self.hostingpackage) + sshpubkey = "good key comment" + form.cleaned_data = {"publickeytext": sshpubkey} + parse_key_text.return_value = sshpubkey.split(" ") retval = form.save() self.assertTrue(isinstance(retval, SshPublicKey)) - self.assertEqual(retval.algorithm, 'good') - self.assertEqual(retval.data, 'key') - self.assertEqual(retval.comment, 'comment') + self.assertEqual(retval.algorithm, "good") + self.assertEqual(retval.data, "key") + self.assertEqual(retval.comment, "comment") class ChangeOsUserPasswordFormTest(TestCase): @@ -142,25 +135,27 @@ class ChangeOsUserPasswordFormTest(TestCase): """ def _setup_user(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") self.user = User.objects.create_user(customer=customer) def test_constructor(self): self._setup_user() form = ChangeOsUserPasswordForm(instance=self.user) - self.assertTrue(hasattr(form, 'instance')) + self.assertTrue(hasattr(form, "instance")) self.assertEqual(form.instance, self.user) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'set_osuser_password', kwargs={'slug': self.user.username})) - self.assertEqual(form.helper.inputs[0].name, 'submit') + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("set_osuser_password", kwargs={"slug": self.user.username}), + ) + self.assertEqual(form.helper.inputs[0].name, "submit") def test_save(self): self._setup_user() form = ChangeOsUserPasswordForm(instance=self.user) - form.cleaned_data = {'password1': 'test'} + form.cleaned_data = {"password1": "test"} user = form.save() - self.assertTrue(sha512_crypt.verify('test', user.shadow.passwd)) + self.assertTrue(sha512_crypt.verify("test", user.shadow.passwd)) class EditSshPublicKeyCommentFormTest(TestCase): @@ -170,7 +165,7 @@ class EditSshPublicKeyCommentFormTest(TestCase): """ def _setup_hostingpackage(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer=customer) self.hostingpackage = Mock(id=42, osuser=user) @@ -178,18 +173,23 @@ class EditSshPublicKeyCommentFormTest(TestCase): instance = MagicMock() with self.assertRaises(KeyError) as ke: EditSshPublicKeyCommentForm(instance) - self.assertEqual(ke.exception.args[0], 'hostingpackage') + self.assertEqual(ke.exception.args[0], "hostingpackage") def test_constructor(self): self._setup_hostingpackage() instance = MagicMock(id=1) form = EditSshPublicKeyCommentForm( - instance=instance, hostingpackage=self.hostingpackage) - self.assertTrue(hasattr(form, 'osuser')) + instance=instance, hostingpackage=self.hostingpackage + ) + self.assertTrue(hasattr(form, "osuser")) self.assertEqual(form.osuser, self.hostingpackage.osuser) - self.assertIn('comment', form.fields) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'edit_ssh_key_comment', - kwargs={'package': self.hostingpackage.id, 'pk': instance.id})) - self.assertEqual(form.helper.inputs[0].name, 'submit') + self.assertIn("comment", form.fields) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse( + "edit_ssh_key_comment", + kwargs={"package": self.hostingpackage.id, "pk": instance.id}, + ), + ) + self.assertEqual(form.helper.inputs[0].name, "submit") diff --git a/gnuviechadmin/osusers/tests/test_models.py b/gnuviechadmin/osusers/tests/test_models.py index 7286683..8e0439a 100644 --- a/gnuviechadmin/osusers/tests/test_models.py +++ b/gnuviechadmin/osusers/tests/test_models.py @@ -1,19 +1,17 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from datetime import date from django.conf import settings +from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.test import TestCase from django.test.utils import override_settings from django.utils import timezone -from django.contrib.auth import get_user_model - from passlib.hash import sha512_crypt from osusers.models import ( - CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL, AdditionalGroup, + CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL, Group, Shadow, SshPublicKey, @@ -21,7 +19,6 @@ from osusers.models import ( ) from taskresults.models import TaskResult - EXAMPLE_KEY_1_RFC4716 = """---- BEGIN SSH2 PUBLIC KEY ---- Comment: "1024-bit RSA, converted from OpenSSH by me@example.com" x-command: /home/me/bin/lock-in-guest.sh @@ -57,12 +54,14 @@ n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5 sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV ---- END SSH2 PUBLIC KEY ----""" -EXAMPLE_KEY_4_OPENSSH = "".join(( - "ssh-rsa ", - "AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", - "YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", - "5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=" -)) +EXAMPLE_KEY_4_OPENSSH = "".join( + ( + "ssh-rsa ", + "AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", + "YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", + "5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=", + ) +) EXAMPLE_KEY_5_RFC4716_MULTILINE = """---- BEGIN SSH2 PUBLIC KEY ---- Comment: DSA Public Key \\ @@ -99,10 +98,7 @@ YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ 5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= ---- END SSH2 PUBLIC KEY ----""" -EXAMPLE_KEY_8_OPENSSH_BROKEN = "".join(( - "ssh-rsa ", - "AschrÖdderöd" -)) +EXAMPLE_KEY_8_OPENSSH_BROKEN = "".join(("ssh-rsa ", "AschrÖdderöd")) EXAMPLE_KEY_9_RFC4716_ONLY_HEADER = "---- BEGIN SSH2 PUBLIC KEY ----" @@ -110,9 +106,7 @@ Customer = get_user_model() @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class TestCaseWithCeleryTasks(TestCase): pass @@ -120,38 +114,44 @@ class TestCaseWithCeleryTasks(TestCase): class AdditionalGroupTest(TestCaseWithCeleryTasks): def setUp(self): - customer = Customer.objects.create(username='test') - self.group1 = Group.objects.create(groupname='test1', gid=1000) + customer = Customer.objects.create(username="test") + self.group1 = Group.objects.create(groupname="test1", gid=1000) self.user = User.objects.create( - customer=customer, username='test', uid=1000, group=self.group1, - homedir='/home/test', shell='/bin/bash') + customer=customer, + username="test", + uid=1000, + group=self.group1, + homedir="/home/test", + shell="/bin/bash", + ) def test_clean_primary_group(self): testsubj = AdditionalGroup(user=self.user, group=self.group1) with self.assertRaises(ValidationError) as cm: testsubj.clean() - self.assertEqual( - cm.exception.message, CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL) + self.assertEqual(cm.exception.message, CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL) def test_clean_other_group(self): - group2 = Group.objects.create(groupname='test2', gid=1001) + group2 = Group.objects.create(groupname="test2", gid=1001) testsubj = AdditionalGroup(user=self.user, group=group2) testsubj.clean() def test_save(self): - group2 = Group.objects.create(groupname='test2', gid=1001) + group2 = Group.objects.create(groupname="test2", gid=1001) addgroup = AdditionalGroup(user=self.user, group=group2) addgroup.save() taskres = TaskResult.objects.all() self.assertTrue(len(taskres), 4) creators = [r.creator for r in taskres] for tcount, tcreator in [ - (2, 'handle_group_created'), (1, 'handle_user_created'), - (1, 'handle_user_added_to_group')]: + (2, "handle_group_created"), + (1, "handle_user_created"), + (1, "handle_user_added_to_group"), + ]: self.assertEqual(creators.count(tcreator), tcount) def test_save_again(self): - group2 = Group.objects.create(groupname='test2', gid=1001) + group2 = Group.objects.create(groupname="test2", gid=1001) TaskResult.objects.all().delete() addgroup = AdditionalGroup(user=self.user, group=group2) addgroup.save() @@ -161,15 +161,18 @@ class AdditionalGroupTest(TestCaseWithCeleryTasks): self.assertEqual(len(taskres), 0) def test_delete(self): - group2 = Group.objects.create(groupname='test2', gid=1001) + group2 = Group.objects.create(groupname="test2", gid=1001) + # noinspection PyUnresolvedReferences addgroup = AdditionalGroup.objects.create(user=self.user, group=group2) addgroup.delete() + # noinspection PyUnresolvedReferences self.assertEqual(len(AdditionalGroup.objects.all()), 0) def test___str__(self): - group2 = Group.objects.create(groupname='test2', gid=1001) + group2 = Group.objects.create(groupname="test2", gid=1001) + # noinspection PyUnresolvedReferences addgroup = AdditionalGroup.objects.create(user=self.user, group=group2) - self.assertEqual(str(addgroup), 'test (1000) in test2 (1001)') + self.assertEqual(str(addgroup), "test (1000) in test2 (1001)") @override_settings(OSUSER_MINGID=10000) @@ -178,56 +181,61 @@ class GroupManagerTest(TestCaseWithCeleryTasks): self.assertEqual(Group.objects.get_next_gid(), 10000) def test_get_next_gid_second(self): - Group.objects.create(gid=10010, groupname='test') + Group.objects.create(gid=10010, groupname="test") self.assertEqual(Group.objects.get_next_gid(), 10011) class GroupTest(TestCaseWithCeleryTasks): def test___str__(self): - group = Group.objects.create(gid=10000, groupname='test') - self.assertEqual(str(group), 'test (10000)') + group = Group.objects.create(gid=10000, groupname="test") + self.assertEqual(str(group), "test (10000)") def test_save(self): - group = Group(gid=10000, groupname='test') + group = Group(gid=10000, groupname="test") self.assertIs(group.save(), group) taskres = TaskResult.objects.all() self.assertTrue(len(taskres), 1) creators = [r.creator for r in taskres] - for tcount, tcreator in [ - (1, 'handle_group_created')]: + for tcount, tcreator in [(1, "handle_group_created")]: self.assertEqual(creators.count(tcreator), tcount) def test_save_again(self): - group = Group.objects.create(gid=10000, groupname='test') - taskres = TaskResult.objects.all().delete() + group = Group.objects.create(gid=10000, groupname="test") + TaskResult.objects.all().delete() group.save() taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 0) def test_delete(self): - group = Group.objects.create(gid=10000, groupname='test') + group = Group.objects.create(gid=10000, groupname="test") self.assertEqual(len(Group.objects.all()), 1) group.delete() self.assertEqual(len(Group.objects.all()), 0) self.assertEqual(len(TaskResult.objects.all()), 2) tr = TaskResult.objects.first() - self.assertEqual(tr.creator, 'handle_group_created') + self.assertEqual(tr.creator, "handle_group_created") class ShadowManagerTest(TestCaseWithCeleryTasks): def setUp(self): - self.customer = Customer.objects.create(username='test') + self.customer = Customer.objects.create(username="test") super(ShadowManagerTest, self).setUp() def test_create_shadow(self): - user = User( - customer=self.customer, username='test', uid=1000, - group=Group(gid=1000, groupname='test'), homedir='/home/test', - shell='/bin/fooshell') - shadow = Shadow.objects.create_shadow(user, 'test') - self.assertTrue(sha512_crypt.verify('test', shadow.passwd)) - self.assertEqual(shadow.changedays, - (timezone.now().date() - date(1970, 1, 1)).days) + group = Group.objects.create(gid=1000, groupname="test") + user = User.objects.create( + customer=self.customer, + username="test", + uid=1000, + group=group, + homedir="/home/test", + shell="/bin/fooshell", + ) + shadow = Shadow.objects.create_shadow(user, "test") + self.assertTrue(sha512_crypt.verify("test", shadow.passwd)) + self.assertEqual( + shadow.changedays, (timezone.now().date() - date(1970, 1, 1)).days + ) self.assertEqual(shadow.user, user) self.assertEqual(shadow.minage, 0) self.assertIsNone(shadow.maxage) @@ -238,39 +246,50 @@ class ShadowManagerTest(TestCaseWithCeleryTasks): class ShadowTest(TestCaseWithCeleryTasks): def setUp(self): - self.customer = Customer.objects.create(username='test') + self.customer = Customer.objects.create(username="test") super(ShadowTest, self).setUp() def test___str__(self): - group = Group.objects.create( - groupname='test', gid=1000) + group = Group.objects.create(groupname="test", gid=1000) user = User.objects.create( - customer=self.customer, username='test', uid=1000, group=group, - homedir='/home/test', shell='/bin/bash') + customer=self.customer, + username="test", + uid=1000, + group=group, + homedir="/home/test", + shell="/bin/bash", + ) shadow = Shadow(user=user) - self.assertEqual(str(shadow), 'for user test (1000)') + self.assertEqual(str(shadow), "for user test (1000)") def test_set_password(self): - group = Group.objects.create( - groupname='test', gid=1000) + group = Group.objects.create(groupname="test", gid=1000) user = User.objects.create( - customer=self.customer, username='test', uid=1000, group=group, - homedir='/home/test', shell='/bin/bash') + customer=self.customer, + username="test", + uid=1000, + group=group, + homedir="/home/test", + shell="/bin/bash", + ) shadow = Shadow(user=user) - shadow.set_password('test') - self.assertTrue(sha512_crypt.verify('test', shadow.passwd)) + shadow.set_password("test") + self.assertTrue(sha512_crypt.verify("test", shadow.passwd)) @override_settings( - OSUSER_MINUID=10000, OSUSER_MINGID=10000, OSUSER_USERNAME_PREFIX='test', - OSUSER_HOME_BASEPATH='/home', OSUSER_DEFAULT_SHELL='/bin/fooshell' + OSUSER_MINUID=10000, + OSUSER_MINGID=10000, + OSUSER_USERNAME_PREFIX="test", + OSUSER_HOME_BASEPATH="/home", + OSUSER_DEFAULT_SHELL="/bin/fooshell", ) class UserManagerTest(TestCaseWithCeleryTasks): def _create_group(self): - return Group.objects.create(gid=10000, groupname='foo') + return Group.objects.create(gid=10000, groupname="foo") def setUp(self): - self.customer = Customer.objects.create(username='test') + self.customer = Customer.objects.create(username="test") super(UserManagerTest, self).setUp() def test_get_next_uid_first(self): @@ -278,40 +297,58 @@ class UserManagerTest(TestCaseWithCeleryTasks): def test_get_next_uid_second(self): User.objects.create( - customer=self.customer, uid=10010, username='foo', - group=self._create_group(), homedir='/home/foo', - shell='/bin/fooshell') + customer=self.customer, + uid=10010, + username="foo", + group=self._create_group(), + homedir="/home/foo", + shell="/bin/fooshell", + ) self.assertEqual(User.objects.get_next_uid(), 10011) def test_get_next_username_first(self): - self.assertEqual(User.objects.get_next_username(), 'test01') + self.assertEqual(User.objects.get_next_username(), "test01") def test_get_next_username_second(self): User.objects.create( - customer=self.customer, uid=10000, username='test01', - group=self._create_group(), homedir='/home/foo', - shell='/bin/fooshell') - self.assertEqual(User.objects.get_next_username(), 'test02') + customer=self.customer, + uid=10000, + username="test01", + group=self._create_group(), + homedir="/home/foo", + shell="/bin/fooshell", + ) + self.assertEqual(User.objects.get_next_username(), "test02") def test_get_next_username_gaps(self): group = self._create_group() User.objects.create( - customer=self.customer, uid=10000, username='test01', group=group, - homedir='/home/foo', shell='/bin/fooshell') + customer=self.customer, + uid=10000, + username="test01", + group=group, + homedir="/home/foo", + shell="/bin/fooshell", + ) User.objects.create( - customer=self.customer, uid=10002, username='test03', group=group, - homedir='/home/foo', shell='/bin/fooshell') - self.assertEqual(User.objects.get_next_username(), 'test02') + customer=self.customer, + uid=10002, + username="test03", + group=group, + homedir="/home/foo", + shell="/bin/fooshell", + ) + self.assertEqual(User.objects.get_next_username(), "test02") def test_create_user_first(self): user = User.objects.create_user(customer=self.customer) self.assertIsInstance(user, User) self.assertEqual(user.uid, 10000) self.assertEqual(user.group.gid, 10000) - self.assertEqual(user.group.groupname, 'test01') - self.assertEqual(user.username, 'test01') - self.assertEqual(user.homedir, '/home/test01') - self.assertEqual(user.shell, '/bin/fooshell') + self.assertEqual(user.group.groupname, "test01") + self.assertEqual(user.username, "test01") + self.assertEqual(user.homedir, "/home/test01") + self.assertEqual(user.shell, "/bin/fooshell") self.assertIsNotNone(user.shadow) def test_create_user_tasks(self): @@ -320,8 +357,10 @@ class UserManagerTest(TestCaseWithCeleryTasks): self.assertEqual(len(taskres), 3) creators = [r.creator for r in taskres] for creator in [ - 'handle_group_created', 'handle_user_created', - 'handle_user_password_set']: + "handle_group_created", + "handle_user_created", + "handle_user_password_set", + ]: self.assertIn(creator, creators) def test_create_user_second(self): @@ -330,36 +369,34 @@ class UserManagerTest(TestCaseWithCeleryTasks): self.assertIsInstance(user, User) self.assertEqual(user.uid, 10001) self.assertEqual(user.group.gid, 10001) - self.assertEqual(user.group.groupname, 'test02') - self.assertEqual(user.username, 'test02') - self.assertEqual(user.homedir, '/home/test02') - self.assertEqual(user.shell, '/bin/fooshell') + self.assertEqual(user.group.groupname, "test02") + self.assertEqual(user.username, "test02") + self.assertEqual(user.homedir, "/home/test02") + self.assertEqual(user.shell, "/bin/fooshell") self.assertIsNotNone(user.shadow) self.assertEqual(len(User.objects.all()), 2) def test_create_user_known_password(self): - user = User.objects.create_user( - customer=self.customer, password='foobar') + user = User.objects.create_user(customer=self.customer, password="foobar") self.assertIsInstance(user, User) self.assertEqual(user.uid, 10000) self.assertEqual(user.group.gid, 10000) - self.assertEqual(user.group.groupname, 'test01') - self.assertEqual(user.username, 'test01') - self.assertEqual(user.homedir, '/home/test01') - self.assertEqual(user.shell, '/bin/fooshell') + self.assertEqual(user.group.groupname, "test01") + self.assertEqual(user.username, "test01") + self.assertEqual(user.homedir, "/home/test01") + self.assertEqual(user.shell, "/bin/fooshell") self.assertIsNotNone(user.shadow) - self.assertTrue(sha512_crypt.verify('foobar', user.shadow.passwd)) + self.assertTrue(sha512_crypt.verify("foobar", user.shadow.passwd)) def test_create_user_predefined_username(self): - user = User.objects.create_user( - customer=self.customer, username='tester') + user = User.objects.create_user(customer=self.customer, username="tester") self.assertIsInstance(user, User) self.assertEqual(user.uid, 10000) self.assertEqual(user.group.gid, 10000) - self.assertEqual(user.group.groupname, 'tester') - self.assertEqual(user.username, 'tester') - self.assertEqual(user.homedir, '/home/tester') - self.assertEqual(user.shell, '/bin/fooshell') + self.assertEqual(user.group.groupname, "tester") + self.assertEqual(user.username, "tester") + self.assertEqual(user.homedir, "/home/tester") + self.assertEqual(user.shell, "/bin/fooshell") self.assertIsNotNone(user.shadow) def test_create_user_commit(self): @@ -367,31 +404,34 @@ class UserManagerTest(TestCaseWithCeleryTasks): self.assertIsInstance(user, User) self.assertEqual(user.uid, 10000) self.assertEqual(user.group.gid, 10000) - self.assertEqual(user.group.groupname, 'test01') - self.assertEqual(user.username, 'test01') - self.assertEqual(user.homedir, '/home/test01') - self.assertEqual(user.shell, '/bin/fooshell') + self.assertEqual(user.group.groupname, "test01") + self.assertEqual(user.username, "test01") + self.assertEqual(user.homedir, "/home/test01") + self.assertEqual(user.shell, "/bin/fooshell") self.assertIsNotNone(user.shadow) @override_settings( - OSUSER_MINUID=10000, OSUSER_MINGID=10000, OSUSER_USERNAME_PREFIX='test', - OSUSER_HOME_BASEPATH='/home', OSUSER_DEFAULT_SHELL='/bin/fooshell' + OSUSER_MINUID=10000, + OSUSER_MINGID=10000, + OSUSER_USERNAME_PREFIX="test", + OSUSER_HOME_BASEPATH="/home", + OSUSER_DEFAULT_SHELL="/bin/fooshell", ) class UserTest(TestCaseWithCeleryTasks): def setUp(self): - self.customer = Customer.objects.create_user('test') + self.customer = Customer.objects.create_user("test") super(UserTest, self).setUp() def test___str__(self): user = User.objects.create_user(self.customer) - self.assertEqual(str(user), 'test01 (10000)') + self.assertEqual(str(user), "test01 (10000)") def test_set_password(self): user = User.objects.create_user(self.customer) - self.assertFalse(sha512_crypt.verify('test', user.shadow.passwd)) - user.set_password('test') - self.assertTrue(sha512_crypt.verify('test', user.shadow.passwd)) + self.assertFalse(sha512_crypt.verify("test", user.shadow.passwd)) + user.set_password("test") + self.assertTrue(sha512_crypt.verify("test", user.shadow.passwd)) def test_save(self): user = User.objects.create_user(self.customer) @@ -400,8 +440,10 @@ class UserTest(TestCaseWithCeleryTasks): self.assertEqual(len(taskres), 3) creators = [r.creator for r in taskres] for task in [ - 'handle_group_created', 'handle_user_created', - 'handle_user_password_set']: + "handle_group_created", + "handle_user_created", + "handle_user_password_set", + ]: self.assertIn(task, creators) def test_delete_only_user(self): @@ -411,30 +453,38 @@ class UserTest(TestCaseWithCeleryTasks): self.assertEqual(len(taskres), 6) creators = [r.creator for r in taskres] for task in [ - 'handle_group_created', 'handle_user_created', - 'handle_user_password_set', 'handle_user_deleted', - 'handle_group_deleted', 'handle_user_deleted']: + "handle_group_created", + "handle_user_created", + "handle_user_password_set", + "handle_user_deleted", + "handle_group_deleted", + "handle_user_deleted", + ]: self.assertIn(task, creators) self.assertEqual(len(User.objects.all()), 0) def test_delete_additional_groups(self): - group1 = Group.objects.create(gid=2000, groupname='group1') - group2 = Group.objects.create(gid=2001, groupname='group2') + group1 = Group.objects.create(gid=2000, groupname="group1") + group2 = Group.objects.create(gid=2001, groupname="group2") user = User.objects.create_user(self.customer) for group in [group1, group2]: + # noinspection PyUnresolvedReferences user.additionalgroup_set.add( - AdditionalGroup.objects.create(user=user, group=group)) + AdditionalGroup.objects.create(user=user, group=group) + ) TaskResult.objects.all().delete() user.delete() taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 5) creators = [t.creator for t in taskres] for tcount, tcreator in [ - (2, 'handle_user_removed_from_group'), - (2, 'handle_user_deleted'), - (1, 'handle_group_deleted')]: + (2, "handle_user_removed_from_group"), + (2, "handle_user_deleted"), + (1, "handle_group_deleted"), + ]: self.assertEqual(creators.count(tcreator), tcount) self.assertEqual(len(User.objects.all()), 0) + # noinspection PyUnresolvedReferences self.assertEqual(len(AdditionalGroup.objects.all()), 0) def test_is_sftp_user(self): @@ -442,138 +492,140 @@ class UserTest(TestCaseWithCeleryTasks): self.assertFalse(user.is_sftp_user()) sftp_group = Group.objects.create( - gid=2000, groupname=settings.OSUSER_SFTP_GROUP) + gid=2000, groupname=settings.OSUSER_SFTP_GROUP + ) + # noinspection PyUnresolvedReferences user.additionalgroup_set.add( - AdditionalGroup.objects.create(user=user, group=sftp_group)) + AdditionalGroup.objects.create(user=user, group=sftp_group) + ) self.assertTrue(user.is_sftp_user()) class SshPublicKeyManagerTest(TestCaseWithCeleryTasks): def test_parse_keytext_rfc4716_1(self): - res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_1_RFC4716) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_1_RFC4716) self.assertEqual(len(res), 3) self.assertGreater(len(res[1]), 40) - self.assertEqual(res[0], 'ssh-rsa') + self.assertEqual(res[0], "ssh-rsa") self.assertEqual( - res[2], '"1024-bit RSA, converted from OpenSSH by me@example.com"') + res[2], '"1024-bit RSA, converted from OpenSSH by me@example.com"' + ) def test_parse_keytext_rfc4716_2(self): - res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_2_RFC4716) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_2_RFC4716) self.assertEqual(len(res), 3) - self.assertEqual(res[0], 'ssh-dss') + self.assertEqual(res[0], "ssh-dss") self.assertGreater(len(res[1]), 40) self.assertEqual( - res[2], - "This is my public key for use on servers which I don't like.") + res[2], "This is my public key for use on servers which I don't like." + ) def test_parse_keytext_rfc4716_3(self): - res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_3_RFC4716) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_3_RFC4716) self.assertEqual(len(res), 3) - self.assertEqual(res[0], 'ssh-dss') + self.assertEqual(res[0], "ssh-dss") self.assertGreater(len(res[1]), 40) self.assertEqual(res[2], "DSA Public Key for use with MyIsp") def test_parse_keytext_openssh(self): - res = SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_4_OPENSSH) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_4_OPENSSH) self.assertEquals(len(res), 3) - self.assertEqual(res[0], 'ssh-rsa') + self.assertEqual(res[0], "ssh-rsa") self.assertGreater(len(res[1]), 40) - self.assertEqual(res[2], '') + self.assertEqual(res[2], "") def test_parse_keytext_invalid_multiline(self): with self.assertRaises(ValueError): - SshPublicKey.objects.parse_keytext("\r\n".join(["xx"]*10)) + SshPublicKey.objects.parse_key_text("\r\n".join(["xx"] * 10)) def test_parse_keytext_empty_line(self): - res = SshPublicKey.objects.parse_keytext( - EXAMPLE_KEY_6_RFC4716_EMPTY_LINE) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_6_RFC4716_EMPTY_LINE) self.assertEqual(len(res), 3) - self.assertEqual(res[0], 'ssh-dss') + self.assertEqual(res[0], "ssh-dss") self.assertGreater(len(res[1]), 40) self.assertEqual(res[2], "DSA Public Key for use with MyIsp") def test_parse_keytext_invalid_empty_rfc4716_header(self): with self.assertRaises(ValueError): - SshPublicKey.objects.parse_keytext( - EXAMPLE_KEY_9_RFC4716_ONLY_HEADER) + SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_9_RFC4716_ONLY_HEADER) def test_parse_keytext_no_comment(self): - res = SshPublicKey.objects.parse_keytext( - EXAMPLE_KEY_7_NO_COMMENT) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_7_NO_COMMENT) self.assertEqual(len(res), 3) - self.assertEqual(res[0], 'ssh-rsa') + self.assertEqual(res[0], "ssh-rsa") self.assertGreater(len(res[1]), 40) - self.assertEqual(res[2], '') + self.assertEqual(res[2], "") def test_parse_keytext_multiline_comment(self): - res = SshPublicKey.objects.parse_keytext( - EXAMPLE_KEY_5_RFC4716_MULTILINE) + res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_5_RFC4716_MULTILINE) self.assertEqual(len(res), 3) - self.assertEqual(res[0], 'ssh-dss') + self.assertEqual(res[0], "ssh-dss") self.assertGreater(len(res[1]), 40) self.assertEqual(res[2], "DSA Public Key for use with MyIsp") def test_parse_keytext_invalid(self): with self.assertRaises(ValueError): - SshPublicKey.objects.parse_keytext('invalid') + SshPublicKey.objects.parse_key_text("invalid") def test_parse_keytext_invalid_openssh(self): with self.assertRaises(ValueError): - SshPublicKey.objects.parse_keytext(EXAMPLE_KEY_8_OPENSSH_BROKEN) + SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_8_OPENSSH_BROKEN) def test_create_ssh_public_key(self): - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") user = User.objects.create_user(customer) - key = SshPublicKey.objects.create_ssh_public_key( - user, EXAMPLE_KEY_4_OPENSSH) + key = SshPublicKey.objects.create_ssh_public_key(user, EXAMPLE_KEY_4_OPENSSH) self.assertIsInstance(key, SshPublicKey) self.assertEqual(key.user, user) - self.assertEqual(key.algorithm, 'ssh-rsa') + self.assertEqual(key.algorithm, "ssh-rsa") self.assertEqual(key.data, EXAMPLE_KEY_4_OPENSSH.split()[1]) - self.assertEqual(key.comment, '') + self.assertEqual(key.comment, "") class SshPublicKeyTest(TestCaseWithCeleryTasks): def setUp(self): super(SshPublicKeyTest, self).setUp() - customer = Customer.objects.create_user('test') + customer = Customer.objects.create_user("test") self.user = User.objects.create_user(customer) TaskResult.objects.all().delete() def test__str__rfc4716(self): res = SshPublicKey.objects.create_ssh_public_key( - self.user, EXAMPLE_KEY_3_RFC4716) + self.user, EXAMPLE_KEY_3_RFC4716 + ) + self.maxDiff = None self.assertEqual( - str(res), 'ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxae' - 'hvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7St' - 'xyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3g' - 'Jq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4' - 'KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/F' - 'XPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVc' - 'GcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LA' - 'o2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWK' - 'jjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP' - '+CDqzCM4loWgV DSA Public Key for use with MyIsp') + str(res), + "ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxae" + "hvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7St" + "xyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3g" + "Jq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4" + "KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/F" + "XPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVc" + "GcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LA" + "o2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWK" + "jjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP" + "+CDqzCM4loWgV DSA Public Key for use with MyIsp", + ) def test__str__openssh(self): res = SshPublicKey.objects.create_ssh_public_key( - self.user, EXAMPLE_KEY_4_OPENSSH) + self.user, EXAMPLE_KEY_4_OPENSSH + ) self.assertEqual(str(res), EXAMPLE_KEY_4_OPENSSH) def test_call_tasks_on_save(self): - SshPublicKey.objects.create_ssh_public_key( - self.user, EXAMPLE_KEY_4_OPENSSH) + SshPublicKey.objects.create_ssh_public_key(self.user, EXAMPLE_KEY_4_OPENSSH) taskresults = TaskResult.objects.all() self.assertEqual(len(taskresults), 1) - self.assertEqual( - taskresults[0].creator, 'handle_ssh_keys_changed') + self.assertEqual(taskresults[0].creator, "handle_ssh_keys_changed") def test_call_tasks_on_delete(self): key = SshPublicKey.objects.create_ssh_public_key( - self.user, EXAMPLE_KEY_4_OPENSSH) + self.user, EXAMPLE_KEY_4_OPENSSH + ) TaskResult.objects.all().delete() key.delete() taskresults = TaskResult.objects.all() self.assertEqual(len(taskresults), 1) - self.assertEqual( - taskresults[0].creator, 'handle_ssh_keys_changed') + self.assertEqual(taskresults[0].creator, "handle_ssh_keys_changed") diff --git a/gnuviechadmin/osusers/tests/test_views.py b/gnuviechadmin/osusers/tests/test_views.py index 401bbdf..7c0d2bb 100644 --- a/gnuviechadmin/osusers/tests/test_views.py +++ b/gnuviechadmin/osusers/tests/test_views.py @@ -2,66 +2,59 @@ This module provides tests for :py:mod:`osusers.views`. """ -from __future__ import absolute_import, unicode_literals -try: - from unittest.mock import patch, MagicMock -except ImportError: - from mock import patch, MagicMock +from unittest.mock import patch, MagicMock -from django.core.urlresolvers import reverse from django.test import TestCase, TransactionTestCase from django.contrib.auth import get_user_model +from django.urls import reverse -from hostingpackages.models import ( - CustomerHostingPackage, - HostingPackageTemplate, -) +from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate from osusers.models import SshPublicKey -from osusers.views import ( - AddSshPublicKey, - DeleteSshPublicKey, - EditSshPublicKeyComment, -) +from osusers.views import AddSshPublicKey, DeleteSshPublicKey, EditSshPublicKeyComment User = get_user_model() -TEST_USER = 'test' -TEST_PASSWORD = 'secret' -TEST_EMAIL = 'test@example.org' -EXAMPLE_KEY = "".join(( - "ssh-rsa ", - "AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", - "YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", - "5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=" -)) +TEST_USER = "test" +TEST_PASSWORD = "secret" +TEST_EMAIL = "test@example.org" +EXAMPLE_KEY = "".join( + ( + "ssh-rsa ", + "AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb", + "YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ", + "5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=", + ) +) class HostingPackageAwareTestMixin(object): + # noinspection PyMethodMayBeStatic def _setup_hosting_package(self, customer): template = HostingPackageTemplate.objects.create( - name='testpackagetemplate', mailboxcount=10, diskspace=1, - diskspace_unit=0) + name="testpackagetemplate", mailboxcount=10, diskspace=1, diskspace_unit=0 + ) package = CustomerHostingPackage.objects.create_from_template( - customer, template, 'testpackage') - with patch('hostingpackages.models.settings') as hmsettings: + customer, template, "testpackage" + ) + with patch("hostingpackages.models.settings") as hmsettings: hmsettings.OSUSER_DEFAULT_GROUPS = [] package.save() return package class AddSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) def _get_url(self): - return reverse('add_ssh_key', kwargs={'package': self.package.id}) + return reverse("add_ssh_key", kwargs={"package": self.package.id}) def test_get_anonymous(self): response = self.client.get(self._get_url()) @@ -73,77 +66,75 @@ class AddSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase): self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertTemplateUsed(response, 'osusers/sshpublickey_create.html') + self.assertTemplateUsed(response, "osusers/sshpublickey_create.html") def test_get_form_kwargs(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = AddSshPublicKey( - request=MagicMock(), kwargs={'package': str(self.package.pk)}) + request=MagicMock(), kwargs={"package": str(self.package.pk)} + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) - self.assertIn('osuser', response.context) - self.assertEqual( - response.context['osuser'], self.package.osuser.username) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) + self.assertIn("osuser", response.context) + self.assertEqual(response.context["osuser"], self.package.osuser.username) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - self._get_url(), - data={'publickeytext': EXAMPLE_KEY}) + self._get_url(), data={"publickeytext": EXAMPLE_KEY} + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - self._get_url(), follow=True, - data={'publickeytext': EXAMPLE_KEY}) - messages = list(response.context['messages']) + self._get_url(), follow=True, data={"publickeytext": EXAMPLE_KEY} + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( - 'Successfully added new ssh-rsa SSH public key.'.format( + "Successfully added new ssh-rsa SSH public key.".format( username=self.package.osuser.username - ), str(messages[0])) + ), + str(messages[0]), + ) class DeleteSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) self.sshkey = SshPublicKey.objects.create( - user=self.package.osuser, algorithm='good', data='key', - comment='comment') + user=self.package.osuser, algorithm="good", data="key", comment="comment" + ) def _get_url(self): return reverse( - 'delete_ssh_key', kwargs={ - 'package': self.package.id, - 'pk': self.sshkey.id - }) + "delete_ssh_key", kwargs={"package": self.package.id, "pk": self.sshkey.id} + ) def test_get_anonymous(self): response = self.client.get(self._get_url()) @@ -155,71 +146,63 @@ class DeleteSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase): self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertTemplateUsed( - response, 'osusers/sshpublickey_confirm_delete.html') + self.assertTemplateUsed(response, "osusers/sshpublickey_confirm_delete.html") def test_get_queryset(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = DeleteSshPublicKey( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'pk': str(self.sshkey.pk) - }) + request=MagicMock(), + kwargs={"package": str(self.package.pk), "pk": str(self.sshkey.pk)}, + ) queryset = view.get_queryset() self.assertQuerysetEqual(queryset, [repr(self.sshkey)]) def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - for key in ('hostingpackage', 'customer', 'osuser'): + for key in ("hostingpackage", "customer", "osuser"): self.assertIn(key, response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertEqual(response.context['customer'], self.customer) - self.assertEqual( - response.context['osuser'], self.package.osuser.username) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertEqual(response.context["customer"], self.customer) + self.assertEqual(response.context["osuser"], self.package.osuser.username) def test_get_success_url(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post( - self._get_url(), - data={'comment': 'new comment'}) - self.assertRedirects(response, reverse('list_ssh_keys', kwargs={ - 'package': self.package.id})) + response = self.client.post(self._get_url(), data={"comment": "new comment"}) + self.assertRedirects( + response, reverse("list_ssh_keys", kwargs={"package": self.package.id}) + ) -class EditSshPublicKeyCommentTest( - HostingPackageAwareTestMixin, TransactionTestCase): - +class EditSshPublicKeyCommentTest(HostingPackageAwareTestMixin, TransactionTestCase): def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) self.sshkey = SshPublicKey.objects.create( - user=self.package.osuser, algorithm='good', data='key', - comment='comment') + user=self.package.osuser, algorithm="good", data="key", comment="comment" + ) def _get_url(self): return reverse( - 'edit_ssh_key_comment', kwargs={ - 'package': self.package.id, - 'pk': self.sshkey.id - }) + "edit_ssh_key_comment", + kwargs={"package": self.package.id, "pk": self.sshkey.id}, + ) def test_get_anonymous(self): response = self.client.get(self._get_url()) @@ -231,74 +214,67 @@ class EditSshPublicKeyCommentTest( self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertTemplateUsed( - response, 'osusers/sshpublickey_edit_comment.html') + self.assertTemplateUsed(response, "osusers/sshpublickey_edit_comment.html") def test_get_queryset(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = EditSshPublicKeyComment( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'pk': str(self.sshkey.pk) - }) + request=MagicMock(), + kwargs={"package": str(self.package.pk), "pk": str(self.sshkey.pk)}, + ) queryset = view.get_queryset() self.assertQuerysetEqual(queryset, [repr(self.sshkey)]) def test_get_form_kwargs(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = EditSshPublicKeyComment( - request=MagicMock(), kwargs={ - 'package': str(self.package.pk), - 'pk': str(self.sshkey.pk) - }) + request=MagicMock(), + kwargs={"package": str(self.package.pk), "pk": str(self.sshkey.pk)}, + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - for key in ('hostingpackage', 'customer', 'osuser'): + for key in ("hostingpackage", "customer", "osuser"): self.assertIn(key, response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertEqual(response.context['customer'], self.customer) - self.assertEqual( - response.context['osuser'], self.package.osuser.username) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertEqual(response.context["customer"], self.customer) + self.assertEqual(response.context["osuser"], self.package.osuser.username) def test_get_success_url(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post( - self._get_url(), - data={'comment': 'new comment'}) - self.assertRedirects(response, reverse('list_ssh_keys', kwargs={ - 'package': self.package.id})) + response = self.client.post(self._get_url(), data={"comment": "new comment"}) + self.assertRedirects( + response, reverse("list_ssh_keys", kwargs={"package": self.package.id}) + ) class ListSshPublicKeysTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) def _get_url(self): - return reverse('list_ssh_keys', kwargs={'package': self.package.id}) + return reverse("list_ssh_keys", kwargs={"package": self.package.id}) def test_get_anonymous(self): response = self.client.get(self._get_url()) @@ -310,45 +286,43 @@ class ListSshPublicKeysTest(HostingPackageAwareTestMixin, TestCase): self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertTemplateUsed(response, 'osusers/sshpublickey_list.html') + self.assertTemplateUsed(response, "osusers/sshpublickey_list.html") def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - for key in ('hostingpackage', 'customer', 'osuser'): + for key in ("hostingpackage", "customer", "osuser"): self.assertIn(key, response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertEqual(response.context['customer'], self.customer) - self.assertEqual( - response.context['osuser'], self.package.osuser.username) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertEqual(response.context["customer"], self.customer) + self.assertEqual(response.context["osuser"], self.package.osuser.username) class SetOsUserPasswordTest(HostingPackageAwareTestMixin, TestCase): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) def _get_url(self): - return reverse('set_osuser_password', kwargs={ - 'slug': self.package.osuser.username}) + return reverse( + "set_osuser_password", kwargs={"slug": self.package.osuser.username} + ) def test_get_anonymous(self): response = self.client.get(self._get_url()) @@ -360,45 +334,48 @@ class SetOsUserPasswordTest(HostingPackageAwareTestMixin, TestCase): self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertTemplateUsed(response, 'osusers/user_setpassword.html') + self.assertTemplateUsed(response, "osusers/user_setpassword.html") def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( self._get_url(), - data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - self._get_url(), follow=True, - data={'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) - messages = list(response.context['messages']) + self._get_url(), + follow=True, + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( - 'New password for {username} has been set successfully.'.format( + "New password for {username} has been set successfully.".format( username=self.package.osuser.username - ), str(messages[0])) + ), + str(messages[0]), + ) diff --git a/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py b/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py index 6b533f0..a7d10ff 100644 --- a/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py +++ b/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py @@ -4,39 +4,32 @@ This module provides tests for the command. """ -from __future__ import unicode_literals -try: - from unittest.mock import patch, MagicMock -except ImportError: - from mock import patch, MagicMock +from unittest.mock import MagicMock, patch from django.test import TestCase +from taskresults.management.commands.fetch_taskresults import Command from taskresults.models import TaskResult -from taskresults.management.commands.fetch_taskresults import Command +TEST_TASK_UUID = "3120f6a8-2665-4fa3-a785-79efd28bfe92" +TEST_TASK_NAME = "test.task" +TEST_TASK_RESULT = "4ll y0ur b453 4r3 b3l0ng t0 u5" -TEST_TASK_UUID = '3120f6a8-2665-4fa3-a785-79efd28bfe92' -TEST_TASK_NAME = 'test.task' -TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5' - - -@patch('taskresults.models.app.AsyncResult') +@patch("taskresults.models.app.AsyncResult") class FetchTaskResultsCommandTest(TestCase): - def test_handle_unfinished(self, asyncresult): resultmock = MagicMock(task_id=TEST_TASK_UUID) sigmock = MagicMock() sigmock.apply_async.return_value = resultmock tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) self.assertFalse(tr.finished) - self.assertEqual(tr.result, '') - self.assertEqual(tr.state, '') + self.assertEqual(tr.result, "") + self.assertEqual(tr.state, "") aresult = asyncresult.return_value - aresult.state = 'PENDING' + aresult.state = "PENDING" aresult.ready.return_value = False Command().handle() @@ -45,8 +38,8 @@ class FetchTaskResultsCommandTest(TestCase): self.assertTrue(asyncresult.called_with(TEST_TASK_UUID)) self.assertTrue(aresult.ready.called_with()) self.assertFalse(tr.finished) - self.assertEqual(tr.result, '') - self.assertEqual(tr.state, 'PENDING') + self.assertEqual(tr.result, "") + self.assertEqual(tr.state, "PENDING") def test_handle_finished(self, asyncresult): resultmock = MagicMock(task_id=TEST_TASK_UUID) @@ -54,11 +47,11 @@ class FetchTaskResultsCommandTest(TestCase): sigmock.apply_async.return_value = resultmock tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) self.assertFalse(tr.finished) - self.assertEqual(tr.result, '') - self.assertEqual(tr.state, '') + self.assertEqual(tr.result, "") + self.assertEqual(tr.state, "") aresult = asyncresult.return_value - aresult.state = 'SUCCESS' + aresult.state = "SUCCESS" aresult.ready.return_value = True aresult.get.return_value = TEST_TASK_RESULT @@ -70,4 +63,4 @@ class FetchTaskResultsCommandTest(TestCase): self.assertTrue(aresult.get.called_with()) self.assertTrue(tr.finished) self.assertEqual(tr.result, TEST_TASK_RESULT) - self.assertEqual(tr.state, 'SUCCESS') + self.assertEqual(tr.state, "SUCCESS") diff --git a/gnuviechadmin/taskresults/tests/test_models.py b/gnuviechadmin/taskresults/tests/test_models.py index 68d7a87..fe6445d 100644 --- a/gnuviechadmin/taskresults/tests/test_models.py +++ b/gnuviechadmin/taskresults/tests/test_models.py @@ -2,25 +2,19 @@ This module provides tests for :py:mod:`taskresults.models`. """ -from __future__ import absolute_import, unicode_literals - -try: - from unittest.mock import patch, MagicMock -except ImportError: - from mock import patch, MagicMock +from unittest.mock import MagicMock, patch from django.test import TestCase from taskresults.models import TaskResult - -TEST_TASK_UUID = '3120f6a8-2665-4fa3-a785-79efd28bfe92' -TEST_TASK_NAME = 'test.task' -TEST_TASK_RESULT = '4ll y0ur b453 4r3 b3l0ng t0 u5' +TEST_TASK_UUID = "3120f6a8-2665-4fa3-a785-79efd28bfe92" +TEST_TASK_NAME = "test.task" +TEST_TASK_RESULT = "4ll y0ur b453 4r3 b3l0ng t0 u5" class TaskResultTest(TestCase): - @patch('taskresults.models.app.AsyncResult') + @patch("taskresults.models.app.AsyncResult") def test_update_taskstatus_unfinished(self, asyncresult): resultmock = MagicMock(task_id=TEST_TASK_UUID) sigmock = MagicMock() @@ -28,13 +22,13 @@ class TaskResultTest(TestCase): tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) self.assertFalse(tr.finished) mymock = asyncresult.return_value - mymock.state = 'PENDING' + mymock.state = "PENDING" mymock.ready.return_value = False tr.fetch_result() mymock.get.assert_not_called() self.assertFalse(tr.finished) - @patch('taskresults.models.app.AsyncResult') + @patch("taskresults.models.app.AsyncResult") def test_update_taskstatus_finished(self, asyncresult): resultmock = MagicMock(task_id=TEST_TASK_UUID) sigmock = MagicMock() @@ -43,7 +37,7 @@ class TaskResultTest(TestCase): tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) self.assertFalse(tr.finished) aresult = asyncresult.return_value - aresult.state = 'SUCCESS' + aresult.state = "SUCCESS" aresult.ready.return_value = True aresult.get.return_value = TEST_TASK_RESULT tr.fetch_result() @@ -61,11 +55,15 @@ class TaskResultTest(TestCase): sigmock = MagicMock() sigmock.apply_async.return_value = resultmock tr = TaskResult.objects.create_task_result(TEST_TASK_NAME, sigmock) - self.assertEqual(str(tr), "{name} ({taskid}): no".format( - name=TEST_TASK_NAME, taskid=TEST_TASK_UUID)) + self.assertEqual( + str(tr), + "{name} ({taskid}): no".format(name=TEST_TASK_NAME, taskid=TEST_TASK_UUID), + ) tr.finished = True - self.assertEqual(str(tr), "{name} ({taskid}): yes".format( - name=TEST_TASK_NAME, taskid=TEST_TASK_UUID)) + self.assertEqual( + str(tr), + "{name} ({taskid}): yes".format(name=TEST_TASK_NAME, taskid=TEST_TASK_UUID), + ) TEST_RESULT = MagicMock() diff --git a/gnuviechadmin/userdbs/tests/test_admin.py b/gnuviechadmin/userdbs/tests/test_admin.py index 592eb0a..3479a30 100644 --- a/gnuviechadmin/userdbs/tests/test_admin.py +++ b/gnuviechadmin/userdbs/tests/test_admin.py @@ -2,35 +2,33 @@ This module provides tests for :py:mod:`userdbs.admin`. """ -from __future__ import absolute_import +from unittest.mock import MagicMock, Mock, patch from django.contrib.admin import AdminSite from django.test import TestCase -from userdbs.admin import (DatabaseUserAdmin, DatabaseUserCreationForm, - UserDatabaseAdmin, UserDatabaseCreationForm) +from userdbs.admin import ( + DatabaseUserAdmin, + DatabaseUserCreationForm, + UserDatabaseAdmin, + UserDatabaseCreationForm, +) from userdbs.models import DB_TYPES, DatabaseUser, UserDatabase -try: - from unittest.mock import MagicMock, Mock, patch -except ImportError: - from mock import MagicMock, Mock, patch - class DatabaseUserCreationFormTest(TestCase): - - @patch('userdbs.admin.DatabaseUser.objects.create_database_user') + @patch("userdbs.admin.DatabaseUser.objects.create_database_user") def test_save(self, create_database_user): create_database_user.return_value = Mock() form = DatabaseUserCreationForm() - mockuser = Mock(name='osuser') - form.cleaned_data = { - 'osuser': mockuser, - 'db_type': DB_TYPES.pgsql - } + mockuser = Mock(name="osuser") + form.cleaned_data = {"osuser": mockuser, "db_type": DB_TYPES.pgsql} retval = form.save() - self.assertTrue(create_database_user.called_with( - osuser=mockuser, db_type=DB_TYPES.pgsql, commit=True)) + self.assertTrue( + create_database_user.called_with( + osuser=mockuser, db_type=DB_TYPES.pgsql, commit=True + ) + ) self.assertEqual(retval, create_database_user.return_value) def test_save_m2m_returns_none(self): @@ -39,16 +37,14 @@ class DatabaseUserCreationFormTest(TestCase): class UserDatabaseCreationFormTest(TestCase): - - @patch('userdbs.admin.UserDatabase.objects.create_userdatabase') + @patch("userdbs.admin.UserDatabase.objects.create_userdatabase") def test_save(self, create_userdatabase): create_userdatabase.return_value = Mock() form = UserDatabaseCreationForm() - mockuser = Mock(name='mockuser') - form.cleaned_data = {'db_user': mockuser} + mockuser = Mock(name="mockuser") + form.cleaned_data = {"db_user": mockuser} retval = form.save() - self.assertTrue(create_userdatabase.called_with( - db_user=mockuser, commit=True)) + self.assertTrue(create_userdatabase.called_with(db_user=mockuser, commit=True)) self.assertEqual(retval, create_userdatabase.return_value) def test_save_m2m_returns_none(self): @@ -57,116 +53,96 @@ class UserDatabaseCreationFormTest(TestCase): class DatabaseUserAdminTest(TestCase): - def setUp(self): site = AdminSite() self.dbuadmin = DatabaseUserAdmin(DatabaseUser, site) super(DatabaseUserAdminTest, self).setUp() def test_get_form_with_instance(self): - form = self.dbuadmin.get_form( - Mock(name='request'), obj=Mock(name='dbuser')) - self.assertEqual( - form.Meta.fields, - ['osuser', 'name', 'db_type'] - ) + form = self.dbuadmin.get_form(Mock(name="request"), obj=Mock(name="dbuser")) + self.assertEqual(form.Meta.fields, ["osuser", "name", "db_type"]) def test_get_form_without_instance(self): - form = self.dbuadmin.get_form(Mock(name='request')) - self.assertEqual( - form.Meta.fields, - ['osuser', 'db_type'] - ) + form = self.dbuadmin.get_form(Mock(name="request")) + self.assertEqual(form.Meta.fields, ["osuser", "db_type"]) def test_get_readonly_fields_with_instance(self): fields = self.dbuadmin.get_readonly_fields( - Mock(name='request'), obj=Mock(name='dbuser')) - self.assertEqual( - fields, ['osuser', 'name', 'db_type']) + Mock(name="request"), obj=Mock(name="dbuser") + ) + self.assertEqual(fields, ["osuser", "name", "db_type"]) def test_get_readonly_fields_without_instance(self): - fields = self.dbuadmin.get_readonly_fields( - Mock(name='request')) + fields = self.dbuadmin.get_readonly_fields(Mock(name="request")) self.assertEqual(fields, []) def test_save_model_change(self): objmock = Mock() - self.dbuadmin.save_model(Mock(name='request'), objmock, Mock(), True) + self.dbuadmin.save_model(Mock(name="request"), objmock, Mock(), True) self.assertTrue(objmock.create_in_database.not_called()) def test_save_model_no_change(self): objmock = Mock() - self.dbuadmin.save_model(Mock(name='request'), objmock, Mock(), False) + self.dbuadmin.save_model(Mock(name="request"), objmock, Mock(), False) self.assertTrue(objmock.create_in_database.called_with()) def test_perform_delete_selected(self): usermock = Mock() selected = Mock() selected.all.return_value = [usermock] - self.dbuadmin.perform_delete_selected(Mock(name='request'), selected) + self.dbuadmin.perform_delete_selected(Mock(name="request"), selected) self.assertTrue(selected.all.called_with()) self.assertTrue(usermock.delete.called_with()) def test_get_actions(self): - requestmock = MagicMock(name='request') - self.assertNotIn( - 'delete_selected', - self.dbuadmin.get_actions(requestmock)) - self.assertIn( - 'perform_delete_selected', - self.dbuadmin.get_actions(requestmock)) + requestmock = MagicMock(name="request") + self.assertNotIn("delete_selected", self.dbuadmin.get_actions(requestmock)) + self.assertIn("perform_delete_selected", self.dbuadmin.get_actions(requestmock)) class UserDatabaseAdminTest(TestCase): - def setUp(self): site = AdminSite() self.udbadmin = UserDatabaseAdmin(UserDatabase, site) super(UserDatabaseAdminTest, self).setUp() def test_get_form_with_instance(self): - form = self.udbadmin.get_form( - Mock(name='request'), obj=Mock(name='userdb')) - self.assertEqual(form.Meta.fields, ['db_name', 'db_user']) + form = self.udbadmin.get_form(Mock(name="request"), obj=Mock(name="userdb")) + self.assertEqual(form.Meta.fields, ["db_name", "db_user"]) def test_get_form_without_instance(self): - form = self.udbadmin.get_form(Mock(name='request')) - self.assertEqual(form.Meta.fields, ['db_user']) + form = self.udbadmin.get_form(Mock(name="request")) + self.assertEqual(form.Meta.fields, ["db_user"]) def test_get_readonly_fields_with_instance(self): fields = self.udbadmin.get_readonly_fields( - Mock(name='request'), obj=Mock(name='userdb')) - self.assertEqual( - fields, ['db_name', 'db_user']) + Mock(name="request"), obj=Mock(name="userdb") + ) + self.assertEqual(fields, ["db_name", "db_user"]) def test_get_readonly_fields_without_instance(self): - fields = self.udbadmin.get_readonly_fields( - Mock(name='request')) + fields = self.udbadmin.get_readonly_fields(Mock(name="request")) self.assertEqual(fields, []) def test_save_model_change(self): objmock = Mock() - self.udbadmin.save_model(Mock(name='request'), objmock, Mock(), True) + self.udbadmin.save_model(Mock(name="request"), objmock, Mock(), True) self.assertTrue(objmock.create_in_database.not_called()) def test_save_model_no_change(self): objmock = Mock() - self.udbadmin.save_model(Mock(name='request'), objmock, Mock(), False) + self.udbadmin.save_model(Mock(name="request"), objmock, Mock(), False) self.assertTrue(objmock.create_in_database.called_with()) def test_perform_delete_selected(self): userdbmock = Mock() selected = Mock() selected.all.return_value = [userdbmock] - self.udbadmin.perform_delete_selected(Mock(name='request'), selected) + self.udbadmin.perform_delete_selected(Mock(name="request"), selected) self.assertTrue(selected.all.called_with()) self.assertTrue(userdbmock.delete.called_with()) def test_get_actions(self): - requestmock = MagicMock(name='request') - self.assertNotIn( - 'delete_selected', - self.udbadmin.get_actions(requestmock)) - self.assertIn( - 'perform_delete_selected', - self.udbadmin.get_actions(requestmock)) + requestmock = MagicMock(name="request") + self.assertNotIn("delete_selected", self.udbadmin.get_actions(requestmock)) + self.assertIn("perform_delete_selected", self.udbadmin.get_actions(requestmock)) diff --git a/gnuviechadmin/userdbs/tests/test_forms.py b/gnuviechadmin/userdbs/tests/test_forms.py index 977e582..2aa1706 100644 --- a/gnuviechadmin/userdbs/tests/test_forms.py +++ b/gnuviechadmin/userdbs/tests/test_forms.py @@ -2,20 +2,15 @@ This module provides tests for :py:mod:`userdbs.forms`. """ -from __future__ import unicode_literals - from django import forms from django.contrib.auth import get_user_model -from django.core.urlresolvers import reverse from django.test import TestCase +from django.urls import reverse from userdbs.forms import AddUserDatabaseForm, ChangeDatabaseUserPasswordForm from userdbs.models import DB_TYPES -try: - from unittest.mock import MagicMock, Mock, patch -except ImportError: - from mock import MagicMock, Mock, patch +from unittest.mock import MagicMock, Mock, patch Customer = get_user_model() @@ -33,66 +28,68 @@ class AddUserDatabaseFormTest(TestCase): def test_constructor_needs_hostingpackage(self): with self.assertRaises(KeyError) as ke: AddUserDatabaseForm(instance=Mock()) - self.assertEqual(ke.exception.args[0], 'hostingpackage') + self.assertEqual(ke.exception.args[0], "hostingpackage") def test_constructor_needs_dbtypes(self): with self.assertRaises(KeyError) as ke: AddUserDatabaseForm(instance=Mock(), hostingpackage=Mock()) - self.assertEqual(ke.exception.args[0], 'dbtypes') + self.assertEqual(ke.exception.args[0], "dbtypes") def test_constructor_one_dbtype(self): self._setup_hostingpackage() dbtypes = [(DB_TYPES.pgsql, DB_TYPES[DB_TYPES.pgsql])] form = AddUserDatabaseForm( - instance=MagicMock(), hostingpackage=self.hostingpackage, - dbtypes=dbtypes) - self.assertIn('db_type', form.fields) - self.assertEqual(form.fields['db_type'].choices, dbtypes) - self.assertTrue(isinstance( - form.fields['db_type'].widget, - forms.HiddenInput)) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'add_userdatabase', kwargs={'package': self.hostingpackage.id})) - self.assertEqual(form.helper.inputs[0].name, 'submit') + instance=MagicMock(), hostingpackage=self.hostingpackage, dbtypes=dbtypes + ) + self.assertIn("db_type", form.fields) + self.assertEqual(form.fields["db_type"].choices, dbtypes) + self.assertTrue(isinstance(form.fields["db_type"].widget, forms.HiddenInput)) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("add_userdatabase", kwargs={"package": self.hostingpackage.id}), + ) + self.assertEqual(form.helper.inputs[0].name, "submit") def test_constructor_multiple_dbtypes(self): self._setup_hostingpackage() dbtypes = [ (DB_TYPES.pgsql, DB_TYPES[DB_TYPES.pgsql]), - (DB_TYPES.mysql, DB_TYPES[DB_TYPES.mysql]) + (DB_TYPES.mysql, DB_TYPES[DB_TYPES.mysql]), ] form = AddUserDatabaseForm( - instance=MagicMock(), hostingpackage=self.hostingpackage, - dbtypes=dbtypes) - self.assertIn('db_type', form.fields) - self.assertEqual(form.fields['db_type'].choices, dbtypes) - self.assertTrue(isinstance( - form.fields['db_type'].widget, - forms.RadioSelect)) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'add_userdatabase', kwargs={'package': self.hostingpackage.id})) - self.assertEqual(form.helper.inputs[0].name, 'submit') + instance=MagicMock(), hostingpackage=self.hostingpackage, dbtypes=dbtypes + ) + self.assertIn("db_type", form.fields) + self.assertEqual(form.fields["db_type"].choices, dbtypes) + self.assertTrue(isinstance(form.fields["db_type"].widget, forms.RadioSelect)) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("add_userdatabase", kwargs={"package": self.hostingpackage.id}), + ) + self.assertEqual(form.helper.inputs[0].name, "submit") - @patch('userdbs.forms.UserDatabase.objects.create_userdatabase_with_user') + @patch("userdbs.forms.UserDatabase.objects.create_userdatabase_with_user") def test_save(self, create_userdatabase_with_user): self._setup_hostingpackage() dbtypes = [ (DB_TYPES.pgsql, DB_TYPES[DB_TYPES.pgsql]), - (DB_TYPES.mysql, DB_TYPES[DB_TYPES.mysql]) + (DB_TYPES.mysql, DB_TYPES[DB_TYPES.mysql]), ] form = AddUserDatabaseForm( - instance=MagicMock(), hostingpackage=self.hostingpackage, - dbtypes=dbtypes) - form.cleaned_data = { - 'db_type': DB_TYPES.pgsql, - 'password1': 'secret', - } + instance=MagicMock(), hostingpackage=self.hostingpackage, dbtypes=dbtypes + ) + form.cleaned_data = {"db_type": DB_TYPES.pgsql, "password1": "secret"} form.save() - self.assertTrue(create_userdatabase_with_user.called_with( - DB_TYPES.pgsql, self.hostingpackage.osuser, - password='secret', commit=True)) + self.assertTrue( + create_userdatabase_with_user.called_with( + DB_TYPES.pgsql, + self.hostingpackage.osuser, + password="secret", + commit=True, + ) + ) class ChangeDatabaseUserPasswordFormTest(TestCase): @@ -107,29 +104,31 @@ class ChangeDatabaseUserPasswordFormTest(TestCase): def test_constructor_needs_hostingpackage(self): with self.assertRaises(KeyError) as ke: ChangeDatabaseUserPasswordForm(instance=Mock()) - self.assertEqual(ke.exception.args[0], 'hostingpackage') + self.assertEqual(ke.exception.args[0], "hostingpackage") def test_constructor(self): self._setup_hostingpackage() instance = MagicMock() - instance.name = 'test' + instance.name = "test" form = ChangeDatabaseUserPasswordForm( - instance=instance, hostingpackage=self.hostingpackage) - self.assertIn('password1', form.fields) - self.assertIn('password2', form.fields) - self.assertTrue(hasattr(form, 'helper')) - self.assertEqual(form.helper.form_action, reverse( - 'change_dbuser_password', kwargs={ - 'slug': 'test', 'package': 42 - })) - self.assertEqual(form.helper.inputs[0].name, 'submit') + instance=instance, hostingpackage=self.hostingpackage + ) + self.assertIn("password1", form.fields) + self.assertIn("password2", form.fields) + self.assertTrue(hasattr(form, "helper")) + self.assertEqual( + form.helper.form_action, + reverse("change_dbuser_password", kwargs={"slug": "test", "package": 42}), + ) + self.assertEqual(form.helper.inputs[0].name, "submit") def test_save(self): instance = MagicMock() - instance.name = 'test' + instance.name = "test" self._setup_hostingpackage() form = ChangeDatabaseUserPasswordForm( - instance=instance, hostingpackage=self.hostingpackage) - form.cleaned_data = {'password1': 'secret'} + instance=instance, hostingpackage=self.hostingpackage + ) + form.cleaned_data = {"password1": "secret"} form.save() - self.assertTrue(instance.set_password.called_with('secret')) + self.assertTrue(instance.set_password.called_with("secret")) diff --git a/gnuviechadmin/userdbs/tests/test_models.py b/gnuviechadmin/userdbs/tests/test_models.py index d8b1024..41306bd 100644 --- a/gnuviechadmin/userdbs/tests/test_models.py +++ b/gnuviechadmin/userdbs/tests/test_models.py @@ -2,8 +2,6 @@ This module provides tests for :py:mod:`userdbs.models`. """ -from __future__ import unicode_literals - from django.contrib.auth import get_user_model from django.test import TestCase from django.test.utils import override_settings @@ -16,9 +14,7 @@ Customer = get_user_model() @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class TestCaseWithCeleryTasks(TestCase): pass @@ -29,93 +25,85 @@ class DatabaseUserManagerTest(TestCaseWithCeleryTasks): Test case for :py:class:`userdbs.models.DatabaseUserManager`. """ + def setUp(self): - self.customer = Customer.objects.create_user(username='testcustomer') + self.customer = Customer.objects.create_user(username="testcustomer") self.osuser = User.objects.create_user(customer=self.customer) TaskResult.objects.all().delete() def test_create_database_user_with_name(self): dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.pgsql, 'testname', 'secret') - self.assertEqual(dbu.name, 'testname') + self.osuser, DB_TYPES.pgsql, "testname", "secret" + ) + self.assertEqual(dbu.name, "testname") self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.pgsql) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(taskres[0].creator, 'handle_dbuser_created') - self.assertEqual(taskres[0].notes, 'pgsql user creation') + self.assertEqual(taskres[0].creator, "handle_dbuser_created") + self.assertEqual(taskres[0].notes, "pgsql user creation") def test_create_database_user_with_name_no_commit(self): dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.pgsql, 'testname', 'secret', False) - self.assertEqual(dbu.name, 'testname') + self.osuser, DB_TYPES.pgsql, "testname", "secret", False + ) + self.assertEqual(dbu.name, "testname") self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.pgsql) self.assertFalse(TaskResult.objects.exists()) def test_create_database_user_generate_name(self): - dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.pgsql) - self.assertEqual(dbu.name, '{user}db01'.format( - user=self.osuser.username)) + dbu = DatabaseUser.objects.create_database_user(self.osuser, DB_TYPES.pgsql) + self.assertEqual(dbu.name, "{user}db01".format(user=self.osuser.username)) self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.pgsql) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(taskres[0].creator, 'handle_dbuser_created') - self.assertEqual(taskres[0].notes, 'pgsql user creation') + self.assertEqual(taskres[0].creator, "handle_dbuser_created") + self.assertEqual(taskres[0].notes, "pgsql user creation") def test_create_database_user_multiple_generate_name(self): - dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.mysql) - self.assertEqual(dbu.name, '{user}db01'.format( - user=self.osuser.username)) + dbu = DatabaseUser.objects.create_database_user(self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, "{user}db01".format(user=self.osuser.username)) self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.mysql) - dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.mysql) - self.assertEqual(dbu.name, '{user}db02'.format( - user=self.osuser.username)) + dbu = DatabaseUser.objects.create_database_user(self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, "{user}db02".format(user=self.osuser.username)) self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.mysql) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 2) - self.assertEqual(taskres[0].creator, 'handle_dbuser_created') - self.assertEqual(taskres[0].notes, 'mysql user creation') - self.assertEqual(taskres[1].creator, 'handle_dbuser_created') - self.assertEqual(taskres[1].notes, 'mysql user creation') + self.assertEqual(taskres[0].creator, "handle_dbuser_created") + self.assertEqual(taskres[0].notes, "mysql user creation") + self.assertEqual(taskres[1].creator, "handle_dbuser_created") + self.assertEqual(taskres[1].notes, "mysql user creation") def test_create_database_user_multiple_gap_generate_name(self): - dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.mysql) - self.assertEqual(dbu.name, '{user}db01'.format( - user=self.osuser.username)) + dbu = DatabaseUser.objects.create_database_user(self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, "{user}db01".format(user=self.osuser.username)) self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.mysql) - dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.mysql) - self.assertEqual(dbu.name, '{user}db02'.format( - user=self.osuser.username)) + dbu = DatabaseUser.objects.create_database_user(self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, "{user}db02".format(user=self.osuser.username)) self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.mysql) DatabaseUser.objects.get( - name='{user}db01'.format(user=self.osuser.username)).delete() - dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.mysql) - self.assertEqual(dbu.name, '{user}db01'.format( - user=self.osuser.username)) + name="{user}db01".format(user=self.osuser.username) + ).delete() + dbu = DatabaseUser.objects.create_database_user(self.osuser, DB_TYPES.mysql) + self.assertEqual(dbu.name, "{user}db01".format(user=self.osuser.username)) self.assertEqual(dbu.osuser, self.osuser) self.assertEqual(dbu.db_type, DB_TYPES.mysql) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 4) - self.assertEqual(taskres[0].creator, 'handle_dbuser_created') - self.assertEqual(taskres[0].notes, 'mysql user creation') - self.assertEqual(taskres[1].creator, 'handle_dbuser_created') - self.assertEqual(taskres[1].notes, 'mysql user creation') - self.assertEqual(taskres[2].creator, 'handle_dbuser_deleted') - self.assertEqual(taskres[2].notes, 'mysql user deletion') - self.assertEqual(taskres[3].creator, 'handle_dbuser_created') - self.assertEqual(taskres[3].notes, 'mysql user creation') + self.assertEqual(taskres[0].creator, "handle_dbuser_created") + self.assertEqual(taskres[0].notes, "mysql user creation") + self.assertEqual(taskres[1].creator, "handle_dbuser_created") + self.assertEqual(taskres[1].notes, "mysql user creation") + self.assertEqual(taskres[2].creator, "handle_dbuser_deleted") + self.assertEqual(taskres[2].notes, "mysql user deletion") + self.assertEqual(taskres[3].creator, "handle_dbuser_created") + self.assertEqual(taskres[3].notes, "mysql user creation") class DatabaseUserTest(TestCaseWithCeleryTasks): @@ -123,41 +111,43 @@ class DatabaseUserTest(TestCaseWithCeleryTasks): Test case for :py:class:`userdbs.models.DatabaseUser`. """ + def setUp(self): - self.customer = Customer.objects.create_user(username='testcustomer') + self.customer = Customer.objects.create_user(username="testcustomer") self.osuser = User.objects.create_user(customer=self.customer) self.dbu = DatabaseUser.objects.create_database_user( - self.osuser, DB_TYPES.pgsql) + self.osuser, DB_TYPES.pgsql + ) TaskResult.objects.all().delete() def test___str__(self): self.assertEqual( str(self.dbu), - '{user}db01 (PostgreSQL for {user})'.format( - user=self.osuser.username)) + "{user}db01 (PostgreSQL for {user})".format(user=self.osuser.username), + ) def test_set_password_pgsql(self): - self.dbu.set_password('secret') + self.dbu.set_password("secret") taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(taskres[0].creator, 'handle_dbuser_password_set') - self.assertEqual(taskres[0].notes, 'pgsql password change') + self.assertEqual(taskres[0].creator, "handle_dbuser_password_set") + self.assertEqual(taskres[0].notes, "pgsql password change") def test_set_password_mysql(self): self.dbu.db_type = DB_TYPES.mysql self.dbu.save() - self.dbu.set_password('secret') + self.dbu.set_password("secret") taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(taskres[0].creator, 'handle_dbuser_password_set') - self.assertEqual(taskres[0].notes, 'mysql password change') + self.assertEqual(taskres[0].creator, "handle_dbuser_password_set") + self.assertEqual(taskres[0].notes, "mysql password change") def test_delete_no_dbs(self): self.dbu.delete() taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(taskres[0].creator, 'handle_dbuser_deleted') - self.assertEqual(taskres[0].notes, 'pgsql user deletion') + self.assertEqual(taskres[0].creator, "handle_dbuser_deleted") + self.assertEqual(taskres[0].notes, "pgsql user deletion") def test_delete_with_dbs(self): db = UserDatabase.objects.create_userdatabase(self.dbu) @@ -166,12 +156,12 @@ class DatabaseUserTest(TestCaseWithCeleryTasks): self.assertFalse(UserDatabase.objects.filter(id=dbid).exists()) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 3) - self.assertEqual(taskres[0].creator, 'handle_userdb_created') - self.assertEqual(taskres[0].notes, 'pgsql database creation') - self.assertEqual(taskres[1].creator, 'handle_userdb_deleted') - self.assertEqual(taskres[1].notes, 'pgsql database deletion') - self.assertEqual(taskres[2].creator, 'handle_dbuser_deleted') - self.assertEqual(taskres[2].notes, 'pgsql user deletion') + self.assertEqual(taskres[0].creator, "handle_userdb_created") + self.assertEqual(taskres[0].notes, "pgsql database creation") + self.assertEqual(taskres[1].creator, "handle_userdb_deleted") + self.assertEqual(taskres[1].notes, "pgsql database deletion") + self.assertEqual(taskres[2].creator, "handle_dbuser_deleted") + self.assertEqual(taskres[2].notes, "pgsql user deletion") class UserDatabaseManagerTest(TestCaseWithCeleryTasks): @@ -179,50 +169,52 @@ class UserDatabaseManagerTest(TestCaseWithCeleryTasks): Test case for :py:class:`userdbs.models.UserDatabaseManager`. """ + def setUp(self): - self.customer = Customer.objects.create_user(username='testcustomer') + self.customer = Customer.objects.create_user(username="testcustomer") self.osuser = User.objects.create_user(customer=self.customer) TaskResult.objects.all().delete() def _create_database_user(self, dbtype): - self.dbu = DatabaseUser.objects.create_database_user( - self.osuser, dbtype) + self.dbu = DatabaseUser.objects.create_database_user(self.osuser, dbtype) TaskResult.objects.all().delete() def test_create_userdatabase_with_user_mysql(self): db = UserDatabase.objects.create_userdatabase_with_user( - DB_TYPES.mysql, self.osuser) - self.assertEqual(db.db_name, '{user}db01'.format( - user=self.osuser.username)) - self.assertEqual(db.db_user.name, '{user}db01'.format( - user=self.osuser.username)) + DB_TYPES.mysql, self.osuser + ) + self.assertEqual(db.db_name, "{user}db01".format(user=self.osuser.username)) + self.assertEqual( + db.db_user.name, "{user}db01".format(user=self.osuser.username) + ) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 2) - self.assertEqual(taskres[0].creator, 'handle_dbuser_created') - self.assertEqual(taskres[0].notes, 'mysql user creation') - self.assertEqual(taskres[1].creator, 'handle_userdb_created') - self.assertEqual(taskres[1].notes, 'mysql database creation') + self.assertEqual(taskres[0].creator, "handle_dbuser_created") + self.assertEqual(taskres[0].notes, "mysql user creation") + self.assertEqual(taskres[1].creator, "handle_userdb_created") + self.assertEqual(taskres[1].notes, "mysql database creation") def test_create_userdatabase_with_user_pgsql(self): db = UserDatabase.objects.create_userdatabase_with_user( - DB_TYPES.pgsql, self.osuser) - self.assertEqual(db.db_name, '{user}db01'.format( - user=self.osuser.username)) - self.assertEqual(db.db_user.name, '{user}db01'.format( - user=self.osuser.username)) + DB_TYPES.pgsql, self.osuser + ) + self.assertEqual(db.db_name, "{user}db01".format(user=self.osuser.username)) + self.assertEqual( + db.db_user.name, "{user}db01".format(user=self.osuser.username) + ) taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 2) - self.assertEqual(taskres[0].creator, 'handle_dbuser_created') - self.assertEqual(taskres[0].notes, 'pgsql user creation') - self.assertEqual(taskres[1].creator, 'handle_userdb_created') - self.assertEqual(taskres[1].notes, 'pgsql database creation') + self.assertEqual(taskres[0].creator, "handle_dbuser_created") + self.assertEqual(taskres[0].notes, "pgsql user creation") + self.assertEqual(taskres[1].creator, "handle_userdb_created") + self.assertEqual(taskres[1].notes, "pgsql database creation") def test_create_userdatabase_given_name_no_new_write(self): self._create_database_user(DB_TYPES.pgsql) - db = UserDatabase.objects.create_userdatabase(self.dbu, db_name='test') + db = UserDatabase.objects.create_userdatabase(self.dbu, db_name="test") taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(db.db_name, 'test') + self.assertEqual(db.db_name, "test") TaskResult.objects.all().delete() db.save() taskres = TaskResult.objects.all() @@ -230,11 +222,11 @@ class UserDatabaseManagerTest(TestCaseWithCeleryTasks): def test_create_userdatabase_given_name(self): self._create_database_user(DB_TYPES.pgsql) - db = UserDatabase.objects.create_userdatabase(self.dbu, db_name='test') - self.assertEqual(db.db_name, 'test') + db = UserDatabase.objects.create_userdatabase(self.dbu, db_name="test") + self.assertEqual(db.db_name, "test") taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 1) - self.assertEqual(db.db_name, 'test') + self.assertEqual(db.db_name, "test") def test_create_userdatabase_generate_name_no_commit(self): self._create_database_user(DB_TYPES.pgsql) @@ -252,19 +244,19 @@ class UserDatabaseManagerTest(TestCaseWithCeleryTasks): db = UserDatabase.objects.create_userdatabase(self.dbu) self.assertEqual(db.db_name, self.dbu.name) db = UserDatabase.objects.create_userdatabase(self.dbu) - self.assertEqual(db.db_name, '{user}_02'.format(user=self.dbu.name)) + self.assertEqual(db.db_name, "{user}_02".format(user=self.dbu.name)) def test_create_userdatabase_multiple_gap_generate_name(self): self._create_database_user(DB_TYPES.pgsql) db = UserDatabase.objects.create_userdatabase(self.dbu) self.assertEqual(db.db_name, self.dbu.name) dbx = UserDatabase.objects.create_userdatabase(self.dbu) - self.assertEqual(dbx.db_name, '{user}_02'.format(user=self.dbu.name)) + self.assertEqual(dbx.db_name, "{user}_02".format(user=self.dbu.name)) db = UserDatabase.objects.create_userdatabase(self.dbu) - self.assertEqual(db.db_name, '{user}_03'.format(user=self.dbu.name)) + self.assertEqual(db.db_name, "{user}_03".format(user=self.dbu.name)) dbx.delete() db = UserDatabase.objects.create_userdatabase(self.dbu) - self.assertEqual(db.db_name, '{user}_02'.format(user=self.dbu.name)) + self.assertEqual(db.db_name, "{user}_02".format(user=self.dbu.name)) class UserDatabaseTest(TestCaseWithCeleryTasks): @@ -272,24 +264,23 @@ class UserDatabaseTest(TestCaseWithCeleryTasks): Test case for :py:class:`userdbs.models.UserDabase`. """ + def test___str__(self): - customer = Customer.objects.create_user(username='testcustomer') + customer = Customer.objects.create_user(username="testcustomer") osuser = User.objects.create_user(customer=customer) - db = UserDatabase.objects.create_userdatabase_with_user( - DB_TYPES.pgsql, osuser) + db = UserDatabase.objects.create_userdatabase_with_user(DB_TYPES.pgsql, osuser) self.assertEqual( str(db), - '{user}db01 ({dbuser})'.format( - user=osuser.username, dbuser=db.db_user)) + "{user}db01 ({dbuser})".format(user=osuser.username, dbuser=db.db_user), + ) def test_delete_mysql_db(self): - customer = Customer.objects.create_user(username='testcustomer') + customer = Customer.objects.create_user(username="testcustomer") osuser = User.objects.create_user(customer=customer) TaskResult.objects.all().delete() - db = UserDatabase.objects.create_userdatabase_with_user( - DB_TYPES.mysql, osuser) + db = UserDatabase.objects.create_userdatabase_with_user(DB_TYPES.mysql, osuser) db.delete() taskres = TaskResult.objects.all() self.assertEqual(len(taskres), 3) - self.assertEqual(taskres[2].creator, 'handle_userdb_deleted') - self.assertEqual(taskres[2].notes, 'mysql database deletion') + self.assertEqual(taskres[2].creator, "handle_userdb_deleted") + self.assertEqual(taskres[2].notes, "mysql database deletion") diff --git a/gnuviechadmin/userdbs/tests/test_signals.py b/gnuviechadmin/userdbs/tests/test_signals.py index c39a0b5..33541e3 100644 --- a/gnuviechadmin/userdbs/tests/test_signals.py +++ b/gnuviechadmin/userdbs/tests/test_signals.py @@ -4,63 +4,60 @@ This module contains explicit tests for corner cases in :py:mod:`userdbs.tests.test_models`. """ -from __future__ import unicode_literals +from unittest.mock import Mock from django.test import TestCase from django.test.utils import override_settings from taskresults.models import TaskResult -from userdbs.signals import (handle_dbuser_created, handle_dbuser_deleted, - handle_dbuser_password_set, handle_userdb_created, - handle_userdb_deleted) - -try: - from unittest.mock import Mock -except ImportError: - from mock import Mock +from userdbs.signals import ( + handle_dbuser_created, + handle_dbuser_deleted, + handle_dbuser_password_set, + handle_userdb_created, + handle_userdb_deleted, +) @override_settings( - CELERY_ALWAYS_EAGER=True, - CELERY_CACHE_BACKEND='memory', - BROKER_BACKEND='memory' + CELERY_ALWAYS_EAGER=True, CELERY_CACHE_BACKEND="memory", BROKER_BACKEND="memory" ) class TestCaseWithCeleryTasks(TestCase): pass class TestWithUnknownDBType(TestCaseWithCeleryTasks): - def test_handle_dbuser_password_set_unknown(self): - instance = Mock(data={'name': 'test', 'db_type': -1}) - handle_dbuser_password_set(Mock(name='sender'), instance, 'secret') + instance = Mock(data={"name": "test", "db_type": -1}) + handle_dbuser_password_set(Mock(name="sender"), instance, "secret") self.assertFalse(TaskResult.objects.exists()) def test_handle_dbuser_create_unknown(self): - instance = Mock(data={'name': 'test', 'db_type': -1}) - handle_dbuser_created( - Mock(name='sender'), instance, True, password='secret') + instance = Mock(data={"name": "test", "db_type": -1}) + handle_dbuser_created(Mock(name="sender"), instance, True, password="secret") self.assertFalse(TaskResult.objects.exists()) def test_handle_dbuser_deleted_unknown(self): - instance = Mock(data={'name': 'test', 'db_type': -1}) - handle_dbuser_deleted(Mock(name='sender'), instance) + instance = Mock(data={"name": "test", "db_type": -1}) + handle_dbuser_deleted(Mock(name="sender"), instance) self.assertFalse(TaskResult.objects.exists()) def test_handle_userdb_created_unknown(self): instance = Mock( data={ - 'db_name': 'test', - 'db_user': Mock(data={'name': 'test', 'db_type': -1, }) - }) - handle_userdb_created(Mock(name='sender'), instance, True) + "db_name": "test", + "db_user": Mock(data={"name": "test", "db_type": -1}), + } + ) + handle_userdb_created(Mock(name="sender"), instance, True) self.assertFalse(TaskResult.objects.exists()) def test_handle_userdb_deleted_unknown(self): instance = Mock( data={ - 'db_name': 'test', - 'db_user': Mock(data={'name': 'test', 'db_type': -1, }) - }) - handle_userdb_deleted(Mock(name='sender'), instance) + "db_name": "test", + "db_user": Mock(data={"name": "test", "db_type": -1}), + } + ) + handle_userdb_deleted(Mock(name="sender"), instance) self.assertFalse(TaskResult.objects.exists()) diff --git a/gnuviechadmin/userdbs/tests/test_views.py b/gnuviechadmin/userdbs/tests/test_views.py index 89aa381..b52cbdf 100644 --- a/gnuviechadmin/userdbs/tests/test_views.py +++ b/gnuviechadmin/userdbs/tests/test_views.py @@ -2,16 +2,11 @@ This module provides tests for :py:mod:`userdbs.views`. """ -from __future__ import absolute_import, unicode_literals +from unittest.mock import patch, MagicMock -try: - from unittest.mock import patch, MagicMock -except ImportError: - from mock import patch, MagicMock - -from django.core.urlresolvers import reverse from django.test import TestCase from django.contrib.auth import get_user_model +from django.urls import reverse from hostingpackages.models import ( CustomerHostingPackage, @@ -26,58 +21,61 @@ from userdbs.views import AddUserDatabase, ChangeDatabaseUserPassword User = get_user_model() -TEST_USER = 'test' -TEST_PASSWORD = 'secret' -TEST_EMAIL = 'test@example.org' +TEST_USER = "test" +TEST_PASSWORD = "secret" +TEST_EMAIL = "test@example.org" class HostingPackageAwareTestMixin(object): + # noinspection PyMethodMayBeStatic def _setup_hosting_package(self, customer): template = HostingPackageTemplate.objects.create( - name='testpackagetemplate', mailboxcount=10, diskspace=1, - diskspace_unit=0) + name="testpackagetemplate", mailboxcount=10, diskspace=1, diskspace_unit=0 + ) package = CustomerHostingPackage.objects.create_from_template( - customer, template, 'testpackage') - with patch('hostingpackages.models.settings') as hmsettings: + customer, template, "testpackage" + ) + with patch("hostingpackages.models.settings") as hmsettings: hmsettings.OSUSER_DEFAULT_GROUPS = [] package.save() return package class CustomerUserDatabaseOptionAwareTestMixin(object): - def __init__(self, *args, **kwargs): - super(CustomerUserDatabaseOptionAwareTestMixin, self).__init__( - *args, **kwargs) + super(CustomerUserDatabaseOptionAwareTestMixin, self).__init__(*args, **kwargs) self._templates = {} def _setup_userdatabaseoption(self, number, dbtype): key = "{}_{}".format(dbtype, number) if key not in self._templates: self._templates[key] = UserDatabaseOption.objects.create( - number=number, db_type=dbtype) + number=number, db_type=dbtype + ) return self._templates[key] def _create_userdatabase_option(self, number=1, dbtype=DB_TYPES.pgsql): + # noinspection PyUnresolvedReferences return CustomerUserDatabaseOption.objects.create( template=self._setup_userdatabaseoption(number, dbtype), - number=number, db_type=dbtype, hosting_package=self.package) + number=number, + db_type=dbtype, + hosting_package=self.package, + ) class AddUserDatabaseTest( - HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, - TestCase + HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, TestCase ): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) def _get_url(self): - return reverse( - 'add_userdatabase', kwargs={'package': self.package.id}) + return reverse("add_userdatabase", kwargs={"package": self.package.id}) def test_get_anonymous(self): response = self.client.get(self._get_url()) @@ -95,31 +93,29 @@ class AddUserDatabaseTest( self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 403) def test_get_staff_user_nodboption(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 400) def test_get_staff_user(self): self._create_userdatabase_option() - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 200) def test_get_regular_user_nofree_db(self): db_option = self._create_userdatabase_option() UserDatabase.objects.create_userdatabase_with_user( - db_option.db_type, self.package.osuser) + db_option.db_type, self.package.osuser + ) self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) self.assertEqual(response.status_code, 400) @@ -128,21 +124,21 @@ class AddUserDatabaseTest( db_option = self._create_userdatabase_option() self.client.login(username=TEST_USER, password=TEST_PASSWORD) view = AddUserDatabase( - request=MagicMock(), kwargs={'package': str(self.package.pk)}) + request=MagicMock(), kwargs={"package": str(self.package.pk)} + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) - self.assertIn('dbtypes', the_kwargs) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) + self.assertIn("dbtypes", the_kwargs) self.assertEqual( - the_kwargs['dbtypes'], - [(db_option.db_type, DB_TYPES[db_option.db_type])], + the_kwargs["dbtypes"], [(db_option.db_type, DB_TYPES[db_option.db_type])] ) def test_get_template(self): self._create_userdatabase_option() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url()) - self.assertTemplateUsed(response, 'userdbs/userdatabase_create.html') + self.assertTemplateUsed(response, "userdbs/userdatabase_create.html") def test_form_valid_redirect(self): db_option = self._create_userdatabase_option() @@ -150,48 +146,55 @@ class AddUserDatabaseTest( response = self.client.post( self._get_url(), data={ - 'db_type': db_option.db_type, 'password1': TEST_PASSWORD, - 'password2': TEST_PASSWORD}) + "db_type": db_option.db_type, + "password1": TEST_PASSWORD, + "password2": TEST_PASSWORD, + }, + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): db_option = self._create_userdatabase_option() self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - self._get_url(), follow=True, + self._get_url(), + follow=True, data={ - 'db_type': db_option.db_type, 'password1': TEST_PASSWORD, - 'password2': TEST_PASSWORD}) - db = UserDatabase.objects.filter( - db_user__osuser=self.package.osuser).get() - messages = list(response.context['messages']) + "db_type": db_option.db_type, + "password1": TEST_PASSWORD, + "password2": TEST_PASSWORD, + }, + ) + db = UserDatabase.objects.filter(db_user__osuser=self.package.osuser).get() + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( - str(messages[0]), ( - 'Successfully create new {type} database {dbname} for user ' - '{dbuser}.').format( - type=db.db_user.db_type, dbname=db.db_name, - dbuser=db.db_user)) + str(messages[0]), + ( + "Successfully create new {type} database {dbname} for user " "{dbuser}." + ).format(type=db.db_user.db_type, dbname=db.db_name, dbuser=db.db_user), + ) class ChangeDatabaseUserPasswordTest( - HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, - TestCase + HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, TestCase ): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) template = self._create_userdatabase_option() database = UserDatabase.objects.create_userdatabase_with_user( - template.db_type, self.package.osuser) + template.db_type, self.package.osuser + ) self.dbuser = database.db_user def _get_url(self, dbuser): return reverse( - 'change_dbuser_password', kwargs={ - 'package': self.package.id, 'slug': dbuser.name}) + "change_dbuser_password", + kwargs={"package": self.package.id, "slug": dbuser.name}, + ) def test_get_anonymous(self): response = self.client.get(self._get_url(self.dbuser)) @@ -203,80 +206,85 @@ class ChangeDatabaseUserPasswordTest( self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url(self.dbuser)) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url(self.dbuser)) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url(self.dbuser)) - self.assertTemplateUsed( - response, 'userdbs/databaseuser_setpassword.html') + self.assertTemplateUsed(response, "userdbs/databaseuser_setpassword.html") def test_get_form_kwargs(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) - view = ChangeDatabaseUserPassword(request=MagicMock(), kwargs={ - 'package': str(self.package.pk), 'slug': self.dbuser.name}) + view = ChangeDatabaseUserPassword( + request=MagicMock(), + kwargs={"package": str(self.package.pk), "slug": self.dbuser.name}, + ) the_kwargs = view.get_form_kwargs() - self.assertIn('hostingpackage', the_kwargs) - self.assertEqual(the_kwargs['hostingpackage'], self.package) + self.assertIn("hostingpackage", the_kwargs) + self.assertEqual(the_kwargs["hostingpackage"], self.package) def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url(self.dbuser)) - self.assertIn('dbuser', response.context) - self.assertEqual(response.context['dbuser'], self.dbuser) - self.assertIn('hostingpackage', response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) + self.assertIn("dbuser", response.context) + self.assertEqual(response.context["dbuser"], self.dbuser) + self.assertIn("hostingpackage", response.context) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) - response = self.client.post(self._get_url(self.dbuser), data={ - 'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) + response = self.client.post( + self._get_url(self.dbuser), + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) self.assertRedirects(response, self.package.get_absolute_url()) def test_form_valid_message(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post( - self._get_url(self.dbuser), follow=True, data={ - 'password1': TEST_PASSWORD, 'password2': TEST_PASSWORD}) - messages = list(response.context['messages']) + self._get_url(self.dbuser), + follow=True, + data={"password1": TEST_PASSWORD, "password2": TEST_PASSWORD}, + ) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), - 'Successfully changed password of database user {dbuser}.'.format( - dbuser=self.dbuser.name)) + "Successfully changed password of database user {dbuser}.".format( + dbuser=self.dbuser.name + ), + ) class DeleteUserDatabaseTest( - HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, - TestCase + HostingPackageAwareTestMixin, CustomerUserDatabaseOptionAwareTestMixin, TestCase ): - def setUp(self): self.customer = User.objects.create_user( - username=TEST_USER, password=TEST_PASSWORD) + username=TEST_USER, password=TEST_PASSWORD + ) self.package = self._setup_hosting_package(self.customer) template = self._create_userdatabase_option() self.database = UserDatabase.objects.create_userdatabase_with_user( - template.db_type, self.package.osuser) + template.db_type, self.package.osuser + ) def _get_url(self, userdatabase): return reverse( - 'delete_userdatabase', kwargs={ - 'package': self.package.id, - 'slug': userdatabase.db_name}) + "delete_userdatabase", + kwargs={"package": self.package.id, "slug": userdatabase.db_name}, + ) def test_get_anonymous(self): response = self.client.get(self._get_url(self.database)) @@ -288,34 +296,31 @@ class DeleteUserDatabaseTest( self.assertEqual(response.status_code, 200) def test_get_other_regular_user(self): - User.objects.create_user( - 'test2', password=TEST_PASSWORD) - self.client.login(username='test2', password=TEST_PASSWORD) + User.objects.create_user("test2", password=TEST_PASSWORD) + self.client.login(username="test2", password=TEST_PASSWORD) response = self.client.get(self._get_url(self.database)) self.assertEqual(response.status_code, 403) def test_get_staff_user(self): - User.objects.create_superuser( - 'admin', email=TEST_EMAIL, password=TEST_PASSWORD) - self.client.login(username='admin', password=TEST_PASSWORD) + User.objects.create_superuser("admin", email=TEST_EMAIL, password=TEST_PASSWORD) + self.client.login(username="admin", password=TEST_PASSWORD) response = self.client.get(self._get_url(self.database)) self.assertEqual(response.status_code, 200) def test_get_template(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url(self.database)) - self.assertTemplateUsed( - response, 'userdbs/userdatabase_confirm_delete.html') + self.assertTemplateUsed(response, "userdbs/userdatabase_confirm_delete.html") def test_get_context_data(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.get(self._get_url(self.database)) - self.assertIn('database', response.context) - self.assertEqual(response.context['database'], self.database) - self.assertIn('hostingpackage', response.context) - self.assertEqual(response.context['hostingpackage'], self.package) - self.assertIn('customer', response.context) - self.assertEqual(response.context['customer'], self.customer) + self.assertIn("database", response.context) + self.assertEqual(response.context["database"], self.database) + self.assertIn("hostingpackage", response.context) + self.assertEqual(response.context["hostingpackage"], self.package) + self.assertIn("customer", response.context) + self.assertEqual(response.context["customer"], self.customer) def test_form_valid_redirect(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) @@ -325,6 +330,6 @@ class DeleteUserDatabaseTest( def test_form_valid_message(self): self.client.login(username=TEST_USER, password=TEST_PASSWORD) response = self.client.post(self._get_url(self.database), follow=True) - messages = list(response.context['messages']) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), "Database deleted.") From e36a8baedfd7abfd7a8e84ef77cd463a13428897 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 5 Jul 2019 21:22:49 +0200 Subject: [PATCH 095/111] Update dependencies --- Pipfile | 2 +- Pipfile.lock | 415 ++++++++++++++++++++++++--------------------------- 2 files changed, 200 insertions(+), 217 deletions(-) diff --git a/Pipfile b/Pipfile index 383f7b9..f7ae411 100644 --- a/Pipfile +++ b/Pipfile @@ -25,4 +25,4 @@ sphinxcontrib-blockdiag = "*" pylama = "*" [requires] -python_version = "3.6" +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 725c3a6..ee55549 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "8a774f3325746a2eb48dfd94b9af22cbb8668b33e267391c64e8e295cee63b72" + "sha256": "b9e2b615f91daeeea6b7c70efcc45f3a8b06bea72cf0189c3f6ffd0152668915" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3.7" }, "sources": [ { @@ -18,31 +18,31 @@ "default": { "amqp": { "hashes": [ - "sha256:9f181e4aef6562e6f9f45660578fc1556150ca06e836ecb9e733e6ea10b48464", - "sha256:c3d7126bfbc640d076a01f1f4f6e609c0e4348508150c1f61336b0d83c738d2b" + "sha256:aa4409446139676943a2eaa27d5f58caf750f4ca5a89f888c452afd86be6a67d", + "sha256:cbb6f87d53cac612a594f982b717cc1c54c6a1e17943a0a0d32dc6cc9e2120c8" ], - "version": "==2.4.0" + "version": "==2.5.0" }, "billiard": { "hashes": [ - "sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e" + "sha256:756bf323f250db8bf88462cd042c992ba60d8f5e07fc5636c24ba7d6f4261d84" ], - "version": "==3.5.0.5" + "version": "==3.6.0.0" }, "celery": { "hashes": [ - "sha256:77dab4677e24dc654d42dfbdfed65fa760455b6bb563a0877ecc35f4cfcfc678", - "sha256:ad7a7411772b80a4d6c64f2f7f723200e39fb66cf614a7fdfab76d345acc7b13" + "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", + "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" ], "index": "pypi", - "version": "==4.2.1" + "version": "==4.3.0" }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", + "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" ], - "version": "==2018.11.29" + "version": "==2019.6.16" }, "chardet": { "hashes": [ @@ -53,25 +53,25 @@ }, "defusedxml": { "hashes": [ - "sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4", - "sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20" + "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", + "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" ], - "version": "==0.5.0" + "version": "==0.6.0" }, "django": { "hashes": [ - "sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8", - "sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3" + "sha256:4d23f61b26892bac785f07401bc38cbf8fa4cec993f400e9cd9ddf28fd51c0ea", + "sha256:6e974d4b57e3b29e4882b244d40171d6a75202ab8d2402b8e8adbd182e25cf0c" ], "index": "pypi", - "version": "==2.1.5" + "version": "==2.2.3" }, "django-allauth": { "hashes": [ - "sha256:45661b6fe308466154adf54461679fab6f0c6850b10c56dc322fbbb9d47e03d9" + "sha256:4444434c2f43188e16e0a7732c3638e12787ca7e5032f69b6d30b2912f53809e" ], "index": "pypi", - "version": "==0.38.0" + "version": "==0.39.1" }, "django-braces": { "hashes": [ @@ -91,11 +91,11 @@ }, "django-model-utils": { "hashes": [ - "sha256:2c057f3bf0859aba27f04389f0cedd2d48f8c9b3848acb86fd9970794e58f477", - "sha256:8cd377744aa45f9f131d652ec460c57d1aaa88d3e9b586c8e27eb709341b9084" + "sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760", + "sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96" ], "index": "pypi", - "version": "==3.1.2" + "version": "==3.2.0" }, "gvacommon": { "git": "https://git.dittberner.info/gnuviech/gvacommon.git", @@ -110,17 +110,17 @@ }, "kombu": { "hashes": [ - "sha256:1ef049243aa05f29e988ab33444ec7f514375540eaa8e0b2e1f5255e81c5e56d", - "sha256:3c9dca2338c5d893f30c151f5d29bfb81196748ab426d33c362ab51f1e8dbf78" + "sha256:55b71d3785def3470a16217fe0780f9e6f95e61bf9ad39ef8dce0177224eab77", + "sha256:eb365ea795cd7e629ba2f1f398e0c3ba354b91ef4de225ffdf6ab45fdfc7d581" ], - "version": "==4.2.2.post1" + "version": "==4.6.3" }, "oauthlib": { "hashes": [ - "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", - "sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e" + "sha256:40a63637707e9163eda62d0f5345120c65e001a790480b8256448543c1f78f66", + "sha256:b4d99ae8ccfb7d33ba9591b59355c64eef5241534aa3da2e4c0435346b84bc8e" ], - "version": "==3.0.1" + "version": "==3.0.2" }, "passlib": { "hashes": [ @@ -132,39 +132,20 @@ }, "psycopg2": { "hashes": [ - "sha256:02445ebbb3a11a3fe8202c413d5e6faf38bb75b4e336203ee144ca2c46529f94", - "sha256:0e9873e60f98f0c52339abf8f0339d1e22bfe5aae0bcf7aabd40c055175035ec", - "sha256:1148a5eb29073280bf9057c7fc45468592c1bb75a28f6df1591adb93c8cb63d0", - "sha256:259a8324e109d4922b0fcd046e223e289830e2568d6f4132a3702439e5fd532b", - "sha256:28dffa9ed4595429e61bacac41d3f9671bb613d1442ff43bcbec63d4f73ed5e8", - "sha256:314a74302d4737a3865d40ea50e430ce1543c921ba10f39d562e807cfe2edf2a", - "sha256:36b60201b6d215d7658a71493fdf6bd5e60ad9a0cffed39906627ff9f4f3afd3", - "sha256:3f9d532bce54c4234161176ff3b8688ff337575ca441ea27597e112dfcd0ee0c", - "sha256:5d222983847b40af989ad96c07fc3f07e47925e463baa5de716be8f805b41d9b", - "sha256:6757a6d2fc58f7d8f5d471ad180a0bd7b4dd3c7d681f051504fbea7ae29c8d6f", - "sha256:6a0e0f1e74edb0ab57d89680e59e7bfefad2bfbdf7c80eb38304d897d43674bb", - "sha256:6ca703ccdf734e886a1cf53eb702261110f6a8b0ed74bcad15f1399f74d3f189", - "sha256:8513b953d8f443c446aa79a4cc8a898bd415fc5e29349054f03a7d696d495542", - "sha256:9262a5ce2038570cb81b4d6413720484cb1bc52c064b2f36228d735b1f98b794", - "sha256:97441f851d862a0c844d981cbee7ee62566c322ebb3d68f86d66aa99d483985b", - "sha256:a07feade155eb8e69b54dd6774cf6acf2d936660c61d8123b8b6b1f9247b67d6", - "sha256:a9b9c02c91b1e3ec1f1886b2d0a90a0ea07cc529cb7e6e472b556bc20ce658f3", - "sha256:ae88216f94728d691b945983140bf40d51a1ff6c7fe57def93949bf9339ed54a", - "sha256:b360ffd17659491f1a6ad7c928350e229c7b7bd83a2b922b6ee541245c7a776f", - "sha256:b4221957ceccf14b2abdabef42d806e791350be10e21b260d7c9ce49012cc19e", - "sha256:b90758e49d5e6b152a460d10b92f8a6ccf318fcc0ee814dcf53f3a6fc5328789", - "sha256:c669ea986190ed05fb289d0c100cc88064351f2b85177cbfd3564c4f4847d18c", - "sha256:d1b61999d15c79cf7f4f7cc9021477aef35277fc52452cf50fd13b713c84424d", - "sha256:de7bb043d1adaaf46e38d47e7a5f703bb3dab01376111e522b07d25e1a79c1e1", - "sha256:e393568e288d884b94d263f2669215197840d097c7e5b0acd1a51c1ea7d1aba8", - "sha256:ed7e0849337bd37d89f2c2b0216a0de863399ee5d363d31b1e5330a99044737b", - "sha256:f153f71c3164665d269a5d03c7fa76ba675c7a8de9dc09a4e2c2cdc9936a7b41", - "sha256:f1fb5a8427af099beb7f65093cbdb52e021b8e6dbdfaf020402a623f4181baf5", - "sha256:f36b333e9f86a2fba960c72b90c34be6ca71819e300f7b1fc3d2b0f0b2c546cd", - "sha256:f4526d078aedd5187d0508aa5f9a01eae6a48a470ed678406da94b4cd6524b7e" + "sha256:128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001", + "sha256:227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323", + "sha256:2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242", + "sha256:4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80", + "sha256:640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b", + "sha256:897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457", + "sha256:8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864", + "sha256:b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f", + "sha256:cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23", + "sha256:d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f", + "sha256:f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1" ], "index": "pypi", - "version": "==2.7.7" + "version": "==2.8.3" }, "python3-openid": { "hashes": [ @@ -175,25 +156,25 @@ }, "pytz": { "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" ], - "version": "==2018.9" + "version": "==2019.1" }, "redis": { "hashes": [ - "sha256:74c892041cba46078ae1ef845241548baa3bd3634f9a6f0f952f006eb1619c71", - "sha256:7ba8612bbfd966dea8c62322543fed0095da2834dbd5a7c124afbc617a156aa7" + "sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175", + "sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273" ], "index": "pypi", - "version": "==3.1.0" + "version": "==3.2.1" }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], - "version": "==2.21.0" + "version": "==2.22.0" }, "requests-oauthlib": { "hashes": [ @@ -203,19 +184,26 @@ "index": "pypi", "version": "==1.2.0" }, + "sqlparse": { + "hashes": [ + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + ], + "version": "==0.3.0" + }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" ], - "version": "==1.24.1" + "version": "==1.25.3" }, "vine": { "hashes": [ - "sha256:3cd505dcf980223cfaf13423d371f2e7ff99247e38d5985a01ec8264e4f2aca1", - "sha256:ee4813e915d0e1a54e5c1963fde0855337f82655678540a6bc5996bca4165f76" + "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", + "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" ], - "version": "==1.2.0" + "version": "==1.3.0" } }, "develop": { @@ -228,10 +216,10 @@ }, "babel": { "hashes": [ - "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", - "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", + "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" ], - "version": "==2.6.0" + "version": "==2.7.0" }, "blockdiag": { "hashes": [ @@ -242,10 +230,10 @@ }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", + "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" ], - "version": "==2018.11.29" + "version": "==2019.6.16" }, "chardet": { "hashes": [ @@ -256,56 +244,56 @@ }, "coverage": { "hashes": [ - "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", - "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", - "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", - "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", - "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", - "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", - "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", - "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", - "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", - "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", - "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", - "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", - "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", - "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", - "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", - "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", - "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", - "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", - "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", - "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", - "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", - "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", - "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", - "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", - "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", - "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", - "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", - "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", - "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", - "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", - "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", + "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", + "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", + "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", + "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", + "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", + "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", + "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", + "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", + "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", + "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", + "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", + "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", + "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", + "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", + "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", + "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", + "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", + "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", + "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", + "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", + "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", + "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", + "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", + "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", + "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", + "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", + "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", + "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", + "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", + "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" ], "index": "pypi", - "version": "==4.5.2" + "version": "==4.5.3" }, "django": { "hashes": [ - "sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8", - "sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3" + "sha256:4d23f61b26892bac785f07401bc38cbf8fa4cec993f400e9cd9ddf28fd51c0ea", + "sha256:6e974d4b57e3b29e4882b244d40171d6a75202ab8d2402b8e8adbd182e25cf0c" ], "index": "pypi", - "version": "==2.1.5" + "version": "==2.2.3" }, "django-debug-toolbar": { "hashes": [ - "sha256:89d75b60c65db363fb24688d977e5fbf0e73386c67acf562d278402a10fc3736", - "sha256:c2b0134119a624f4ac9398b44f8e28a01c7686ac350a12a74793f3dd57a9eea0" + "sha256:17c53cd6bf4e7d69902aedf9a1d26c5d3b7369b54c5718744704f27b5a72f35d", + "sha256:9a23ada2e43cd989195db3c18710b5d7451134a0d48127ab64c1d2ad81700342" ], "index": "pypi", - "version": "==1.11" + "version": "==2.0" }, "docutils": { "hashes": [ @@ -337,43 +325,43 @@ }, "jinja2": { "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" ], - "version": "==2.10" + "version": "==2.10.1" }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "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:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "version": "==1.1.0" + "version": "==1.1.1" }, "mccabe": { "hashes": [ @@ -391,38 +379,34 @@ }, "pillow": { "hashes": [ - "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", - "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", - "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", - "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", - "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", - "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", - "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", - "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", - "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", - "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", - "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", - "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", - "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", - "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", - "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", - "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", - "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", - "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", - "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", - "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", - "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", - "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", - "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", - "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", - "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", - "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", - "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", - "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", - "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", - "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" + "sha256:0804f77cb1e9b6dbd37601cee11283bba39a8d44b9ddb053400c58e0c0d7d9de", + "sha256:0ab7c5b5d04691bcbd570658667dd1e21ca311c62dcfd315ad2255b1cd37f64f", + "sha256:0b3e6cf3ea1f8cecd625f1420b931c83ce74f00c29a0ff1ce4385f99900ac7c4", + "sha256:365c06a45712cd723ec16fa4ceb32ce46ad201eb7bbf6d3c16b063c72b61a3ed", + "sha256:38301fbc0af865baa4752ddae1bb3cbb24b3d8f221bf2850aad96b243306fa03", + "sha256:3aef1af1a91798536bbab35d70d35750bd2884f0832c88aeb2499aa2d1ed4992", + "sha256:3fe0ab49537d9330c9bba7f16a5f8b02da615b5c809cdf7124f356a0f182eccd", + "sha256:45a619d5c1915957449264c81c008934452e3fd3604e36809212300b2a4dab68", + "sha256:49f90f147883a0c3778fd29d3eb169d56416f25758d0f66775db9184debc8010", + "sha256:571b5a758baf1cb6a04233fb23d6cf1ca60b31f9f641b1700bfaab1194020555", + "sha256:5ac381e8b1259925287ccc5a87d9cf6322a2dc88ae28a97fe3e196385288413f", + "sha256:6153db744a743c0c8c91b8e3b9d40e0b13a5d31dbf8a12748c6d9bfd3ddc01ad", + "sha256:6fd63afd14a16f5d6b408f623cc2142917a1f92855f0df997e09a49f0341be8a", + "sha256:70acbcaba2a638923c2d337e0edea210505708d7859b87c2bd81e8f9902ae826", + "sha256:70b1594d56ed32d56ed21a7fbb2a5c6fd7446cdb7b21e749c9791eac3a64d9e4", + "sha256:76638865c83b1bb33bcac2a61ce4d13c17dba2204969dedb9ab60ef62bede686", + "sha256:7b2ec162c87fc496aa568258ac88631a2ce0acfe681a9af40842fc55deaedc99", + "sha256:7cee2cef07c8d76894ebefc54e4bb707dfc7f258ad155bd61d87f6cd487a70ff", + "sha256:7d16d4498f8b374fc625c4037742fbdd7f9ac383fd50b06f4df00c81ef60e829", + "sha256:b50bc1780681b127e28f0075dfb81d6135c3a293e0c1d0211133c75e2179b6c0", + "sha256:bd0582f831ad5bcad6ca001deba4568573a4675437db17c4031939156ff339fa", + "sha256:cfd40d8a4b59f7567620410f966bb1f32dc555b2b19f82a91b147fac296f645c", + "sha256:e3ae410089de680e8f84c68b755b42bc42c0ceb8c03dbea88a5099747091d38e", + "sha256:e9046e559c299b395b39ac7dbf16005308821c2f24a63cae2ab173bd6aa11616", + "sha256:ef6be704ae2bc8ad0ebc5cb850ee9139493b0fc4e81abcc240fb392a63ebc808", + "sha256:f8dc19d92896558f9c4317ee365729ead9d7bbcf2052a9a19a3ef17abbb8ac5b" ], - "version": "==5.4.1" + "version": "==6.1.0" }, "pycodestyle": { "hashes": [ @@ -441,39 +425,39 @@ }, "pyflakes": { "hashes": [ - "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", - "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" ], - "version": "==2.1.0" + "version": "==2.1.1" }, "pygments": { "hashes": [ - "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", - "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" + "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", + "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" ], - "version": "==2.3.1" + "version": "==2.4.2" }, "pylama": { "hashes": [ - "sha256:7e0327ee9b2a350ed73fe54c240894e534e2bccfb23a59ed5ce89f5a5689ee94", - "sha256:f81bf3bbd15db802b620903df491e5cd6469dcd542424ce6718425037dcc4d10" + "sha256:9bae53ef9c1a431371d6a8dca406816a60d547147b60a4934721898f553b7d8f", + "sha256:fd61c11872d6256b019ef1235be37b77c922ef37ac9797df6bd489996dddeb15" ], "index": "pypi", - "version": "==7.6.6" + "version": "==7.7.1" }, "pyparsing": { "hashes": [ - "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", - "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3" + "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", + "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" ], - "version": "==2.3.1" + "version": "==2.4.0" }, "pytz": { "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" ], - "version": "==2018.9" + "version": "==2019.1" }, "releases": { "hashes": [ @@ -485,10 +469,10 @@ }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], - "version": "==2.21.0" + "version": "==2.22.0" }, "semantic-version": { "hashes": [ @@ -506,10 +490,9 @@ }, "snowballstemmer": { "hashes": [ - "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", - "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" ], - "version": "==1.2.1" + "version": "==1.9.0" }, "sphinx": { "hashes": [ @@ -529,31 +512,31 @@ }, "sphinxcontrib-websupport": { "hashes": [ - "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", - "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + "sha256:1501befb0fdf1d1c29a800fdbf4ef5dc5369377300ddbdd16d2cd40e54c6eefc", + "sha256:e02f717baf02d0b6c3dd62cf81232ffca4c9d5c331e03766982e3ff9f1d2bc3f" ], - "version": "==1.1.0" + "version": "==1.1.2" }, "sqlparse": { "hashes": [ - "sha256:ce028444cfab83be538752a2ffdb56bc417b7784ff35bb9a3062413717807dec", - "sha256:d9cf190f51cbb26da0412247dfe4fb5f4098edb73db84e02f9fc21fdca31fed4" + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" ], - "version": "==0.2.4" + "version": "==0.3.0" }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" ], - "version": "==1.24.1" + "version": "==1.25.3" }, "webcolors": { "hashes": [ - "sha256:030562f624467a9901f0b455fef05486a88cfb5daa1e356bd4aacea043850b59", - "sha256:b3b88e5ef2b35fa9e01e3fabe99dddf49da074459c44774c59f3ccab3be4f121" + "sha256:18c091bd4bd75efd1e9f84f5eca4a54f6e6485eaa3967d2a55700835a1b1c418", + "sha256:771adfb75a8d9189724de748b2e9564edf64104726bff0c4f6e11d15635e6517" ], - "version": "==1.8.1" + "version": "==1.9.1" } } } From 977189c2631c55cceed0e3905ee05b53ea20c3e4 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 5 Jul 2019 21:23:24 +0200 Subject: [PATCH 096/111] Update and improve docker image This commit updates the docker image to be based on Debian 10 Buster. The startup script waits until the database container becomes available. Needed dependencies for building the PostgreSQL driver were added to the docker image. --- Dockerfile | 9 ++++++--- gnuviechadmin.sh | 13 +++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e268ca1..7846bf3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,14 @@ -FROM debian:stretch -LABEL maintainer "Jan Dittberner " +FROM debian:buster +LABEL maintainer="Jan Dittberner " RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential \ dumb-init \ gettext \ git \ libpq-dev \ + postgresql-client \ python3-dev \ python3-pip \ && apt-get clean \ @@ -20,11 +22,12 @@ ENV LANG=C.UTF-8 RUN python3 -m pip install -U pip && pip3 install pipenv -COPY gnuviechadmin.sh /srv/ COPY Pipfile Pipfile.lock /srv/gnuviechadmin/ RUN pipenv install --system --deploy --ignore-pipfile --dev +COPY gnuviechadmin.sh /srv/ + EXPOSE 8000 VOLUME /srv/gnuviechadmin diff --git a/gnuviechadmin.sh b/gnuviechadmin.sh index f5d74c2..7ea96c1 100755 --- a/gnuviechadmin.sh +++ b/gnuviechadmin.sh @@ -2,6 +2,19 @@ set -e +DB_HOST="${GVA_PGSQL_HOST:-db}" +DB_PORT="${GVA_PGSQL_PORT:-5432}" +DB_USER="${GVA_PGSQL_USER:-gnuviechadmin}" +DB_NAME="${GVA_PGSQL_DATABASE:-gnuviechadmin}" + +until pg_isready -q -h "${DB_HOST}" -p "${DB_PORT}" -U "${PG_USER}" -d "${DB_NAME}" +do + echo -n "." + sleep 1 +done + +echo "db is ready" + python3 manage.py compilemessages python3 manage.py collectstatic --noinput python3 manage.py migrate --noinput From e487dad0264fc5c555a981f7773684a9556fcaf6 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 5 Jul 2019 21:25:31 +0200 Subject: [PATCH 097/111] Improve settings The commit removes the non-existing sys.path import, simplifies the condition in the show_debug_toolbar function and defines ALLOWED_HOSTS for the test environment. --- gnuviechadmin/gnuviechadmin/settings.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py index 26b5b9d..b6cc710 100644 --- a/gnuviechadmin/gnuviechadmin/settings.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -6,7 +6,6 @@ Common settings and globals. """ from os.path import abspath, basename, dirname, join, normpath -from sys import path from django.contrib.messages import constants as messages @@ -23,9 +22,6 @@ SITE_ROOT = dirname(DJANGO_ROOT) # Site name: SITE_NAME = basename(DJANGO_ROOT) -# Add our project to our pythonpath, this way we don't need to type our project -# name in our dotted import paths: -path.append(DJANGO_ROOT) # ######### END PATH CONFIGURATION @@ -363,7 +359,7 @@ STATIC_ROOT = "/srv/gnuviechadmin/static/" def show_debug_toolbar(request): - return DEBUG == True and GVA_ENVIRONMENT == "local" + return DEBUG and GVA_ENVIRONMENT == "local" if GVA_ENVIRONMENT == "local": @@ -431,6 +427,7 @@ if GVA_ENVIRONMENT == "local": # ######### END TOOLBAR CONFIGURATION elif GVA_ENVIRONMENT == "test": + ALLOWED_HOSTS = ["localhost"] PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) LOGGING["handlers"].update( { From 54c1fbfed0b808494b8502f854d8bf19870371a0 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 2 Mar 2020 16:49:23 +0100 Subject: [PATCH 098/111] Update dependencies --- Dockerfile | 5 +- Pipfile | 19 +- Pipfile.lock | 529 ++++++++++++++++++++++++++------------------- docker-compose.yml | 8 +- 4 files changed, 322 insertions(+), 239 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7846bf3..13032e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ RUN apt-get update \ postgresql-client \ python3-dev \ python3-pip \ + python3-setuptools \ + python3-wheel \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*.* @@ -20,7 +22,8 @@ WORKDIR /srv/gnuviechadmin ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 -RUN python3 -m pip install -U pip && pip3 install pipenv +RUN python3 -m pip install -U --prefix=/usr/local pip && \ + /usr/local/bin/pip3 install --prefix=/usr/local pipenv COPY Pipfile Pipfile.lock /srv/gnuviechadmin/ diff --git a/Pipfile b/Pipfile index f7ae411..0c6343e 100644 --- a/Pipfile +++ b/Pipfile @@ -3,18 +3,23 @@ url = "https://pypi.org/simple" verify_ssl = true name = "pypi" +[[source]] +url = "https://pypi.gnuviech-server.de/simple" +verify_ssl = true +name = "gnuviech" + [packages] -django = "*" -django-model-utils = "*" "psycopg2" = "*" -passlib = "*" +Django = "<3" celery = "*" -redis = "*" -gvacommon = {ref = "0.4.0", git = "https://git.dittberner.info/gnuviech/gvacommon.git"} -requests-oauthlib = "*" django-allauth = "*" -django-crispy-forms = "*" django-braces = "*" +django-crispy-forms = "*" +django-model-utils = "*" +gvacommon = {version = "*",index = "gnuviech"} +passlib = "*" +redis = "*" +requests-oauthlib = "*" [dev-packages] coverage = "*" diff --git a/Pipfile.lock b/Pipfile.lock index ee55549..0525797 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b9e2b615f91daeeea6b7c70efcc45f3a8b06bea72cf0189c3f6ffd0152668915" + "sha256": "1c0b7bdab385f10279c852fa7fe7ae2c022dc1c4495d0e55fd407aea947bc976" }, "pipfile-spec": 6, "requires": { @@ -12,37 +12,43 @@ "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:aa4409446139676943a2eaa27d5f58caf750f4ca5a89f888c452afd86be6a67d", - "sha256:cbb6f87d53cac612a594f982b717cc1c54c6a1e17943a0a0d32dc6cc9e2120c8" + "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", + "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" ], - "version": "==2.5.0" + "version": "==2.5.2" }, "billiard": { "hashes": [ - "sha256:756bf323f250db8bf88462cd042c992ba60d8f5e07fc5636c24ba7d6f4261d84" + "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", + "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" ], - "version": "==3.6.0.0" + "version": "==3.6.3.0" }, "celery": { "hashes": [ - "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", - "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" + "sha256:3c5fcd6bfcf9a6323cb742cfc121d1790d50cfeddf300ba723cfa0b356413f07", + "sha256:a650525303ee866fb0c62c82f68681fcc2183eebbfafae552c27d30125fe518b" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.4.1" }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.6.16" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -60,92 +66,105 @@ }, "django": { "hashes": [ - "sha256:4d23f61b26892bac785f07401bc38cbf8fa4cec993f400e9cd9ddf28fd51c0ea", - "sha256:6e974d4b57e3b29e4882b244d40171d6a75202ab8d2402b8e8adbd182e25cf0c" + "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a", + "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038" ], "index": "pypi", - "version": "==2.2.3" + "version": "==2.2.10" }, "django-allauth": { "hashes": [ - "sha256:4444434c2f43188e16e0a7732c3638e12787ca7e5032f69b6d30b2912f53809e" + "sha256:7ab91485b80d231da191d5c7999ba93170ef1bf14ab6487d886598a1ad03e1d8" ], "index": "pypi", - "version": "==0.39.1" + "version": "==0.41.0" }, "django-braces": { "hashes": [ - "sha256:a457d74ea29478123c0c4652272681b3cea0bf1232187fd9f9b6f1d97d32a890", - "sha256:ba68e98b817c6f01d71d10849f359979617b3fe4cefb7f289adefddced092ddc" + "sha256:83705b78948de00804bfacf40c315d001bb39630f35bbdd8588211c2d5b4d43f", + "sha256:a6d9b34cf3e4949635e54884097c30410d7964fc7bec7231445ea7079b8c5722" ], "index": "pypi", - "version": "==1.13.0" + "version": "==1.14.0" }, "django-crispy-forms": { "hashes": [ - "sha256:5952bab971110d0b86c278132dae0aa095beee8f723e625c3d3fa28888f1675f", - "sha256:705ededc554ad8736157c666681165fe22ead2dec0d5446d65fc9dd976a5a876" + "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:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", + "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" + ], + "markers": "python_version < '3.8'", + "version": "==1.5.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" }, - "django-model-utils": { - "hashes": [ - "sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760", - "sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96" - ], - "index": "pypi", - "version": "==3.2.0" - }, - "gvacommon": { - "git": "https://git.dittberner.info/gnuviech/gvacommon.git", - "ref": "8ba50c4fef85199ae3b48fc38b25081d10ce22c6" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" - }, - "kombu": { - "hashes": [ - "sha256:55b71d3785def3470a16217fe0780f9e6f95e61bf9ad39ef8dce0177224eab77", - "sha256:eb365ea795cd7e629ba2f1f398e0c3ba354b91ef4de225ffdf6ab45fdfc7d581" - ], - "version": "==4.6.3" - }, - "oauthlib": { - "hashes": [ - "sha256:40a63637707e9163eda62d0f5345120c65e001a790480b8256448543c1f78f66", - "sha256:b4d99ae8ccfb7d33ba9591b59355c64eef5241534aa3da2e4c0435346b84bc8e" - ], - "version": "==3.0.2" - }, - "passlib": { - "hashes": [ - "sha256:3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", - "sha256:43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280" - ], - "index": "pypi", - "version": "==1.7.1" - }, "psycopg2": { "hashes": [ - "sha256:128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001", - "sha256:227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323", - "sha256:2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242", - "sha256:4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80", - "sha256:640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b", - "sha256:897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457", - "sha256:8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864", - "sha256:b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f", - "sha256:cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23", - "sha256:d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f", - "sha256:f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1" + "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677", + "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d", + "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b", + "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c", + "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0", + "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f", + "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4", + "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38", + "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6", + "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b", + "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151", + "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a", + "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6" ], "index": "pypi", - "version": "==2.8.3" + "version": "==2.8.4" }, "python3-openid": { "hashes": [ @@ -156,47 +175,54 @@ }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "version": "==2019.1" + "version": "==2019.3" }, "redis": { "hashes": [ - "sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175", - "sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273" + "sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f", + "sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833" ], "index": "pypi", - "version": "==3.2.1" + "version": "==3.4.1" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "requests-oauthlib": { "hashes": [ - "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57", - "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140" + "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.3.0" + }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], - "version": "==0.3.0" + "version": "==0.3.1" }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" ], - "version": "==1.25.3" + "version": "==1.25.8" }, "vine": { "hashes": [ @@ -204,6 +230,13 @@ "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" ], "version": "==1.3.0" + }, + "zipp": { + "hashes": [ + "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2", + "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a" + ], + "version": "==3.0.0" } }, "develop": { @@ -214,26 +247,33 @@ ], "version": "==0.7.12" }, + "asgiref": { + "hashes": [ + "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", + "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" + ], + "version": "==3.2.3" + }, "babel": { "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" ], - "version": "==2.7.0" + "version": "==2.8.0" }, "blockdiag": { "hashes": [ - "sha256:8dd6570a2ac41b3c0dfe5706de20913cdbebe1bbd2e6dea9ebc13db79df8c151", - "sha256:929125db1cb59145e09dc561021389c7ca71108ef4e4c51a12728eea5b75fccc" + "sha256:16a69dd9f3b44c9e0869999ce82aa968586698febc86ece9ca0c902dba772397", + "sha256:fa0b47cf25bfc4d546b7fc284c70c3bac875a066e744b4a6b1d9ba457e4ed077" ], - "version": "==1.5.4" + "version": "==2.0.1" }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.6.16" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -244,64 +284,63 @@ }, "coverage": { "hashes": [ - "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", - "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", - "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", - "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", - "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", - "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", - "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", - "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", - "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", - "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", - "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", - "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", - "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", - "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", - "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", - "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", - "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", - "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", - "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", - "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", - "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", - "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", - "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", - "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", - "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", - "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", - "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", - "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", - "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", - "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", - "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" + "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", + "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", + "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", + "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", + "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", + "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", + "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", + "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", + "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", + "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", + "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", + "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", + "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", + "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", + "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", + "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", + "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", + "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", + "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", + "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", + "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", + "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", + "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", + "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", + "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", + "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", + "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", + "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", + "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", + "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", + "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" ], "index": "pypi", - "version": "==4.5.3" + "version": "==5.0.3" }, "django": { "hashes": [ - "sha256:4d23f61b26892bac785f07401bc38cbf8fa4cec993f400e9cd9ddf28fd51c0ea", - "sha256:6e974d4b57e3b29e4882b244d40171d6a75202ab8d2402b8e8adbd182e25cf0c" + "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a", + "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038" ], "index": "pypi", - "version": "==2.2.3" + "version": "==2.2.10" }, "django-debug-toolbar": { "hashes": [ - "sha256:17c53cd6bf4e7d69902aedf9a1d26c5d3b7369b54c5718744704f27b5a72f35d", - "sha256:9a23ada2e43cd989195db3c18710b5d7451134a0d48127ab64c1d2ad81700342" + "sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943", + "sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c" ], "index": "pypi", - "version": "==2.0" + "version": "==2.2" }, "docutils": { "hashes": [ - "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" ], - "version": "==0.14" + "version": "==0.16" }, "funcparserlib": { "hashes": [ @@ -311,24 +350,24 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "imagesize": { "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", + "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "jinja2": { "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" ], - "version": "==2.10.1" + "version": "==2.11.1" }, "markupsafe": { "hashes": [ @@ -336,13 +375,16 @@ "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", @@ -359,7 +401,9 @@ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, @@ -372,41 +416,37 @@ }, "packaging": { "hashes": [ - "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", - "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" + "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", + "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" ], - "version": "==19.0" + "version": "==20.1" }, "pillow": { "hashes": [ - "sha256:0804f77cb1e9b6dbd37601cee11283bba39a8d44b9ddb053400c58e0c0d7d9de", - "sha256:0ab7c5b5d04691bcbd570658667dd1e21ca311c62dcfd315ad2255b1cd37f64f", - "sha256:0b3e6cf3ea1f8cecd625f1420b931c83ce74f00c29a0ff1ce4385f99900ac7c4", - "sha256:365c06a45712cd723ec16fa4ceb32ce46ad201eb7bbf6d3c16b063c72b61a3ed", - "sha256:38301fbc0af865baa4752ddae1bb3cbb24b3d8f221bf2850aad96b243306fa03", - "sha256:3aef1af1a91798536bbab35d70d35750bd2884f0832c88aeb2499aa2d1ed4992", - "sha256:3fe0ab49537d9330c9bba7f16a5f8b02da615b5c809cdf7124f356a0f182eccd", - "sha256:45a619d5c1915957449264c81c008934452e3fd3604e36809212300b2a4dab68", - "sha256:49f90f147883a0c3778fd29d3eb169d56416f25758d0f66775db9184debc8010", - "sha256:571b5a758baf1cb6a04233fb23d6cf1ca60b31f9f641b1700bfaab1194020555", - "sha256:5ac381e8b1259925287ccc5a87d9cf6322a2dc88ae28a97fe3e196385288413f", - "sha256:6153db744a743c0c8c91b8e3b9d40e0b13a5d31dbf8a12748c6d9bfd3ddc01ad", - "sha256:6fd63afd14a16f5d6b408f623cc2142917a1f92855f0df997e09a49f0341be8a", - "sha256:70acbcaba2a638923c2d337e0edea210505708d7859b87c2bd81e8f9902ae826", - "sha256:70b1594d56ed32d56ed21a7fbb2a5c6fd7446cdb7b21e749c9791eac3a64d9e4", - "sha256:76638865c83b1bb33bcac2a61ce4d13c17dba2204969dedb9ab60ef62bede686", - "sha256:7b2ec162c87fc496aa568258ac88631a2ce0acfe681a9af40842fc55deaedc99", - "sha256:7cee2cef07c8d76894ebefc54e4bb707dfc7f258ad155bd61d87f6cd487a70ff", - "sha256:7d16d4498f8b374fc625c4037742fbdd7f9ac383fd50b06f4df00c81ef60e829", - "sha256:b50bc1780681b127e28f0075dfb81d6135c3a293e0c1d0211133c75e2179b6c0", - "sha256:bd0582f831ad5bcad6ca001deba4568573a4675437db17c4031939156ff339fa", - "sha256:cfd40d8a4b59f7567620410f966bb1f32dc555b2b19f82a91b147fac296f645c", - "sha256:e3ae410089de680e8f84c68b755b42bc42c0ceb8c03dbea88a5099747091d38e", - "sha256:e9046e559c299b395b39ac7dbf16005308821c2f24a63cae2ab173bd6aa11616", - "sha256:ef6be704ae2bc8ad0ebc5cb850ee9139493b0fc4e81abcc240fb392a63ebc808", - "sha256:f8dc19d92896558f9c4317ee365729ead9d7bbcf2052a9a19a3ef17abbb8ac5b" + "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", + "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", + "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", + "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", + "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", + "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", + "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", + "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", + "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", + "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", + "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", + "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", + "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", + "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", + "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", + "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", + "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", + "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", + "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", + "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", + "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", + "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" ], - "version": "==6.1.0" + "version": "==7.0.0" }, "pycodestyle": { "hashes": [ @@ -417,11 +457,10 @@ }, "pydocstyle": { "hashes": [ - "sha256:2258f9b0df68b97bf3a6c29003edc5238ff8879f1efb6f1999988d934e432bd8", - "sha256:5741c85e408f9e0ddf873611085e819b809fca90b619f5fd7f34bd4959da3dd4", - "sha256:ed79d4ec5e92655eccc21eb0c6cf512e69512b4a97d215ace46d17e4990f2039" + "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586", + "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5" ], - "version": "==3.0.0" + "version": "==5.0.2" }, "pyflakes": { "hashes": [ @@ -432,10 +471,10 @@ }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.4.2" + "version": "==2.5.2" }, "pylama": { "hashes": [ @@ -447,32 +486,32 @@ }, "pyparsing": { "hashes": [ - "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", - "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" ], - "version": "==2.4.0" + "version": "==2.4.6" }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "version": "==2019.1" + "version": "==2019.3" }, "releases": { "hashes": [ - "sha256:16cc20a59bd00854a8c6070b5b5eb410b093cd59c69bc8feb0cbaadd4ae8bc23", - "sha256:7a1b3163ab062d41dc4e61d8f51df055756d146f1fb6e18ee5cf55d91c9cda54" + "sha256:555ae4c97a671a420281c1c782e9236be25157b449fdf20b4c4b293fe93db2f1", + "sha256:cb3435ba372a6807433800fbe473760cfa781171513f670f3c4b76983ac80f18" ], "index": "pypi", - "version": "==1.6.1" + "version": "==1.6.3" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "semantic-version": { "hashes": [ @@ -483,60 +522,96 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.12.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ - "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" + "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", + "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" ], - "version": "==1.9.0" + "version": "==2.0.0" }, "sphinx": { "hashes": [ - "sha256:217a7705adcb573da5bbe1e0f5cab4fa0bd89fd9342c9159121746f593c2d5a4", - "sha256:a602513f385f1d5785ff1ca420d9c7eb1a1b63381733b2f0ea8188a391314a86" + "sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88", + "sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709" ], "index": "pypi", - "version": "==1.7.9" + "version": "==2.4.3" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", + "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" + ], + "version": "==1.0.2" }, "sphinxcontrib-blockdiag": { "hashes": [ - "sha256:2d2ccde16bafb061ae8d2008f9524726e8ccd2a8502651b76a1e7f07a4ffd8eb", - "sha256:7cdff966d8f372b9536374954314a6cf4280e0e48bc2321a4f25cc7f2114f8f0" + "sha256:51ce7cff8d25dfd4c8a753d5aa5491e6dbf280004719c49e8001e583ecda7d91", + "sha256:91fd35b64f1f25db59d80b8a5196ed4ffadf57a81f63ee207e34d53ec36d8f97" ], "index": "pypi", - "version": "==1.5.5" + "version": "==2.0.0" }, - "sphinxcontrib-websupport": { + "sphinxcontrib-devhelp": { "hashes": [ - "sha256:1501befb0fdf1d1c29a800fdbf4ef5dc5369377300ddbdd16d2cd40e54c6eefc", - "sha256:e02f717baf02d0b6c3dd62cf81232ffca4c9d5c331e03766982e3ff9f1d2bc3f" + "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", + "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" ], - "version": "==1.1.2" + "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:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], - "version": "==0.3.0" + "version": "==0.3.1" }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" ], - "version": "==1.25.3" + "version": "==1.25.8" }, "webcolors": { "hashes": [ - "sha256:18c091bd4bd75efd1e9f84f5eca4a54f6e6485eaa3967d2a55700835a1b1c418", - "sha256:771adfb75a8d9189724de748b2e9564edf64104726bff0c4f6e11d15635e6517" + "sha256:76f360636957d1c976db7466bc71dcb713bb95ac8911944dffc55c01cb516de6", + "sha256:b8cd5d865a25c51ff1218f0c90d0c0781fc64312a49b746b320cf50de1648f6e" ], - "version": "==1.9.1" + "version": "==1.11.1" } } } diff --git a/docker-compose.yml b/docker-compose.yml index ed08e19..146e76e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,19 @@ version: "3" services: db: - image: gnuviech/pgsql:stretch + image: gnuviech/pgsql:buster ports: - "15432:5432" env_file: .env volumes: - - "pg_data:/var/lib/postgresql/9.6/main" + - "pg_data:/var/lib/postgresql/11/main" mq: - image: gnuviech/mq:stretch + image: gnuviech/mq:buster env_file: .env volumes: - "mq_data:/var/lib/rabbitmq/mnesia" redis: - image: gnuviech/redis:stretch + image: gnuviech/redis:buster env_file: .env volumes: - "redis_data:/var/lib/redis" From 0bf37d1bea076acb604e12f4561ab3f4374d6648 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 3 Mar 2020 13:10:09 +0100 Subject: [PATCH 099/111] Improve docker setup Add gvaweb and gvaldap containers to docker-compose.yml. Unify most of Dockerfile with gvaweb and gvaldap. Add empty directories for mounting asset and media files into bind mounted docker volumes. Run application as separate system user. --- .gitignore | 7 +++- Dockerfile | 47 +++++++++++++++++-------- docker-compose.yml | 36 +++++++++++++++++-- docker/django_media/.empty | 0 docker/django_static/.empty | 0 gnuviechadmin/gnuviechadmin/settings.py | 2 +- gnuviechadmin.sh => gva.sh | 4 ++- 7 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 docker/django_media/.empty create mode 100644 docker/django_static/.empty rename gnuviechadmin.sh => gva.sh (84%) diff --git a/.gitignore b/.gitignore index bc825fe..1adb268 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,9 @@ gnuviechadmin/assets/ coverage-report/ .idea/ -.env \ No newline at end of file +.env + +/docker/django_media +/docker/django_static +!/docker/django_media/.empty +!/docker/django_static/.empty diff --git a/Dockerfile b/Dockerfile index 13032e5..53ba4cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,56 @@ -FROM debian:buster +ARG DEBIAN_RELEASE=buster +FROM debian:$DEBIAN_RELEASE LABEL maintainer="Jan Dittberner " +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ build-essential \ dumb-init \ gettext \ git \ - libpq-dev \ - postgresql-client \ python3-dev \ python3-pip \ python3-setuptools \ + python3-virtualenv \ python3-wheel \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*.* -VOLUME /srv/gnuviechadmin/media /srv/gnuviechadmin/static -WORKDIR /srv/gnuviechadmin +RUN python3 -m pip install --prefix=/usr/local pipenv -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libpq-dev \ + postgresql-client \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*.* -RUN python3 -m pip install -U --prefix=/usr/local pip && \ - /usr/local/bin/pip3 install --prefix=/usr/local pipenv +ARG GVAGID=2000 +ARG GVAUID=2000 -COPY Pipfile Pipfile.lock /srv/gnuviechadmin/ +ARG GVAAPP=gva -RUN pipenv install --system --deploy --ignore-pipfile --dev +VOLUME /srv/$GVAAPP/media /srv/$GVAAPP/static -COPY gnuviechadmin.sh /srv/ +WORKDIR /srv/$GVAAPP + +COPY Pipfile Pipfile.lock /srv/$GVAAPP/ + +RUN addgroup --gid $GVAGID $GVAAPP ; \ + adduser --home /home/$GVAAPP --shell /bin/bash --uid $GVAUID --gid $GVAGID --disabled-password --gecos "User for gnuviechadmin component $GVAAPP" $GVAAPP + +USER $GVAAPP +RUN python3 -m virtualenv --python=python3 /home/$GVAAPP/$GVAAPP-venv ; \ + /home/$GVAAPP/$GVAAPP-venv/bin/python3 -m pip install -U pip ; \ + VIRTUAL_ENV=/home/$GVAAPP/$GVAAPP-venv pipenv install --deploy --ignore-pipfile --dev + +VOLUME /srv/$GVAAPP EXPOSE 8000 -VOLUME /srv/gnuviechadmin -ENTRYPOINT ["dumb-init", "/srv/gnuviechadmin.sh"] +COPY gva.sh /srv/ + +ENTRYPOINT ["dumb-init", "/srv/gva.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 146e76e..7361242 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,8 +18,12 @@ services: volumes: - "redis_data:/var/lib/redis" gva: + image: gnuviech/gva:buster build: context: . + args: + GVAGID: 1000 + GVAUID: 1000 ports: - "8000:8000" depends_on: @@ -32,9 +36,35 @@ services: GVA_DOMAIN_NAME: localhost GVA_SITE_NAME: localhost volumes: - - "django_media:/srv/gnuviechadmin/media" - - "django_static:/srv/gnuviechadmin/static" - - "./gnuviechadmin:/srv/gnuviechadmin" + - "./docker/django_media:/srv/gva/media" + - "./docker/django_static:/srv/gva/static" + - ".:/srv/gva" + web: + image: gnuviech/gvaweb:buster + build: + context: ../gvaweb + args: + GVAGID: 1000 + GVAUID: 1000 + depends_on: + - mq + - redis + env_file: ../gvaweb/.env + volumes: + - "../gvaweb:/srv/gvaweb" + ldap: + image: gnuviech/gvaldap:buster + build: + context: ../gvaldap + args: + GVAGID: 1000 + GVAUID: 1000 + depends_on: + - mq + - redis + env_file: ../gvaldap/.env + volumes: + - "../gvaldap:/srv/gvaldap" volumes: django_media: django_static: diff --git a/docker/django_media/.empty b/docker/django_media/.empty new file mode 100644 index 0000000..e69de29 diff --git a/docker/django_static/.empty b/docker/django_static/.empty new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py index b6cc710..1e5384c 100644 --- a/gnuviechadmin/gnuviechadmin/settings.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -355,7 +355,7 @@ GVA_ENVIRONMENT = get_env_variable("GVA_ENVIRONMENT", default="prod") # ######### STATIC FILE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = "/srv/gnuviechadmin/static/" +STATIC_ROOT = "/srv/gva/static/" def show_debug_toolbar(request): diff --git a/gnuviechadmin.sh b/gva.sh similarity index 84% rename from gnuviechadmin.sh rename to gva.sh index 7ea96c1..4b0f3c2 100755 --- a/gnuviechadmin.sh +++ b/gva.sh @@ -13,8 +13,10 @@ do sleep 1 done -echo "db is ready" +echo " db is ready" +. /home/gva/gva-venv/bin/activate +cd /srv/gva/gnuviechadmin python3 manage.py compilemessages python3 manage.py collectstatic --noinput python3 manage.py migrate --noinput From d10eaee382cce27aa9cf29527cf7a95119579417 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 4 Mar 2020 17:19:40 +0100 Subject: [PATCH 100/111] Add file node to docker-compose setup --- docker-compose.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 7361242..f315a65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,6 +65,19 @@ services: env_file: ../gvaldap/.env volumes: - "../gvaldap:/srv/gvaldap" + file: + image: gnuviech/gvafile:buster + build: + context: ../gvafile + args: + GVAGID: 1000 + GVAUID: 1000 + depends_on: + - mq + - redis + env_file: ../gvafile/.env + volumes: + - "../gvafile:/srv/gvafile" volumes: django_media: django_static: From fda129e81aa9db1c5ff2c102ac068225d26771c3 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 4 Mar 2020 18:30:29 +0100 Subject: [PATCH 101/111] Add mysql node to docker-compose setup --- docker-compose.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index f315a65..dd174b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,6 +78,32 @@ services: env_file: ../gvafile/.env volumes: - "../gvafile:/srv/gvafile" + pgsql: + image: gnuviech/gvapgsql:buster + build: + context: ../gvapgsql + args: + GVAGID: 1000 + GVAUID: 1000 + depends_on: + - mq + - redis + env_file: ../gvapgsql/.env + volumes: + - "../gvapgsql:/srv/gvapgsql" + mysql: + image: gnuviech/gvamysql:buster + build: + context: ../gvamysql + args: + GVAGID: 1000 + GVAUID: 1000 + depends_on: + - mq + - redis + env_file: ../gvamysql/.env + volumes: + - "../gvamysql:/srv/gvamysql" volumes: django_media: django_static: From 47a1e1dc5560c59ce266e808f6823c8fa326e9eb Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 7 Mar 2020 18:25:49 +0100 Subject: [PATCH 102/111] Fix settings to work with salt state definitions - move from assets to static --- .gitignore | 1 + gnuviechadmin/gnuviechadmin/settings.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1adb268..e90e027 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ coverage-report/ /docker/django_static !/docker/django_media/.empty !/docker/django_static/.empty +/static/ diff --git a/gnuviechadmin/gnuviechadmin/settings.py b/gnuviechadmin/gnuviechadmin/settings.py index 1e5384c..0d9ee46 100644 --- a/gnuviechadmin/gnuviechadmin/settings.py +++ b/gnuviechadmin/gnuviechadmin/settings.py @@ -99,7 +99,7 @@ MEDIA_URL = "/media/" STATIC_URL = "/static/" # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # noqa -STATICFILES_DIRS = (normpath(join(SITE_ROOT, "gnuviechadmin", "assets")),) +STATICFILES_DIRS = (normpath(join(SITE_ROOT, "gnuviechadmin", "static")),) # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa STATICFILES_FINDERS = ( From 5bd55bc418754675e66ed9a277aa5b1c227fdab2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 4 Apr 2020 14:08:02 +0200 Subject: [PATCH 103/111] Update dependencies --- Pipfile.lock | 164 +++++++++++++++++++++++++-------------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 0525797..fbb3cbe 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -37,11 +37,11 @@ }, "celery": { "hashes": [ - "sha256:3c5fcd6bfcf9a6323cb742cfc121d1790d50cfeddf300ba723cfa0b356413f07", - "sha256:a650525303ee866fb0c62c82f68681fcc2183eebbfafae552c27d30125fe518b" + "sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f", + "sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a" ], "index": "pypi", - "version": "==4.4.1" + "version": "==4.4.2" }, "certifi": { "hashes": [ @@ -66,11 +66,11 @@ }, "django": { "hashes": [ - "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a", - "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038" + "sha256:69897097095f336d5aeef45b4103dceae51c00afa6d3ae198a2a18e519791b7a", + "sha256:6ecd229e1815d4fc5240fc98f1cca78c41e7a8cd3e3f2eefadc4735031077916" ], "index": "pypi", - "version": "==2.2.10" + "version": "==2.2.12" }, "django-allauth": { "hashes": [ @@ -119,11 +119,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", - "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" ], "markers": "python_version < '3.8'", - "version": "==1.5.0" + "version": "==1.6.0" }, "kombu": { "hashes": [ @@ -233,10 +233,10 @@ }, "zipp": { "hashes": [ - "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2", - "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==3.0.0" + "version": "==3.1.0" } }, "develop": { @@ -249,10 +249,10 @@ }, "asgiref": { "hashes": [ - "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", - "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" + "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5", + "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c" ], - "version": "==3.2.3" + "version": "==3.2.7" }, "babel": { "hashes": [ @@ -284,48 +284,48 @@ }, "coverage": { "hashes": [ - "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", - "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", - "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", - "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", - "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", - "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", - "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", - "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", - "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", - "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", - "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", - "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", - "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", - "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", - "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", - "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", - "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", - "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", - "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", - "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", - "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", - "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", - "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", - "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", - "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", - "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", - "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", - "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", - "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", - "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", - "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" + "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.3" + "version": "==5.0.4" }, "django": { "hashes": [ - "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a", - "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038" + "sha256:69897097095f336d5aeef45b4103dceae51c00afa6d3ae198a2a18e519791b7a", + "sha256:6ecd229e1815d4fc5240fc98f1cca78c41e7a8cd3e3f2eefadc4735031077916" ], "index": "pypi", - "version": "==2.2.10" + "version": "==2.2.12" }, "django-debug-toolbar": { "hashes": [ @@ -416,37 +416,37 @@ }, "packaging": { "hashes": [ - "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", - "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==20.1" + "version": "==20.3" }, "pillow": { "hashes": [ - "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", - "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", - "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", - "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", - "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", - "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", - "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", - "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", - "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", - "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", - "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", - "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", - "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", - "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", - "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", - "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", - "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", - "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", - "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", - "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", - "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", - "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" + "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.0.0" + "version": "==7.1.1" }, "pycodestyle": { "hashes": [ @@ -471,10 +471,10 @@ }, "pygments": { "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.5.2" + "version": "==2.6.1" }, "pylama": { "hashes": [ @@ -536,11 +536,11 @@ }, "sphinx": { "hashes": [ - "sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88", - "sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709" + "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66", + "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb" ], "index": "pypi", - "version": "==2.4.3" + "version": "==2.4.4" }, "sphinxcontrib-applehelp": { "hashes": [ From 367a3197a115293200c76a0111dcca6cb1ca8a4f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 5 Apr 2020 14:22:35 +0200 Subject: [PATCH 104/111] Add architecture diagram --- docs/gnuviechadmin_architecture.drawio | 1 + docs/gnuviechadmin_architecture.svg | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 docs/gnuviechadmin_architecture.drawio create mode 100644 docs/gnuviechadmin_architecture.svg diff --git a/docs/gnuviechadmin_architecture.drawio b/docs/gnuviechadmin_architecture.drawio new file mode 100644 index 0000000..951a193 --- /dev/null +++ b/docs/gnuviechadmin_architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/gnuviechadmin_architecture.svg b/docs/gnuviechadmin_architecture.svg new file mode 100644 index 0000000..a68effd --- /dev/null +++ b/docs/gnuviechadmin_architecture.svg @@ -0,0 +1,3 @@ + + +
    Redis
    Redis
    gvapgsql
    gvapgsql
    gvamysql
    gvamysql
    gvafile
    gvafile
    gvaldap
    gvaldap
    gvaweb
    gvaweb
    send
    command
    send...
    pull
    results
    pull...
    gva
    gva
    RabbitMQ
    RabbitMQ
    Admins
    Admins
    Viewer does not support full SVG 1.1
    \ No newline at end of file From d90c0e096c3a219d6c730b21df990814df3dfc31 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 5 Apr 2020 14:24:43 +0200 Subject: [PATCH 105/111] Add PNG export of architecture diagram --- docs/gnuviechadmin_architecture.png | Bin 0 -> 87793 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/gnuviechadmin_architecture.png diff --git a/docs/gnuviechadmin_architecture.png b/docs/gnuviechadmin_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..dde221a62964fd33129306d3499436f10548c77f GIT binary patch literal 87793 zcmZs@bwJhK(l$&>cc*lB!=_WZM7q0C8VTt}N;(xJm5>sUPHB+t5TqL0%1Cg`%t$3=F~;3=AABG7|WS34I0^49s(w z7qU`1J~;=uo<=(MME$mQ@3BQYItVS}>t~HU4~^KIa0m{%qt)2zcR*y-@bnA6!o?pfVKHO%C&R%H)rfFq&vVRw=IWr$Cu+7f)qA_CgSAO z)cV#|7`J6#p=b7$-Ruu|HbG};3k#aX7I|z0DMe;sVPQ9K@BY-Xsfp4W$}F;PJ(I;h z-5zc|8@%J6BZoYX=`{C$2EHQCZ_HE~&b0?gL?n`9P~>LWEmR`P$LGdj7<#S81<{ck z2XF^`5H_fHiTr-K*FEjQ!D0Gi_{s$jtb^_K(_=u>_wN>6JugaE3#ds2CcD?nEo&T> zb$#+$QN8n(GaAhZM7)lW;z{|r0=wvLZf`fvcgCEKMZhMc-a}}?0)`lvnNj?&4m#hP zcNdnGg%h$bnpIBdIMuGiP7%>y69=|T3k5PDDC-&&DSTb+*nZ9JF^AmL+`Rs);*}E> zp;S~_TpYTPsHo*e&mb3nt^!=5&`-k2;RwBcxSTF#_%`BSX8zx3;#b1N6YY!g8d~;((E`pL1|vUR_-gU+iGp z*-cAU%p9K1hW(kdXrT&Bcj4+)-HrDMQ&^SKSu*aSTjAs`+Bl+t++s?*e%f@fLv5Hl z>K{OUA-HkO zJ(kdMvN%`MPhc5eQe;jm_RZ-RDPhIza}klOLm38O<1OT5Td=R!YAPz|4X&#RkL_5& zF)=I)-wuOMQ*1kJ`lWK(Nn_WdbV>$Zv_8Nv|7jj*y8fh54*IEAg=aQ1z03ifT^9*ugaa;p~1unY@Qp-o4KKY8}6EUh$ z#oCOXyD0TRW^Tr?_|yxdp3hs)S&lf25_3Zbmx}FH*NJv_r+cQ$rq*|MBJc0tHmIXO zqavQ#7dSt)t*pF!SDy{*?ZXUJjFOU_>l{f+$>_VP2WlhqnEv_!KH1)7xwlG~E~~ye ze=HO-6v37?-M!bh$zaZV9mk6eIZMmS%nyWUef<45AMUQ5@;O3aVCd-R%x1qB6GNdNrsn;5#_VCi78kxD`%u88#X;m$iOEX-`PFMfBTPO!;{L&W

    s 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%0A;W|;x&Vin9;@rT2-y6P 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

    k`(`vX`NHf?2objS?b8p%9en+MWB+0R>&5|D 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 HGQa&lk#U=A literal 0 HcmV?d00001 From 5b7049486327527371d8a4daa31d18b7193f204f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 5 Apr 2020 14:32:17 +0200 Subject: [PATCH 106/111] Document current project page --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 3d7e316..ff40464 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,7 @@ Gnuviechadmin is based on Django_ and Celery_ .. _Django: https://djangoproject.com/ .. _Celery: http://www.celeryproject.com/ -The project page for gnuviechadmin is at http://dev.gnuviech-server.de/gva. If -you find some problem or have some feature suggestions you can post a new -ticket in our issue tracker on the project page. +The project page for gnuviechadmin is at +http://git.dittberner.info/gnuviech/gva. If you find some problem or have some +feature suggestions you can post a new ticket in our issue tracker on the +project page. From b1da42e6b7784a096a57879bf557fdb672ebfcd0 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 10 Apr 2020 14:04:13 +0200 Subject: [PATCH 107/111] Update dependencies --- Pipfile.lock | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index fbb3cbe..50db093 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -45,10 +45,10 @@ }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -149,22 +149,22 @@ }, "psycopg2": { "hashes": [ - "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677", - "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d", - "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b", - "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c", - "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0", - "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f", - "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4", - "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38", - "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6", - "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b", - "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151", - "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a", - "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6" + "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.4" + "version": "==2.8.5" }, "python3-openid": { "hashes": [ @@ -270,10 +270,10 @@ }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -464,10 +464,10 @@ }, "pyflakes": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "version": "==2.1.1" + "version": "==2.2.0" }, "pygments": { "hashes": [ @@ -486,10 +486,10 @@ }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.6" + "version": "==2.4.7" }, "pytz": { "hashes": [ @@ -536,11 +536,11 @@ }, "sphinx": { "hashes": [ - "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66", - "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb" + "sha256:6a099e6faffdc3ceba99ca8c2d09982d43022245e409249375edf111caf79ed3", + "sha256:b63a0c879c4ff9a4dffcb05217fa55672ce07abdeb81e33c73303a563f8d8901" ], "index": "pypi", - "version": "==2.4.4" + "version": "==3.0.0" }, "sphinxcontrib-applehelp": { "hashes": [ From 6a88ef6bd3dc011d1d810ebabed50aca3c6862e8 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 10 Apr 2020 14:18:29 +0200 Subject: [PATCH 108/111] Update changelog --- docs/changelog.rst | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0c7ac9a..9dc7c9a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,19 +1,34 @@ Changelog ========= +* :release:`0.12.0 <2020-04-10>` +* :support:`-` add architecture diagramm for documentation +* :support:`-` drop environment specific settings +* :support:`-` update to Python 3 +* :support:`-` use Pipenv for dependency management +* :support:`-` switch result backend to Redis +* :bug:`-` fix template error in password reset response * :support:`-` use separate test vhost for celery queues * :support:`-` switch licensing to AGPLv3+ * :support:`-` add a Vagrant setup to ease development -* :support:`-` add example provisioning defined as saltstack states +* :support:`-` add Docker setup for local development * :feature:`-` let all celery tasks run asynchronously and move task processing to signal handlers * :feature:`-` add unit tests for all the code * :feature:`-` add proper configuration for coverage, flake8 and pep8 -* :feature:`-` update to Django 1.9.1 +* :feature:`-` update to Django 2.2.12 * :support:`-` use gvacommon from separate repository -* :feature:`17` add DNS zone management * :support:`-` update documentation +* :release:`0.11.6 <2020-02-14>` + :support:`-` Update dependencies to versions that work with Debian Stretch + +* :release:`0.11.5 <2018-12-26>` + :support:`-` Remove Xing support from settings and templates + +* :release:`0.11.4 <2016-12-31>` + :bug:`-` fix wrong tag in password reset done template + * :release:`0.11.3 <2015-02-21>` * :bug:`-` fix handling of OpenSSH formatted keys with whitespace in comments * :bug:`-` the ssh key list does not show SSH keys of other users anymore From 42b6f8d91d3f200650b8efb72ca8f061deeedae2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 10 Apr 2020 14:19:36 +0200 Subject: [PATCH 109/111] Bump version to 0.12.0 --- gnuviechadmin/gnuviechadmin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuviechadmin/gnuviechadmin/__init__.py b/gnuviechadmin/gnuviechadmin/__init__.py index f02ae64..4f7caa1 100644 --- a/gnuviechadmin/gnuviechadmin/__init__.py +++ b/gnuviechadmin/gnuviechadmin/__init__.py @@ -1,4 +1,4 @@ # import celery_app to initialize it from gnuviechadmin.celery import app as celery_app # NOQA -__version__ = '0.12.0-alpha' +__version__ = '0.12.0' From 73b70bf35f395fd6b4c733f5d157935de8b52d66 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 10 Apr 2020 14:34:41 +0200 Subject: [PATCH 110/111] Fix documentation build --- docs/changelog.rst | 7 +++---- docs/conf.py | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9dc7c9a..883156e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,7 +7,6 @@ Changelog * :support:`-` update to Python 3 * :support:`-` use Pipenv for dependency management * :support:`-` switch result backend to Redis -* :bug:`-` fix template error in password reset response * :support:`-` use separate test vhost for celery queues * :support:`-` switch licensing to AGPLv3+ * :support:`-` add a Vagrant setup to ease development @@ -21,13 +20,13 @@ Changelog * :support:`-` update documentation * :release:`0.11.6 <2020-02-14>` - :support:`-` Update dependencies to versions that work with Debian Stretch +* :support:`-` Update dependencies to versions that work with Debian Stretch * :release:`0.11.5 <2018-12-26>` - :support:`-` Remove Xing support from settings and templates +* :support:`-` Remove Xing support from settings and templates * :release:`0.11.4 <2016-12-31>` - :bug:`-` fix wrong tag in password reset done template +* :bug:`-` fix wrong tag in password reset done template * :release:`0.11.3 <2015-02-21>` * :bug:`-` fix handling of OpenSSH formatted keys with whitespace in comments diff --git a/docs/conf.py b/docs/conf.py index 11a3fa5..e778564 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,6 +22,7 @@ import django # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath(os.path.join('..', 'gnuviechadmin'))) +os.environ['DJANGO_SETTINGS_MODULE'] = 'gnuviechadmin.settings' os.environ['GVA_SITE_ADMINMAIL'] = 'admin@gva.example.org' django.setup() @@ -38,8 +39,8 @@ extensions = [ 'sphinxcontrib.blockdiag'] # configuration for releases extension -releases_issue_uri = 'https://dev.gnuviech-server.de/gva/ticket/%s' -releases_release_uri = 'https://dev.gnuviech-server.de/gva/browser/?rev=%s' +releases_issue_uri = 'https://git.dittberner.info/gnuviech/gva/issues/%s' +releases_release_uri = 'https://git.dittberner.info/gnuviech/gva/src/tag/%s' # configuration for blockdiag extension blockdiag_fontpath = '/usr/share/fonts/truetype/dejavu/' @@ -58,7 +59,7 @@ master_doc = 'index' # General information about the project. project = u'gnuviechadmin' -copyright = u'2014, 2015, 2016 Jan Dittberner' +copyright = u'2014-2020, Jan Dittberner' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 9e3b7ba133f909cf2343c8bfb50db683b8333126 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 10 Apr 2020 14:36:22 +0200 Subject: [PATCH 111/111] Update install documentation --- docs/install.rst | 63 ++++++++++-------------------------------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 2ac2b6e..3b22344 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,59 +1,22 @@ -Installation -============ +.. index:: installation -You have several options in setting up your working environment. We recommend -using virtualenv to separate the dependencies of your project from your -system's python environment. If on Linux or Mac OS X, you can also use -virtualenvwrapper to help manage multiple virtualenvs across different -projects. +======= +Install +======= -Virtualenv Only ---------------- +Working Environment +=================== -First, make sure you are using virtualenv (http://www.virtualenv.org). Once -that's installed, create your virtualenv:: +To get a running work environment use `pipenv`_. - $ virtualenv --distribute gnuviechadmin +.. _pipenv: https://pipenv.kennethreitz.org/en/latest/ -You will also need to ensure that the virtualenv has the project directory -added to the path. Adding the project directory will allow `django-admin.py` to -be able to change settings using the `--settings` flag. +To get started install `pip` and `pipenv` and use `pipenv install --dev`: -Virtualenv with virtualenvwrapper ------------------------------------- +.. code-block:: sh -In Linux and Mac OSX, you can install virtualenvwrapper -(http://virtualenvwrapper.readthedocs.org/en/latest/), which will take care of -managing your virtual environments and adding the project path to the -`site-directory` for you:: + $ apt install python3-pip + $ python3 -m pip install --user -U pipenv + $ pipenv install --dev - $ mkdir gnuviechadmin - $ mkvirtualenv -a gnuviechadmin gnuviechadmin-dev $ cd gnuviechadmin && add2virtualenv `pwd` - -Installation of Dependencies -============================= - -Depending on where you are installing dependencies: - -In development:: - - $ pip install -r requirements/local.txt - -For production:: - - $ pip install -r requirements.txt - -PowerDNS setup -============== - -The models in :py:mod:`domains.models` are meant to be used together with a -PowerDNS setup with the generic PostgreSQL backend -(https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/). The -database schema differs a bit from the original schema to fit the Django model -conventions. To make PowerDNS work you have to redefine the SQL statements by -copying the following content to -:file:`/etc/powerdns/pdns.d/pdns.local.gva_queries.conf`. - -.. literalinclude:: pdns.local.gva_queries.conf - :language: properties