\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/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/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 7178c84..64819eb 100644
--- a/gnuviechadmin/taskresults/models.py
+++ b/gnuviechadmin/taskresults/models.py
@@ -10,17 +10,25 @@ 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, 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):
+class TaskResult(TimeStampedModel):
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)
@@ -30,10 +38,11 @@ class TaskResult(models.Model):
class Meta:
verbose_name = _('Task result')
verbose_name_plural = _('Task results')
+ ordering = ['created']
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 +50,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/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..a7d10ff
--- /dev/null
+++ b/gnuviechadmin/taskresults/tests/management/commands/test_fetch_taskresults.py
@@ -0,0 +1,66 @@
+"""
+This module provides tests for the
+:py:mod:`taskresults.management.commands.fetch_taskresults` Django management
+command.
+
+"""
+
+from unittest.mock import MagicMock, patch
+
+from django.test import TestCase
+
+from taskresults.management.commands.fetch_taskresults import Command
+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"
+
+
+@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 dcace59..fe6445d 100644
--- a/gnuviechadmin/taskresults/tests/test_models.py
+++ b/gnuviechadmin/taskresults/tests/test_models.py
@@ -1,42 +1,69 @@
-from __future__ import absolute_import, unicode_literals
+"""
+This module provides tests for :py:mod:`taskresults.models`.
+
+"""
+from unittest.mock import MagicMock, patch
+
from django.test import TestCase
-from mock import patch, MagicMock
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')
- 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)
+ @patch("taskresults.models.app.AsyncResult")
+ def test_update_taskstatus_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)
- 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(no_ack=True, timeout=1)
- self.assertTrue(tr.finished)
+ mymock.get.assert_not_called()
+ self.assertFalse(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
- mock.state = 'SUCCESS'
- mock.result = TEST_RESULT
- tr = TaskResult.objects.create_task_result(mock, TEST_TASK_NAME)
+ @patch("taskresults.models.app.AsyncResult")
+ def test_update_taskstatus_finished(self, asyncresult):
+ resultmock = MagicMock(task_id=TEST_TASK_UUID)
+ 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()
@@ -47,8 +74,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)
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/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' %}:
diff --git a/gnuviechadmin/templates/socialaccount/snippets/provider_list.html b/gnuviechadmin/templates/socialaccount/snippets/provider_list.html
index 8bff939..3b7d9fb 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/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/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/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/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 4cc2b76..c9eafb5 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,15 +77,13 @@ 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
-
@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(
@@ -121,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):
@@ -164,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)
@@ -204,7 +161,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.
@@ -237,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
@@ -247,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()
@@ -261,39 +219,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..4a5bc32
--- /dev/null
+++ b/gnuviechadmin/userdbs/signals.py
@@ -0,0 +1,331 @@
+"""
+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
+
+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.
+
+ :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(
+ '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.
+
+ :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())
+ # 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.
+
+ :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(
+ '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.
+
+ :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:
+ 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.
+
+ :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(
+ '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)
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..3479a30
--- /dev/null
+++ b/gnuviechadmin/userdbs/tests/test_admin.py
@@ -0,0 +1,148 @@
+"""
+This module provides tests for :py:mod:`userdbs.admin`.
+
+"""
+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.models import DB_TYPES, DatabaseUser, UserDatabase
+
+
+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..2aa1706
--- /dev/null
+++ b/gnuviechadmin/userdbs/tests/test_forms.py
@@ -0,0 +1,134 @@
+"""
+This module provides tests for :py:mod:`userdbs.forms`.
+
+"""
+from django import forms
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from django.urls import reverse
+
+from userdbs.forms import AddUserDatabaseForm, ChangeDatabaseUserPasswordForm
+from userdbs.models import DB_TYPES
+
+from unittest.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..41306bd
--- /dev/null
+++ b/gnuviechadmin/userdbs/tests/test_models.py
@@ -0,0 +1,286 @@
+"""
+This module provides tests for :py:mod:`userdbs.models`.
+
+"""
+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..33541e3
--- /dev/null
+++ b/gnuviechadmin/userdbs/tests/test_signals.py
@@ -0,0 +1,63 @@
+"""
+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 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,
+)
+
+
+@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())
diff --git a/gnuviechadmin/userdbs/tests/test_views.py b/gnuviechadmin/userdbs/tests/test_views.py
new file mode 100644
index 0000000..b52cbdf
--- /dev/null
+++ b/gnuviechadmin/userdbs/tests/test_views.py
@@ -0,0 +1,335 @@
+"""
+This module provides tests for :py:mod:`userdbs.views`.
+
+"""
+from unittest.mock import patch, MagicMock
+
+from django.test import TestCase
+from django.contrib.auth import get_user_model
+from django.urls import reverse
+
+from hostingpackages.models import (
+ CustomerHostingPackage,
+ CustomerUserDatabaseOption,
+ HostingPackageTemplate,
+ UserDatabaseOption,
+)
+
+from userdbs.models import DB_TYPES, UserDatabase
+from userdbs.views import AddUserDatabase, ChangeDatabaseUserPassword
+
+
+User = get_user_model()
+
+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
+ )
+ 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):
+ # noinspection PyUnresolvedReferences
+ 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, 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, dbuser):
+ return reverse(
+ "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))
+ 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(
+ 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/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/userdbs/views.py b/gnuviechadmin/userdbs/views.py
index e8794d6..d16eb5a 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):
@@ -61,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)
)
@@ -97,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())
@@ -126,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()
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/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"
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/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/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):
"""
diff --git a/gva.sh b/gva.sh
new file mode 100755
index 0000000..4b0f3c2
--- /dev/null
+++ b/gva.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+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"
+
+. /home/gva/gva-venv/bin/activate
+cd /srv/gva/gnuviechadmin
+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/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 8d92d6b..0000000
--- a/requirements/base.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-Django==1.7.4
-bpython==0.13.2
-django-braces==1.4.0
-django-model-utils==2.2
-django-crispy-forms==1.4.0
-logutils==0.3.3
-psycopg2==2.5.4
-passlib==1.6.2
-celery==3.1.17
-billiard==3.3.0.23
-kombu==3.0.37
-pytz==2019.1
-pyaml==14.12.10
-django-allauth==0.19.0
-oauthlib==0.7.2
-python-openid==2.2.5
-requests==2.5.1
-requests-oauthlib==0.4.2
diff --git a/requirements/local.txt b/requirements/local.txt
deleted file mode 100644
index 97a9db7..0000000
--- a/requirements/local.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-# 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
-releases==0.7.0
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 8bf1098..0000000
--- a/requirements/test.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# Test dependencies go here.
--r base.txt
-coverage==3.7.1
-mock==1.0.1