Merge branch 'release/0.6.0' into production

* release/0.6.0: (32 commits)
  define version number, mark version in changelog
  plug users and hosting packages together
  implement CustomerHostingPackageDetails view
  introduce new settings for groups and upload server
  implement osusers.forms.ChangeOsUserPasswordForm
  refactor dashboard.views.UserDashboardView
  generate documentation for gvacommon.viewmixins
  implement viewmixins.StaffOrSelfLoginRequiredMixin
  create system user when creating a new hosting package
  fix some test issues
  incomplete create_hosting_package view
  fix issue with mailbox count calculation
  remove unused LogoutView and corresponding url pattern
  add docstrings to managemails.models
  add task stub for ldaptasks.tasks.set_ldap_user_password
  update changelog
  adapt documentation to changed module structure
  refactor osusers.tasks into fileservertasks and ldaptasks
  add a list of planned features and development ideas
  remove newline at EOF
  ...
This commit is contained in:
Jan Dittberner 2015-01-24 16:33:54 +01:00
commit b27dfd5d68
70 changed files with 3943 additions and 221 deletions

View file

@ -3,3 +3,8 @@ gnuviechadmin
============= =============
Customer center for gnuviech servers. Customer center for gnuviech servers.
Gnuviechadmin is based on Django_ and Celery_
.. _Django: https://djangoproject.com/
.. _Celery: http://www.celeryproject.com/

View file

@ -1,6 +1,21 @@
Changelog Changelog
========= =========
* :release:`0.6.0 <2015-01-24>`
* :feature:`-` add frontend functionality to set an os users' sftp password
(needs gvaldap >= 0.4.0 on the LDAP side)
* :support:`-` remove unused dashboard.views.LogoutView and the corresponding
URL in dashboard.urls
* :feature:`-` add new task stub to set an ldap user's password
* :support:`-` refactor osusers.tasks, split into fileservertasks.tasks and
ldaptasks.tasks
* :feature:`-` show hosting package information on user dashboard
* :feature:`-` implement new hostingpackages app to provide hosting package
templates, hosting options and customer hosting packages as well as customer
specific hosting package options
* :feature:`-` add template tags for database icons and human readable names in
:py:mod:`userdbs.templatetags.userdb`
* :release:`0.5.2 <2015-01-18>` * :release:`0.5.2 <2015-01-18>`
* :bug:`-` define proper allauth production settings with https and mandatory * :bug:`-` define proper allauth production settings with https and mandatory
email verification email verification

View file

@ -9,16 +9,35 @@ administrators and customers.
.. _Django: https://www.djangoproject.com/ .. _Django: https://www.djangoproject.com/
Common code
===========
.. toctree::
code/gvacommon
Celery task stubs
=================
.. toctree::
code/fileservertasks
code/ldaptasks
code/mysqltasks
code/pgsqltasks
Django app code
===============
.. toctree:: .. toctree::
code/gnuviechadmin code/gnuviechadmin
code/gvacommon
code/dashboard code/dashboard
code/domains code/domains
code/hostingpackages
code/managemails code/managemails
code/mysqltasks
code/osusers code/osusers
code/pgsqltasks
code/taskresults code/taskresults
code/userdbs code/userdbs

View file

@ -0,0 +1,12 @@
:py:mod:`fileservertasks` app
=============================
.. automodule:: fileservertasks
:py:mod:`tasks <fileservertasks.tasks>`
---------------------------------------
.. automodule:: fileservertasks.tasks
:members:
:undoc-members:

View file

@ -13,3 +13,11 @@ provides some functionality that is common to all gnuviechadmin subprojects.
.. automodule:: gvacommon.celeryrouters .. automodule:: gvacommon.celeryrouters
:members: :members:
:undoc-members: :undoc-members:
:py:mod:`viewmixins <gvacommon.viewmixins>`
-------------------------------------------
.. automodule:: gvacommon.viewmixins
:members:
:undoc-members:

View file

@ -0,0 +1,38 @@
:py:mod:`hostingpackages` app
=============================
.. automodule:: hostingpackages
:py:mod:`admin <hostingpackages.admin>`
---------------------------------------
.. automodule:: hostingpackages.admin
:members:
:py:mod:`apps <hostingpackages.apps>`
-------------------------------------
.. automodule:: hostingpackages.apps
:members:
:py:mod:`models <hostingpackages.models>`
-----------------------------------------
.. automodule:: hostingpackages.models
:members:
:py:mod:`views <hostingpackages.views>`
---------------------------------------
.. automodule:: hostingpackages.views
:members:
:py:mod:`urls <hostingpackages.urls>`
-------------------------------------
.. automodule:: hostingpackages.urls
:members:

12
docs/code/ldaptasks.rst Normal file
View file

@ -0,0 +1,12 @@
:py:mod:`ldaptasks` app
=======================
.. automodule:: ldaptasks
:py:mod:`tasks <ldaptasks.tasks>`
---------------------------------
.. automodule:: ldaptasks.tasks
:members:
:undoc-members:

View file

@ -9,9 +9,4 @@
.. automodule:: mysqltasks.tasks .. automodule:: mysqltasks.tasks
:members: :members:
:undoc-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

View file

@ -18,6 +18,13 @@
:members: :members:
:py:mod:`forms <osusers.forms>`
-------------------------------
.. automodule:: osusers.forms
:members:
:py:mod:`models <osusers.models>` :py:mod:`models <osusers.models>`
--------------------------------- ---------------------------------
@ -25,21 +32,14 @@
:members: :members:
:py:mod:`tasks <osusers.tasks>` :py:mod:`urls <osusers.urls>`
-----------------------------
.. automodule:: osusers.urls
:py:mod:`views <osusers.views>`
------------------------------- -------------------------------
.. automodule:: osusers.tasks .. automodule:: osusers.views
:members:
.. 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

View file

@ -9,9 +9,5 @@
---------------------------------- ----------------------------------
.. automodule:: pgsqltasks.tasks .. automodule:: pgsqltasks.tasks
:members:
.. autotask:: pgsqltasks.tasks.create_pgsql_database :undoc-members:
.. autotask:: pgsqltasks.tasks.create_pgsql_user
.. autotask:: pgsqltasks.tasks.delete_pgsql_database
.. autotask:: pgsqltasks.tasks.delete_pgsql_user
.. autotask:: pgsqltasks.tasks.set_pgsql_userpassword

View file

@ -23,8 +23,14 @@
.. automodule:: userdbs.models .. automodule:: userdbs.models
:members: :members:
:py:mod:`templatetags <userdbs.templatetags>`
---------------------------------------------
:py:mod:`views <userdbs.views>` .. automodule:: userdbs.templatetags
-------------------------------
.. automodule:: userdbs.views
:py:mod:`userdb <userdbs.templatetags.userdb>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: userdbs.templatetags.userdb
:members:

View file

@ -60,9 +60,9 @@ copyright = u'2014, 2015 Jan Dittberner'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.5' version = '0.6'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.5.2' release = '0.6.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.

19
docs/ideas.rst Normal file
View file

@ -0,0 +1,19 @@
Development ideas and planned features
======================================
* password reset for
- OS users
- mailboxes
* add ssh key management for sftp users
* link to phpmyadmin and phppgadmin
* link to webmail
* list mail domains
* CRUD for mailboxes
* CRUD for mail addresses

View file

@ -25,6 +25,7 @@ Contents:
deploy deploy
tests tests
code code
ideas
changelog changelog

View file

@ -4,7 +4,6 @@ from django.conf.urls import patterns, url
from .views import ( from .views import (
IndexView, IndexView,
LogoutView,
UserDashboardView, UserDashboardView,
) )
@ -14,6 +13,4 @@ urlpatterns = patterns(
url(r'^$', IndexView.as_view(), name='dashboard'), url(r'^$', IndexView.as_view(), name='dashboard'),
url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$', url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$',
UserDashboardView.as_view(), name='customer_dashboard'), UserDashboardView.as_view(), name='customer_dashboard'),
url(r'^logout/',
LogoutView.as_view(), name='logout'),
) )

View file

@ -4,16 +4,15 @@ This module defines the views for the gnuviechadmin customer dashboard.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.http import HttpResponseForbidden
from django.views.generic import ( from django.views.generic import (
DetailView, DetailView,
TemplateView, TemplateView,
) )
from django.views.generic.base import RedirectView from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from django.contrib.auth import get_user_model, logout
from braces.views import LoginRequiredMixin from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from hostingpackages.models import CustomerHostingPackage
class IndexView(TemplateView): class IndexView(TemplateView):
@ -24,7 +23,7 @@ class IndexView(TemplateView):
template_name = 'dashboard/index.html' template_name = 'dashboard/index.html'
class UserDashboardView(LoginRequiredMixin, DetailView): class UserDashboardView(StaffOrSelfLoginRequiredMixin, DetailView):
""" """
This is the user dashboard view. This is the user dashboard view.
@ -34,23 +33,16 @@ class UserDashboardView(LoginRequiredMixin, DetailView):
slug_field = 'username' slug_field = 'username'
template_name = 'dashboard/user_dashboard.html' template_name = 'dashboard/user_dashboard.html'
def dispatch(self, request, *args, **kwargs): def get_context_data(self, **kwargs):
if (request.user.is_staff or request.user == self.get_object()): context = super(UserDashboardView, self).get_context_data(**kwargs)
return super(UserDashboardView, self).dispatch( context['hosting_packages'] = CustomerHostingPackage.objects.filter(
request, *args, **kwargs customer=self.object
)
return HttpResponseForbidden(
_('You are not allowed to view this page.')
) )
return context
class LogoutView(RedirectView): def get_customer_object(self):
pattern_name = 'dashboard' """
Returns the customer object.
def get(self, *args, **kwargs): """
logout(self.request) return self.get_object()
return super(LogoutView, self).get(*args, **kwargs)
def get_redirect_url(self, *args, **kwargs):
if 'next' in self.request.GET:
return self.request.GET['next']
return super(LogoutView, self).get_redirect_url(*args, **kwargs)

View file

@ -0,0 +1,4 @@
"""
This module contains :py:mod:`fileservertasks.tasks`.
"""

View file

@ -0,0 +1,94 @@
"""
This module defines task stubs for the tasks implemented in the gvafile Celery
worker.
"""
from __future__ import absolute_import
from celery import shared_task
@shared_task
def setup_file_sftp_userdir(username):
"""
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
def delete_file_sftp_userdir(username):
"""
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
def setup_file_mail_userdir(username):
"""
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
def delete_file_mail_userdir(username):
"""
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
def create_file_mailbox(username, mailboxname):
"""
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
def delete_file_mailbox(username, mailboxname):
"""
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
"""

View file

@ -262,6 +262,7 @@ LOCAL_APPS = (
'osusers', 'osusers',
'managemails', 'managemails',
'userdbs', 'userdbs',
'hostingpackages',
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@ -351,4 +352,8 @@ OSUSER_MINGID = int(get_env_variable('GVA_MIN_OS_GID'))
OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX') OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX')
OSUSER_HOME_BASEPATH = get_env_variable('GVA_OSUSER_HOME_BASEPATH') OSUSER_HOME_BASEPATH = get_env_variable('GVA_OSUSER_HOME_BASEPATH')
OSUSER_DEFAULT_SHELL = get_env_variable('GVA_OSUSER_DEFAULT_SHELL') OSUSER_DEFAULT_SHELL = get_env_variable('GVA_OSUSER_DEFAULT_SHELL')
OSUSER_SFTP_GROUP = 'sftponly'
OSUSER_SSH_GROUP = 'sshusers'
OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP]
OSUSER_UPLOAD_SERVER = get_env_variable('GVA_OSUSER_UPLOADSERVER')
########## END CUSTOM APP CONFIGURATION ########## END CUSTOM APP CONFIGURATION

View file

@ -3,15 +3,15 @@ from __future__ import absolute_import
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
from django.conf import settings from django.conf import settings
import dashboard.urls
from django.contrib import admin from django.contrib import admin
admin.autodiscover() admin.autodiscover()
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'', include(dashboard.urls)), url(r'', include('dashboard.urls')),
url(r'^accounts/', include('allauth.urls')), url(r'^accounts/', include('allauth.urls')),
url(r'^hosting/', include('hostingpackages.urls')),
url(r'^osuser/', include('osusers.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
) )

View file

@ -0,0 +1,42 @@
"""
This module defines mixins for gnuviechadmin views.
"""
from __future__ import unicode_literals
from django.http import HttpResponseForbidden
from django.utils.translation import ugettext as _
from braces.views import LoginRequiredMixin
class StaffOrSelfLoginRequiredMixin(LoginRequiredMixin):
"""
Mixin that makes sure that a user is logged in and matches the current
customer or is a staff user.
"""
def dispatch(self, request, *args, **kwargs):
if (
request.user.is_staff or
request.user == self.get_customer_object()
):
return super(StaffOrSelfLoginRequiredMixin, self).dispatch(
request, *args, **kwargs
)
return HttpResponseForbidden(
_('You are not allowed to view this page.')
)
def get_customer_object(self):
"""
Views based on this mixin have to implement this method to return
the customer that must be an object of the same class as the
django.contrib.auth user type.
:return: customer
:rtype: settings.AUTH_USER_MODEL
"""
raise NotImplemented("subclass has to implement get_customer_object")

View file

@ -0,0 +1,5 @@
"""
This app takes care of hosting packages.
"""
default_app_config = 'hostingpackages.apps.HostingPackagesAppConfig'

View file

@ -0,0 +1,146 @@
"""
This module contains the admin site interface for hosting packages.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.contrib import admin
from .models import (
CustomerHostingPackage,
CustomerDiskSpaceOption,
CustomerUserDatabaseOption,
CustomerMailboxOption,
DiskSpaceOption,
HostingPackageTemplate,
MailboxOption,
UserDatabaseOption,
)
class CustomerHostingPackageCreateForm(forms.ModelForm):
"""
This is the form class for creating new customer hosting packages.
"""
class Meta:
model = CustomerHostingPackage
fields = ['customer', 'template', 'name']
def save(self, **kwargs):
"""
Save the customer hosting package.
:param kwargs: keyword arguments passed to create method
:return: customer hosting package instance
:rtype: :py:class:`hostingpackages.models.CustomerHostingPackage`
"""
hostinpackages = CustomerHostingPackage.objects.create_from_template(
customer=self.cleaned_data['customer'],
template=self.cleaned_data['template'],
name=self.cleaned_data['name'],
**kwargs)
return hostinpackages
def save_m2m(self):
pass
class CustomerDiskSpaceOptionInline(admin.TabularInline):
"""
This class implements the inline editor for customer hosting package disk
space options.
"""
model = CustomerDiskSpaceOption
extra = 0
class CustomerMailboxOptionInline(admin.TabularInline):
"""
This class implements the inline editor for customer hosting package
mailbox options.
"""
model = CustomerMailboxOption
extra = 0
class CustomerUserDatabaseOptionInline(admin.TabularInline):
"""
This class implements the inline editor for customer hosting package user
database options.
"""
model = CustomerUserDatabaseOption
extra = 0
class CustomerHostingPackageAdmin(admin.ModelAdmin):
"""
This class implements the admin interface for
:py:class:`CustomerHostingPackage`.
"""
add_form = CustomerHostingPackageCreateForm
add_fieldsets = (
(None, {
'fields': ('customer', 'template', 'name')
}),
)
inlines = [
CustomerDiskSpaceOptionInline,
CustomerMailboxOptionInline,
CustomerUserDatabaseOptionInline,
]
list_display = ['name', 'customer', 'osuser']
def get_form(self, request, obj=None, **kwargs):
"""
Use special form for customer hosting package creation.
:param request: the current HTTP request
:param obj: either a :py:class:`CustomerHostingPackage
<hostingpackages.models.CustomerHostingPackage>` instance or None
for a new customer hosting package
: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,
'fields': admin.options.flatten_fieldsets(self.add_fieldsets),
})
defaults.update(kwargs)
return super(CustomerHostingPackageAdmin, self).get_form(
request, obj, **defaults)
def get_readonly_fields(self, request, obj=None):
"""
Make sure that customer and template are not editable for existing
customer hosting packages.
:param request: the current HTTP request
:param obj: either a :py:class:`CustomerHostingPackage
<hostingpackages.models.CustomerHostingPackage>` instance or None
for a new customer hosting package
:return: a list of fields
:rtype: list
"""
if obj:
return ['customer', 'template']
return []
admin.site.register(CustomerHostingPackage, CustomerHostingPackageAdmin)
admin.site.register(DiskSpaceOption)
admin.site.register(HostingPackageTemplate)
admin.site.register(MailboxOption)
admin.site.register(UserDatabaseOption)

View file

@ -0,0 +1,17 @@
"""
This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`hostingpackages` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class HostingPackagesAppConfig(AppConfig):
"""
AppConfig for the :py:mod:`hostingpackages` app.
"""
name = 'hostingpackages'
verbose_name = _('Hosting Packages and Options')

View file

@ -0,0 +1,44 @@
"""
This module contains the form classes related to hosting packages.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from .models import CustomerHostingPackage
class CreateHostingPackageForm(forms.ModelForm):
"""
This form class is used for creating new customer hosting packages.
"""
class Meta:
model = CustomerHostingPackage
fields = ['template', 'name', 'description']
def __init__(self, instance, *args, **kwargs):
username = kwargs.pop('user')
super(CreateHostingPackageForm, self).__init__(
*args, **kwargs
)
self.fields['description'].widget.attrs['rows'] = 2
self.helper = FormHelper()
self.helper.form_action = reverse(
'create_hosting_package', kwargs={'user': username}
)
self.helper.layout = Layout(
'template',
'name',
'description',
Submit('submit', _('Add Hosting Package')),
)

View file

@ -0,0 +1,192 @@
# 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: gnuviechadmin hostingpackages\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-18 13:36+0100\n"
"PO-Revision-Date: 2015-01-18 13:17+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\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"
#: apps.py:17
msgid "Hosting Packages and Options"
msgstr "Hostingpakete und -Optionen"
#: models.py:20
msgid "MiB"
msgstr "MiB"
#: models.py:21
msgid "GiB"
msgstr "GiB"
#: models.py:22
msgid "TiB"
msgstr "TiB"
#: models.py:28
msgid "description"
msgstr "Beschreibung"
#: models.py:29
msgid "mailbox count"
msgstr "Anzahl Postfächer"
#: models.py:31 models.py:58
msgid "disk space"
msgstr "Speicherplatz"
#: models.py:31
msgid "disk space for the hosting package"
msgstr "Speicherplatz für das Hostingpaket"
#: models.py:33 models.py:60
msgid "unit of disk space"
msgstr "Maßeinheit für den Speicherplatz"
#: models.py:43 models.py:192
msgid "name"
msgstr "Name"
#: models.py:46
msgid "Hosting package"
msgstr "Hostingpaket"
#: models.py:47
msgid "Hosting packages"
msgstr "Hostingpakete"
#: models.py:66
msgid "Disk space option"
msgstr "Speicherplatzoption"
#: models.py:67
msgid "Disk space options"
msgstr "Speicherplatzoptionen"
#: models.py:70
#, python-brace-format
msgid "Additional disk space {space} {unit}"
msgstr "Zusätzlicher Speicherplatz {space} {unit}"
#: models.py:84
msgid "number of databases"
msgstr "Anzahl von Datenbanken"
#: models.py:86
msgid "database type"
msgstr "Datenbanktyp"
#: models.py:92
msgid "Database option"
msgstr "Datenbankoption"
#: models.py:93
msgid "Database options"
msgstr "Datenbankoptionen"
#: models.py:97
#, python-brace-format
msgid "{type} database"
msgid_plural "{count} {type} databases"
msgstr[0] "{type}-Datenbank"
msgstr[1] "{count} {type}-Datenbanken"
#: models.py:120
msgid "number of mailboxes"
msgstr "Anzahl von Postfächern"
#: models.py:125
msgid "Mailbox option"
msgstr "Postfachoption"
#: models.py:126
msgid "Mailbox options"
msgstr "Postfachoptionen"
#: models.py:130
#, python-brace-format
msgid "{count} additional mailbox"
msgid_plural "{count} additional mailboxes"
msgstr[0] "{count} zusätzliches Postfach"
msgstr[1] "{count} zusätzliche Postfächer"
#: models.py:185
msgid "customer"
msgstr "Kunde"
#: models.py:187
msgid "hosting package template"
msgstr "Hostingpaketvorlage"
#: models.py:189
msgid "The hosting package template that this hosting package is based on"
msgstr "Die Hostingpaketvorlage, auf der dieses Hostingpaket aufgebaut ist"
#: models.py:194
msgid "Operating system user"
msgstr "Betriebssystemnutzer"
#: models.py:201
msgid "customer hosting package"
msgstr "Kundenhostingpaket"
#: models.py:202
msgid "customer hosting packages"
msgstr "Kundenhostingpakete"
#: models.py:211
msgid "hosting package"
msgstr "Hostingpaket"
#: models.py:214
msgid "customer hosting option"
msgstr "kundenspezifische Hostingoption"
#: models.py:215
msgid "customer hosting options"
msgstr "kundenspezifische Hostingoptionen"
#: models.py:227
msgid "disk space option template"
msgstr "Speicherplatzoptionsvorlage"
#: models.py:229
msgid "The disk space option template that this disk space option is based on"
msgstr ""
"Die Speicherplatzoptionsvorlage auf der diese Speicherplatzoption aufgebaut "
"ist"
#: models.py:243
msgid "user database option template"
msgstr "Nutzerdatenbankoptionsvorlage"
#: models.py:245
msgid "The user database option template that this database option is based on"
msgstr ""
"Die Nutzerdatenbankoptionsvorlage auf der diese Datenbankoption aufgebaut ist"
#: models.py:259
msgid "mailbox option template"
msgstr "Postfachoptionsvorlage"
#: models.py:261
msgid "The mailbox option template that this mailbox option is based on"
msgstr "Die Postfachoptionsvorlage auf der diese Postfachoption aufgebaut ist"
#~ msgid "Hosting option"
#~ msgstr "Hostingoption"
#~ msgid "Hosting options"
#~ msgstr "Hostingoptionen"

View file

@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
from django.conf import settings
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CustomerHostingPackage',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'customer hosting package',
'verbose_name_plural': 'customer hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerHostingPackageOption',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
],
options={
'verbose_name': 'customer hosting option',
'verbose_name_plural': 'customer hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerDiskSpaceOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=('hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='CustomerMailboxOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=('hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='CustomerUserDatabaseOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
},
bases=('hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='HostingOption',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
],
options={
'verbose_name': 'Hosting option',
'verbose_name_plural': 'Hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='DiskSpaceOption',
fields=[
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=('hostingpackages.hostingoption', models.Model),
),
migrations.CreateModel(
name='HostingPackageTemplate',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'verbose_name': 'Hosting package',
'verbose_name_plural': 'Hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailboxOption',
fields=[
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=('hostingpackages.hostingoption', models.Model),
),
migrations.CreateModel(
name='UserDatabaseOption',
fields=[
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
},
bases=('hostingpackages.hostingoption', models.Model),
),
migrations.AlterUniqueTogether(
name='userdatabaseoption',
unique_together=set([('number', 'db_type')]),
),
migrations.AlterUniqueTogether(
name='diskspaceoption',
unique_together=set([('diskspace', 'diskspace_unit')]),
),
migrations.AddField(
model_name='customeruserdatabaseoption',
name='template',
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this hosting option is based on'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customeruserdatabaseoption',
unique_together=set([('number', 'db_type')]),
),
migrations.AddField(
model_name='customermailboxoption',
name='template',
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this hosting option is based on'),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackageoption',
name='hosting_package',
field=models.ForeignKey(verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage'),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackage',
name='template',
field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on.'),
preserve_default=True,
),
migrations.AddField(
model_name='customerdiskspaceoption',
name='template',
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this hosting option is based on'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerdiskspaceoption',
unique_together=set([('diskspace', 'diskspace_unit')]),
),
]

View file

@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
from django.conf import settings
import model_utils.fields
class Migration(migrations.Migration):
replaces = [('hostingpackages', '0001_initial'), ('hostingpackages', '0002_auto_20150118_1149'), ('hostingpackages', '0003_auto_20150118_1221'), ('hostingpackages', '0004_customerhostingpackage_osuser'), ('hostingpackages', '0005_auto_20150118_1303')]
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('osusers', '0004_auto_20150104_1751'),
]
operations = [
migrations.CreateModel(
name='CustomerHostingPackage',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'customer hosting package',
'verbose_name_plural': 'customer hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerHostingPackageOption',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
],
options={
'verbose_name': 'customer hosting option',
'verbose_name_plural': 'customer hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerDiskSpaceOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=('hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='CustomerMailboxOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=('hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='CustomerUserDatabaseOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.CustomerHostingPackageOption')),
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
},
bases=('hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='HostingOption',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
],
options={
'verbose_name': 'Hosting option',
'verbose_name_plural': 'Hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='DiskSpaceOption',
fields=[
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
('diskspace', models.PositiveIntegerField(verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=('hostingpackages.hostingoption', models.Model),
),
migrations.CreateModel(
name='HostingPackageTemplate',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(help_text='disk space for the hosting package', verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(verbose_name='unit of disk space', choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'verbose_name': 'Hosting package',
'verbose_name_plural': 'Hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailboxOption',
fields=[
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
('number', models.PositiveIntegerField(unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=('hostingpackages.hostingoption', models.Model),
),
migrations.CreateModel(
name='UserDatabaseOption',
fields=[
('hostingoption_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='hostingpackages.HostingOption')),
('number', models.PositiveIntegerField(default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(verbose_name='database type', choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
},
bases=('hostingpackages.hostingoption', models.Model),
),
migrations.AlterUniqueTogether(
name='userdatabaseoption',
unique_together=set([('number', 'db_type')]),
),
migrations.AlterUniqueTogether(
name='diskspaceoption',
unique_together=set([('diskspace', 'diskspace_unit')]),
),
migrations.AddField(
model_name='customeruserdatabaseoption',
name='template',
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this hosting option is based on'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customeruserdatabaseoption',
unique_together=set([('number', 'db_type')]),
),
migrations.AddField(
model_name='customermailboxoption',
name='template',
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this mailbox option is based on'),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackageoption',
name='hosting_package',
field=models.ForeignKey(verbose_name='hosting package', to='hostingpackages.CustomerHostingPackage'),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackage',
name='template',
field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on'),
preserve_default=True,
),
migrations.AddField(
model_name='customerdiskspaceoption',
name='template',
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this hosting option is based on'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerdiskspaceoption',
unique_together=set([('diskspace', 'diskspace_unit')]),
),
migrations.AlterField(
model_name='customerdiskspaceoption',
name='template',
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this disk space option is based on'),
preserve_default=True,
),
migrations.AlterField(
model_name='customeruserdatabaseoption',
name='template',
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this database option is based on'),
preserve_default=True,
),
migrations.AlterField(
model_name='customerhostingpackage',
name='name',
field=models.CharField(max_length=128, verbose_name='name'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerhostingpackage',
unique_together=set([('customer', 'name')]),
),
migrations.AddField(
model_name='customerhostingpackage',
name='osuser',
field=models.OneToOneField(null=True, blank=True, to='osusers.User', verbose_name='Operating system user'),
preserve_default=True,
),
]

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='customerdiskspaceoption',
name='template',
field=models.ForeignKey(verbose_name='disk space option template', to='hostingpackages.DiskSpaceOption', help_text='The disk space option template that this disk space option is based on'),
preserve_default=True,
),
migrations.AlterField(
model_name='customerhostingpackage',
name='template',
field=models.ForeignKey(verbose_name='hosting package template', to='hostingpackages.HostingPackageTemplate', help_text='The hosting package template that this hosting package is based on'),
preserve_default=True,
),
migrations.AlterField(
model_name='customermailboxoption',
name='template',
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.UserDatabaseOption', help_text='The mailbox option template that this mailbox option is based on'),
preserve_default=True,
),
migrations.AlterField(
model_name='customeruserdatabaseoption',
name='template',
field=models.ForeignKey(verbose_name='user database option template', to='hostingpackages.UserDatabaseOption', help_text='The user database option template that this database option is based on'),
preserve_default=True,
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0001_squashed_0005_auto_20150118_1303'),
]
operations = [
migrations.AlterModelOptions(
name='hostingoption',
options={},
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0002_auto_20150118_1149'),
]
operations = [
migrations.AlterField(
model_name='customerhostingpackage',
name='name',
field=models.CharField(max_length=128, verbose_name='name'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerhostingpackage',
unique_together=set([('customer', 'name')]),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0002_auto_20150118_1319'),
]
operations = [
migrations.AlterField(
model_name='customermailboxoption',
name='template',
field=models.ForeignKey(verbose_name='mailbox option template', to='hostingpackages.MailboxOption', help_text='The mailbox option template that this mailbox option is based on'),
preserve_default=True,
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('osusers', '0004_auto_20150104_1751'),
('hostingpackages', '0003_auto_20150118_1221'),
]
operations = [
migrations.AddField(
model_name='customerhostingpackage',
name='osuser',
field=models.ForeignKey(verbose_name='Operating system user', blank=True, to='osusers.User', null=True),
preserve_default=True,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0004_customerhostingpackage_osuser'),
]
operations = [
migrations.AlterField(
model_name='customerhostingpackage',
name='osuser',
field=models.OneToOneField(null=True, blank=True, to='osusers.User', verbose_name='Operating system user'),
preserve_default=True,
),
]

View file

@ -0,0 +1,389 @@
"""
This module contains the hosting package models.
"""
from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _, ungettext
from model_utils import Choices
from model_utils.models import TimeStampedModel
from managemails.models import Mailbox
from osusers.models import (
AdditionalGroup,
Group,
User as OsUser,
)
from userdbs.models import DB_TYPES
DISK_SPACE_UNITS = Choices(
(0, 'M', _('MiB')),
(1, 'G', _('GiB')),
(2, 'T', _('TiB')),
)
DISK_SPACE_FACTORS = (
(1, None, None),
(1024, 1, None),
(1024 * 1024, 1024, 1),
)
@python_2_unicode_compatible
class HostingPackageBase(TimeStampedModel):
description = models.TextField(_('description'), blank=True)
mailboxcount = models.PositiveIntegerField(_('mailbox count'))
diskspace = models.PositiveIntegerField(
_('disk space'), help_text=_('disk space for the hosting package'))
diskspace_unit = models.PositiveSmallIntegerField(
_('unit of disk space'), choices=DISK_SPACE_UNITS)
class Meta:
abstract = True
def __str__(self):
return self.name
class HostingPackageTemplate(HostingPackageBase):
name = models.CharField(_('name'), max_length=128, unique=True)
class Meta:
verbose_name = _('Hosting package')
verbose_name_plural = _('Hosting packages')
class HostingOption(TimeStampedModel):
"""
This is the base class for several types of hosting options.
"""
@python_2_unicode_compatible
class DiskSpaceOptionBase(models.Model):
diskspace = models.PositiveIntegerField(_('disk space'))
diskspace_unit = models.PositiveSmallIntegerField(
_('unit of disk space'), choices=DISK_SPACE_UNITS)
class Meta:
abstract = True
ordering = ['diskspace_unit', 'diskspace']
unique_together = ['diskspace', 'diskspace_unit']
verbose_name = _('Disk space option')
verbose_name_plural = _('Disk space options')
def __str__(self):
return _("Additional disk space {space} {unit}").format(
space=self.diskspace, unit=self.get_diskspace_unit_display())
class DiskSpaceOption(DiskSpaceOptionBase, HostingOption):
"""
This is a class for hosting options adding additional disk space to
existing hosting packages.
"""
@python_2_unicode_compatible
class UserDatabaseOptionBase(models.Model):
number = models.PositiveIntegerField(
_('number of databases'), default=1)
db_type = models.PositiveSmallIntegerField(
_('database type'), choices=DB_TYPES)
class Meta:
abstract = True
ordering = ['db_type', 'number']
unique_together = ['number', 'db_type']
verbose_name = _('Database option')
verbose_name_plural = _('Database options')
def __str__(self):
return ungettext(
'{type} database',
'{count} {type} databases',
self.number
).format(
type=self.get_db_type_display(), count=self.number
)
class UserDatabaseOption(UserDatabaseOptionBase, HostingOption):
"""
This is a class for hosting options adding user databases to existing
hosting packages.
"""
@python_2_unicode_compatible
class MailboxOptionBase(models.Model):
"""
Base class for mailbox options.
"""
number = models.PositiveIntegerField(
_('number of mailboxes'), unique=True)
class Meta:
abstract = True
ordering = ['number']
verbose_name = _('Mailbox option')
verbose_name_plural = _('Mailbox options')
def __str__(self):
return ungettext(
'{count} additional mailbox',
'{count} additional mailboxes',
self.number
).format(
count=self.number
)
class MailboxOption(MailboxOptionBase, HostingOption):
"""
This is a class for hosting options adding more mailboxes to existing
hosting packages.
"""
class CustomerHostingPackageManager(models.Manager):
"""
This is the default manager implementation for
:py:class:`CustomerHostingPackage`.
"""
def create_from_template(self, customer, template, name, **kwargs):
"""
Use this method to create a new :py:class:`CustomerHostingPackage` from
a :py:class:`HostingPackageTemplate`.
The method copies data from the template to the new
:py:class:`CustomerHostingPackage` instance.
:param customer: a Django user representing a customer
:param template: a :py:class:`HostingPackageTemplate`
:param str name: the name of the hosting package there must only be
one hosting package of this name for each customer
:return: customer hosting package
:rtype: :py:class:`CustomerHostingPackage`
"""
package = CustomerHostingPackage(
customer=customer, template=template, name=name)
package.description = template.description
package.copy_template_attributes()
if 'commit' in kwargs and kwargs['commit'] is True:
package.save(**kwargs)
return package
@python_2_unicode_compatible
class CustomerHostingPackage(HostingPackageBase):
"""
This class defines customer specific hosting packages.
"""
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('customer'))
template = models.ForeignKey(
HostingPackageTemplate, verbose_name=_('hosting package template'),
help_text=_(
'The hosting package template that this hosting package is based'
' on'
))
name = models.CharField(_('name'), max_length=128)
osuser = models.OneToOneField(
OsUser, verbose_name=_('Operating system user'),
blank=True, null=True)
objects = CustomerHostingPackageManager()
class Meta:
unique_together = ['customer', 'name']
verbose_name = _('customer hosting package')
verbose_name_plural = _('customer hosting packages')
def __str__(self):
return _("{name} for {customer}").format(
name=self.name, customer=self.customer
)
def get_absolute_url(self):
return reverse('hosting_package_details', kwargs={
'user': self.customer.username,
'pk': self.id,
})
def copy_template_attributes(self):
"""
Copy the attributes of the hosting package's template to the package.
"""
for attrname in ('diskspace', 'diskspace_unit', 'mailboxcount'):
setattr(self, attrname, getattr(self.template, attrname))
def get_disk_space(self, unit=None):
"""
Get the total disk space reserved for this hosting package and all its
additional disk space options.
:return: disk space
:rtype: int
"""
diskspace = self.diskspace
min_unit = self.diskspace_unit
options = CustomerDiskSpaceOption.objects.filter(hosting_package=self)
for option in options:
if option.diskspace_unit == min_unit:
diskspace += option.disk_space
elif option.diskspace_unit > min_unit:
diskspace += (
DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] *
option.diskspace)
else:
diskspace = (
DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] *
diskspace) + option.diskspace
min_unit = option.diskspace_unit
if unit is None:
return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024**2
if unit > min_unit:
return DISK_SPACE_FACTORS[unit][min_unit] * diskspace
return DISK_SPACE_FACTORS[min_unit][unit] * diskspace
def get_quota(self):
soft = 1024 * self.get_disk_space(DISK_SPACE_UNITS.M)
hard = soft * 105 / 100
return (soft, hard)
def get_used_mailboxes(self):
"""
Get the number of used mailboxes for this hosting package.
"""
if self.osuser:
return Mailbox.objects.filter(osuser=self.osuser).count()
return 0
def get_mailboxes(self):
"""
Get the number of mailboxes provided by this hosting package and all
of its mailbox options.
"""
result = CustomerMailboxOption.objects.filter(
hosting_package=self
).aggregate(
mailbox_sum=models.Sum('number')
)
return self.mailboxcount + (result['mailbox_sum'] or 0)
def get_databases(self):
"""
Get the number of user databases provided by user database hosting
options for this hosting package.
"""
return CustomerUserDatabaseOption.objects.values(
'db_type'
).filter(hosting_package=self).annotate(
number=models.Sum('number')
).all()
@transaction.atomic
def save(self, *args, **kwargs):
"""
Save the hosting package to the database.
If this is a new hosting package a new operating system user is
created and assigned to the hosting package.
: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:`CustomerHostingPackage`
"""
if self.pk is None:
self.copy_template_attributes()
self.osuser = OsUser.objects.create_user(self.customer)
for group in settings.OSUSER_DEFAULT_GROUPS:
AdditionalGroup.objects.create(
user=self.osuser, group=Group.objects.get(groupname=group)
)
return super(CustomerHostingPackage, self).save(*args, **kwargs)
class CustomerHostingPackageOption(TimeStampedModel):
"""
This class defines options for customer hosting packages.
"""
hosting_package = models.ForeignKey(
CustomerHostingPackage, verbose_name=_('hosting package'))
class Meta:
verbose_name = _('customer hosting option')
verbose_name_plural = _('customer hosting options')
class CustomerDiskSpaceOption(DiskSpaceOptionBase,
CustomerHostingPackageOption):
"""
This is a class for customer hosting package options adding additional disk
space to existing customer hosting package.
"""
template = models.ForeignKey(
DiskSpaceOption,
verbose_name=_('disk space option template'),
help_text=_(
'The disk space option template that this disk space option is'
' based on'
))
class CustomerUserDatabaseOption(UserDatabaseOptionBase,
CustomerHostingPackageOption):
"""
This is a class for customer hosting package options adding user databases
to existing customer hosting packages.
"""
template = models.ForeignKey(
UserDatabaseOption,
verbose_name=_('user database option template'),
help_text=_(
'The user database option template that this database option is'
' based on'
))
class CustomerMailboxOption(MailboxOptionBase,
CustomerHostingPackageOption):
"""
This is a class for customer hosting package options adding additional
mailboxes to existing customer hosting packages.
"""
template = models.ForeignKey(
MailboxOption,
verbose_name=_('mailbox option template'),
help_text=_(
'The mailbox option template that this mailbox option is based on'
))

View file

@ -0,0 +1,31 @@
"""
Test for models.
"""
from django.test import TestCase
from hostingpackages.models import (
DISK_SPACE_UNITS,
CustomerHostingPackage,
)
class CustomerHostingPackageTest(TestCase):
def test_get_disk_space_bytes(self):
package = CustomerHostingPackage(
diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G
)
self.assertEqual(package.get_disk_space(), 10 * 1024 * 1024**2)
def test_get_disk_space_mib(self):
package = CustomerHostingPackage(
diskspace=10, diskspace_unit=DISK_SPACE_UNITS.G
)
self.assertEqual(package.get_disk_space(DISK_SPACE_UNITS.M), 10 * 1024)
def test_get_quota(self):
package = CustomerHostingPackage(
diskspace=256, diskspace_unit=DISK_SPACE_UNITS.M
)
self.assertEqual(package.get_quota(), (262144, 275251))

View file

@ -0,0 +1,22 @@
"""
This module defines the URL patterns for hosting package related views.
"""
from __future__ import absolute_import, unicode_literals
from django.conf.urls import patterns, url
from .views import (
CreateHostingPackage,
CustomerHostingPackageDetails,
)
urlpatterns = patterns(
'',
url(r'^(?P<user>[\w0-9@.+-_]+)/create$', CreateHostingPackage.as_view(),
name='create_hosting_package'),
url(r'^(?P<user>[\w0-9@.+-_]+)/hostingpackage/(?P<pk>\d+)/$',
CustomerHostingPackageDetails.as_view(),
name='hosting_package_details'),
)

View file

@ -0,0 +1,86 @@
"""
This module defines views related to hosting packages.
"""
from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.views.generic import DetailView
from django.views.generic.edit import CreateView
from django.contrib import messages
from django.contrib.auth import get_user_model
from braces.views import (
LoginRequiredMixin,
StaffuserRequiredMixin,
)
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from .forms import CreateHostingPackageForm
from .models import CustomerHostingPackage
class CreateHostingPackage(
LoginRequiredMixin, StaffuserRequiredMixin, CreateView
):
"""
Create a hosting package.
"""
model = CustomerHostingPackage
raise_exception = True
template_name_suffix = '_create'
form_class = CreateHostingPackageForm
def get_form_kwargs(self):
kwargs = super(CreateHostingPackage, self).get_form_kwargs()
kwargs.update(self.kwargs)
return kwargs
def get_customer_object(self):
return get_user_model().objects.get(username=self.kwargs['user'])
def get_context_data(self, **kwargs):
context = super(CreateHostingPackage, self).get_context_data(**kwargs)
context['customer'] = self.get_customer_object()
return context
def get_success_url(self):
return reverse(
'customer_dashboard', kwargs={'slug': self.kwargs['user']})
def form_valid(self, form):
hostingpackage = form.save(commit=False)
hostingpackage.customer = self.get_customer_object()
hostingpackage.save()
messages.success(
self.request,
_('Started setup of new hosting package {name}.').format(
name=hostingpackage.name)
)
return redirect(self.get_success_url())
class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView):
"""
This view is for showing details of a customer hosting package.
"""
model = CustomerHostingPackage
context_object_name = 'hostingpackage'
def get_customer_object(self):
return get_user_model().objects.get(username=self.kwargs['user'])
def get_context_data(self, **kwargs):
context = super(CustomerHostingPackageDetails, self).get_context_data(
**kwargs)
context.update({
'customer': self.get_customer_object(),
'uploadserver': settings.OSUSER_UPLOAD_SERVER,
})
return context

View file

@ -0,0 +1,4 @@
"""
This module contains :py:mod:`ldaptasks.tasks`.
"""

View file

@ -1,6 +1,6 @@
""" """
This module defines task stubs for the tasks implemented on the Celery This module defines task stubs for the tasks implemented in the gvaldap Celery
workers. worker.
""" """
from __future__ import absolute_import from __future__ import absolute_import
@ -51,6 +51,20 @@ def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
""" """
@shared_task
def set_ldap_user_password(self, username, password):
"""
This task sets the password of an existing :py:class:`LDAP user
<ldapentities.models.LdapUser>`.
:param str username: the user name
:param str password: teh clear text password
:return: :py:const:`True` if the password has been set, :py:const:`False`
if the user does not exist.
"""
@shared_task @shared_task
def add_ldap_user_to_group(username, groupname): def add_ldap_user_to_group(username, groupname):
""" """
@ -115,89 +129,3 @@ def delete_ldap_group(groupname):
:rtype: boolean :rtype: boolean
""" """
@shared_task
def setup_file_sftp_userdir(username):
"""
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
def delete_file_sftp_userdir(username):
"""
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
def setup_file_mail_userdir(username):
"""
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
def delete_file_mail_userdir(username):
"""
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
def create_file_mailbox(username, mailboxname):
"""
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
def delete_file_mailbox(username, mailboxname):
"""
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
"""

View file

@ -1,3 +1,9 @@
"""
This module defines the database models for mail handling.
"""
from __future__ import unicode_literals
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -7,7 +13,11 @@ from model_utils.models import TimeStampedModel
from domains.models import MailDomain from domains.models import MailDomain
from osusers.models import User as OsUser from osusers.models import User as OsUser
from osusers.tasks import create_file_mailbox, delete_file_mailbox
from fileservertasks.tasks import (
create_file_mailbox,
delete_file_mailbox,
)
class ActivateAbleMixin(models.Model): class ActivateAbleMixin(models.Model):
@ -22,8 +32,21 @@ class ActivateAbleMixin(models.Model):
class MailboxManager(models.Manager): class MailboxManager(models.Manager):
"""
This is the default manager class for :py:class:`Mailbox`.
"""
def get_next_mailbox_name(self, osuser): def get_next_mailbox_name(self, osuser):
"""
Get the next available mailbox name for the given operating system
user.
:param osuser: a :py:class:`operating system user <osuser.models.User>`
instance
:return: name of the mailbox
:rtype: str
"""
count = 1 count = 1
mailboxformat = "{0}p{1:02d}" mailboxformat = "{0}p{1:02d}"
mailboxname = mailboxformat.format(osuser.username, count) mailboxname = mailboxformat.format(osuser.username, count)
@ -41,6 +64,10 @@ class MailboxManager(models.Manager):
@python_2_unicode_compatible @python_2_unicode_compatible
class Mailbox(ActivateAbleMixin, TimeStampedModel, models.Model): class Mailbox(ActivateAbleMixin, TimeStampedModel, models.Model):
"""
This is the model class for a mailbox.
"""
osuser = models.ForeignKey(OsUser) osuser = models.ForeignKey(OsUser)
username = models.CharField(max_length=128, unique=True) username = models.CharField(max_length=128, unique=True)
password = models.CharField(max_length=255) password = models.CharField(max_length=255)
@ -53,6 +80,12 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel, models.Model):
verbose_name_plural = _('Mailboxes') verbose_name_plural = _('Mailboxes')
def set_password(self, password): def set_password(self, password):
"""
Set the hashed password for the mailbox.
:param str password: the clear text password
"""
self.password = sha512_crypt.encrypt(password) self.password = sha512_crypt.encrypt(password)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -69,6 +102,10 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel, models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model): class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
"""
This is the model class for a mail address.
"""
localpart = models.CharField(max_length=128) localpart = models.CharField(max_length=128)
domain = models.ForeignKey(MailDomain) domain = models.ForeignKey(MailDomain)
@ -84,6 +121,10 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class MailAddressMailbox(TimeStampedModel, models.Model): class MailAddressMailbox(TimeStampedModel, models.Model):
"""
This is the model class to assign a mail address to a mailbox.
"""
mailaddress = models.OneToOneField(MailAddress, primary_key=True) mailaddress = models.OneToOneField(MailAddress, primary_key=True)
mailbox = models.ForeignKey(Mailbox) mailbox = models.ForeignKey(Mailbox)
@ -95,6 +136,10 @@ class MailAddressMailbox(TimeStampedModel, models.Model):
class MailAddressForward(TimeStampedModel, models.Model): class MailAddressForward(TimeStampedModel, models.Model):
"""
This is a model class to map mail addresses to forwarding addresses.
"""
mailaddress = models.ForeignKey(MailAddress) mailaddress = models.ForeignKey(MailAddress)
target = models.EmailField(max_length=254) target = models.EmailField(max_length=254)

View file

@ -160,7 +160,7 @@ class MailBoxAdminTest(TestCase):
form = self.mbadmin.get_form(Mock) form = self.mbadmin.get_form(Mock)
self.assertEqual( self.assertEqual(
form.Meta.fields, form.Meta.fields,
['username', 'password1', 'password2'] ['osuser', 'password1', 'password2']
) )
@override_settings( @override_settings(

View file

@ -1,5 +1,5 @@
""" """
This module defines Celery_ tasks to manage MySQL users and databases. This module defines Celery tasks to manage MySQL users and databases.
""" """
from __future__ import absolute_import from __future__ import absolute_import

View file

@ -6,6 +6,9 @@ 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
from .forms import (
PASSWORD_MISMATCH_ERROR
)
from .models import ( from .models import (
AdditionalGroup, AdditionalGroup,
Group, Group,
@ -13,11 +16,6 @@ from .models import (
User, User,
) )
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
"""
Error message for non matching passwords.
"""
class AdditionalGroupInline(admin.TabularInline): class AdditionalGroupInline(admin.TabularInline):
""" """

View file

@ -0,0 +1,71 @@
"""
This module defines operating system user related forms.
"""
from __future__ import unicode_literals
from django import forms
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from .models import User
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
"""
Error message for non matching passwords.
"""
class ChangeOsUserPasswordForm(forms.ModelForm):
"""
A form for setting an OS user's password.
"""
password1 = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
required=False,
)
password2 = forms.CharField(
label=_('Password (again)'), widget=forms.PasswordInput,
required=False,
)
class Meta:
model = User
fields = []
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
super(ChangeOsUserPasswordForm, self).__init__(*args, **kwargs)
self.helper.form_action = reverse(
'set_osuser_password', kwargs={'slug': self.instance.username})
self.helper.add_input(Submit('submit', _('Set password')))
def clean_password2(self):
"""
Check that the two password entries match.
:return: the validated password
:rtype: str or None
"""
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise forms.ValidationError(PASSWORD_MISMATCH_ERROR)
return password2
def save(self, commit=True):
"""
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`
"""
self.instance.set_password(self.cleaned_data['password1'])
return super(ChangeOsUserPasswordForm, self).save(commit=commit)

View file

@ -22,15 +22,19 @@ from passlib.utils import generate_password
from taskresults.models import TaskResult from taskresults.models import TaskResult
from .tasks import ( from ldaptasks.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_sftp_userdir,
delete_ldap_group, delete_ldap_group,
delete_ldap_user, delete_ldap_user,
remove_ldap_user_from_group, remove_ldap_user_from_group,
set_ldap_user_password,
)
from fileservertasks.tasks import (
delete_file_mail_userdir,
delete_file_sftp_userdir,
setup_file_mail_userdir, setup_file_mail_userdir,
setup_file_sftp_userdir, setup_file_sftp_userdir,
) )
@ -242,15 +246,26 @@ class User(TimeStampedModel, models.Model):
""" """
if hasattr(self, 'shadow'): if hasattr(self, 'shadow'):
self.shadow.set_password(password) self.shadow.set_password(password)
success = set_ldap_user_password.delay(
self.username, password).get()
if success:
_LOGGER.info(
"successfully set LDAP password for %s", self.username)
else:
_LOGGER.error(
"setting the LDAP password for %s failed", self.username)
return success
else: else:
self.shadow = Shadow.objects.create_shadow( self.shadow = Shadow.objects.create_shadow(
user=self, password=password user=self, password=password
) )
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 self.homedir, self.shell, password
).get() ).get()
logging.info("set LDAP password for %s", dn) _LOGGER.info("set LDAP password for %s", dn)
return True
@transaction.atomic @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View file

@ -6,17 +6,14 @@ from django.test.utils import override_settings
from mock import patch, Mock from mock import patch, Mock
from osusers.models import ( from osusers.models import (
DeleteTaskResult,
Group, Group,
User, User,
) )
from osusers.admin import ( from osusers.admin import (
DeleteTaskResultAdmin,
GroupAdmin, GroupAdmin,
PASSWORD_MISMATCH_ERROR, PASSWORD_MISMATCH_ERROR,
UserAdmin, UserAdmin,
UserCreationForm, UserCreationForm,
UserTaskResultInline,
) )
@ -85,7 +82,7 @@ class UserAdminTest(TestCase):
form = self.uadmin.get_form(Mock(name='request')) form = self.uadmin.get_form(Mock(name='request'))
self.assertEqual( self.assertEqual(
form.Meta.fields, form.Meta.fields,
['password1', 'password2'] ['customer', 'password1', 'password2']
) )
@override_settings( @override_settings(
@ -103,7 +100,7 @@ class UserAdminTest(TestCase):
def test_get_inline_instances_without_object(self): def test_get_inline_instances_without_object(self):
inlines = self.uadmin.get_inline_instances(Mock(name='request')) inlines = self.uadmin.get_inline_instances(Mock(name='request'))
self.assertEqual(inlines, []) self.assertEqual(len(inlines), 2)
@override_settings( @override_settings(
CELERY_ALWAYS_EAGER=True, CELERY_ALWAYS_EAGER=True,

View file

@ -12,12 +12,9 @@ from passlib.hash import sha512_crypt
from osusers.models import ( from osusers.models import (
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL, CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL,
AdditionalGroup, AdditionalGroup,
DeleteTaskResult,
Group, Group,
GroupTaskResult,
Shadow, Shadow,
User, User,
UserTaskResult,
) )
@ -62,13 +59,7 @@ class AdditionalGroupTest(TestCaseWithCeleryTasks):
def test_delete(self): def test_delete(self):
group2 = Group.objects.create(groupname='test2', gid=1001) group2 = Group.objects.create(groupname='test2', gid=1001)
addgroup = AdditionalGroup.objects.create(user=self.user, group=group2) addgroup = AdditionalGroup.objects.create(user=self.user, group=group2)
DeleteTaskResult.objects.all().delete()
addgroup.delete() addgroup.delete()
taskres = DeleteTaskResult.objects.all()
self.assertTrue(len(taskres), 1)
self.assertEqual(taskres[0].task_name, 'remove_ldap_user_from_group')
self.assertEqual(taskres[0].modeltype, 'usergroup')
self.assertEqual(taskres[0].modelname, 'test (1000) in test2 (1001)')
self.assertEqual(len(AdditionalGroup.objects.all()), 0) self.assertEqual(len(AdditionalGroup.objects.all()), 0)
def test___str__(self): def test___str__(self):
@ -107,12 +98,6 @@ class GroupTest(TestCaseWithCeleryTasks):
group.delete() group.delete()
self.assertEqual(len(Group.objects.all()), 0) self.assertEqual(len(Group.objects.all()), 0)
self.assertEqual(len(GroupTaskResult.objects.all()), 0) self.assertEqual(len(GroupTaskResult.objects.all()), 0)
taskres = DeleteTaskResult.objects.all()
self.assertEqual(len(taskres), 1)
self.assertEqual(taskres[0].task_name,
'delete_ldap_group_if_empty')
self.assertEqual(taskres[0].modeltype, 'group')
self.assertEqual(taskres[0].modelname, 'test')
class ShadowManagerTest(TestCaseWithCeleryTasks): class ShadowManagerTest(TestCaseWithCeleryTasks):

View file

@ -1,22 +0,0 @@
from django.test import TestCase
from osusers.tasks import LdapRouter
class LdapRouterTest(TestCase):
def setUp(self):
self.router = LdapRouter()
super(LdapRouterTest, self).setUp()
def test_ldap_tasks_are_routed_to_ldap_queue(self):
route = self.router.route_for_task(
'some_ldap_task')
self.assertEqual(
route,
{'exchange': 'ldap',
'exchange_type': 'direct',
'queue': 'ldap'})
def test_non_ldap_tasks_are_routed_to_default(self):
self.assertIsNone(
self.router.route_for_task('other'))

View file

@ -0,0 +1,16 @@
"""
This module defines the URL patterns for operating system user related views.
"""
from __future__ import absolute_import, unicode_literals
from django.conf.urls import patterns, url
from .views import SetOsUserPassword
urlpatterns = patterns(
'',
url(r'^(?P<slug>[\w0-9@.+-_]+)/setpassword$', SetOsUserPassword.as_view(),
name='set_osuser_password'),
)

View file

@ -0,0 +1,45 @@
"""
This module defines the views for gnuviechadmin operating system user handling.
"""
from __future__ import unicode_literals, absolute_import
from django.shortcuts import redirect
from django.views.generic import UpdateView
from django.utils.translation import ugettext as _
from django.contrib import messages
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from .forms import ChangeOsUserPasswordForm
from .models import User
class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView):
"""
This view is used for setting a new operating system user password.
"""
model = User
slug_field = 'username'
template_name_suffix = '_setpassword'
context_object_name = 'osuser'
form_class = ChangeOsUserPasswordForm
def get_customer_object(self):
return self.get_object().customer
def get_context_data(self, *args, **kwargs):
context = super(SetOsUserPassword, self).get_context_data(
*args, **kwargs)
context['customer'] = self.get_customer_object()
return context
def form_valid(self, form):
osuser = form.save()
messages.success(
self.request,
_("New password for {username} has been set successfully.").format(
username=osuser.username
))
return redirect(osuser.customerhostingpackage)

View file

@ -1,5 +1,5 @@
""" """
This module defines Celery_ tasks to manage PostgreSQL users and databases. This module defines Celery tasks to manage PostgreSQL users and databases.
""" """
from __future__ import absolute_import from __future__ import absolute_import

View file

@ -0,0 +1,318 @@
/*
* Font Mfizz v1.2
* Copyright 2013 Mfizz Inc, Joe Lauer
* MIT License
*
* Project: http://mfizz.com/oss/font-mfizz
*
* The font designed for technology and software geeks representing programming
* languages, operating systems, software engineering, and technology.
*
* Mfizz Inc
* Web: http://mfizz.com/
* Twitter: http://twitter.com/mfizz_inc
*
* Joe Lauer
* Web: http://lauer.bz/
* Twitter: http://twitter.com/jjlauer
*/
@font-face {
font-family: "FontMfizz";
src: url("font-mfizz.eot");
src: url("font-mfizz.eot?#iefix") format("embedded-opentype"),
url("font-mfizz.woff") format("woff"),
url("font-mfizz.ttf") format("truetype"),
url("font-mfizz.svg#font-mfizz") format("svg");
font-weight: normal;
font-style: normal;
}
.icon-microscope:before,
.icon-cplusplus:before,
.icon-wireless:before,
.icon-fire-alt:before,
.icon-mobile-device:before,
.icon-objc:before,
.icon-redhat:before,
.icon-freebsd:before,
.icon-heroku:before,
.icon-python:before,
.icon-java:before,
.icon-satellite:before,
.icon-debian:before,
.icon-grails:before,
.icon-c:before,
.icon-postgres:before,
.icon-database-alt2:before,
.icon-raspberrypi:before,
.icon-nginx:before,
.icon-ruby-on-rails:before,
.icon-redis:before,
.icon-scala:before,
.icon-gnome:before,
.icon-perl:before,
.icon-mysql:before,
.icon-fedora:before,
.icon-ghost:before,
.icon-google:before,
.icon-netbsd:before,
.icon-aws:before,
.icon-bomb:before,
.icon-looking:before,
.icon-ruby:before,
.icon-mysql-alt:before,
.icon-playframework-alt:before,
.icon-osx:before,
.icon-database:before,
.icon-database-alt:before,
.icon-shell:before,
.icon-script:before,
.icon-antenna:before,
.icon-coffee-bean:before,
.icon-scala-alt:before,
.icon-platter:before,
.icon-java-duke:before,
.icon-iphone:before,
.icon-script-alt:before,
.icon-google-alt:before,
.icon-haskell:before,
.icon-mariadb:before,
.icon-phone-retro:before,
.icon-phone-alt:before,
.icon-csharp:before,
.icon-php:before,
.icon-postgres-alt:before,
.icon-html:before,
.icon-mfizz:before,
.icon-apache:before,
.icon-hadoop:before,
.icon-ruby-on-rails-alt:before,
.icon-mobile-phone-broadcast:before,
.icon-css:before,
.icon-playframework:before,
.icon-clojure:before,
.icon-mobile-phone-alt:before,
.icon-suse:before,
.icon-java-bold:before,
.icon-nginx-alt:before,
.icon-nginx-alt2:before,
.icon-linux-mint:before,
.icon-dreamhost:before,
.icon-blackberry:before,
.icon-javascript:before,
.icon-ubuntu:before,
.icon-php-alt:before,
.icon-centos:before,
.icon-nodejs:before,
.icon-splatter:before,
.icon-3dprint:before,
.icon-line-graph:before,
.icon-cassandra:before,
.icon-solaris:before,
.icon-jetty:before,
.icon-tomcat:before,
.icon-oracle:before,
.icon-oracle-alt:before,
.icon-mssql:before,
.icon-google-developers:before,
.icon-google-code:before,
.icon-kde:before,
.icon-grails-alt:before {
font-family: "FontMfizz";
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
display: inline-block;
text-decoration: inherit;
}
.icon-microscope:before { content: "\f100"; }
.icon-cplusplus:before { content: "\f101"; }
.icon-wireless:before { content: "\f102"; }
.icon-fire-alt:before { content: "\f103"; }
.icon-mobile-device:before { content: "\f104"; }
.icon-objc:before { content: "\f105"; }
.icon-redhat:before { content: "\f106"; }
.icon-freebsd:before { content: "\f107"; }
.icon-heroku:before { content: "\f108"; }
.icon-python:before { content: "\f109"; }
.icon-java:before { content: "\f10a"; }
.icon-satellite:before { content: "\f10b"; }
.icon-debian:before { content: "\f10c"; }
.icon-grails:before { content: "\f10d"; }
.icon-c:before { content: "\f10e"; }
.icon-postgres:before { content: "\f10f"; }
.icon-database-alt2:before { content: "\f110"; }
.icon-raspberrypi:before { content: "\f111"; }
.icon-nginx:before { content: "\f112"; }
.icon-ruby-on-rails:before { content: "\f113"; }
.icon-redis:before { content: "\f114"; }
.icon-scala:before { content: "\f115"; }
.icon-gnome:before { content: "\f116"; }
.icon-perl:before { content: "\f117"; }
.icon-mysql:before { content: "\f118"; }
.icon-fedora:before { content: "\f119"; }
.icon-ghost:before { content: "\f11a"; }
.icon-google:before { content: "\f11b"; }
.icon-netbsd:before { content: "\f11c"; }
.icon-aws:before { content: "\f11d"; }
.icon-bomb:before { content: "\f11e"; }
.icon-looking:before { content: "\f11f"; }
.icon-ruby:before { content: "\f120"; }
.icon-mysql-alt:before { content: "\f121"; }
.icon-playframework-alt:before { content: "\f122"; }
.icon-osx:before { content: "\f123"; }
.icon-database:before { content: "\f124"; }
.icon-database-alt:before { content: "\f125"; }
.icon-shell:before { content: "\f126"; }
.icon-script:before { content: "\f127"; }
.icon-antenna:before { content: "\f128"; }
.icon-coffee-bean:before { content: "\f129"; }
.icon-scala-alt:before { content: "\f12a"; }
.icon-platter:before { content: "\f12b"; }
.icon-java-duke:before { content: "\f12c"; }
.icon-iphone:before { content: "\f12d"; }
.icon-script-alt:before { content: "\f12e"; }
.icon-google-alt:before { content: "\f12f"; }
.icon-haskell:before { content: "\f130"; }
.icon-mariadb:before { content: "\f131"; }
.icon-phone-retro:before { content: "\f132"; }
.icon-phone-alt:before { content: "\f133"; }
.icon-csharp:before { content: "\f134"; }
.icon-php:before { content: "\f135"; }
.icon-postgres-alt:before { content: "\f136"; }
.icon-html:before { content: "\f137"; }
.icon-mfizz:before { content: "\f138"; }
.icon-apache:before { content: "\f139"; }
.icon-hadoop:before { content: "\f13a"; }
.icon-ruby-on-rails-alt:before { content: "\f13b"; }
.icon-mobile-phone-broadcast:before { content: "\f13c"; }
.icon-css:before { content: "\f13d"; }
.icon-playframework:before { content: "\f13e"; }
.icon-clojure:before { content: "\f13f"; }
.icon-mobile-phone-alt:before { content: "\f140"; }
.icon-suse:before { content: "\f141"; }
.icon-java-bold:before { content: "\f142"; }
.icon-nginx-alt:before { content: "\f143"; }
.icon-nginx-alt2:before { content: "\f144"; }
.icon-linux-mint:before { content: "\f145"; }
.icon-dreamhost:before { content: "\f146"; }
.icon-blackberry:before { content: "\f147"; }
.icon-javascript:before { content: "\f148"; }
.icon-ubuntu:before { content: "\f149"; }
.icon-php-alt:before { content: "\f14a"; }
.icon-centos:before { content: "\f14b"; }
.icon-nodejs:before { content: "\f14c"; }
.icon-splatter:before { content: "\f14d"; }
.icon-3dprint:before { content: "\f14e"; }
.icon-line-graph:before { content: "\f14f"; }
.icon-cassandra:before { content: "\f150"; }
.icon-solaris:before { content: "\f151"; }
.icon-jetty:before { content: "\f152"; }
.icon-tomcat:before { content: "\f153"; }
.icon-oracle:before { content: "\f154"; }
.icon-oracle-alt:before { content: "\f155"; }
.icon-mssql:before { content: "\f156"; }
.icon-google-developers:before { content: "\f157"; }
.icon-google-code:before { content: "\f158"; }
.icon-kde:before { content: "\f159"; }
.icon-grails-alt:before { content: "\f15a"; }
/* These classes only added to fix FontFamily to display FontMfizz during debug/inspection */
.icon-osx,
.icon-bomb,
.icon-mobile-phone-broadcast,
.icon-objc,
.icon-nginx-alt2,
.icon-mysql,
.icon-phone-retro,
.icon-netbsd,
.icon-mobile-device,
.icon-ruby-on-rails,
.icon-phone-alt,
.icon-line-graph,
.icon-postgres,
.icon-playframework,
.icon-python,
.icon-ruby-on-rails-alt,
.icon-nginx,
.icon-database-alt2,
.icon-google-alt,
.icon-microscope,
.icon-blackberry,
.icon-dreamhost,
.icon-google,
.icon-centos,
.icon-kde,
.icon-csharp,
.icon-scala,
.icon-redis,
.icon-looking,
.icon-database-alt,
.icon-javascript,
.icon-postgres-alt,
.icon-linux-mint,
.icon-ubuntu,
.icon-apache,
.icon-script-alt,
.icon-mssql,
.icon-c,
.icon-gnome,
.icon-java-duke,
.icon-scala-alt,
.icon-clojure,
.icon-oracle-alt,
.icon-redhat,
.icon-haskell,
.icon-3dprint,
.icon-mariadb,
.icon-java,
.icon-script,
.icon-cplusplus,
.icon-jetty,
.icon-perl,
.icon-heroku,
.icon-nginx-alt,
.icon-iphone,
.icon-splatter,
.icon-shell,
.icon-mysql-alt,
.icon-wireless,
.icon-ruby,
.icon-playframework-alt,
.icon-raspberrypi,
.icon-suse,
.icon-nodejs,
.icon-java-bold,
.icon-google-developers,
.icon-mobile-phone-alt,
.icon-grails-alt,
.icon-coffee-bean,
.icon-cassandra,
.icon-google-code,
.icon-fedora,
.icon-antenna,
.icon-hadoop,
.icon-solaris,
.icon-html,
.icon-css,
.icon-satellite,
.icon-aws,
.icon-mfizz,
.icon-php,
.icon-debian,
.icon-ghost,
.icon-php-alt,
.icon-tomcat,
.icon-database,
.icon-grails,
.icon-freebsd,
.icon-oracle,
.icon-fire-alt,
.icon-platter{
font-family: "FontMfizz";
}

Binary file not shown.

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Binary file not shown.

View file

@ -11,6 +11,7 @@
<!-- Le styles --> <!-- Le styles -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet"> <link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet">
<link href="{% static 'fonts/font-mfizz.css' %}" rel="stylesheet">
<style> <style>
body { body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */

View file

@ -3,10 +3,49 @@
{% block title %}{{ block.super }} - {% blocktrans with full_name=dashboard_user.get_full_name %}Dashboard for {{ full_name }}{% endblocktrans %}{% endblock title %} {% block title %}{{ block.super }} - {% blocktrans with full_name=dashboard_user.get_full_name %}Dashboard for {{ full_name }}{% endblocktrans %}{% endblock title %}
{% block page_title %}{% blocktrans with full_name=dashboard_user.get_full_name %}Dashboard for {{ full_name }}{% endblocktrans %}{% endblock page_title %} {% block page_title %}{% blocktrans with full_name=dashboard_user.get_full_name %}Dashboard for {{ full_name }}{% endblocktrans %}{% endblock page_title %}
{% block content %} {% block content %}
<p>Contract details</p> <div class="row">
<ul> <div class="col-lg-12 col-md-12 col-xs-12">
<li>Domains</li> <div class="panel panel-default">
<li>Email mailboxes</li> <div class="panel-heading">{% trans "Hosting packages" %}</div>
<li>Email addresses</li> <div class="panel-body">
</ul> {% if hosting_packages %}
<table class="table">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Disk space" %}</th>
<th>{% trans "Mailboxes" %}</th>
<th>{% trans "Databases" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for package in hosting_packages %}
<tr>
<th><a href="{{ package.get_absolute_url }}" title="{% blocktrans with packagename=package.name %}Show details for {{ packagename }}{% endblocktrans %}">{{ package.name }}</a></th>
<th>
{% with diskspace=package.get_disk_space %}
<span title="{% blocktrans %}The reserved disk space for your hosting package is {{ diskspace }} bytes.{% endblocktrans %}">{{ diskspace|filesizeformat }}</span>
{% endwith %}
</th>
<th>{% blocktrans with num=package.get_used_mailboxes total=package.get_mailboxes %}used {{ num }} of {{ total }}{% endblocktrans %}</th>
<th>{% for dbtype in package.get_databases %}
{{ dbtype.number }} {% include "userdbs/snippets/db_type.html" with db_type=dbtype.db_type %}
{% if not forloop.last %} / {% endif %}
{% endfor %}</th>
<th></th>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-info">{% if user == object %}{% trans "You have no hosting packages yet." %}{% else %}{% trans "This user has no hosting packages assigned yet." %}{% endif %}</p>
{% endif %}
{% if user.is_staff %}
<a href="{% url "create_hosting_package" user=object.username %}" class="btn btn-primary">{% trans "Add hosting package" %}</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock content %} {% endblock content %}

View file

@ -0,0 +1 @@
{% extends "base.html" %}

View file

@ -0,0 +1,7 @@
{% extends "hostingpackages/base.html" %}
{% load i18n crispy_forms_tags %}
{% block title %}{{ block.super }} - {% blocktrans with full_name=customer.get_full_name %}Add hosting package for Customer {{ full_name }}{% endblocktrans %}{% endblock title %}
{% block page_title %}{% blocktrans with full_name=customer.get_full_name %}Add Hosting Package for Customer {{ full_name }}{% endblocktrans %}{% endblock page_title %}
{% block content %}
{% crispy form %}
{% endblock content %}

View file

@ -0,0 +1,48 @@
{% extends "hostingpackages/base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} - {% spaceless %}
{% if user == customer %}
{% blocktrans with package=hostingpackage.name %}Details for your Hosting Package {{ package }}{% endblocktrans %}
{% else %}
{% blocktrans with package=hostingpackage.name full_name=customer.get_full_name %}Details for Hosting Package {{ package }} of {{ full_name }}{% endblocktrans %}
{% endif %}
{% endspaceless %}{% endblock title %}
{% block page_title %}{% blocktrans with package=hostingpackage.name %}Details of Hosting Package {{ package }}{% endblocktrans %}{% endblock page_title %}
{% block content %}
<div class="row">
<div class="col-lg-6 col-md-6 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Hosting Package Information" %}<div class="pull-right"><a class="panel-title" href="#" title="{% trans "Edit Hosting Package Information" %}"><i class="glyphicon glyphicon-cog"></i></a></div>
</div>
<dl class="panel-body dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ hostingpackage.name }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ hostingpackage.description|default:"-" }}</dd>
<dt>{% trans "Disk space" %}</dt>
{% with diskspace=hostingpackage.get_disk_space %}
<dd title="{% blocktrans %}The reserved disk space for your hosting package is {{ diskspace }} bytes.{% endblocktrans %}">{{ diskspace|filesizeformat }}</dd>
{% endwith %}
<dt>{% trans "Mailboxes" %}</dt>
<dd>{% blocktrans with num=hostingpackage.get_used_mailboxes total=hostingpackage.get_mailboxes %}{{ num }} of {{ total }} in use{% endblocktrans %}</dd>
<dt>{% if hostingpackage.osuser.is_sftp_user %}{% trans "SFTP username" %}{% else %}{% trans "SSH/SFTP username" %}{% endif %}</dt>
<dd>{{ hostingpackage.osuser.username }}</dd>
<dt>{% trans "Upload server" %}</dt>
<dd>{{ uploadserver }}</dd>
</dl>
</div>
</div>
<div class="col-lg-6 col-md-6 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">{% trans "Hosting Package Actions" %}</div>
<ul class="list-group">
<li class="list-group-item"><a href="{% url "set_osuser_password" slug=hostingpackage.osuser.username %}">{% if hostingpackage.osuser.is_sftp %}{% trans "Set SFTP password" %}{% else %}{% trans "Set SSH/SFTP password" %}{% endif %}</a></li>
</ul>
</div>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1 @@
{% extends "base.html" %}

View file

@ -0,0 +1,30 @@
{% extends "osusers/base.html" %}
{% load i18n crispy_forms_tags %}
{% block title %}{{ block.super }} - {% spaceless %}
{% if customer == user %}
{% blocktrans with osuser=osuser.username %}Set new password for user {{ osuser }}{% endblocktrans %}
{% else %}
{% blocktrans with osuser=osuser.username full_name=customer.get_full_name %}Set new password for user {{ osuser }} of customer {{ full_name }}{% endblocktrans %}
{% endif %}
{% endspaceless %}{% endblock title %}
{% block page_title %}{% spaceless %}
{% if customer == user %}
{% blocktrans with osuser=osuser.username %}Set new password for user {{ osuser }}{% endblocktrans %}
{% else %}
{% blocktrans with osuser=osuser.username full_name=customer.get_full_name %}Set new password for user {{ osuser }} of customer {{ full_name }}{% endblocktrans %}
{% endif %}
{% endspaceless %}{% endblock page_title %}
{% block content %}
{% crispy form %}
{% endblock content %}
{% block extra_js %}
<script type="text/javascript">
$(document).ready(function() {
$('input[type=password]').val('');
$('input[type=password]').first().focus();
});
</script>
{% endblock extra_js %}

View file

@ -0,0 +1,3 @@
{# format database types #}
{% load userdb %}
<i class="{% db_type_icon_class %}" title="{% db_type_name %}"></i><span class="sr-only">{% db_type_name %}</span>

View file

@ -0,0 +1,4 @@
"""
This module provides custom template tags for user databases.
"""

View file

@ -0,0 +1,55 @@
"""
This is the template tag library for user databases.
"""
from django import template
from django.utils.translation import gettext_lazy as _
from userdbs.models import DB_TYPES
register = template.Library()
_TYPE_NAME_MAP = {
DB_TYPES.mysql: 'mysql',
DB_TYPES.pgsql: 'postgres',
}
def db_type_icon_class(context):
"""
This template tag derives the matching icon name for the numeric database
type stored in the context variable db_type.
The icon names used are those of `Font Mfizz
<http://mfizz.com/oss/font-mfizz>`_.
:param context: the template context
:return: icon name
:rtype: str
"""
db_type = context['db_type']
if db_type in _TYPE_NAME_MAP:
return 'icon-' + _TYPE_NAME_MAP[db_type]
return 'icon-database'
register.simple_tag(db_type_icon_class, takes_context=True)
def db_type_name(context):
"""
This template tag gets the human readable database type for the numeric
database type stored in the context variable db_type.
:param context: the template context
:return: human readable database type name
:rtype: str
"""
db_type = context['db_type']
return _(DB_TYPES[db_type])
register.simple_tag(db_type_name, takes_context=True)

View file

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.