From d31c1d0fbf02bde5d4dc2be24762d872da64935f Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 15:43:05 +0100 Subject: [PATCH 01/18] add new route 'web' for web server configuration --- celeryrouters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/celeryrouters.py b/celeryrouters.py index ec7b122..44c4b4f 100644 --- a/celeryrouters.py +++ b/celeryrouters.py @@ -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, From 24b4bab0b0a332cfbe4df5fcba12fd987ec9e589 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 18:10:08 +0100 Subject: [PATCH 02/18] update to fileservertasks interface 0.4.0 version --- docs/changelog.rst | 3 +++ gnuviechadmin/fileservertasks/tasks.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5437e41..b9e8097 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,9 @@ Changelog ========= +* :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 diff --git a/gnuviechadmin/fileservertasks/tasks.py b/gnuviechadmin/fileservertasks/tasks.py index bbae738..67ea3e4 100644 --- a/gnuviechadmin/fileservertasks/tasks.py +++ b/gnuviechadmin/fileservertasks/tasks.py @@ -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 + + """ From 57d4b128f58f2f7c9fd763f1693f542e85e0e88c Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 20:58:43 +0100 Subject: [PATCH 03/18] add webtasks interface - add webtasks interface code - add webtasks to generated code documentation - add webtasks and fileservertasks to INSTALLED_APPS --- docs/changelog.rst | 1 + docs/code.rst | 1 + docs/code/webtasks.rst | 12 +++ gnuviechadmin/gnuviechadmin/settings/base.py | 2 + gnuviechadmin/webtasks/__init__.py | 4 + gnuviechadmin/webtasks/tasks.py | 80 ++++++++++++++++++++ 6 files changed, 100 insertions(+) create mode 100644 docs/code/webtasks.rst create mode 100644 gnuviechadmin/webtasks/__init__.py create mode 100644 gnuviechadmin/webtasks/tasks.py diff --git a/docs/changelog.rst b/docs/changelog.rst index b9e8097..ead7ebe 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :support:`-` add webtasks interface * :support:`-` update to new fileservertasks interface (requires gvafile >= 0.4.0 on the fileserver) diff --git a/docs/code.rst b/docs/code.rst index e334f04..7cfbead 100644 --- a/docs/code.rst +++ b/docs/code.rst @@ -27,6 +27,7 @@ Celery task stubs code/ldaptasks code/mysqltasks code/pgsqltasks + code/webtasks Django app code diff --git a/docs/code/webtasks.rst b/docs/code/webtasks.rst new file mode 100644 index 0000000..68139bc --- /dev/null +++ b/docs/code/webtasks.rst @@ -0,0 +1,12 @@ +:py:mod:`webtasks` +================== + +.. automodule:: webtasks + + +:py:mod:`tasks ` +-------------------------------- + +.. automodule:: webtasks.tasks + :members: + :undoc-members: diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 6d52afb..5effec9 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -258,6 +258,8 @@ LOCAL_APPS = ( 'taskresults', 'mysqltasks', 'pgsqltasks', + 'fileservertasks', + 'webtasks', 'domains', 'osusers', 'managemails', diff --git a/gnuviechadmin/webtasks/__init__.py b/gnuviechadmin/webtasks/__init__.py new file mode 100644 index 0000000..9e02bbf --- /dev/null +++ b/gnuviechadmin/webtasks/__init__.py @@ -0,0 +1,4 @@ +""" +This module contains :py:mod:`webtasks.tasks`. + +""" diff --git a/gnuviechadmin/webtasks/tasks.py b/gnuviechadmin/webtasks/tasks.py new file mode 100644 index 0000000..4f2cea3 --- /dev/null +++ b/gnuviechadmin/webtasks/tasks.py @@ -0,0 +1,80 @@ +""" +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): + """ + 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 + :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 + + """ From ba85ad8ad924b861e95fcac547504d3c06daf7c4 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 21:00:52 +0100 Subject: [PATCH 04/18] add django generated websites app --- gnuviechadmin/websites/__init__.py | 0 gnuviechadmin/websites/admin.py | 3 +++ gnuviechadmin/websites/migrations/__init__.py | 0 gnuviechadmin/websites/models.py | 3 +++ gnuviechadmin/websites/views.py | 3 +++ 5 files changed, 9 insertions(+) create mode 100644 gnuviechadmin/websites/__init__.py create mode 100644 gnuviechadmin/websites/admin.py create mode 100644 gnuviechadmin/websites/migrations/__init__.py create mode 100644 gnuviechadmin/websites/models.py create mode 100644 gnuviechadmin/websites/views.py diff --git a/gnuviechadmin/websites/__init__.py b/gnuviechadmin/websites/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/websites/admin.py b/gnuviechadmin/websites/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/gnuviechadmin/websites/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/gnuviechadmin/websites/migrations/__init__.py b/gnuviechadmin/websites/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gnuviechadmin/websites/models.py b/gnuviechadmin/websites/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/gnuviechadmin/websites/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/gnuviechadmin/websites/views.py b/gnuviechadmin/websites/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/gnuviechadmin/websites/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 7fbeb668da954bdc2636cac76fa4aa5ce4c1ba06 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 21:36:24 +0100 Subject: [PATCH 05/18] add wildcard parameter to create_web_vhost_config task --- gnuviechadmin/webtasks/tasks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gnuviechadmin/webtasks/tasks.py b/gnuviechadmin/webtasks/tasks.py index 4f2cea3..e7e1bf9 100644 --- a/gnuviechadmin/webtasks/tasks.py +++ b/gnuviechadmin/webtasks/tasks.py @@ -8,13 +8,15 @@ from celery import shared_task @shared_task -def create_web_vhost_config(username, sitename): +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 From b98b05220f1c6c7b20923caa979fe23f18900431 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 21:45:27 +0100 Subject: [PATCH 06/18] make manage.py executable --- gnuviechadmin/manage.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gnuviechadmin/manage.py diff --git a/gnuviechadmin/manage.py b/gnuviechadmin/manage.py old mode 100644 new mode 100755 From cff35dd408e6b76f5a0cbe48f2435498570f3a6d Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 21:49:22 +0100 Subject: [PATCH 07/18] define User.is_sftp_user and fix minor template issues --- gnuviechadmin/osusers/models.py | 4 ++++ .../hostingpackages/customerhostingpackage_detail.html | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index 5f64a12..2f62463 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -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): diff --git a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html index 0f13250..f487f4f 100644 --- a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html +++ b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html @@ -37,7 +37,7 @@ {% endwith %}
{% trans "Mailboxes" %}
{% blocktrans with num=hostingpackage.used_mailbox_count total=hostingpackage.mailbox_count %}{{ num }} of {{ total }} in use{% endblocktrans %}
+ glyphicon-info-sign" title="{% blocktrans with mailboxcount=hostingpackage.mailboxcount %}The package provides {{ mailboxcount }} mailboxes the difference comes from mailbox options.{% endblocktrans %}">
{% if osuser.is_sftp_user %}{% trans "SFTP username" %}{% else %}{% trans "SSH/SFTP username" %}{% endif %}
{{ osuser.username }}
{% trans "Upload server" %}
@@ -67,7 +67,7 @@
{% trans "Hosting Package Actions" %}
From 711a96212c2264a0793e4e7e28f116e50bb1784a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 22:47:49 +0100 Subject: [PATCH 08/18] implement adding websites - implement websites.models.Website - add migration - implement websites.views.AddWebsite - implement websites.forms.AddWebsiteForm - define URL pattern 'add_website' in websites.urls - register Website model in websites.admin - add templates websites/base.html and websites/website_create.html - add german translation for new strings - add website URLs to gnuviechadmin.urls - add websites to INSTALLED_APPS - add changelog entry --- docs/changelog.rst | 1 + gnuviechadmin/gnuviechadmin/settings/base.py | 1 + gnuviechadmin/gnuviechadmin/urls.py | 1 + gnuviechadmin/templates/websites/base.html | 1 + .../templates/websites/website_create.html | 30 +++++++++ gnuviechadmin/websites/admin.py | 11 +++- gnuviechadmin/websites/forms.py | 62 +++++++++++++++++++ .../websites/locale/de/LC_MESSAGES/django.po | 44 +++++++++++++ .../websites/migrations/0001_initial.py | 34 ++++++++++ gnuviechadmin/websites/models.py | 38 +++++++++++- gnuviechadmin/websites/urls.py | 18 ++++++ gnuviechadmin/websites/views.py | 61 +++++++++++++++++- 12 files changed, 297 insertions(+), 5 deletions(-) create mode 100644 gnuviechadmin/templates/websites/base.html create mode 100644 gnuviechadmin/templates/websites/website_create.html create mode 100644 gnuviechadmin/websites/forms.py create mode 100644 gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po create mode 100644 gnuviechadmin/websites/migrations/0001_initial.py create mode 100644 gnuviechadmin/websites/urls.py diff --git a/docs/changelog.rst b/docs/changelog.rst index ead7ebe..12ea8c1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :feature:`-` implement setup of websites * :support:`-` add webtasks interface * :support:`-` update to new fileservertasks interface (requires gvafile >= 0.4.0 on the fileserver) diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 5effec9..f447d70 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -265,6 +265,7 @@ LOCAL_APPS = ( 'managemails', 'userdbs', 'hostingpackages', + 'websites', ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps diff --git a/gnuviechadmin/gnuviechadmin/urls.py b/gnuviechadmin/gnuviechadmin/urls.py index 4dad925..7a79105 100644 --- a/gnuviechadmin/gnuviechadmin/urls.py +++ b/gnuviechadmin/gnuviechadmin/urls.py @@ -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)), diff --git a/gnuviechadmin/templates/websites/base.html b/gnuviechadmin/templates/websites/base.html new file mode 100644 index 0000000..94d9808 --- /dev/null +++ b/gnuviechadmin/templates/websites/base.html @@ -0,0 +1 @@ +{% extends "base.html" %} diff --git a/gnuviechadmin/templates/websites/website_create.html b/gnuviechadmin/templates/websites/website_create.html new file mode 100644 index 0000000..3fb890f --- /dev/null +++ b/gnuviechadmin/templates/websites/website_create.html @@ -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 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 page_title %} + +{% block content %} +{% crispy form %} +{% endblock %} + +{% block extra_js %} + +{% endblock extra_js %} diff --git a/gnuviechadmin/websites/admin.py b/gnuviechadmin/websites/admin.py index 8c38f3f..eeb47c8 100644 --- a/gnuviechadmin/websites/admin.py +++ b/gnuviechadmin/websites/admin.py @@ -1,3 +1,12 @@ +""" +Admin site for websites. + +""" +from __future__ import absolute_import + from django.contrib import admin -# Register your models here. +from .models import Website + + +admin.site.register(Website) diff --git a/gnuviechadmin/websites/forms.py b/gnuviechadmin/websites/forms.py new file mode 100644 index 0000000..a6fca20 --- /dev/null +++ b/gnuviechadmin/websites/forms.py @@ -0,0 +1,62 @@ +""" +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 .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) + if Website.objects.filter(wildcard=True).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')) + 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) diff --git a/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..ca2ee6e --- /dev/null +++ b/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,44 @@ +# 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 , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: websites gnuviechadmin app\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-01-26 21:37+0100\n" +"PO-Revision-Date: 2015-01-26 21:39+0100\n" +"Last-Translator: Jan Dittberner \n" +"Language-Team: Jan Dittberner \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" + +#: models.py:22 +msgid "sub domain" +msgstr "Subdomain" + +#: models.py:24 +msgid "operating system user" +msgstr "Betriebssystemnutzer" + +#: models.py:26 +msgid "domain" +msgstr "Domain" + +#: models.py:27 +msgid "wildcard" +msgstr "Wildcard" + +#: models.py:31 +msgid "website" +msgstr "Webauftritt" + +#: models.py:32 +msgid "websites" +msgstr "Webauftritte" diff --git a/gnuviechadmin/websites/migrations/0001_initial.py b/gnuviechadmin/websites/migrations/0001_initial.py new file mode 100644 index 0000000..bfbb1a5 --- /dev/null +++ b/gnuviechadmin/websites/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/gnuviechadmin/websites/models.py b/gnuviechadmin/websites/models.py index 71a8362..2bc8928 100644 --- a/gnuviechadmin/websites/models.py +++ b/gnuviechadmin/websites/models.py @@ -1,3 +1,37 @@ -from django.db import models +""" +This module defines the database models for website handling. -# Create your models here. +""" +from __future__ import absolute_import, unicode_literals + +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext as _ + +from domains.models import HostingDomain +from osusers.models import User as OsUser + + +@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 "{subdomain}.{domain}".format( + subdomain=self.subdomain, domain=self.domain.domain + ) diff --git a/gnuviechadmin/websites/urls.py b/gnuviechadmin/websites/urls.py new file mode 100644 index 0000000..c98a252 --- /dev/null +++ b/gnuviechadmin/websites/urls.py @@ -0,0 +1,18 @@ +""" +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, +) + + +urlpatterns = patterns( + '', + url(r'^(?P\d+)/(?P[\w0-9.-]+)/create$', + AddWebsite.as_view(), name='add_website'), +) diff --git a/gnuviechadmin/websites/views.py b/gnuviechadmin/websites/views.py index 91ea44a..64a8fea 100644 --- a/gnuviechadmin/websites/views.py +++ b/gnuviechadmin/websites/views.py @@ -1,3 +1,60 @@ -from django.shortcuts import render +""" +This module defines views for website handling. -# Create your views here. +""" +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, +) +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()) From 5ad32e6894ba18ce242abb73b6280ce545f38382 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 26 Jan 2015 22:53:57 +0100 Subject: [PATCH 09/18] link from hostingpackage detail view to 'add_website' --- .../customerhostingpackage_detail.html | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html index f487f4f..f3c7634 100644 --- a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html +++ b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html @@ -90,23 +90,36 @@ {% for domain in domains %} {{ domain.domain }} - {% if domain.domain.maildomain %} + {% if domain.domain.maildomain.mailaddress_set.exists %} {% with maildomain=domain.domain.maildomain %} {% for mailaddress in maildomain.mailaddresses %}{% spaceless %} {{ mailaddress }} - - {% endspaceless %}{% if not forloop.last %}, {% endif %} - {% endfor %} + {% trans "Delete mail address" %} + {% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %} + {% endwith %} + + {% else %} + {% trans "None" %} + {% endif %} + {% if domain.domain.website_set.exists %} + + {% with domain=domain.domain %} + {% for website in domain.website_set.all %}{% spaceless %} + {{ website }} + {% trans "Delete website" %} + {% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %} {% endwith %} {% else %} {% trans "None" %} {% endif %} - {% with maildomain=domain.domain.maildomain %} - + {% trans "Add mail address" %} + {% endwith %} + {% with hostingdomain=domain.domain %} + {% trans "Add website" %} {% endwith %} From 7da5cfe40695ecf8780f86d4a8747e4999ac349a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 16:26:10 +0100 Subject: [PATCH 10/18] implement website deletion - implement websites.views.DeleteWebsite - add URL pattern 'delete_website' to websites.urls - add template website_confirm_delete.html - add link from hostingpackage page to 'delete_website' - add changelog entry --- docs/changelog.rst | 1 + .../customerhostingpackage_detail.html | 2 +- .../websites/website_confirm_delete.html | 34 +++++++++++++++++++ gnuviechadmin/websites/urls.py | 3 ++ gnuviechadmin/websites/views.py | 23 +++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 gnuviechadmin/templates/websites/website_confirm_delete.html diff --git a/docs/changelog.rst b/docs/changelog.rst index 12ea8c1..b98b466 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :feature:`-` implement deletion of websites * :feature:`-` implement setup of websites * :support:`-` add webtasks interface * :support:`-` update to new fileservertasks interface (requires gvafile >= diff --git a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html index f3c7634..e66ae9b 100644 --- a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html +++ b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html @@ -107,7 +107,7 @@ {% with domain=domain.domain %} {% for website in domain.website_set.all %}{% spaceless %} {{ website }} - {% trans "Delete website" %} + {% trans "Delete website" %} {% endspaceless %}{% if not forloop.last %}, {% endif %}{% endfor %} {% endwith %} diff --git a/gnuviechadmin/templates/websites/website_confirm_delete.html b/gnuviechadmin/templates/websites/website_confirm_delete.html new file mode 100644 index 0000000..92cb0e9 --- /dev/null +++ b/gnuviechadmin/templates/websites/website_confirm_delete.html @@ -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 {{ website }}{% endblocktrans %} +{% else %} +{% blocktrans with full_name=customer.get_full_name %}Delete Website {{ website }} of Customer {{ full_name }}{% endblocktrans %} +{% endif %} +{% endspaceless %}{% endblock page_title %} + +{% block content %} +
+
+ {% blocktrans %}Do you really want to delete the website {{ website }}?{% endblocktrans %} +
+
+

{% 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. All data in the website directory will be lost!{% endblocktrans %}

+
+ {% csrf_token %} + + {% trans "Cancel" %} +
+
+
+{% endblock content %} diff --git a/gnuviechadmin/websites/urls.py b/gnuviechadmin/websites/urls.py index c98a252..4bcd5e0 100644 --- a/gnuviechadmin/websites/urls.py +++ b/gnuviechadmin/websites/urls.py @@ -8,6 +8,7 @@ from django.conf.urls import patterns, url from .views import ( AddWebsite, + DeleteWebsite, ) @@ -15,4 +16,6 @@ urlpatterns = patterns( '', url(r'^(?P\d+)/(?P[\w0-9.-]+)/create$', AddWebsite.as_view(), name='add_website'), + url(r'^(?P\d+)/(?P[\w0-9.-]+)/(?P\d+)/delete$', + DeleteWebsite.as_view(), name='delete_website'), ) diff --git a/gnuviechadmin/websites/views.py b/gnuviechadmin/websites/views.py index 64a8fea..803386f 100644 --- a/gnuviechadmin/websites/views.py +++ b/gnuviechadmin/websites/views.py @@ -8,6 +8,7 @@ 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 @@ -58,3 +59,25 @@ class AddWebsite( ) ) 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() From 7c9509c159b3be1f12139a1ca7ca9523d953200e Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 16:41:44 +0100 Subject: [PATCH 11/18] implement domain name validation - implement domains.forms.relative_domain_validator - use the validator for domain field validation in domains.forms.CreateHostingDomainForm - use the validator for subdomain field validation in websites.forms.AddWebsiteForm --- docs/changelog.rst | 1 + gnuviechadmin/domains/forms.py | 15 +++++++++++++++ gnuviechadmin/websites/forms.py | 8 +++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b98b466..a53cc9d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :support:`-` implement domain name validation * :feature:`-` implement deletion of websites * :feature:`-` implement setup of websites * :support:`-` add webtasks interface diff --git a/gnuviechadmin/domains/forms.py b/gnuviechadmin/domains/forms.py index ba2a654..fff18cb 100644 --- a/gnuviechadmin/domains/forms.py +++ b/gnuviechadmin/domains/forms.py @@ -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}(? Date: Tue, 27 Jan 2015 18:40:22 +0100 Subject: [PATCH 12/18] implement website.models.Website.save - implement save method and let it call these tasks: - create_web_php_fpm_pool_config if the user has no website yet - create_file_website_hierarchy - create_web_vhost_config - enable_web_vhost --- gnuviechadmin/websites/models.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/gnuviechadmin/websites/models.py b/gnuviechadmin/websites/models.py index 2bc8928..4827d48 100644 --- a/gnuviechadmin/websites/models.py +++ b/gnuviechadmin/websites/models.py @@ -4,13 +4,22 @@ This module defines the database models for website handling. """ from __future__ import absolute_import, unicode_literals -from django.db import models +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, +) +from webtasks.tasks import ( + create_web_php_fpm_pool_config, + create_web_vhost_config, + enable_web_vhost, +) + @python_2_unicode_compatible class Website(models.Model): @@ -32,6 +41,23 @@ class Website(models.Model): 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) From 1f485e6b294026a1480524d8e00401f7e92d79ef Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 18:42:07 +0100 Subject: [PATCH 13/18] implement websites.models.Website.delete - implement delete method and let it call these tasks: - disable_web_vhost - delete_web_vhost_config - delete_file_website_hierarchy - delete_web_php_fpm_pool_config if this was the last website of the user --- gnuviechadmin/websites/models.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gnuviechadmin/websites/models.py b/gnuviechadmin/websites/models.py index 4827d48..c58e904 100644 --- a/gnuviechadmin/websites/models.py +++ b/gnuviechadmin/websites/models.py @@ -13,10 +13,14 @@ 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, ) @@ -61,3 +65,15 @@ class Website(models.Model): 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 From 5322e003459bdce963ce9bc9609c1b73d5c8f1ee Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 18:51:04 +0100 Subject: [PATCH 14/18] add code documentation for websites app --- docs/code.rst | 1 + docs/code/domains.rst | 2 +- docs/code/websites.rst | 46 ++++++++++++++++++++++++++++++ gnuviechadmin/websites/__init__.py | 5 ++++ gnuviechadmin/websites/apps.py | 17 +++++++++++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 docs/code/websites.rst create mode 100644 gnuviechadmin/websites/apps.py diff --git a/docs/code.rst b/docs/code.rst index 7cfbead..bee201c 100644 --- a/docs/code.rst +++ b/docs/code.rst @@ -43,3 +43,4 @@ Django app code code/osusers code/taskresults code/userdbs + code/websites diff --git a/docs/code/domains.rst b/docs/code/domains.rst index 48b543b..dffea80 100644 --- a/docs/code/domains.rst +++ b/docs/code/domains.rst @@ -40,7 +40,7 @@ :py:mod:`views ` ---------------------------------- +------------------------------- .. automodule:: domains.views :members: diff --git a/docs/code/websites.rst b/docs/code/websites.rst new file mode 100644 index 0000000..50299dd --- /dev/null +++ b/docs/code/websites.rst @@ -0,0 +1,46 @@ +:py:mod:`websites` app +====================== + +.. automodule:: websites + + +:py:mod:`admin ` +-------------------------------- + +.. automodule:: websites.admin + :members: + + +:py:mod:`apps ` +------------------------------ + +.. automodule:: websites.apps + :members: + + +:py:mod:`forms ` +-------------------------------- + +.. automodule:: websites.forms + :members: + + +:py:mod:`models ` +---------------------------------- + +.. automodule:: websites.models + :members: + + +:py:mod:`urls ` +------------------------------ + +.. automodule:: websites.urls + :members: + + +:py:mod:`views ` +-------------------------------- + +.. automodule:: websites.views + :members: diff --git a/gnuviechadmin/websites/__init__.py b/gnuviechadmin/websites/__init__.py index e69de29..8e793c3 100644 --- a/gnuviechadmin/websites/__init__.py +++ b/gnuviechadmin/websites/__init__.py @@ -0,0 +1,5 @@ +""" +This app takes care of websites. + +""" +default_app_config = 'websites.apps.WebsitesAppConfig' diff --git a/gnuviechadmin/websites/apps.py b/gnuviechadmin/websites/apps.py new file mode 100644 index 0000000..363d7ad --- /dev/null +++ b/gnuviechadmin/websites/apps.py @@ -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') From 742f0d0e33f2cd1a621d7f88c7121aab35bc54eb Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 19:08:13 +0100 Subject: [PATCH 15/18] update translations, add new strings --- .../domains/locale/de/LC_MESSAGES/django.po | 14 +- .../gvacommon/locale/de/LC_MESSAGES/django.po | 2 +- .../locale/de/LC_MESSAGES/django.po | 8 +- .../locale/de/LC_MESSAGES/django.po | 10 +- gnuviechadmin/locale/de/LC_MESSAGES/django.po | 132 ++++++++++++++---- .../locale/de/LC_MESSAGES/django.po | 12 +- .../osusers/locale/de/LC_MESSAGES/django.po | 51 ++++--- .../locale/de/LC_MESSAGES/django.po | 2 +- .../userdbs/locale/de/LC_MESSAGES/django.po | 2 +- .../websites/locale/de/LC_MESSAGES/django.po | 33 +++-- 10 files changed, 183 insertions(+), 83 deletions(-) diff --git a/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po index 92a255b..5dc5688 100644 --- a/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/domains/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \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" diff --git a/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po index 4251126..15de43e 100644 --- a/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/gvacommon/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \n" diff --git a/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po index e460e21..8fffff1 100644 --- a/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/gvawebcore/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \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" diff --git a/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po index d8993d0..e9f078b 100644 --- a/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/hostingpackages/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \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." diff --git a/gnuviechadmin/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/locale/de/LC_MESSAGES/django.po index 69d5683..f318895 100644 --- a/gnuviechadmin/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \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 %(website)s" +msgstr "Webauftritt löschen %(website)s" + +#: templates/websites/website_confirm_delete.html:16 +#, python-format +msgid "Delete Website %(website)s of Customer %(full_name)s" +msgstr "" +"Webauftritt löschen %(website)s des Kunden %(full_name)s" + +#: 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. All data in the website directory will be lost!" +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. Alle Daten im Verzeichnis des " +"Webauftritts werden verloren gehen!" + +#: 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 for Subdomain of %(domain)s" +msgstr "Website anlegen für Subdomain von %(domain)s" + +#: templates/websites/website_create.html:16 +#, python-format +msgid "" +"Add Website for Subdomain of Domain %(domain)s of Customer " +"%(full_name)s" +msgstr "" +"Webauftritt anlegen für Subdomain der Domain %(domain)s des Kunden " +"%(full_name)s" + #, fuzzy #~| msgid "Password Reset" #~ msgid "Password (again)" diff --git a/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po index 017be4f..9aefc44 100644 --- a/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \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." diff --git a/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po index 2c74f30..0e6cc3f 100644 --- a/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \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" diff --git a/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po index fd37349..0decaab 100644 --- a/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/taskresults/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \n" diff --git a/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po index c05e5d4..618f3cb 100644 --- a/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/userdbs/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Jan Dittberner \n" diff --git a/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po b/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po index ca2ee6e..c0c1851 100644 --- a/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po +++ b/gnuviechadmin/websites/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: websites gnuviechadmin app\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-26 21:37+0100\n" -"PO-Revision-Date: 2015-01-26 21:39+0100\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 \n" "Language-Team: Jan Dittberner \n" "Language: de\n" @@ -19,26 +19,43 @@ msgstr "" "X-Generator: Poedit 1.6.10\n" "X-Poedit-SourceCharset: UTF-8\n" -#: models.py:22 +#: 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" -#: models.py:24 +#: websites/models.py:37 msgid "operating system user" msgstr "Betriebssystemnutzer" -#: models.py:26 +#: websites/models.py:39 msgid "domain" msgstr "Domain" -#: models.py:27 +#: websites/models.py:40 msgid "wildcard" msgstr "Wildcard" -#: models.py:31 +#: websites/models.py:44 msgid "website" msgstr "Webauftritt" -#: models.py:32 +#: 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" From b92bb9ac9af6b6e986a3ed8fbd4e4ddf0f38960a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 19:09:21 +0100 Subject: [PATCH 16/18] mark bugs as major to include them in 0.7.0 changelog --- docs/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a53cc9d..2fb4924 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,9 +21,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 From 8d3f9582ef009e38471372952a89def718e21a19 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 19:09:46 +0100 Subject: [PATCH 17/18] add changelog entry --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2fb4924..058c65d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +* :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 From 35a784300fe3470603a4f25e83cf45666fff9bf5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 27 Jan 2015 19:13:41 +0100 Subject: [PATCH 18/18] add release version in changelog, update conf.py --- docs/changelog.rst | 1 + docs/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 058c65d..919eb10 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ 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 diff --git a/docs/conf.py b/docs/conf.py index 6ff2870..8a8295e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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.