Merge branch 'feature/userdbs'
* feature/userdbs: add autogenerated documentation for module members make userdbs admin work properly add initial migration for userdbs add admin and a bit of documentation add new incomplete userdbs app
This commit is contained in:
commit
8e498c3e44
11 changed files with 664 additions and 0 deletions
|
@ -2,6 +2,8 @@ Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
* :feature:`-` add mysqltasks and pgsqltasks
|
* :feature:`-` add mysqltasks and pgsqltasks
|
||||||
|
* :feature:`-` add :py:mod:`userdbs` app to allow management of user databases
|
||||||
|
via :py:mod:`mysqltasks` and :py:mod:`pgsqltasks`
|
||||||
* :feature:`-` add new app :py:mod:`taskresults` that takes care of handling
|
* :feature:`-` add new app :py:mod:`taskresults` that takes care of handling
|
||||||
asynchronous `Celery`_ results
|
asynchronous `Celery`_ results
|
||||||
* :feature:`-` add new task :py:func:`osusers.tasks.delete_ldap_group` (needs
|
* :feature:`-` add new task :py:func:`osusers.tasks.delete_ldap_group` (needs
|
||||||
|
|
|
@ -114,6 +114,7 @@ provides some functionality that is common to all gnuviechadmin subprojects.
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
.. automodule:: mysqltasks.tasks
|
.. automodule:: mysqltasks.tasks
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
:py:mod:`osusers` app
|
:py:mod:`osusers` app
|
||||||
|
@ -167,6 +168,7 @@ provides some functionality that is common to all gnuviechadmin subprojects.
|
||||||
========================
|
========================
|
||||||
|
|
||||||
.. automodule:: pgsqltasks
|
.. automodule:: pgsqltasks
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
:py:mod:`tasks <pgsqltasks.tasks>`
|
:py:mod:`tasks <pgsqltasks.tasks>`
|
||||||
|
@ -199,3 +201,23 @@ provides some functionality that is common to all gnuviechadmin subprojects.
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
.. automodule:: taskresults.models
|
.. automodule:: taskresults.models
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`userdbs` app
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. automodule:: userdbs
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`admin <userdbs.admin>`
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: userdbs.admin
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`models <userdbs.models>`
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: userdbs.models
|
||||||
|
:members:
|
||||||
|
|
|
@ -230,6 +230,7 @@ LOCAL_APPS = (
|
||||||
'domains',
|
'domains',
|
||||||
'osusers',
|
'osusers',
|
||||||
'managemails',
|
'managemails',
|
||||||
|
'userdbs',
|
||||||
)
|
)
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
|
|
5
gnuviechadmin/userdbs/__init__.py
Normal file
5
gnuviechadmin/userdbs/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
"""
|
||||||
|
This app is for managing database users and user databases.
|
||||||
|
|
||||||
|
"""
|
||||||
|
default_app_config = 'userdbs.apps.UserdbsAppConfig'
|
279
gnuviechadmin/userdbs/admin.py
Normal file
279
gnuviechadmin/userdbs/admin.py
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
"""
|
||||||
|
Admin functionality for the :py:mod:`userdbs.models` models.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
DatabaseUser,
|
||||||
|
UserDatabase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseUserCreationForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
A form for creating :py:class:`database users
|
||||||
|
<userdbs.models.DatabaseUser>`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DatabaseUser
|
||||||
|
fields = ['osuser', 'db_type']
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
"""
|
||||||
|
Save the database user.
|
||||||
|
|
||||||
|
:param boolean commit: whether to save the created database user
|
||||||
|
:return: database user instance
|
||||||
|
:rtype: :py:class:`userdbs.models.DatabaseUser`
|
||||||
|
|
||||||
|
"""
|
||||||
|
dbuser = DatabaseUser.objects.create_database_user(
|
||||||
|
osuser=self.cleaned_data['osuser'],
|
||||||
|
db_type=self.cleaned_data['db_type'], commit=commit)
|
||||||
|
return dbuser
|
||||||
|
|
||||||
|
def save_m2m(self):
|
||||||
|
"""
|
||||||
|
Noop.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class UserDatabaseCreationForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
A form for creating :py:class:`user databases
|
||||||
|
<userdbs.models.UserDatabase>`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserDatabase
|
||||||
|
fields = ['db_user']
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
"""
|
||||||
|
Save the user database.
|
||||||
|
|
||||||
|
:param boolean commit: whether to save the created user database
|
||||||
|
:return: user database instance
|
||||||
|
:rtype: :py:class:`userdbs.models.UserDatabase`
|
||||||
|
|
||||||
|
"""
|
||||||
|
database = UserDatabase.objects.create_userdatabase(
|
||||||
|
db_user=self.cleaned_data['db_user'], commit=commit)
|
||||||
|
return database
|
||||||
|
|
||||||
|
def save_m2m(self):
|
||||||
|
"""
|
||||||
|
Noop.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseUserAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin class for working with :py:class:`database users
|
||||||
|
<userdbs.models.DatabaseUser>`
|
||||||
|
|
||||||
|
"""
|
||||||
|
actions = ['perform_delete_selected']
|
||||||
|
add_form = DatabaseUserCreationForm
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Use special form for database user creation.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: either a :py:class:`Database user
|
||||||
|
<userdbs.models.DatabaseUser>` instance or None for a new database
|
||||||
|
user
|
||||||
|
:param kwargs: keyword arguments to be passed to
|
||||||
|
:py:meth:`django.contrib.admin.ModelAdmin.get_form`
|
||||||
|
:return: form instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
defaults = {}
|
||||||
|
if obj is None:
|
||||||
|
defaults.update({
|
||||||
|
'form': self.add_form,
|
||||||
|
})
|
||||||
|
defaults.update(kwargs)
|
||||||
|
return super(DatabaseUserAdmin, self).get_form(
|
||||||
|
request, obj, **defaults)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""
|
||||||
|
Make sure that osuser, name and db_type are not editable for existing
|
||||||
|
database users.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: either a :py:class:`Database user
|
||||||
|
<userdbs.models.DatabaseUser>` instance or None for a new database
|
||||||
|
user
|
||||||
|
:return: a list of fields
|
||||||
|
:rtype: list
|
||||||
|
|
||||||
|
"""
|
||||||
|
if obj:
|
||||||
|
return ['osuser', 'name', 'db_type']
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
"""
|
||||||
|
Make sure that the user is created in the target database.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: a :py:class:`Database user <userdbs.models.DatabaseUser>`
|
||||||
|
instance
|
||||||
|
:param form: the form instance
|
||||||
|
:param boolean change: whether this is a change operation or not
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not change:
|
||||||
|
obj.create_in_database()
|
||||||
|
super(DatabaseUserAdmin, self).save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def perform_delete_selected(self, request, queryset):
|
||||||
|
"""
|
||||||
|
Action to delete a list of selected database users.
|
||||||
|
|
||||||
|
This action calls the delete method of each selected database user in
|
||||||
|
contrast to the default `delete_selected`
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param queryset: Django ORM queryset representing the selected database
|
||||||
|
users
|
||||||
|
|
||||||
|
"""
|
||||||
|
for dbuser in queryset.all():
|
||||||
|
dbuser.delete()
|
||||||
|
perform_delete_selected.short_description = _(
|
||||||
|
'Delete selected database users')
|
||||||
|
|
||||||
|
def get_actions(self, request):
|
||||||
|
"""
|
||||||
|
Get the available actions for database users.
|
||||||
|
|
||||||
|
This overrides the default behavior to remove the default
|
||||||
|
`delete_selected` action.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:return: list of actions
|
||||||
|
:rtype: list
|
||||||
|
|
||||||
|
"""
|
||||||
|
actions = super(DatabaseUserAdmin, self).get_actions(request)
|
||||||
|
if 'delete_selected' in actions:
|
||||||
|
del actions['delete_selected']
|
||||||
|
return actions
|
||||||
|
|
||||||
|
|
||||||
|
class UserDatabaseAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin class for working with :py:class:`user databases
|
||||||
|
<userdbs.models.UserDatabase>`
|
||||||
|
|
||||||
|
"""
|
||||||
|
actions = ['perform_delete_selected']
|
||||||
|
add_form = UserDatabaseCreationForm
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Use special form for user database creation.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: either a :py:class:`User database
|
||||||
|
<userdbs.models.UserDatabase>` instance or None for a new user
|
||||||
|
database
|
||||||
|
:param kwargs: keyword arguments to be passed to
|
||||||
|
:py:meth:`django.contrib.admin.ModelAdmin.get_form`
|
||||||
|
:return: form instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
defaults = {}
|
||||||
|
if obj is None:
|
||||||
|
defaults.update({
|
||||||
|
'form': self.add_form,
|
||||||
|
})
|
||||||
|
defaults.update(kwargs)
|
||||||
|
return super(UserDatabaseAdmin, self).get_form(
|
||||||
|
request, obj, **defaults)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""
|
||||||
|
Make sure that db_name and db_user are not editable for existing user
|
||||||
|
databases.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: either a :py:class:`User database
|
||||||
|
<userdbs.models.UserDatabase>` instance or None for a new user
|
||||||
|
database
|
||||||
|
:return: a list of fields
|
||||||
|
:rtype: list
|
||||||
|
|
||||||
|
"""
|
||||||
|
if obj:
|
||||||
|
return ['db_name', 'db_user']
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
"""
|
||||||
|
Make sure that the database is created in the target database server.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: a :py:class:`Database user <userdbs.models.DatabaseUser>`
|
||||||
|
instance
|
||||||
|
:param form: the form instance
|
||||||
|
:param boolean change: whether this is a change operation or not
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not change:
|
||||||
|
obj.create_in_database()
|
||||||
|
super(UserDatabaseAdmin, self).save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def perform_delete_selected(self, request, queryset):
|
||||||
|
"""
|
||||||
|
Action to delete a list of selected user databases.
|
||||||
|
|
||||||
|
This action calls the delete method of each selected user database in
|
||||||
|
contrast to the default `delete_selected`
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param queryset: Django ORM queryset representing the selected user
|
||||||
|
databases
|
||||||
|
|
||||||
|
"""
|
||||||
|
for dbuser in queryset.all():
|
||||||
|
dbuser.delete()
|
||||||
|
for database in queryset.all():
|
||||||
|
database.delete()
|
||||||
|
perform_delete_selected.short_description = _(
|
||||||
|
'Delete selected user databases')
|
||||||
|
|
||||||
|
def get_actions(self, request):
|
||||||
|
"""
|
||||||
|
Get the available actions for user databases.
|
||||||
|
|
||||||
|
This overrides the default behavior to remove the default
|
||||||
|
`delete_selected` action.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:return: list of actions
|
||||||
|
:rtype: list
|
||||||
|
|
||||||
|
"""
|
||||||
|
actions = super(UserDatabaseAdmin, self).get_actions(request)
|
||||||
|
if 'delete_selected' in actions:
|
||||||
|
del actions['delete_selected']
|
||||||
|
return actions
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(DatabaseUser, DatabaseUserAdmin)
|
||||||
|
admin.site.register(UserDatabase, UserDatabaseAdmin)
|
18
gnuviechadmin/userdbs/apps.py
Normal file
18
gnuviechadmin/userdbs/apps.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
"""
|
||||||
|
This module contains the :py:class:`django.apps.AppConfig` instance for the
|
||||||
|
:py:mod:`userdbs` app.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class UserdbsAppConfig(AppConfig):
|
||||||
|
"""
|
||||||
|
AppConfig for the :py:mod:`userdbs` app.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'userdbs'
|
||||||
|
verbose_name = _('Database Users and their Databases')
|
55
gnuviechadmin/userdbs/migrations/0001_initial.py
Normal file
55
gnuviechadmin/userdbs/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('osusers', '0004_auto_20150104_1751'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'database user',
|
||||||
|
'verbose_name_plural': 'database users',
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user database',
|
||||||
|
'verbose_name_plural': 'user specific database',
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='userdatabase',
|
||||||
|
unique_together=set([('db_name', 'db_user')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='databaseuser',
|
||||||
|
unique_together=set([('name', 'db_type')]),
|
||||||
|
),
|
||||||
|
]
|
0
gnuviechadmin/userdbs/migrations/__init__.py
Normal file
0
gnuviechadmin/userdbs/migrations/__init__.py
Normal file
276
gnuviechadmin/userdbs/models.py
Normal file
276
gnuviechadmin/userdbs/models.py
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.db import transaction
|
||||||
|
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')),
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
Database type choice enumeration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseUserManager(models.Manager):
|
||||||
|
"""
|
||||||
|
Default Manager for :py:class:`userdbs.models.DatabaseUser`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_next_dbuser_name(self, osuser, db_type):
|
||||||
|
"""
|
||||||
|
Get the next available database user name.
|
||||||
|
|
||||||
|
:param osuser: :py:class:`osusers.models.User` instance
|
||||||
|
:param db_type: value from :py:data:`DB_TYPES`
|
||||||
|
:return: database user name
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
count = 1
|
||||||
|
dbuser_name_format = "{0}db{{0:02d}}".format(osuser.username)
|
||||||
|
nextname = dbuser_name_format.format(count)
|
||||||
|
|
||||||
|
for user in self.values('name').filter(
|
||||||
|
osuser=osuser, db_type=db_type
|
||||||
|
).order_by('name'):
|
||||||
|
if user['name'] == nextname:
|
||||||
|
count += 1
|
||||||
|
nextname = dbuser_name_format.format(count)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return nextname
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create_database_user(
|
||||||
|
self, osuser, db_type, username=None, password=None, commit=True
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a database user of the given type for the given OS user.
|
||||||
|
|
||||||
|
If username or password are not specified they are generated.
|
||||||
|
|
||||||
|
:param osuser: the :py:class:`osusers.models.User` instance
|
||||||
|
:param db_type: value from :py:data:`DB_TYPES`
|
||||||
|
:param str username: database user name
|
||||||
|
:param str password: initial password or None
|
||||||
|
:param boolean commit: whether the user should be persisted
|
||||||
|
:return: :py:class:`userdbs.models.DatabaseUser` instance
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The password is not persisted it is only used to set the password
|
||||||
|
on the database side.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if username is None:
|
||||||
|
username = self._get_next_dbuser_name(osuser, db_type)
|
||||||
|
db_user = DatabaseUser(
|
||||||
|
osuser=osuser, db_type=db_type, name=username, password=password)
|
||||||
|
if commit:
|
||||||
|
db_user.create_in_database()
|
||||||
|
db_user.save()
|
||||||
|
return db_user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class DatabaseUser(TimeStampedModel, models.Model):
|
||||||
|
osuser = models.ForeignKey(OsUser)
|
||||||
|
name = models.CharField(
|
||||||
|
_('username'), max_length=63)
|
||||||
|
db_type = models.PositiveSmallIntegerField(
|
||||||
|
_('database type'), choices=DB_TYPES)
|
||||||
|
|
||||||
|
objects = DatabaseUserManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ['name', 'db_type']
|
||||||
|
verbose_name = _('database user')
|
||||||
|
verbose_name_plural = _('database users')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%(name)s (%(db_type)s for %(osuser)s)" % {
|
||||||
|
'name': self.name,
|
||||||
|
'db_type': self.get_db_type_display(),
|
||||||
|
'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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Delete the database user 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`
|
||||||
|
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class UserDatabaseManager(models.Manager):
|
||||||
|
"""
|
||||||
|
Default manager for :py:class:`userdbs.models.UserDatabase` instances.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_next_dbname(self, db_user):
|
||||||
|
"""
|
||||||
|
Get the next available database name for the given database user.
|
||||||
|
|
||||||
|
:param db_user: :py:class:`userdbs.models.DatabaseUser` instance
|
||||||
|
:return: database name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
count = 1
|
||||||
|
db_name_format = "{0}_{{0:02d}}".format(db_user.name)
|
||||||
|
# first db is named the same as the user
|
||||||
|
nextname = db_user.name
|
||||||
|
for name in self.values('db_name').filter(db_user=db_user).order_by(
|
||||||
|
'db_name'
|
||||||
|
):
|
||||||
|
if name['db_name'] == nextname:
|
||||||
|
count += 1
|
||||||
|
nextname = db_name_format.format(count)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return nextname
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create_userdatabase(self, db_user, db_name=None, commit=True):
|
||||||
|
"""
|
||||||
|
Creates a new user database.
|
||||||
|
|
||||||
|
:param db_user: :py:class:`userdbs.models.DatabaseUser` instance
|
||||||
|
:param str db_name: database name
|
||||||
|
:param boolean commit: whether the database should be persisted
|
||||||
|
:return: :py:class:`userdbs.models.UserDatabase` instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
if db_name is None:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
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'))
|
||||||
|
|
||||||
|
objects = UserDatabaseManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ['db_name', 'db_user']
|
||||||
|
verbose_name = _('user database')
|
||||||
|
verbose_name_plural = _('user specific database')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%(db_name)s (%(db_user)s)" % {
|
||||||
|
'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`
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.db_user.db_type == DB_TYPES.pgsql:
|
||||||
|
delete_pgsql_database.delay(self.db_name, self.db_user.name).get()
|
||||||
|
elif self.db_user.db_type == DB_TYPES.mysql:
|
||||||
|
delete_mysql_database.delay(self.db_name, self.db_user.name).get()
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown database type %d' % self.db_type)
|
||||||
|
super(UserDatabase, self).delete(*args, **kwargs)
|
3
gnuviechadmin/userdbs/tests.py
Normal file
3
gnuviechadmin/userdbs/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
gnuviechadmin/userdbs/views.py
Normal file
3
gnuviechadmin/userdbs/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Reference in a new issue