Merge branch 'release/0.9.0' into production

* release/0.9.0:
  add release version in changelog, update conf.py
  add changelog entry
  mark bugs as major to include them in 0.7.0 changelog
  update translations, add new strings
  add code documentation for websites app
  implement websites.models.Website.delete
  implement website.models.Website.save
  implement domain name validation
  implement website deletion
  link from hostingpackage detail view to 'add_website'
  implement adding websites
  define User.is_sftp_user and fix minor template issues
  make manage.py executable
  add wildcard parameter to create_web_vhost_config task
  add django generated websites app
  add webtasks interface
  update to fileservertasks interface 0.4.0 version
  add new route 'web' for web server configuration
This commit is contained in:
Jan Dittberner 2015-01-27 19:13:59 +01:00
commit e718c472e8
38 changed files with 835 additions and 89 deletions

View file

@ -1,6 +1,16 @@
Changelog
=========
* :release:`0.9.0 <2015-01-27>`
* :feature:`-` setup nginx virtual host and PHP configuration for websites
(requires gvaweb >= 0.1.0 on web server)
* :support:`-` implement domain name validation
* :feature:`-` implement deletion of websites
* :feature:`-` implement setup of websites
* :support:`-` add webtasks interface
* :support:`-` update to new fileservertasks interface (requires gvafile >=
0.4.0 on the fileserver)
* :release:`0.8.0 <2015-01-26>`
* :feature:`-` implement deletion of user database and database users
* :feature:`-` implement password changes for database users
@ -14,9 +24,9 @@ Changelog
* :feature:`-` implement mail address deletion
* :feature:`-` implement adding mail address to mail domains
* :feature:`-` implement adding options to hosting packages
* :bug:`-` fix disk space calculation in
* :bug:`- major` fix disk space calculation in
hostingpackages.models.CustomerHostingPackage
* :bug:`-` fix unique constraints on
* :bug:`- major` fix unique constraints on
hostingpackages.models.CustomerDiskSpaceOption and
hostingpackages.models.CustomerDatabaseOption
* :feature:`-` implement password change functionality for mailboxes

View file

@ -27,6 +27,7 @@ Celery task stubs
code/ldaptasks
code/mysqltasks
code/pgsqltasks
code/webtasks
Django app code
@ -42,3 +43,4 @@ Django app code
code/osusers
code/taskresults
code/userdbs
code/websites

View file

@ -40,7 +40,7 @@
:py:mod:`views <domains.views>`
---------------------------------
-------------------------------
.. automodule:: domains.views
:members:

46
docs/code/websites.rst Normal file
View file

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

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

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

View file

@ -60,9 +60,9 @@ copyright = u'2014, 2015 Jan Dittberner'
# built documents.
#
# The short X.Y version.
version = '0.8'
version = '0.9'
# The full version, including alpha/beta/rc tags.
release = '0.8.0'
release = '0.9.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -4,6 +4,8 @@ This module defines form classes for domain editing.
"""
from __future__ import absolute_import, unicode_literals
import re
from django import forms
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
@ -17,6 +19,18 @@ from crispy_forms.layout import (
from .models import HostingDomain
def relative_domain_validator(value):
"""
This validator ensures that the given value is a valid lowercase
"""
if len(value) > 254:
raise forms.ValidationError(
_('host name too long'), code='too-long')
allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$")
if not all(allowed.match(x) for x in value.split('.')):
raise forms.ValidationError(_('invalid domain name'))
class CreateHostingDomainForm(forms.ModelForm):
"""
This form is used to create new HostingDomain instances.
@ -29,6 +43,7 @@ class CreateHostingDomainForm(forms.ModelForm):
def __init__(self, instance, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
super(CreateHostingDomainForm, self).__init__(*args, **kwargs)
self.fields['domain'].validators.append(relative_domain_validator)
self.helper = FormHelper()
self.helper.form_action = reverse(
'create_hosting_domain', kwargs={

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gnuviechadmin domains\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-25 00:46+0100\n"
"PO-Revision-Date: 2015-01-25 00:56+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-27 19:06+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
"Language: de\n"
@ -23,7 +23,15 @@ msgstr ""
msgid "Domains"
msgstr "Domains"
#: domains/forms.py:39
#: domains/forms.py:28
msgid "host name too long"
msgstr "zu langer Hostname"
#: domains/forms.py:31
msgid "invalid domain name"
msgstr "ungültiger Domainname"
#: domains/forms.py:54
msgid "Add Hosting Domain"
msgstr "Hostingdomain hinzufügen"

View file

@ -92,3 +92,29 @@ def delete_file_mailbox(username, mailboxname):
:rtype: str
"""
@shared_task
def create_file_website_hierarchy(username, sitename):
"""
This task creates the directory hierarchy for a website.
:param str username: the user name
:param str sitename: name of the website
:return: the directory name
:rtype: str
"""
@shared_task
def delete_file_website_hierarchy(username, sitename):
"""
This task deletes the website hierarchy recursively.
:param str username: the user name
:param str sitename: name of the website
:return: the directory name
:rtype: str
"""

View file

@ -258,11 +258,14 @@ LOCAL_APPS = (
'taskresults',
'mysqltasks',
'pgsqltasks',
'fileservertasks',
'webtasks',
'domains',
'osusers',
'managemails',
'userdbs',
'hostingpackages',
'websites',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps

View file

@ -13,6 +13,7 @@ urlpatterns = patterns(
url(r'^database/', include('userdbs.urls')),
url(r'^domains/', include('domains.urls')),
url(r'^hosting/', include('hostingpackages.urls')),
url(r'^website/', include('websites.urls')),
url(r'^mail/', include('managemails.urls')),
url(r'^osuser/', include('osusers.urls')),
url(r'^admin/', include(admin.site.urls)),

View file

@ -5,7 +5,7 @@ from __future__ import unicode_literals
class GvaRouter(object):
def route_for_task(self, task, args=None, kwargs=None):
for route in ['ldap', 'file', 'mysql', 'pgsql']:
for route in ['ldap', 'file', 'mysql', 'pgsql', 'web']:
if route in task:
return {
'exchange': route,

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gvacommon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-24 17:40+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-24 18:25+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gvawebcore\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-25 11:49+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-25 11:49+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
@ -19,14 +19,14 @@ msgstr ""
"X-Generator: Poedit 1.6.10\n"
"X-Poedit-SourceCharset: UTF-8\n"
#: forms.py:12
#: gvawebcore/forms.py:12
msgid "Passwords don't match"
msgstr "Passwörter stimmen nicht überein"
#: forms.py:25
#: gvawebcore/forms.py:25
msgid "Password"
msgstr "Passwort: "
#: forms.py:28
#: gvawebcore/forms.py:28
msgid "Password (again)"
msgstr "Passwortwiederholung"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gnuviechadmin hostingpackages\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-25 15:46+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-25 15:49+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
@ -215,19 +215,19 @@ msgstr "Die Postfachoptionsvorlage auf der diese Postfachoption aufgebaut ist"
msgid "Started setup of new hosting package {name}."
msgstr "Einrichtung des Hostingpakets {name} wurde gestartet."
#: hostingpackages/views.py:146
#: hostingpackages/views.py:152
msgid "Disk space"
msgstr "Speicherplatz"
#: hostingpackages/views.py:149
#: hostingpackages/views.py:155
msgid "Mailboxes"
msgstr "Postfächer"
#: hostingpackages/views.py:152
#: hostingpackages/views.py:158
msgid "Databases"
msgstr "Datenbanken"
#: hostingpackages/views.py:222
#: hostingpackages/views.py:228
#, python-brace-format
msgid "Successfully added option {option} to hosting package {package}."
msgstr "Option {option} erfolgreich zum Hostingpaket {package} hinzugefügt."

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gnuviechadmin\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-26 13:42+0100\n"
"PO-Revision-Date: 2015-01-26 13:51+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-27 19:06+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
"Language: de\n"
@ -524,14 +524,14 @@ msgid "Mailboxes"
msgstr "Postfächer"
#: templates/dashboard/user_dashboard.html:18
#: templates/hostingpackages/customerhostingpackage_detail.html:163
#: templates/hostingpackages/customerhostingpackage_detail.html:176
msgid "Databases"
msgstr "Datenbanken"
#: templates/dashboard/user_dashboard.html:19
#: templates/hostingpackages/customerhostingpackage_detail.html:86
#: templates/hostingpackages/customerhostingpackage_detail.html:136
#: templates/hostingpackages/customerhostingpackage_detail.html:171
#: templates/hostingpackages/customerhostingpackage_detail.html:149
#: templates/hostingpackages/customerhostingpackage_detail.html:184
msgid "Actions"
msgstr "Aktionen"
@ -715,7 +715,7 @@ msgid "Domain name"
msgstr "Domainname"
#: templates/hostingpackages/customerhostingpackage_detail.html:84
#: templates/hostingpackages/customerhostingpackage_detail.html:134
#: templates/hostingpackages/customerhostingpackage_detail.html:147
msgid "Mail addresses"
msgstr "E-Mailadressen"
@ -735,88 +735,97 @@ msgstr "E-Mailadressziele bearbeiten"
msgid "Delete mail address"
msgstr "E-Mailadresse löschen"
#: templates/hostingpackages/customerhostingpackage_detail.html:104
#: templates/hostingpackages/customerhostingpackage_detail.html:103
#: templates/hostingpackages/customerhostingpackage_detail.html:115
msgid "None"
msgstr "Keine"
#: templates/hostingpackages/customerhostingpackage_detail.html:109
#: templates/hostingpackages/customerhostingpackage_detail.html:110
msgid "Delete website"
msgstr "Webauftritt löschen"
#: templates/hostingpackages/customerhostingpackage_detail.html:119
msgid "Add mail address"
msgstr "E-Mailadresse hinzufügen"
#: templates/hostingpackages/customerhostingpackage_detail.html:117
#: templates/hostingpackages/customerhostingpackage_detail.html:122
msgid "Add website"
msgstr "Webauftritt anlegen"
#: templates/hostingpackages/customerhostingpackage_detail.html:130
msgid "There are no domains assigned to this hosting package yet."
msgstr "Diesem Paket sind noch keine Domains zugeordnet."
#: templates/hostingpackages/customerhostingpackage_detail.html:120
#: templates/hostingpackages/customerhostingpackage_detail.html:133
msgid "Add domain"
msgstr "Domain hinzufügen"
#: templates/hostingpackages/customerhostingpackage_detail.html:128
#: templates/hostingpackages/customerhostingpackage_detail.html:141
msgid "E-Mail-Accounts"
msgstr "E-Mailkonten"
#: templates/hostingpackages/customerhostingpackage_detail.html:133
#: templates/hostingpackages/customerhostingpackage_detail.html:146
msgid "Mailbox"
msgstr "Postfach"
#: templates/hostingpackages/customerhostingpackage_detail.html:135
#: templates/hostingpackages/customerhostingpackage_detail.html:144
#: templates/hostingpackages/customerhostingpackage_detail.html:148
#: templates/hostingpackages/customerhostingpackage_detail.html:157
msgid "Active"
msgstr "Aktiv"
#: templates/hostingpackages/customerhostingpackage_detail.html:136
#: templates/hostingpackages/customerhostingpackage_detail.html:149
msgid "Mailbox actions"
msgstr "Postfachaktionen"
#: templates/hostingpackages/customerhostingpackage_detail.html:144
#: templates/hostingpackages/customerhostingpackage_detail.html:157
msgid "inactive"
msgstr "inaktiv"
#: templates/hostingpackages/customerhostingpackage_detail.html:146
#: templates/hostingpackages/customerhostingpackage_detail.html:159
msgid "Set mailbox password"
msgstr "Postfachpasswort setzen"
#: templates/hostingpackages/customerhostingpackage_detail.html:152
#: templates/hostingpackages/customerhostingpackage_detail.html:165
msgid "There are no mailboxes assigned to this hosting package yet."
msgstr "Diesem Hostingpaket sind noch keine Postfächer zugeordnet."
#: templates/hostingpackages/customerhostingpackage_detail.html:155
#: templates/hostingpackages/customerhostingpackage_detail.html:168
msgid "Add mailbox"
msgstr "Postfach hinzufügen"
#: templates/hostingpackages/customerhostingpackage_detail.html:168
#: templates/hostingpackages/customerhostingpackage_detail.html:181
msgid "Database name"
msgstr "Datenbankname"
#: templates/hostingpackages/customerhostingpackage_detail.html:169
#: templates/hostingpackages/customerhostingpackage_detail.html:182
msgid "Database user"
msgstr "Datenbanknutzer"
#: templates/hostingpackages/customerhostingpackage_detail.html:170
#: templates/hostingpackages/customerhostingpackage_detail.html:183
msgid "Database type"
msgstr "Datenbanktyp"
#: templates/hostingpackages/customerhostingpackage_detail.html:170
#: templates/hostingpackages/customerhostingpackage_detail.html:183
msgid "Type"
msgstr "Typ"
#: templates/hostingpackages/customerhostingpackage_detail.html:171
#: templates/hostingpackages/customerhostingpackage_detail.html:184
msgid "Database actions"
msgstr "Datenbankaktionen"
#: templates/hostingpackages/customerhostingpackage_detail.html:181
#: templates/hostingpackages/customerhostingpackage_detail.html:194
msgid "Set database user password"
msgstr "Datenbanknutzerpasswort setzen"
#: templates/hostingpackages/customerhostingpackage_detail.html:182
#: templates/hostingpackages/customerhostingpackage_detail.html:195
msgid "Delete database"
msgstr "Datenbank löschen"
#: templates/hostingpackages/customerhostingpackage_detail.html:189
#: templates/hostingpackages/customerhostingpackage_detail.html:202
msgid "There are no databases assigned to this hosting package yet."
msgstr "Diesem Hostingpaket sind noch keine Datenbanken zugeordnet."
#: templates/hostingpackages/customerhostingpackage_detail.html:192
#: templates/hostingpackages/customerhostingpackage_detail.html:205
msgid "Add database"
msgstr "Datenbank hinzufügen"
@ -848,11 +857,13 @@ msgstr "Wollen Sie die E-Mailadresse %(mailaddress)s wirklich löschen?"
#: templates/managemails/mailaddress_confirm_delete.html:28
#: templates/userdbs/userdatabase_confirm_delete.html:29
#: templates/websites/website_confirm_delete.html:29
msgid "Yes, do it!"
msgstr "Ja, so soll es sein!"
#: templates/managemails/mailaddress_confirm_delete.html:29
#: templates/userdbs/userdatabase_confirm_delete.html:30
#: templates/websites/website_confirm_delete.html:30
msgid "Cancel"
msgstr "Abbrechen"
@ -1118,6 +1129,71 @@ msgstr ""
"Bitte geben Sie ein Passwort für den neuen Datenbanknutzer für Ihre "
"Datenbank ein."
#: templates/websites/website_confirm_delete.html:6
#, python-format
msgid "Delete Website %(website)s"
msgstr "Webauftritt %(website)s löschen"
#: templates/websites/website_confirm_delete.html:8
#, python-format
msgid "Delete Website %(website)s of Customer %(full_name)s"
msgstr "Webauftritt %(website)s des Kunden %(full_name)s löschen"
#: templates/websites/website_confirm_delete.html:14
#, python-format
msgid "Delete Website <small>%(website)s</small>"
msgstr "Webauftritt löschen <small>%(website)s</small>"
#: templates/websites/website_confirm_delete.html:16
#, python-format
msgid "Delete Website <small>%(website)s of Customer %(full_name)s</small>"
msgstr ""
"Webauftritt löschen <small>%(website)s des Kunden %(full_name)s</small>"
#: templates/websites/website_confirm_delete.html:23
#, python-format
msgid "Do you really want to delete the website %(website)s?"
msgstr "Wollen Sie den Webauftritt %(website)s wirklich löschen?"
#: templates/websites/website_confirm_delete.html:26
msgid ""
"Please be aware that the website directory is removed from the webserver and "
"the webserver configuration is changed so that the website will not be "
"reachable anymore. <strong>All data in the website directory will be lost!</"
"strong>"
msgstr ""
"Bitte beachten Sie, dass das Verzeichnis des Webauftritts vom Webserver "
"gelöscht und die Konfiguration so angepasst wird, dass der Webauftritt nicht "
"mehr erreichbar sein wird. <strong>Alle Daten im Verzeichnis des "
"Webauftritts werden verloren gehen!</strong>"
#: templates/websites/website_create.html:6
#, python-format
msgid "Add Website for Subdomain of %(domain)s"
msgstr "Webauftritt für Subdomain von %(domain)s anlegen"
#: templates/websites/website_create.html:8
#, python-format
msgid ""
"Add Website for Subdomain of Domain %(domain)s of Customer %(full_name)s"
msgstr ""
"Webauftritt für Subdomain der Domain %(domain)s des Kunden %(full_name)s "
"anlegen"
#: templates/websites/website_create.html:14
#, python-format
msgid "Add Website <small>for Subdomain of %(domain)s</small>"
msgstr "Website anlegen <small>für Subdomain von %(domain)s</small>"
#: templates/websites/website_create.html:16
#, python-format
msgid ""
"Add Website <small>for Subdomain of Domain %(domain)s of Customer "
"%(full_name)s</small>"
msgstr ""
"Webauftritt anlegen <small>für Subdomain der Domain %(domain)s des Kunden "
"%(full_name)s</small>"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password (again)"

0
gnuviechadmin/manage.py Normal file → Executable file
View file

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: managemails\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-25 22:13+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-25 22:17+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
@ -124,27 +124,27 @@ msgstr "E-Mailadresse"
msgid "mailbox"
msgstr "Postfach"
#: managemails/views.py:71
#: managemails/views.py:51
msgid "You are not allowed to add more mailboxes to this hosting package"
msgstr "Sie können keine weiteren Postfächer zu diesem Hostingpaket hinzufügen"
#: managemails/views.py:90
#: managemails/views.py:70
#, python-brace-format
msgid "Mailbox {mailbox} created successfully."
msgstr "Postfach {mailbox} erfolgreich angelegt."
#: managemails/views.py:125
#: managemails/views.py:105
#, python-brace-format
msgid "Successfully set new password for mailbox {mailbox}."
msgstr ""
"Für das Postfach {mailbox} wurde erfolgreich ein neues Passwort gesetzt."
#: managemails/views.py:164
#: managemails/views.py:144
#, python-brace-format
msgid "Successfully added mail address {mailaddress}"
msgstr "E-Mailadresse {mailaddress} erfolgreich hinzugefügt"
#: managemails/views.py:242
#: managemails/views.py:222
#, python-brace-format
msgid "Successfully updated mail address {mailaddress} targets."
msgstr "Ziele der E-Mailadresse {mailaddress} erfolgreich aktualisiert."

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: osusers\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-24 17:40+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-24 18:25+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"
@ -19,11 +19,11 @@ msgstr ""
"X-Generator: Poedit 1.6.10\n"
"X-Poedit-SourceCharset: UTF-8\n"
#: osusers/admin.py:45 osusers/forms.py:28
#: osusers/admin.py:45
msgid "Password"
msgstr "Passwort"
#: osusers/admin.py:49 osusers/forms.py:32
#: osusers/admin.py:49
msgid "Password (again)"
msgstr "Passwortwiederholung"
@ -39,11 +39,7 @@ msgstr "Ausgewählte Gruppen löschen"
msgid "Operating System Users and Groups"
msgstr "Betriebssystemnutzer- und Gruppen"
#: osusers/forms.py:16
msgid "Passwords don't match"
msgstr "Passwörter stimmen nicht überein"
#: osusers/forms.py:45
#: osusers/forms.py:33
msgid "Set password"
msgstr "Passwort setzen"
@ -95,7 +91,7 @@ msgstr "Home-Verzeichnis"
msgid "Login shell"
msgstr "Loginshell"
#: osusers/models.py:230 osusers/models.py:366
#: osusers/models.py:230 osusers/models.py:370
msgid "User"
msgstr "Nutzer"
@ -103,48 +99,48 @@ msgstr "Nutzer"
msgid "Users"
msgstr "Nutzer"
#: osusers/models.py:367
#: osusers/models.py:371
msgid "Encrypted password"
msgstr "Verschlüsseltes Passwort"
#: osusers/models.py:369
#: osusers/models.py:373
msgid "Date of last change"
msgstr "Datum der letzten Änderung"
#: osusers/models.py:370
#: osusers/models.py:374
msgid "This is expressed in days since Jan 1, 1970"
msgstr "Ausgedrückt als Tage seit dem 1. Januar 1970"
#: osusers/models.py:373
#: osusers/models.py:377
msgid "Minimum age"
msgstr "Minimales Alter"
#: osusers/models.py:374
#: osusers/models.py:378
msgid "Minimum number of days before the password can be changed"
msgstr "Minmale Anzahl von Tagen bevor das Passwort geändert werden kann"
#: osusers/models.py:378
#: osusers/models.py:382
msgid "Maximum age"
msgstr "Maximales Alter"
#: osusers/models.py:379
#: osusers/models.py:383
msgid "Maximum number of days after which the password has to be changed"
msgstr ""
"Maximale Anzahl von Tagen, nach denen das Passwort geändert werden muss"
#: osusers/models.py:383
#: osusers/models.py:387
msgid "Grace period"
msgstr "Duldungsperiode"
#: osusers/models.py:384
#: osusers/models.py:388
msgid "The number of days before the password is going to expire"
msgstr "Anzahl von Tagen nach denen das Passwort verfällt"
#: osusers/models.py:388
#: osusers/models.py:392
msgid "Inactivity period"
msgstr "Inaktivitätsperiode"
#: osusers/models.py:389
#: osusers/models.py:393
msgid ""
"The number of days after the password has expired during which the password "
"should still be accepted"
@ -152,29 +148,29 @@ msgstr ""
"Die Anzahl von Tagen für die ein verfallenes Passwort noch akzeptiert werden "
"soll"
#: osusers/models.py:393
#: osusers/models.py:397
msgid "Account expiration date"
msgstr "Kontoverfallsdatum"
#: osusers/models.py:394
#: osusers/models.py:398
msgid ""
"The date of expiration of the account, expressed as number of days since Jan "
"1, 1970"
msgstr "Kontoverfallsdatum in Tagen seit dem 1. Januar 1970"
#: osusers/models.py:401
#: osusers/models.py:405
msgid "Shadow password"
msgstr "Shadow-Passwort"
#: osusers/models.py:402
#: osusers/models.py:406
msgid "Shadow passwords"
msgstr "Shadow-Passwörter"
#: osusers/models.py:428
#: osusers/models.py:432
msgid "Additional group"
msgstr "Weitere Gruppe"
#: osusers/models.py:429
#: osusers/models.py:433
msgid "Additional groups"
msgstr "Weitere Gruppen"
@ -182,3 +178,6 @@ msgstr "Weitere Gruppen"
#, python-brace-format
msgid "New password for {username} has been set successfully."
msgstr "Für {username} wurde erfolgreich ein neues Passwort gesetzt."
#~ msgid "Passwords don't match"
#~ msgstr "Passwörter stimmen nicht überein"

View file

@ -266,6 +266,10 @@ class User(TimeStampedModel, models.Model):
_LOGGER.info("set LDAP password for %s", dn)
return True
def is_sftp_user(self):
return self.additionalgroup_set.filter(
group__groupname=settings.OSUSER_SFTP_GROUP
).exists()
@transaction.atomic
def save(self, *args, **kwargs):

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gnuviechadmin taskresults\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-17 15:59+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-17 16:00+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"

View file

@ -37,7 +37,7 @@
{% endwith %}
<dt>{% trans "Mailboxes" %}</dt>
<dd>{% blocktrans with num=hostingpackage.used_mailbox_count total=hostingpackage.mailbox_count %}{{ num }} of {{ total }} in use{% endblocktrans %} <span class="glyphicon
glyphicon-info-sign" title="{% blocktrans with mailboxes=hostingpackage.mailboxcount %}The package provides {{ mailboxcount }} mailboxes the difference comes from mailbox options.{% endblocktrans %}"></span></dd>
glyphicon-info-sign" title="{% blocktrans with mailboxcount=hostingpackage.mailboxcount %}The package provides {{ mailboxcount }} mailboxes the difference comes from mailbox options.{% endblocktrans %}"></span></dd>
<dt>{% if osuser.is_sftp_user %}{% trans "SFTP username" %}{% else %}{% trans "SSH/SFTP username" %}{% endif %}</dt>
<dd>{{ osuser.username }}</dd>
<dt>{% trans "Upload server" %}</dt>
@ -67,7 +67,7 @@
<div class="panel-heading">{% trans "Hosting Package Actions" %}</div>
<ul class="list-group">
<li class="list-group-item"><a href="#" title="{% trans "Edit Hosting Package Description" %}">{% trans "Edit description" %}</a></li>
<li class="list-group-item"><a href="{% url "set_osuser_password" slug=osuser.username %}">{% if osuser.is_sftp %}{% trans "Set SFTP password" %}{% else %}{% trans "Set SSH/SFTP password" %}{% endif %}</a></li>
<li class="list-group-item"><a href="{% url "set_osuser_password" slug=osuser.username %}">{% if osuser.is_sftp_user %}{% trans "Set SFTP password" %}{% else %}{% trans "Set SSH/SFTP password" %}{% endif %}</a></li>
</ul>
</div>
</div>
@ -90,23 +90,36 @@
{% for domain in domains %}
<tr>
<td>{{ domain.domain }}</td>
{% if domain.domain.maildomain %}
{% if domain.domain.maildomain.mailaddress_set.exists %}
<td>
{% with maildomain=domain.domain.maildomain %}
{% for mailaddress in maildomain.mailaddresses %}{% spaceless %}
<a href="{% url 'edit_mailaddress' package=hostingpackage.id domain=maildomain.domain pk=mailaddress.id %}" title="{% trans "Edit mail address targets" %}">{{ mailaddress }}</a>
<a href="{% url 'delete_mailaddress' package=hostingpackage.id domain=maildomain.domain pk=mailaddress.id %}" title="{% trans "Delete mail address" %}"><i class="glyphicon glyphicon-trash"></i></a>
{% endspaceless %}{% if not forloop.last %}, {% endif %}
{% endfor %}
<a href="{% url 'delete_mailaddress' package=hostingpackage.id domain=maildomain.domain pk=mailaddress.id %}" title="{% trans "Delete mail address" %}"><i class="glyphicon glyphicon-trash"></i><span class="sr-only"> {% trans "Delete mail address" %}</span></a>
{% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endwith %}
</td>
{% else %}
<td class="text-info">{% trans "None" %}</td>
{% endif %}
{% if domain.domain.website_set.exists %}
<td>
{% with domain=domain.domain %}
{% for website in domain.website_set.all %}{% spaceless %}
{{ website }}
<a href="{% url 'delete_website' package=hostingpackage.id domain=domain.domain pk=website.id %}" titel="{% trans "Delete website" %}"><i class="glyphicon glyphicon-trash"></i><span class="sr-only"> {% trans "Delete website" %}</span></a>
{% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endwith %}
</td>
{% else %}
<td class="text-info">{% trans "None" %}</td>
{% endif %}
<td></td>
<td>
{% with maildomain=domain.domain.maildomain %}
<a href="{% url 'add_mailaddress' package=hostingpackage.id domain=maildomain.domain %}" title="{% trans "Add mail address" %}"><i class="fa fa-envelope"></i></a>
<a href="{% url 'add_mailaddress' package=hostingpackage.id domain=maildomain.domain %}" title="{% trans "Add mail address" %}"><i class="fa fa-envelope"></i><span class="sr-only"> {% trans "Add mail address" %}</span></a>
{% endwith %}
{% with hostingdomain=domain.domain %}
<a href="{% url 'add_website' package=hostingpackage.id domain=hostingdomain.domain %}" title="{% trans "Add website" %}"><i class="glyphicon glyphicon-globe"></i><span class="sr-only"> {% trans "Add website" %}</span></a>
{% endwith %}
</td>
</tr>

View file

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

View file

@ -0,0 +1,34 @@
{% extends "websites/base.html" %}
{% load i18n %}
{% block title %}{{ block.super }} - {% spaceless %}
{% if user == customer %}
{% blocktrans %}Delete Website {{ website }}{% endblocktrans %}
{% else %}
{% blocktrans with full_name=customer.get_full_name %}Delete Website {{ website }} of Customer {{ full_name }}{% endblocktrans %}
{% endif %}
{% endspaceless %}{% endblock title %}
{% block page_title %}{% spaceless %}
{% if user == customer %}
{% blocktrans %}Delete Website <small>{{ website }}</small>{% endblocktrans %}
{% else %}
{% blocktrans with full_name=customer.get_full_name %}Delete Website <small>{{ website }} of Customer {{ full_name }}</small>{% endblocktrans %}
{% endif %}
{% endspaceless %}{% endblock page_title %}
{% block content %}
<div class="panel panel-warning">
<div class="panel-heading">
{% blocktrans %}Do you really want to delete the website {{ website }}?{% endblocktrans %}
</div>
<div class="panel-body">
<p>{% blocktrans %}Please be aware that the website directory is removed from the webserver and the webserver configuration is changed so that the website will not be reachable anymore. <strong>All data in the website directory will be lost!</strong>{% endblocktrans %}</p>
<form action="{% url 'delete_website' package=hostingpackage.id domain=website.domain.domain pk=website.id %}" method="post" class="form">
{% csrf_token %}
<input class="btn btn-warning" type="submit" value="{% trans "Yes, do it!" %}" />
<a class="btn btn-default" href="{{ hostingpackage.get_absolute_url }}">{% trans "Cancel" %}</a>
</form>
</div>
</div>
{% endblock content %}

View file

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

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gnuviechadmin userdbs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-26 13:42+0100\n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-26 13:44+0100\n"
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
"Language-Team: Jan Dittberner <jan@dittberner.info>\n"

View file

@ -0,0 +1,5 @@
"""
This app takes care of websites.
"""
default_app_config = 'websites.apps.WebsitesAppConfig'

View file

@ -0,0 +1,12 @@
"""
Admin site for websites.
"""
from __future__ import absolute_import
from django.contrib import admin
from .models import Website
admin.site.register(Website)

View file

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

View file

@ -0,0 +1,68 @@
"""
This module defines form classes for website editing.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from crispy_forms.bootstrap import AppendedText
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from domains.forms import relative_domain_validator
from .models import Website
class AddWebsiteForm(forms.ModelForm):
"""
This form is used to create new Website instances.
"""
class Meta:
model = Website
fields = ['subdomain', 'wildcard']
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.hosting_domain = kwargs.pop('domain')
super(AddWebsiteForm, self).__init__(*args, **kwargs)
self.fields['subdomain'].validators.append(relative_domain_validator)
if Website.objects.filter(
wildcard=True, domain=self.hosting_domain
).exists():
self.fields['wildcard'].widget = forms.HiddenInput()
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_website', kwargs={
'package': self.hosting_package.id,
'domain': self.hosting_domain.domain,
}
)
self.helper.layout = Layout(
AppendedText('subdomain', '.' + self.hosting_domain.domain),
'wildcard',
Submit('submit', _('Add website')),
)
def clean_subdomain(self):
data = self.cleaned_data['subdomain']
if Website.objects.filter(
domain=self.hosting_domain, subdomain=data
).exists():
raise forms.ValidationError(
_('There is already a website for this subdomain'))
relative_domain_validator(
"{0}.{1}".format(data, self.hosting_domain.domain))
return data
def save(self, commit=True):
self.instance.domain = self.hosting_domain
self.instance.osuser = self.hosting_package.osuser
return super(AddWebsiteForm, self).save(commit)

View file

@ -0,0 +1,61 @@
# 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: websites gnuviechadmin app\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-27 18:55+0100\n"
"PO-Revision-Date: 2015-01-27 19:00+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"
#: websites/apps.py:17
msgid "Websites"
msgstr "Webauftritte"
#: websites/forms.py:51
msgid "Add website"
msgstr "Webauftritt anlegen"
#: websites/forms.py:60
msgid "There is already a website for this subdomain"
msgstr "Es gibt bereits einen Webauftritt mit dieser Subdomain"
#: websites/models.py:35
msgid "sub domain"
msgstr "Subdomain"
#: websites/models.py:37
msgid "operating system user"
msgstr "Betriebssystemnutzer"
#: websites/models.py:39
msgid "domain"
msgstr "Domain"
#: websites/models.py:40
msgid "wildcard"
msgstr "Wildcard"
#: websites/models.py:44
msgid "website"
msgstr "Webauftritt"
#: websites/models.py:45
msgid "websites"
msgstr "Webauftritte"
#: websites/views.py:57
#, python-brace-format
msgid "Successfully added website {subdomain}.{domain}"
msgstr "Webauftritt {subdomain}.{domain} erfolgreich angelegt"

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('osusers', '0004_auto_20150104_1751'),
('domains', '0002_auto_20150124_1909'),
]
operations = [
migrations.CreateModel(
name='Website',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('subdomain', models.CharField(max_length=64, verbose_name='sub domain')),
('wildcard', models.BooleanField(default=False, verbose_name='wildcard')),
('domain', models.ForeignKey(verbose_name='domain', to='domains.HostingDomain')),
('osuser', models.ForeignKey(verbose_name='operating system user', to='osusers.User')),
],
options={
'verbose_name': 'website',
'verbose_name_plural': 'websites',
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='website',
unique_together=set([('domain', 'subdomain')]),
),
]

View file

@ -0,0 +1,79 @@
"""
This module defines the database models for website handling.
"""
from __future__ import absolute_import, unicode_literals
from django.db import models, transaction
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from domains.models import HostingDomain
from osusers.models import User as OsUser
from fileservertasks.tasks import (
create_file_website_hierarchy,
delete_file_website_hierarchy,
)
from webtasks.tasks import (
create_web_php_fpm_pool_config,
create_web_vhost_config,
delete_web_php_fpm_pool_config,
delete_web_vhost_config,
disable_web_vhost,
enable_web_vhost,
)
@python_2_unicode_compatible
class Website(models.Model):
"""
This is the model for a website.
"""
subdomain = models.CharField(
_('sub domain'), max_length=64)
osuser = models.ForeignKey(
OsUser, verbose_name=_('operating system user'))
domain = models.ForeignKey(
HostingDomain, verbose_name=_('domain'))
wildcard = models.BooleanField(_('wildcard'), default=False)
class Meta:
unique_together = [('domain', 'subdomain')]
verbose_name = _('website')
verbose_name_plural = _('websites')
def __str__(self):
return self.get_fqdn()
def get_fqdn(self):
return "{subdomain}.{domain}".format(
subdomain=self.subdomain, domain=self.domain.domain
)
@transaction.atomic
def save(self, *args, **kwargs):
if not self.pk:
fqdn = self.get_fqdn()
if not Website.objects.filter(osuser=self.osuser).exists():
create_web_php_fpm_pool_config.delay(
self.osuser.username).get()
create_file_website_hierarchy.delay(
self.osuser.username, fqdn).get()
create_web_vhost_config.delay(
self.osuser.username, fqdn, self.wildcard).get()
enable_web_vhost.delay(fqdn).get()
return super(Website, self).save(*args, **kwargs)
@transaction.atomic
def delete(self, *args, **kwargs):
fqdn = self.get_fqdn()
osuser = self.osuser
disable_web_vhost.delay(fqdn).get()
delete_web_vhost_config.delay(fqdn).get()
delete_file_website_hierarchy.delay(osuser.username, fqdn).get()
deleted = super(Website, self).delete(*args, **kwargs)
if not Website.objects.filter(osuser=osuser):
delete_web_php_fpm_pool_config.delay(osuser.username).get()
return deleted

View file

@ -0,0 +1,21 @@
"""
This module defines the URL patterns for website related views.
"""
from __future__ import absolute_import, unicode_literals
from django.conf.urls import patterns, url
from .views import (
AddWebsite,
DeleteWebsite,
)
urlpatterns = patterns(
'',
url(r'^(?P<package>\d+)/(?P<domain>[\w0-9.-]+)/create$',
AddWebsite.as_view(), name='add_website'),
url(r'^(?P<package>\d+)/(?P<domain>[\w0-9.-]+)/(?P<pk>\d+)/delete$',
DeleteWebsite.as_view(), name='delete_website'),
)

View file

@ -0,0 +1,83 @@
"""
This module defines views for website handling.
"""
from __future__ import absolute_import, unicode_literals
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import ugettext as _
from django.views.generic.edit import (
CreateView,
DeleteView,
)
from django.contrib import messages
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from gvawebcore.views import HostingPackageAndCustomerMixin
from domains.models import HostingDomain
from .forms import AddWebsiteForm
from .models import Website
class AddWebsite(
HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, CreateView
):
"""
This view is used to setup new websites for a customer hosting package.
"""
model = Website
context_object_name = 'website'
template_name_suffix = '_create'
form_class = AddWebsiteForm
def get_form_kwargs(self):
kwargs = super(AddWebsite, self).get_form_kwargs()
kwargs.update({
'hostingpackage': self.get_hosting_package(),
'domain': get_object_or_404(
HostingDomain, domain=self.kwargs['domain']),
})
return kwargs
def get_context_data(self, **kwargs):
context = super(AddWebsite, self).get_context_data(**kwargs)
context.update({
'customer': self.get_customer_object(),
'domain': get_object_or_404(
HostingDomain, domain=self.kwargs['domain'])
})
return context
def form_valid(self, form):
website = form.save()
messages.success(
self.request,
_('Successfully added website {subdomain}.{domain}').format(
subdomain=website.subdomain, domain=website.domain.domain
)
)
return redirect(self.get_hosting_package())
class DeleteWebsite(
HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, DeleteView
):
"""
This view is used to delete websites in a customer hosting package.
"""
context_object_name = 'website'
model = Website
def get_context_data(self, **kwargs):
context = super(DeleteWebsite, self).get_context_data(**kwargs)
context.update({
'customer': self.get_customer_object(),
'hostingpackage': self.get_hosting_package(),
})
return context
def get_success_url(self):
return self.get_hosting_package().get_absolute_url()

View file

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

View file

@ -0,0 +1,82 @@
"""
This module defines Celery_ tasks to manage website configurations.
"""
from __future__ import absolute_import
from celery import shared_task
@shared_task
def create_web_vhost_config(username, sitename, wildcard):
"""
This task creates a virtual host configuration on an nginx web
server.
:param str username: user who owns the site
:param str sitename: site name
:param boolean wildcard: designates whether this is website has a wildcard
subdomain
:return: :py:const:`True` if the creation finished successfully
:rtype: boolean
"""
@shared_task
def disable_web_vhost(sitename):
"""
This task disables a virtual host configuration on an nginx web server.
:param str sitename: site name
:return: :py:const:`True` if the virtual host has been disabled
:rtype: boolean
"""
@shared_task
def enable_web_vhost(sitename):
"""
This task enables an existing virtual host configuration on an nginx web
server.
:param str sitename: site name
:return: :py:const:`True` if the virtual host has been enabled
:rtype: boolean
"""
@shared_task
def delete_web_vhost_config(sitename):
"""
This task removes a virtual host configuration on an nginx web server.
:param str sitename: site name
:return: :py:const:`True` if the configuration has been deleted
:rtype: boolean
"""
@shared_task
def create_web_php_fpm_pool_config(username):
"""
This task creates a PHP FPM pool configuration.
:param str username: user name
:return: :py:const:`True` if the creation finished successfully
:rtype: boolean
"""
@shared_task
def delete_web_php_fpm_pool_config(username):
"""
This task deletes a PHP FPM pool configuration.
:param str username: user name
:return: :py:const:`True` if the pool has been deleted
:rtype: boolean
"""