Merge branch 'release/0.4.0' into production
* release/0.4.0: (25 commits) define version number, update changelog set database password at the appropriate place remove username argument from delete_pgsql_database call remove username argument of pgsqltasks.tasks.delete_pgsql_database fix documentation issues 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 document addition of mysqltasks and pgsqltasks add mysqltasks and pgsqltasks with placeholders for the real tasks set default locale to en-us to avoid translated migrations add migration for verbose_name and verbose_name_plural in osusers.models.User switch to gvacommon.celeryrouters.GvaRouter unify routers, add support for mysql and pgsql tasks use taskresults app and delete_ldap_group task add taskresults app to handle celery task results add new task delete_ldap_group define celery timezone, restrict celery content to json ...
This commit is contained in:
commit
0ef151f780
39 changed files with 1962 additions and 74 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -43,3 +43,4 @@ Desktop.ini
|
||||||
htmlcov/
|
htmlcov/
|
||||||
tags
|
tags
|
||||||
_build/
|
_build/
|
||||||
|
*.mo
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
* :release:`0.4.0 <2015-01-11>`
|
||||||
|
* :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
|
||||||
|
asynchronous `Celery`_ results
|
||||||
|
* :feature:`-` add new task :py:func:`osusers.tasks.delete_ldap_group` (needs
|
||||||
|
gvaldap >= 0.2.0 on the LDAP side)
|
||||||
|
* :feature:`-` add a `customer` field to :py:class:`osusers.models.User`
|
||||||
|
* :feature:`-` allow empty password input in
|
||||||
|
:py:class:`osusers.admin.UserCreationForm` to allow generated passwords for
|
||||||
|
new users
|
||||||
|
|
||||||
* :release:`0.3.0 <2014-12-27>`
|
* :release:`0.3.0 <2014-12-27>`
|
||||||
* :feature:`-` call create/delete mailbox tasks when saving/deleting mailboxes
|
* :feature:`-` call create/delete mailbox tasks when saving/deleting mailboxes
|
||||||
* :support:`-` use celery routers from gvacommon
|
* :support:`-` use celery routers from gvacommon
|
||||||
|
@ -23,10 +36,11 @@ Changelog
|
||||||
* :feature:`-` full test suite for osusers
|
* :feature:`-` full test suite for osusers
|
||||||
* :feature:`-` full test suite for managemails app
|
* :feature:`-` full test suite for managemails app
|
||||||
* :feature:`-` full test suite for domains app
|
* :feature:`-` full test suite for domains app
|
||||||
* :feature:`-` `Celery <http://www.celeryproject.com/>`_ integration for ldap
|
* :feature:`-` `Celery`_ integration for ldap synchronization
|
||||||
synchronization
|
|
||||||
|
|
||||||
* :release:`0.1 <2014-05-25>`
|
* :release:`0.1 <2014-05-25>`
|
||||||
* :feature:`-` initial model code for os users
|
* :feature:`-` initial model code for os users
|
||||||
* :feature:`-` initial model code for mail address and mailbox management
|
* :feature:`-` initial model code for mail address and mailbox management
|
||||||
* :feature:`-` initial model code for domains
|
* :feature:`-` initial model code for domains
|
||||||
|
|
||||||
|
.. _Celery: http://www.celeryproject.org/
|
||||||
|
|
235
docs/code.rst
Normal file
235
docs/code.rst
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
******************
|
||||||
|
Code documentation
|
||||||
|
******************
|
||||||
|
|
||||||
|
.. index:: Django
|
||||||
|
|
||||||
|
gva is implemented as `Django`_ project and provides a frontend for
|
||||||
|
administrators and customers.
|
||||||
|
|
||||||
|
.. _Django: https://www.djangoproject.com/
|
||||||
|
|
||||||
|
|
||||||
|
The project module :py:mod:`gnuviechadmin`
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`celery <gnuviechadmin.celery>`
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.celery
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`urls <gnuviechadmin.urls>`
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.urls
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`wsgi <gnuviechadmin.wsgi>`
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.wsgi
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`settings <gnuviechadmin.settings>`
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.settings
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`base <gnuviechadmin.settings.base>`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.settings.base
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`local <gnuviechadmin.settings.local>`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.settings.local
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`production <gnuviechadmin.settings.production>`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.settings.production
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`test <gnuviechadmin.settings.test>`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: gnuviechadmin.settings.test
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`gvacommon`
|
||||||
|
===================
|
||||||
|
|
||||||
|
This module is imported from a separate git project via git subtree and
|
||||||
|
provides some functionality that is common to all gnuviechadmin subprojects.
|
||||||
|
|
||||||
|
.. automodule:: gvacommon
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`celeryrouters <gvacommon.celeryrouters>`
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: gvacommon.celeryrouters
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`managemails` app
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. automodule:: managemails
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`admin <managemails.admin>`
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: managemails.admin
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`models <managemails.models>`
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: managemails.models
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`mysqltasks` app
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. automodule:: mysqltasks
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`tasks <mysqltasks.tasks>`
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: mysqltasks.tasks
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autotask:: mysqltasks.tasks.create_mysql_database
|
||||||
|
.. autotask:: mysqltasks.tasks.create_mysql_user
|
||||||
|
.. autotask:: mysqltasks.tasks.delete_mysql_database
|
||||||
|
.. autotask:: mysqltasks.tasks.delete_mysql_user
|
||||||
|
.. autotask:: mysqltasks.tasks.set_mysql_userpassword
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`osusers` app
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. automodule:: osusers
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`admin <osusers.admin>`
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: osusers.admin
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`apps <osusers.apps>`
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. automodule:: osusers.apps
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`models <osusers.models>`
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: osusers.models
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`tasks <osusers.tasks>`
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: osusers.tasks
|
||||||
|
|
||||||
|
.. autotask:: osusers.tasks.add_ldap_user_to_group
|
||||||
|
.. autotask:: osusers.tasks.create_file_mailbox
|
||||||
|
.. autotask:: osusers.tasks.create_ldap_group
|
||||||
|
.. autotask:: osusers.tasks.create_ldap_user
|
||||||
|
.. autotask:: osusers.tasks.delete_file_mail_userdir
|
||||||
|
.. autotask:: osusers.tasks.delete_file_mailbox
|
||||||
|
.. autotask:: osusers.tasks.delete_file_sftp_userdir
|
||||||
|
.. autotask:: osusers.tasks.delete_ldap_group
|
||||||
|
.. autotask:: osusers.tasks.delete_ldap_group_if_empty
|
||||||
|
.. autotask:: osusers.tasks.delete_ldap_user
|
||||||
|
.. autotask:: osusers.tasks.remove_ldap_user_from_group
|
||||||
|
.. autotask:: osusers.tasks.setup_file_mail_userdir
|
||||||
|
.. autotask:: osusers.tasks.setup_file_sftp_userdir
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`pgsqltasks` app
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. automodule:: pgsqltasks
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`tasks <pgsqltasks.tasks>`
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: pgsqltasks.tasks
|
||||||
|
|
||||||
|
.. autotask:: pgsqltasks.tasks.create_pgsql_database
|
||||||
|
.. autotask:: pgsqltasks.tasks.create_pgsql_user
|
||||||
|
.. autotask:: pgsqltasks.tasks.delete_pgsql_database
|
||||||
|
.. autotask:: pgsqltasks.tasks.delete_pgsql_user
|
||||||
|
.. autotask:: pgsqltasks.tasks.set_pgsql_userpassword
|
||||||
|
|
||||||
|
|
||||||
|
:py:mod:`taskresults` app
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. automodule:: taskresults
|
||||||
|
|
||||||
|
:py:mod:`admin <taskresults.admin>`
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: taskresults.admin
|
||||||
|
|
||||||
|
:py:mod:`management.commands <taskresults.management.commands>`
|
||||||
|
---------------------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: taskresults.management.commands
|
||||||
|
|
||||||
|
:py:mod:`fetch_taskresults <taskresult.management.commands.fetch_taskresults>`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: taskresults.management.commands.fetch_taskresults
|
||||||
|
|
||||||
|
:py:mod:`models <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:
|
17
docs/conf.py
17
docs/conf.py
|
@ -13,13 +13,18 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
#import sys
|
import sys
|
||||||
#import os
|
import os
|
||||||
|
import django
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
sys.path.insert(0, os.path.abspath(os.path.join('..', 'gnuviechadmin')))
|
||||||
|
|
||||||
|
os.environ['GVA_SITE_ADMINMAIL'] = 'admin@gva.example.org'
|
||||||
|
|
||||||
|
django.setup()
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
@ -48,16 +53,16 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'gnuviechadmin'
|
project = u'gnuviechadmin'
|
||||||
copyright = u'2014, Jan Dittberner'
|
copyright = u'2014, 2015 Jan Dittberner'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.3'
|
version = '0.4'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.3.0'
|
release = '0.4.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
@ -14,6 +14,7 @@ Contents:
|
||||||
install
|
install
|
||||||
deploy
|
deploy
|
||||||
tests
|
tests
|
||||||
|
code
|
||||||
changelog
|
changelog
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ DATABASES = {
|
||||||
TIME_ZONE = 'Europe/Berlin'
|
TIME_ZONE = 'Europe/Berlin'
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||||
LANGUAGE_CODE = 'de-de'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
@ -224,9 +224,13 @@ DJANGO_APPS = (
|
||||||
|
|
||||||
# Apps specific for this project go here.
|
# Apps specific for this project go here.
|
||||||
LOCAL_APPS = (
|
LOCAL_APPS = (
|
||||||
|
'taskresults',
|
||||||
|
'mysqltasks',
|
||||||
|
'pgsqltasks',
|
||||||
'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
|
||||||
|
@ -279,10 +283,11 @@ CELERY_RESULT_BACKEND = 'amqp'
|
||||||
CELERY_RESULT_PERSISTENT = True
|
CELERY_RESULT_PERSISTENT = True
|
||||||
CELERY_TASK_RESULT_EXPIRES = None
|
CELERY_TASK_RESULT_EXPIRES = None
|
||||||
CELERY_ROUTES = (
|
CELERY_ROUTES = (
|
||||||
'gvacommon.celeryrouters.LdapRouter',
|
'gvacommon.celeryrouters.GvaRouter',
|
||||||
'gvacommon.celeryrouters.FileRouter',
|
|
||||||
)
|
)
|
||||||
CELERY_ACCEPT_CONTENT = ['pickle', 'yaml', 'json']
|
CELERY_TIMEZONE = 'Europe/Berlin'
|
||||||
|
CELERY_ENABLE_UTC = True
|
||||||
|
CELERY_ACCEPT_CONTENT = ['json']
|
||||||
CELERY_TASK_SERIALIZER = 'json'
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
CELERY_RESULT_SERIALIZER = 'json'
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
########## END CELERY CONFIGURATION
|
########## END CELERY CONFIGURATION
|
||||||
|
|
1
gnuviechadmin/gvacommon/.gitignore
vendored
1
gnuviechadmin/gvacommon/.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
.*.swp
|
.*.swp
|
||||||
*.pyc
|
*.pyc
|
||||||
|
.ropeproject/
|
||||||
|
|
|
@ -2,23 +2,14 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
class LdapRouter(object):
|
class GvaRouter(object):
|
||||||
|
|
||||||
def route_for_task(self, task, args=None, kwargs=None):
|
def route_for_task(self, task, args=None, kwargs=None):
|
||||||
if 'ldap' in task:
|
for route in ['ldap', 'file', 'mysql', 'pgsql']:
|
||||||
return {'exchange': 'ldap',
|
if route in task:
|
||||||
|
return {
|
||||||
|
'exchange': route,
|
||||||
'exchange_type': 'direct',
|
'exchange_type': 'direct',
|
||||||
'queue': 'ldap'}
|
'queue': route,
|
||||||
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class FileRouter(object):
|
|
||||||
|
|
||||||
def route_for_task(self, task, args=None, kwargs=None):
|
|
||||||
if 'file' in task:
|
|
||||||
return {'exchange': 'file',
|
|
||||||
'exchange_type': 'direct',
|
|
||||||
'queue': 'file'}
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
60
gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po
Normal file
60
gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: managemails\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2014-12-27 22:45+0100\n"
|
||||||
|
"PO-Revision-Date: 2014-12-27 22:57+0100\n"
|
||||||
|
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||||
|
"Language-Team: de <de@li.org>\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 1.6.10\n"
|
||||||
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
|
||||||
|
#: admin.py:14
|
||||||
|
msgid "Passwords don't match"
|
||||||
|
msgstr "Passwörter stimmen nicht überein"
|
||||||
|
|
||||||
|
#: admin.py:21 tests/test_admin.py:37
|
||||||
|
msgid "Hash"
|
||||||
|
msgstr "Hash-Code"
|
||||||
|
|
||||||
|
#: admin.py:44
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Passwort"
|
||||||
|
|
||||||
|
#: admin.py:46
|
||||||
|
msgid "Password (again)"
|
||||||
|
msgstr "Passwortwiederholung"
|
||||||
|
|
||||||
|
#: admin.py:100
|
||||||
|
msgid "Activate"
|
||||||
|
msgstr "Aktivieren"
|
||||||
|
|
||||||
|
#: admin.py:101
|
||||||
|
msgid "Deactivate"
|
||||||
|
msgstr "Deaktivieren"
|
||||||
|
|
||||||
|
#: models.py:51
|
||||||
|
msgid "Mailbox"
|
||||||
|
msgstr "Postfach"
|
||||||
|
|
||||||
|
#: models.py:52
|
||||||
|
msgid "Mailboxes"
|
||||||
|
msgstr "Postfächer"
|
||||||
|
|
||||||
|
#: models.py:76
|
||||||
|
msgid "Mail address"
|
||||||
|
msgstr "E-Mailadresse"
|
||||||
|
|
||||||
|
#: models.py:77
|
||||||
|
msgid "Mail addresses"
|
||||||
|
msgstr "E-Mailadressen"
|
4
gnuviechadmin/mysqltasks/__init__.py
Normal file
4
gnuviechadmin/mysqltasks/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
This module contains :py:mod:`mysqltasks.tasks`.
|
||||||
|
|
||||||
|
"""
|
4
gnuviechadmin/mysqltasks/models.py
Normal file
4
gnuviechadmin/mysqltasks/models.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
Empty models to make Django accept mysqltasks as an app.
|
||||||
|
|
||||||
|
"""
|
72
gnuviechadmin/mysqltasks/tasks.py
Normal file
72
gnuviechadmin/mysqltasks/tasks.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
"""
|
||||||
|
This module defines Celery_ tasks to manage MySQL users and databases.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def create_mysql_user(username, password):
|
||||||
|
"""
|
||||||
|
This task creates a new MySQL user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str password: the password
|
||||||
|
:return: the created user's name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def set_mysql_userpassword(username, password):
|
||||||
|
"""
|
||||||
|
Set a new password for an existing MySQL user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str password: the password
|
||||||
|
:return: True if the password could be set, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def delete_mysql_user(username):
|
||||||
|
"""
|
||||||
|
This task deletes an existing MySQL user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:return: True if the user has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def create_mysql_database(dbname, username):
|
||||||
|
"""
|
||||||
|
This task creates a new MySQL database for the given MySQL user.
|
||||||
|
|
||||||
|
:param str dbname: database name
|
||||||
|
:param str username: the user name of an existing MySQL user
|
||||||
|
:return: the database name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def delete_mysql_database(dbname, username):
|
||||||
|
"""
|
||||||
|
This task deletes an existing MySQL database and revokes privileges of the
|
||||||
|
given user on that database.
|
||||||
|
|
||||||
|
:param str dbname: database name
|
||||||
|
:param str username: the user name of an existing MySQL user
|
||||||
|
:return: True if the database has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""
|
||||||
|
This app is for managing operating system users and groups.
|
||||||
|
|
||||||
|
"""
|
||||||
|
default_app_config = 'osusers.apps.OsusersAppConfig'
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module contains the Django admin classes of the :py:mod:`osusers` app.
|
||||||
|
|
||||||
|
"""
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -10,13 +14,24 @@ from .models import (
|
||||||
)
|
)
|
||||||
|
|
||||||
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
|
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
|
||||||
|
"""
|
||||||
|
Error message for non matching passwords.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AdditionalGroupInline(admin.TabularInline):
|
class AdditionalGroupInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline for :py:class:`osusers.models.AdditionalGroup` instances.
|
||||||
|
|
||||||
|
"""
|
||||||
model = AdditionalGroup
|
model = AdditionalGroup
|
||||||
|
|
||||||
|
|
||||||
class ShadowInline(admin.TabularInline):
|
class ShadowInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline for :py:class:`osusers.models.ShadowInline` instances.
|
||||||
|
|
||||||
|
"""
|
||||||
model = Shadow
|
model = Shadow
|
||||||
readonly_fields = ['passwd']
|
readonly_fields = ['passwd']
|
||||||
can_delete = False
|
can_delete = False
|
||||||
|
@ -24,22 +39,30 @@ class ShadowInline(admin.TabularInline):
|
||||||
|
|
||||||
class UserCreationForm(forms.ModelForm):
|
class UserCreationForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
A form for creating system users.
|
A form for creating :py:class:`operating system users
|
||||||
|
<osusers.models.User>`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
password1 = forms.CharField(label=_('Password'),
|
password1 = forms.CharField(
|
||||||
widget=forms.PasswordInput)
|
label=_('Password'), widget=forms.PasswordInput,
|
||||||
password2 = forms.CharField(label=_('Password (again)'),
|
required=False,
|
||||||
widget=forms.PasswordInput)
|
)
|
||||||
|
password2 = forms.CharField(
|
||||||
|
label=_('Password (again)'), widget=forms.PasswordInput,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = []
|
fields = ['customer']
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
"""
|
"""
|
||||||
Check that the two password entries match.
|
Check that the two password entries match.
|
||||||
|
|
||||||
|
:return: the validated password
|
||||||
|
:rtype: str or None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
password1 = self.cleaned_data.get('password1')
|
password1 = self.cleaned_data.get('password1')
|
||||||
password2 = self.cleaned_data.get('password2')
|
password2 = self.cleaned_data.get('password2')
|
||||||
|
@ -51,8 +74,13 @@ class UserCreationForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Save the provided password in hashed format.
|
Save the provided password in hashed format.
|
||||||
|
|
||||||
|
:param boolean commit: whether to save the created user
|
||||||
|
:return: user instance
|
||||||
|
:rtype: :py:class:`osusers.models.User`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
|
customer=self.cleaned_data['customer'],
|
||||||
password=self.cleaned_data['password1'], commit=commit)
|
password=self.cleaned_data['password1'], commit=commit)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@ -60,10 +88,16 @@ class UserCreationForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
No additional groups are created when this form is saved, so this
|
No additional groups are created when this form is saved, so this
|
||||||
method just does nothing.
|
method just does nothing.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin class for working with :py:class:`operating system users
|
||||||
|
<osusers.models.User>`.
|
||||||
|
|
||||||
|
"""
|
||||||
actions = ['perform_delete_selected']
|
actions = ['perform_delete_selected']
|
||||||
add_form = UserCreationForm
|
add_form = UserCreationForm
|
||||||
inlines = [AdditionalGroupInline, ShadowInline]
|
inlines = [AdditionalGroupInline, ShadowInline]
|
||||||
|
@ -71,13 +105,20 @@ class UserAdmin(admin.ModelAdmin):
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
'fields': ('password1', 'password2')}),
|
'fields': ('customer', 'password1', 'password2')}),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Use special form during user creation.
|
Use special form during user creation.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: either a :py:class:`User <osusers.models.User>` instance or
|
||||||
|
None for a new user
|
||||||
|
:param kwargs: keyword arguments to be passed to
|
||||||
|
:py:meth:`django.contrib.admin.ModelAdmin.get_form`
|
||||||
|
:return: form instance
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defaults = {}
|
defaults = {}
|
||||||
if obj is None:
|
if obj is None:
|
||||||
|
@ -89,16 +130,47 @@ class UserAdmin(admin.ModelAdmin):
|
||||||
return super(UserAdmin, self).get_form(request, obj, **defaults)
|
return super(UserAdmin, self).get_form(request, obj, **defaults)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""
|
||||||
|
Make sure that uid is not editable for existing users.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param obj: either a :py:class:`User <osusers.models.User>` instance or
|
||||||
|
None for a new user
|
||||||
|
:return: a list of fields
|
||||||
|
:rtype: list
|
||||||
|
|
||||||
|
"""
|
||||||
if obj:
|
if obj:
|
||||||
return ['uid']
|
return ['uid']
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def perform_delete_selected(self, request, queryset):
|
def perform_delete_selected(self, request, queryset):
|
||||||
|
"""
|
||||||
|
Action to delete a list of selected users.
|
||||||
|
|
||||||
|
This action calls the delete method of each selected user in contrast
|
||||||
|
to the default `delete_selected`.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param queryset: Django ORM queryset representing the selected users
|
||||||
|
|
||||||
|
"""
|
||||||
for user in queryset.all():
|
for user in queryset.all():
|
||||||
user.delete()
|
user.delete()
|
||||||
perform_delete_selected.short_description = _('Delete selected users')
|
perform_delete_selected.short_description = _('Delete selected users')
|
||||||
|
|
||||||
def get_actions(self, request):
|
def get_actions(self, request):
|
||||||
|
"""
|
||||||
|
Get the available actions for 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(UserAdmin, self).get_actions(request)
|
actions = super(UserAdmin, self).get_actions(request)
|
||||||
if 'delete_selected' in actions:
|
if 'delete_selected' in actions:
|
||||||
del actions['delete_selected']
|
del actions['delete_selected']
|
||||||
|
@ -106,19 +178,40 @@ class UserAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class GroupAdmin(admin.ModelAdmin):
|
class GroupAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin class for workint with :py:class:`operating system groups
|
||||||
|
<osusers.models.Group>`.
|
||||||
|
|
||||||
|
"""
|
||||||
actions = ['perform_delete_selected']
|
actions = ['perform_delete_selected']
|
||||||
|
|
||||||
def get_inline_instances(self, request, obj=None):
|
|
||||||
if obj is None:
|
|
||||||
return []
|
|
||||||
return super(GroupAdmin, self).get_inline_instances(request, obj)
|
|
||||||
|
|
||||||
def perform_delete_selected(self, request, queryset):
|
def perform_delete_selected(self, request, queryset):
|
||||||
|
"""
|
||||||
|
Action to delete a list of selected groups.
|
||||||
|
|
||||||
|
This action calls the delete method of each selected group in contrast
|
||||||
|
to the default `delete_selected`.
|
||||||
|
|
||||||
|
:param request: the current HTTP request
|
||||||
|
:param queryset: Django ORM queryset representing the selected groups
|
||||||
|
|
||||||
|
"""
|
||||||
for group in queryset.all():
|
for group in queryset.all():
|
||||||
group.delete()
|
group.delete()
|
||||||
perform_delete_selected.short_description = _('Delete selected groups')
|
perform_delete_selected.short_description = _('Delete selected groups')
|
||||||
|
|
||||||
def get_actions(self, request):
|
def get_actions(self, request):
|
||||||
|
"""
|
||||||
|
Get the available actions for groups.
|
||||||
|
|
||||||
|
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(GroupAdmin, self).get_actions(request)
|
actions = super(GroupAdmin, self).get_actions(request)
|
||||||
if 'delete_selected' in actions:
|
if 'delete_selected' in actions:
|
||||||
del actions['delete_selected']
|
del actions['delete_selected']
|
||||||
|
|
17
gnuviechadmin/osusers/apps.py
Normal file
17
gnuviechadmin/osusers/apps.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
"""
|
||||||
|
This module contains the :py:class:`django.apps.AppConfig` instance for the
|
||||||
|
:py:mod:`osusers` app.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class OsusersAppConfig(AppConfig):
|
||||||
|
"""
|
||||||
|
AppConfig for the :py:mod:`osusers` app.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'osusers'
|
||||||
|
verbose_name = _('Operating System Users and Groups')
|
175
gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po
Normal file
175
gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: osusers\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2014-12-27 22:46+0100\n"
|
||||||
|
"PO-Revision-Date: 2014-12-27 22:54+0100\n"
|
||||||
|
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||||
|
"Language-Team: de <de@li.org>\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 1.6.10\n"
|
||||||
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
|
||||||
|
#: admin.py:16
|
||||||
|
msgid "Passwords don't match"
|
||||||
|
msgstr "Passwörter stimmen nicht überein"
|
||||||
|
|
||||||
|
#: admin.py:47
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Passwort"
|
||||||
|
|
||||||
|
#: admin.py:51
|
||||||
|
msgid "Password (again)"
|
||||||
|
msgstr "Passwortwiederholung"
|
||||||
|
|
||||||
|
#: admin.py:160
|
||||||
|
msgid "Delete selected users"
|
||||||
|
msgstr "Ausgewählte Nutzer löschen"
|
||||||
|
|
||||||
|
#: admin.py:201
|
||||||
|
msgid "Delete selected groups"
|
||||||
|
msgstr "Ausgewählte Gruppen löschen"
|
||||||
|
|
||||||
|
#: apps.py:17
|
||||||
|
msgid "Operating System Users and Groups"
|
||||||
|
msgstr "Betriebssystemnutzer- und Gruppen"
|
||||||
|
|
||||||
|
#: models.py:41
|
||||||
|
msgid "You can not use a user's primary group."
|
||||||
|
msgstr "Sie können nicht die primäre Gruppe des Nutzers verwenden."
|
||||||
|
|
||||||
|
#: models.py:71
|
||||||
|
msgid "Group name"
|
||||||
|
msgstr "Gruppenname"
|
||||||
|
|
||||||
|
#: models.py:73
|
||||||
|
msgid "Group ID"
|
||||||
|
msgstr "Gruppen-ID"
|
||||||
|
|
||||||
|
#: models.py:74
|
||||||
|
msgid "Description"
|
||||||
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
|
#: models.py:76
|
||||||
|
msgid "Group password"
|
||||||
|
msgstr "Gruppenpasswort"
|
||||||
|
|
||||||
|
#: models.py:81 models.py:212
|
||||||
|
msgid "Group"
|
||||||
|
msgstr "Gruppe"
|
||||||
|
|
||||||
|
#: models.py:82
|
||||||
|
msgid "Groups"
|
||||||
|
msgstr "Gruppen"
|
||||||
|
|
||||||
|
#: models.py:209
|
||||||
|
msgid "User name"
|
||||||
|
msgstr "Nutzername"
|
||||||
|
|
||||||
|
#: models.py:211
|
||||||
|
msgid "User ID"
|
||||||
|
msgstr "Nutzer-ID"
|
||||||
|
|
||||||
|
#: models.py:213
|
||||||
|
msgid "Gecos field"
|
||||||
|
msgstr "GECOS-Feld"
|
||||||
|
|
||||||
|
#: models.py:214
|
||||||
|
msgid "Home directory"
|
||||||
|
msgstr "Home-Verzeichnis"
|
||||||
|
|
||||||
|
#: models.py:215
|
||||||
|
msgid "Login shell"
|
||||||
|
msgstr "Loginshell"
|
||||||
|
|
||||||
|
#: models.py:221 models.py:335
|
||||||
|
msgid "User"
|
||||||
|
msgstr "Nutzer"
|
||||||
|
|
||||||
|
#: models.py:222
|
||||||
|
msgid "Users"
|
||||||
|
msgstr "Nutzer"
|
||||||
|
|
||||||
|
#: models.py:336
|
||||||
|
msgid "Encrypted password"
|
||||||
|
msgstr "Verschlüsseltes Passwort"
|
||||||
|
|
||||||
|
#: models.py:338
|
||||||
|
msgid "Date of last change"
|
||||||
|
msgstr "Datum der letzten Änderung"
|
||||||
|
|
||||||
|
#: models.py:339
|
||||||
|
msgid "This is expressed in days since Jan 1, 1970"
|
||||||
|
msgstr "Ausgedrückt als Tage seit dem 1. Januar 1970"
|
||||||
|
|
||||||
|
#: models.py:342
|
||||||
|
msgid "Minimum age"
|
||||||
|
msgstr "Minimales Alter"
|
||||||
|
|
||||||
|
#: models.py:343
|
||||||
|
msgid "Minimum number of days before the password can be changed"
|
||||||
|
msgstr "Minmale Anzahl von Tagen bevor das Passwort geändert werden kann"
|
||||||
|
|
||||||
|
#: models.py:347
|
||||||
|
msgid "Maximum age"
|
||||||
|
msgstr "Maximales Alter"
|
||||||
|
|
||||||
|
#: models.py:348
|
||||||
|
msgid "Maximum number of days after which the password has to be changed"
|
||||||
|
msgstr ""
|
||||||
|
"Maximale Anzahl von Tagen, nach denen das Passwort geändert werden muss"
|
||||||
|
|
||||||
|
#: models.py:352
|
||||||
|
msgid "Grace period"
|
||||||
|
msgstr "Duldungsperiode"
|
||||||
|
|
||||||
|
#: models.py:353
|
||||||
|
msgid "The number of days before the password is going to expire"
|
||||||
|
msgstr "Anzahl von Tagen nach denen das Passwort verfällt"
|
||||||
|
|
||||||
|
#: models.py:357
|
||||||
|
msgid "Inactivity period"
|
||||||
|
msgstr "Inaktivitätsperiode"
|
||||||
|
|
||||||
|
#: models.py:358
|
||||||
|
msgid ""
|
||||||
|
"The number of days after the password has expired during which the password "
|
||||||
|
"should still be accepted"
|
||||||
|
msgstr ""
|
||||||
|
"Die Anzahl von Tagen für die ein verfallenes Passwort noch akzeptiert werden "
|
||||||
|
"soll"
|
||||||
|
|
||||||
|
#: models.py:362
|
||||||
|
msgid "Account expiration date"
|
||||||
|
msgstr "Kontoverfallsdatum"
|
||||||
|
|
||||||
|
#: models.py:363
|
||||||
|
msgid ""
|
||||||
|
"The date of expiration of the account, expressed as number of days since Jan "
|
||||||
|
"1, 1970"
|
||||||
|
msgstr "Kontoverfallsdatum in Tagen seit dem 1. Januar 1970"
|
||||||
|
|
||||||
|
#: models.py:370
|
||||||
|
msgid "Shadow password"
|
||||||
|
msgstr "Shadow-Passwort"
|
||||||
|
|
||||||
|
#: models.py:371
|
||||||
|
msgid "Shadow passwords"
|
||||||
|
msgstr "Shadow-Passwörter"
|
||||||
|
|
||||||
|
#: models.py:397
|
||||||
|
msgid "Additional group"
|
||||||
|
msgstr "Weitere Gruppe"
|
||||||
|
|
||||||
|
#: models.py:398
|
||||||
|
msgid "Additional groups"
|
||||||
|
msgstr "Weitere Gruppen"
|
22
gnuviechadmin/osusers/migrations/0003_user_customer.py
Normal file
22
gnuviechadmin/osusers/migrations/0003_user_customer.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('osusers', '0002_auto_20141226_1456'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='customer',
|
||||||
|
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
24
gnuviechadmin/osusers/migrations/0004_auto_20150104_1751.py
Normal file
24
gnuviechadmin/osusers/migrations/0004_auto_20150104_1751.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('osusers', '0003_user_customer'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='user',
|
||||||
|
options={'verbose_name': 'User', 'verbose_name_plural': 'Users'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='shadow',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(primary_key=True, serialize=False, to='osusers.User', verbose_name='User'),
|
||||||
|
preserve_default=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module defines the database models of operating system users.
|
||||||
|
|
||||||
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
@ -16,13 +20,15 @@ from model_utils.models import TimeStampedModel
|
||||||
from passlib.hash import sha512_crypt
|
from passlib.hash import sha512_crypt
|
||||||
from passlib.utils import generate_password
|
from passlib.utils import generate_password
|
||||||
|
|
||||||
|
from taskresults.models import TaskResult
|
||||||
|
|
||||||
from .tasks import (
|
from .tasks import (
|
||||||
add_ldap_user_to_group,
|
add_ldap_user_to_group,
|
||||||
create_ldap_group,
|
create_ldap_group,
|
||||||
create_ldap_user,
|
create_ldap_user,
|
||||||
delete_file_mail_userdir,
|
delete_file_mail_userdir,
|
||||||
delete_file_sftp_userdir,
|
delete_file_sftp_userdir,
|
||||||
delete_ldap_group_if_empty,
|
delete_ldap_group,
|
||||||
delete_ldap_user,
|
delete_ldap_user,
|
||||||
remove_ldap_user_from_group,
|
remove_ldap_user_from_group,
|
||||||
setup_file_mail_userdir,
|
setup_file_mail_userdir,
|
||||||
|
@ -30,7 +36,7 @@ from .tasks import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
|
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
|
||||||
|
@ -38,8 +44,19 @@ CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
|
||||||
|
|
||||||
|
|
||||||
class GroupManager(models.Manager):
|
class GroupManager(models.Manager):
|
||||||
|
"""
|
||||||
|
Manager class for :py:class:`osusers.models.Group`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def get_next_gid(self):
|
def get_next_gid(self):
|
||||||
|
"""
|
||||||
|
Get the next available group id.
|
||||||
|
|
||||||
|
:returns: group id
|
||||||
|
:rtype: int
|
||||||
|
|
||||||
|
"""
|
||||||
q = self.aggregate(models.Max('gid'))
|
q = self.aggregate(models.Max('gid'))
|
||||||
if q['gid__max'] is None:
|
if q['gid__max'] is None:
|
||||||
return settings.OSUSER_MINGID
|
return settings.OSUSER_MINGID
|
||||||
|
@ -48,6 +65,10 @@ class GroupManager(models.Manager):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Group(TimeStampedModel, models.Model):
|
class Group(TimeStampedModel, models.Model):
|
||||||
|
"""
|
||||||
|
This entity class corresponds to an operating system group.
|
||||||
|
|
||||||
|
"""
|
||||||
groupname = models.CharField(
|
groupname = models.CharField(
|
||||||
_('Group name'), max_length=16, unique=True)
|
_('Group name'), max_length=16, unique=True)
|
||||||
gid = models.PositiveSmallIntegerField(
|
gid = models.PositiveSmallIntegerField(
|
||||||
|
@ -67,34 +88,76 @@ class Group(TimeStampedModel, models.Model):
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Save the group to the database and synchronizes group information to
|
||||||
|
LDAP.
|
||||||
|
|
||||||
|
:param args: positional arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.save`
|
||||||
|
:param kwargs: keyword arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.save`
|
||||||
|
:return: self
|
||||||
|
:rtype: :py:class:`osusers.models.Group`
|
||||||
|
|
||||||
|
"""
|
||||||
super(Group, self).save(*args, **kwargs)
|
super(Group, self).save(*args, **kwargs)
|
||||||
dn = create_ldap_group.delay(
|
dn = create_ldap_group.delay(
|
||||||
self.groupname, self.gid, self.descr).get()
|
self.groupname, self.gid, self.descr).get()
|
||||||
logger.info("created LDAP group with dn %s", dn)
|
_LOGGER.info("created LDAP group with dn %s", dn)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
delete_ldap_group_if_empty.delay(self.groupname).get()
|
"""
|
||||||
|
Delete the group from LDAP and the database.
|
||||||
|
|
||||||
|
:param args: positional arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.delete`
|
||||||
|
:param kwargs: keyword arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.delete`
|
||||||
|
|
||||||
|
"""
|
||||||
|
TaskResult.objects.create_task_result(
|
||||||
|
delete_ldap_group.delay(self.groupname),
|
||||||
|
'delete_ldap_group'
|
||||||
|
)
|
||||||
super(Group, self).delete(*args, **kwargs)
|
super(Group, self).delete(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserManager(models.Manager):
|
class UserManager(models.Manager):
|
||||||
|
"""
|
||||||
|
Manager class for :py:class:`osusers.models.User`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def get_next_uid(self):
|
def get_next_uid(self):
|
||||||
|
"""
|
||||||
|
Get the next available user id.
|
||||||
|
|
||||||
|
:return: user id
|
||||||
|
:rtype: int
|
||||||
|
|
||||||
|
"""
|
||||||
q = self.aggregate(models.Max('uid'))
|
q = self.aggregate(models.Max('uid'))
|
||||||
if q['uid__max'] is None:
|
if q['uid__max'] is None:
|
||||||
return settings.OSUSER_MINUID
|
return settings.OSUSER_MINUID
|
||||||
return max(settings.OSUSER_MINUID, q['uid__max'] + 1)
|
return max(settings.OSUSER_MINUID, q['uid__max'] + 1)
|
||||||
|
|
||||||
def get_next_username(self):
|
def get_next_username(self):
|
||||||
|
"""
|
||||||
|
Get the next available user name.
|
||||||
|
|
||||||
|
:return: user name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
count = 1
|
count = 1
|
||||||
usernameformat = "{0}{1:02d}"
|
usernameformat = "{0}{1:02d}"
|
||||||
nextuser = usernameformat.format(settings.OSUSER_USERNAME_PREFIX,
|
nextuser = usernameformat.format(settings.OSUSER_USERNAME_PREFIX,
|
||||||
count)
|
count)
|
||||||
for user in self.values('username').filter(
|
for user in self.values('username').filter(
|
||||||
username__startswith=settings.OSUSER_USERNAME_PREFIX).order_by(
|
username__startswith=settings.OSUSER_USERNAME_PREFIX
|
||||||
'username'):
|
).order_by('username'):
|
||||||
if user['username'] == nextuser:
|
if user['username'] == nextuser:
|
||||||
count += 1
|
count += 1
|
||||||
nextuser = usernameformat.format(
|
nextuser = usernameformat.format(
|
||||||
|
@ -104,7 +167,26 @@ class UserManager(models.Manager):
|
||||||
return nextuser
|
return nextuser
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_user(self, username=None, password=None, commit=False):
|
def create_user(
|
||||||
|
self, customer, username=None, password=None, commit=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a new user with a primary group named the same as the user and
|
||||||
|
an initial password.
|
||||||
|
|
||||||
|
If username is None the result of :py:meth:`get_next_username` is used.
|
||||||
|
If password is None a new password will be generated using passlib's
|
||||||
|
:py:func:`generate_password`.
|
||||||
|
|
||||||
|
:param customer: Django User instance this user is associated to
|
||||||
|
:param str username: the username or None
|
||||||
|
:param str password: the password or None
|
||||||
|
:param boolean commit: whether to commit the user data to the database
|
||||||
|
or not
|
||||||
|
:return: new user
|
||||||
|
:rtype: :py:class:`osusers.models.User`
|
||||||
|
|
||||||
|
"""
|
||||||
uid = self.get_next_uid()
|
uid = self.get_next_uid()
|
||||||
gid = Group.objects.get_next_gid()
|
gid = Group.objects.get_next_gid()
|
||||||
if username is None:
|
if username is None:
|
||||||
|
@ -114,7 +196,7 @@ class UserManager(models.Manager):
|
||||||
homedir = os.path.join(settings.OSUSER_HOME_BASEPATH, username)
|
homedir = os.path.join(settings.OSUSER_HOME_BASEPATH, username)
|
||||||
group = Group.objects.create(groupname=username, gid=gid)
|
group = Group.objects.create(groupname=username, gid=gid)
|
||||||
user = self.create(username=username, group=group, uid=uid,
|
user = self.create(username=username, group=group, uid=uid,
|
||||||
homedir=homedir,
|
homedir=homedir, customer=customer,
|
||||||
shell=settings.OSUSER_DEFAULT_SHELL)
|
shell=settings.OSUSER_DEFAULT_SHELL)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
if commit:
|
if commit:
|
||||||
|
@ -124,6 +206,10 @@ class UserManager(models.Manager):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class User(TimeStampedModel, models.Model):
|
class User(TimeStampedModel, models.Model):
|
||||||
|
"""
|
||||||
|
This entity class corresponds to an operating system user.
|
||||||
|
|
||||||
|
"""
|
||||||
username = models.CharField(
|
username = models.CharField(
|
||||||
_('User name'), max_length=64, unique=True)
|
_('User name'), max_length=64, unique=True)
|
||||||
uid = models.PositiveSmallIntegerField(
|
uid = models.PositiveSmallIntegerField(
|
||||||
|
@ -132,6 +218,7 @@ class User(TimeStampedModel, models.Model):
|
||||||
gecos = models.CharField(_('Gecos field'), max_length=128, blank=True)
|
gecos = models.CharField(_('Gecos field'), max_length=128, blank=True)
|
||||||
homedir = models.CharField(_('Home directory'), max_length=256)
|
homedir = models.CharField(_('Home directory'), max_length=256)
|
||||||
shell = models.CharField(_('Login shell'), max_length=64)
|
shell = models.CharField(_('Login shell'), max_length=64)
|
||||||
|
customer = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
|
@ -144,6 +231,15 @@ class User(TimeStampedModel, models.Model):
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
|
"""
|
||||||
|
Set the password of the user.
|
||||||
|
|
||||||
|
The password is set to the user's
|
||||||
|
:py:class:`Shadow <osusers.models.Shadow>` instance and to LDAP.
|
||||||
|
|
||||||
|
:param str password: the new password
|
||||||
|
|
||||||
|
"""
|
||||||
if hasattr(self, 'shadow'):
|
if hasattr(self, 'shadow'):
|
||||||
self.shadow.set_password(password)
|
self.shadow.set_password(password)
|
||||||
else:
|
else:
|
||||||
|
@ -158,23 +254,56 @@ class User(TimeStampedModel, models.Model):
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Save the user to the database, create user directories and synchronize
|
||||||
|
user information to LDAP.
|
||||||
|
|
||||||
|
:param args: positional arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.save`
|
||||||
|
:param kwargs: keyword arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.save`
|
||||||
|
:return: self
|
||||||
|
:rtype: :py:class:`osusers.models.User`
|
||||||
|
|
||||||
|
"""
|
||||||
dn = create_ldap_user.delay(
|
dn = create_ldap_user.delay(
|
||||||
self.username, self.uid, self.group.gid, self.gecos,
|
self.username, self.uid, self.group.gid, self.gecos,
|
||||||
self.homedir, self.shell, password=None).get()
|
self.homedir, self.shell, password=None).get()
|
||||||
sftp_dir = setup_file_sftp_userdir.delay(self.username).get()
|
TaskResult.objects.create_task_result(
|
||||||
mail_dir = setup_file_mail_userdir.delay(self.username).get()
|
setup_file_sftp_userdir.delay(self.username),
|
||||||
logger.info(
|
'setup_file_sftp_userdir'
|
||||||
"created user %(user)s with LDAP dn %(dn)s, home directory "
|
)
|
||||||
"%(homedir)s and mail base directory %(maildir)s.", {
|
TaskResult.objects.create_task_result(
|
||||||
|
setup_file_mail_userdir.delay(self.username),
|
||||||
|
'setup_file_mail_userdir'
|
||||||
|
)
|
||||||
|
_LOGGER.info(
|
||||||
|
"created user %(user)s with LDAP dn %(dn)s, scheduled home "
|
||||||
|
"directory and mail base directory creation.", {
|
||||||
'user': self, 'dn': dn,
|
'user': self, 'dn': dn,
|
||||||
'homedir': sftp_dir, 'maildir': mail_dir
|
|
||||||
})
|
})
|
||||||
return super(User, self).save(*args, **kwargs)
|
return super(User, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
delete_file_mail_userdir.delay(self.username).get()
|
"""
|
||||||
delete_file_sftp_userdir.delay(self.username).get()
|
Delete the user and its groups from LDAP and the database and remove
|
||||||
|
the user's directories.
|
||||||
|
|
||||||
|
:param args: positional arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.delete`
|
||||||
|
:param kwargs: keyword arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.delete`
|
||||||
|
|
||||||
|
"""
|
||||||
|
TaskResult.objects.create_task_result(
|
||||||
|
delete_file_mail_userdir.delay(self.username),
|
||||||
|
'delete_file_mail_userdir'
|
||||||
|
)
|
||||||
|
TaskResult.objects.create_task_result(
|
||||||
|
delete_file_sftp_userdir.delay(self.username),
|
||||||
|
'delete_file_sftp_userdir'
|
||||||
|
)
|
||||||
for group in [ag.group for ag in self.additionalgroup_set.all()]:
|
for group in [ag.group for ag in self.additionalgroup_set.all()]:
|
||||||
remove_ldap_user_from_group.delay(
|
remove_ldap_user_from_group.delay(
|
||||||
self.username, group.groupname).get()
|
self.username, group.groupname).get()
|
||||||
|
@ -184,9 +313,23 @@ class User(TimeStampedModel, models.Model):
|
||||||
|
|
||||||
|
|
||||||
class ShadowManager(models.Manager):
|
class ShadowManager(models.Manager):
|
||||||
|
"""
|
||||||
|
Manager class for :py:class:`osusers.models.Shadow`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_shadow(self, user, password):
|
def create_shadow(self, user, password):
|
||||||
|
"""
|
||||||
|
Create a new shadow instance with typical Linux settings for the given
|
||||||
|
user with the given password.
|
||||||
|
|
||||||
|
:param user: :py:class:`User <osusers.models.User>` instance
|
||||||
|
:param str password: the password
|
||||||
|
:return: new Shadow instance
|
||||||
|
:rtype: :py:class:`osusers.models.Shadow` instance
|
||||||
|
|
||||||
|
"""
|
||||||
changedays = (timezone.now().date() - date(1970, 1, 1)).days
|
changedays = (timezone.now().date() - date(1970, 1, 1)).days
|
||||||
shadow = self.create(
|
shadow = self.create(
|
||||||
user=user, changedays=changedays,
|
user=user, changedays=changedays,
|
||||||
|
@ -200,6 +343,11 @@ class ShadowManager(models.Manager):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Shadow(TimeStampedModel, models.Model):
|
class Shadow(TimeStampedModel, models.Model):
|
||||||
|
"""
|
||||||
|
This entity class corresponds to an operating system user's shadow file
|
||||||
|
entry.
|
||||||
|
|
||||||
|
"""
|
||||||
user = models.OneToOneField(User, primary_key=True, verbose_name=_('User'))
|
user = models.OneToOneField(User, primary_key=True, verbose_name=_('User'))
|
||||||
passwd = models.CharField(_('Encrypted password'), max_length=128)
|
passwd = models.CharField(_('Encrypted password'), max_length=128)
|
||||||
changedays = models.PositiveSmallIntegerField(
|
changedays = models.PositiveSmallIntegerField(
|
||||||
|
@ -242,11 +390,21 @@ class Shadow(TimeStampedModel, models.Model):
|
||||||
return 'for user {0}'.format(self.user)
|
return 'for user {0}'.format(self.user)
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
|
"""
|
||||||
|
Set and encrypt the password.
|
||||||
|
|
||||||
|
:param str password: the password
|
||||||
|
"""
|
||||||
self.passwd = sha512_crypt.encrypt(password)
|
self.passwd = sha512_crypt.encrypt(password)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class AdditionalGroup(TimeStampedModel, models.Model):
|
class AdditionalGroup(TimeStampedModel, models.Model):
|
||||||
|
"""
|
||||||
|
This entity class corresponds to additional group assignments for an
|
||||||
|
:py:class:`operating system user <osusers.models.User>`.
|
||||||
|
|
||||||
|
"""
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
group = models.ForeignKey(Group)
|
group = models.ForeignKey(Group)
|
||||||
|
|
||||||
|
@ -255,21 +413,48 @@ class AdditionalGroup(TimeStampedModel, models.Model):
|
||||||
verbose_name = _('Additional group')
|
verbose_name = _('Additional group')
|
||||||
verbose_name_plural = _('Additional groups')
|
verbose_name_plural = _('Additional groups')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{0} in {1}'.format(self.user, self.group)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Ensure that the assigned group is different from the user's primary
|
||||||
|
group.
|
||||||
|
|
||||||
|
"""
|
||||||
if self.user.group == self.group:
|
if self.user.group == self.group:
|
||||||
raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL)
|
raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Persists the group assignment to LDAP and the database.
|
||||||
|
|
||||||
|
:param args: positional arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.save`
|
||||||
|
:param kwargs: keyword arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.save`
|
||||||
|
:return: this instance
|
||||||
|
:rtype: :py:class:`AdditionalGroup <osusers.models.AdditionalGroup>`
|
||||||
|
|
||||||
|
"""
|
||||||
add_ldap_user_to_group.delay(
|
add_ldap_user_to_group.delay(
|
||||||
self.user.username, self.group.groupname).get()
|
self.user.username, self.group.groupname).get()
|
||||||
super(AdditionalGroup, self).save(*args, **kwargs)
|
return super(AdditionalGroup, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
remove_ldap_user_from_group.delay(
|
"""
|
||||||
self.user.username, self.group.groupname).get()
|
Delete the group assignment from LDAP and the database.
|
||||||
super(AdditionalGroup, self).delete(*args, **kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
:param args: positional arguments to be passed on to
|
||||||
return '{0} in {1}'.format(self.user, self.group)
|
:py:meth:`django.db.Model.delete`
|
||||||
|
:param kwargs: keyword arguments to be passed on to
|
||||||
|
:py:meth:`django.db.Model.delete`
|
||||||
|
"""
|
||||||
|
TaskResult.objects.create_task_result(
|
||||||
|
remove_ldap_user_from_group.delay(
|
||||||
|
self.user.username, self.group.groupname),
|
||||||
|
'remove_ldap_user_from_group'
|
||||||
|
)
|
||||||
|
super(AdditionalGroup, self).delete(*args, **kwargs)
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
This module defines task stubs for the tasks implemented on the Celery
|
||||||
|
workers.
|
||||||
|
|
||||||
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
@ -5,59 +10,194 @@ from celery import shared_task
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def create_ldap_group(groupname, gid, descr):
|
def create_ldap_group(groupname, gid, descr):
|
||||||
pass
|
"""
|
||||||
|
This task creates an :py:class:`LDAP group <ldapentities.models.LdapGroup>`
|
||||||
|
if it does not exist yet.
|
||||||
|
|
||||||
|
If a group with the given name exists its group id and description
|
||||||
|
attributes are updated.
|
||||||
|
|
||||||
|
:param str groupname: the group name
|
||||||
|
:param int gid: the group id
|
||||||
|
:param str descr: description text for the group
|
||||||
|
:return: the distinguished name of the group
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
|
def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
|
||||||
pass
|
"""
|
||||||
|
This task creates an :py:class:`LDAP user <ldapentities.models.LdapUser>`
|
||||||
|
if it does not exist yet.
|
||||||
|
|
||||||
|
The task is rejected if the primary group of the user is not defined.
|
||||||
|
|
||||||
|
The user's fields are updated if the user already exists.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param int uid: the user id
|
||||||
|
:param int gid: the user's primary group's id
|
||||||
|
:param str gecos: the text for the GECOS field
|
||||||
|
:param str homedir: the user's home directory
|
||||||
|
:param str shell: the user's login shell
|
||||||
|
:param str or None password: the clear text password, if :py:const:`None`
|
||||||
|
is passed the password is not touched
|
||||||
|
:raises celery.exceptions.Reject: if the specified primary group does not
|
||||||
|
exist
|
||||||
|
:return: the distinguished name of the user
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def add_ldap_user_to_group(username, groupname):
|
def add_ldap_user_to_group(username, groupname):
|
||||||
pass
|
"""
|
||||||
|
This task adds the specified user to the given group.
|
||||||
|
|
||||||
|
This task does nothing if the user is already member of the group.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str groupname: the group name
|
||||||
|
:raises celery.exceptions.Retry: if the user does not exist yet,
|
||||||
|
:py:func:`create_ldap_user` should be called before
|
||||||
|
:return: True if the user has been added to the group otherwise False
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def remove_ldap_user_from_group(username, groupname):
|
def remove_ldap_user_from_group(username, groupname):
|
||||||
pass
|
"""
|
||||||
|
This task removes the given user from the given group.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str groupname: the group name
|
||||||
|
:return: True if the user has been removed, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def delete_ldap_user(username):
|
def delete_ldap_user(username):
|
||||||
pass
|
"""
|
||||||
|
This task deletes the given user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:return: True if the user has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def delete_ldap_group_if_empty(groupname):
|
def delete_ldap_group_if_empty(groupname):
|
||||||
pass
|
"""
|
||||||
|
This task deletes the given group if it is empty.
|
||||||
|
|
||||||
|
:param str groupname: the group name
|
||||||
|
:return: True if the user has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def delete_ldap_group(groupname):
|
||||||
|
"""
|
||||||
|
This taks deletes the given group.
|
||||||
|
|
||||||
|
:param str groupname: the group name
|
||||||
|
:return: True if the user has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def setup_file_sftp_userdir(username):
|
def setup_file_sftp_userdir(username):
|
||||||
pass
|
"""
|
||||||
|
This task creates the home directory for an SFTP user if it does not exist
|
||||||
|
yet.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:raises Exception: if the SFTP directory of the user cannot be created
|
||||||
|
:return: the created directory name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def delete_file_sftp_userdir(username):
|
def delete_file_sftp_userdir(username):
|
||||||
pass
|
"""
|
||||||
|
This task recursively deletes the home directory of an SFTP user if it
|
||||||
|
does not exist yet.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:raises Exception: if the SFTP directory of the user cannot be removed
|
||||||
|
:return: the removed directory name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def setup_file_mail_userdir(username):
|
def setup_file_mail_userdir(username):
|
||||||
pass
|
"""
|
||||||
|
This task creates the mail base directory for a user if it does not exist
|
||||||
|
yet.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:raises Exception: if the mail base directory for the user cannot be
|
||||||
|
created
|
||||||
|
:return: the created directory name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def delete_file_mail_userdir(username):
|
def delete_file_mail_userdir(username):
|
||||||
pass
|
"""
|
||||||
|
This task recursively deletes the mail base directory for a user if it
|
||||||
|
does not exist yet.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:raises Exception: if the mail base directory of the user cannot be removed
|
||||||
|
:return: the removed directory name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def create_file_mailbox(username, mailboxname):
|
def create_file_mailbox(username, mailboxname):
|
||||||
pass
|
"""
|
||||||
|
This task creates a new mailbox directory for the given user and mailbox
|
||||||
|
name.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str mailboxname: the mailbox name
|
||||||
|
:raises GVAFileException: if the mailbox directory cannot be created
|
||||||
|
:return: the created mailbox directory name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def delete_file_mailbox(username, mailboxname):
|
def delete_file_mailbox(username, mailboxname):
|
||||||
pass
|
"""
|
||||||
|
This task deletes the given mailbox of the given user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str mailboxname: the mailbox name
|
||||||
|
:raises GVAFileException: if the mailbox directory cannot be deleted
|
||||||
|
:return: the deleted mailbox directory name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
4
gnuviechadmin/pgsqltasks/__init__.py
Normal file
4
gnuviechadmin/pgsqltasks/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
This module contains :py:mod:`pgsqltasks.tasks`.
|
||||||
|
|
||||||
|
"""
|
4
gnuviechadmin/pgsqltasks/models.py
Normal file
4
gnuviechadmin/pgsqltasks/models.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
Empty models to make Django accept pgsqltasks as an app.
|
||||||
|
|
||||||
|
"""
|
70
gnuviechadmin/pgsqltasks/tasks.py
Normal file
70
gnuviechadmin/pgsqltasks/tasks.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
"""
|
||||||
|
This module defines Celery_ tasks to manage PostgreSQL users and databases.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def create_pgsql_user(username, password):
|
||||||
|
"""
|
||||||
|
This task creates a new PostgreSQL user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str password: the password
|
||||||
|
:return: the created user's name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def set_pgsql_userpassword(username, password):
|
||||||
|
"""
|
||||||
|
Set a new password for an existing PostgreSQL user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:param str password: the password
|
||||||
|
:return: True if the password could be set, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def delete_pgsql_user(username):
|
||||||
|
"""
|
||||||
|
This task deletes an existing PostgreSQL user.
|
||||||
|
|
||||||
|
:param str username: the user name
|
||||||
|
:return: True if the user has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def create_pgsql_database(dbname, username):
|
||||||
|
"""
|
||||||
|
This task creates a new PostgreSQL database for the given PostgreSQL user.
|
||||||
|
|
||||||
|
:param str dbname: database name
|
||||||
|
:param str username: the user name of an existing PostgreSQL user
|
||||||
|
:return: the database name
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def delete_pgsql_database(dbname):
|
||||||
|
"""
|
||||||
|
This task deletes an existing PostgreSQL database.
|
||||||
|
|
||||||
|
:param str dbname: database name
|
||||||
|
:return: True if the database has been deleted, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
5
gnuviechadmin/taskresults/__init__.py
Normal file
5
gnuviechadmin/taskresults/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
"""
|
||||||
|
This is the taskresults app that is used for storing the results from
|
||||||
|
asynchronous Celery_ tasks.
|
||||||
|
|
||||||
|
"""
|
12
gnuviechadmin/taskresults/admin.py
Normal file
12
gnuviechadmin/taskresults/admin.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"""
|
||||||
|
This module defines the admin interface for the taskresults app.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import TaskResult
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(TaskResult)
|
0
gnuviechadmin/taskresults/management/__init__.py
Normal file
0
gnuviechadmin/taskresults/management/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
This module defines management commands for the taskresults app.
|
||||||
|
|
||||||
|
"""
|
|
@ -0,0 +1,20 @@
|
||||||
|
"""
|
||||||
|
This model contains the implementation of a management command to fetch the
|
||||||
|
results of all `Celery <http://www.celeryproject.org/>`_ tasks that are not
|
||||||
|
marked as finished yet.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from taskresults.models import TaskResult
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "fetch task results"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
for taskresult in TaskResult.objects.filter(finished=False):
|
||||||
|
taskresult.fetch_result()
|
||||||
|
taskresult.save()
|
29
gnuviechadmin/taskresults/migrations/0001_initial.py
Normal file
29
gnuviechadmin/taskresults/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TaskResult',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('task_id', models.CharField(max_length=36, verbose_name='Task id')),
|
||||||
|
('task_name', models.CharField(max_length=64, verbose_name='Task name')),
|
||||||
|
('result', models.TextField(verbose_name='Task result')),
|
||||||
|
('finished', models.BooleanField(default=False)),
|
||||||
|
('state', models.CharField(max_length=16, verbose_name='Task state')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Task result',
|
||||||
|
'verbose_name_plural': 'Task results',
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
]
|
0
gnuviechadmin/taskresults/migrations/__init__.py
Normal file
0
gnuviechadmin/taskresults/migrations/__init__.py
Normal file
47
gnuviechadmin/taskresults/models.py
Normal file
47
gnuviechadmin/taskresults/models.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
"""
|
||||||
|
This model defines the database models to handle Celery AsyncResults.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from gnuviechadmin.celery import app
|
||||||
|
|
||||||
|
|
||||||
|
class TaskResultManager(models.Manager):
|
||||||
|
def create_task_result(self, asyncresult, name):
|
||||||
|
taskresult = self.create(task_id=asyncresult.id, task_name=name)
|
||||||
|
return taskresult
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class TaskResult(models.Model):
|
||||||
|
task_id = models.CharField(_('Task id'), max_length=36)
|
||||||
|
task_name = models.CharField(_('Task name'), max_length=64)
|
||||||
|
result = models.TextField(_('Task result'))
|
||||||
|
finished = models.BooleanField(default=False)
|
||||||
|
state = models.CharField(_('Task state'), max_length=16)
|
||||||
|
|
||||||
|
objects = TaskResultManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Task result')
|
||||||
|
verbose_name_plural = _('Task results')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{task_name} ({task_id}): {finished}".format(
|
||||||
|
task_name=self.task_name,
|
||||||
|
task_id=self.task_id,
|
||||||
|
finished=_('yes') if self.finished else _('no')
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
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)
|
||||||
|
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)
|
||||||
|
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).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