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)