From f2dbc0ed424857d01d4b767c099ddc6241de1180 Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Wed, 25 Feb 2015 17:29:39 +0000 Subject: [PATCH] websites: Prevent multiple domains on the same port --- orchestra/apps/mailboxes/backends.py | 21 ++++++++++++--------- orchestra/apps/mailboxes/models.py | 12 ++++++------ orchestra/apps/websites/admin.py | 23 ++++++++++++++++++++--- orchestra/apps/websites/forms.py | 20 ++++++++++++++++++++ orchestra/apps/websites/models.py | 12 ++++++------ orchestra/apps/websites/serializers.py | 15 +++++++++++++++ 6 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 orchestra/apps/websites/forms.py diff --git a/orchestra/apps/mailboxes/backends.py b/orchestra/apps/mailboxes/backends.py index 96488856..84f8cfd0 100644 --- a/orchestra/apps/mailboxes/backends.py +++ b/orchestra/apps/mailboxes/backends.py @@ -131,15 +131,17 @@ class PostfixAddressBackend(ServiceController): self.append('sed -i "/^%(domain)s\s*/d" %(virtual_alias_domains)s' % context) def update_virtual_alias_maps(self, address, context): - destination = [] - for mailbox in address.get_mailboxes(): - context['mailbox'] = mailbox - destination.append("%(mailbox)s@%(mailbox_domain)s" % context) - for forward in address.forward: - if '@' in forward: - destination.append(forward) + # Virtual mailbox stuff +# destination = [] +# for mailbox in address.get_mailboxes(): +# context['mailbox'] = mailbox +# destination.append("%(mailbox)s@%(mailbox_domain)s" % context) +# for forward in address.forward: +# if '@' in forward: +# destination.append(forward) + destination = address.destination if destination: - context['destination'] = ' '.join(destination) + context['destination'] = destination self.append(textwrap.dedent(""" LINE="%(email)s\t%(destination)s" if [[ ! $(grep "^%(email)s\s" %(virtual_alias_maps)s) ]]; then @@ -153,7 +155,7 @@ class PostfixAddressBackend(ServiceController): fi""") % context) else: logger.warning("Address %i is empty" % address.pk) - self.append('sed -i "/^%(email)s\s/d" %(virtual_alias_maps)s') + self.append('sed -i "/^%(email)s\s/d" %(virtual_alias_maps)s' % context) self.append('UPDATED_VIRTUAL_ALIAS_MAPS=1') def exclude_virtual_alias_maps(self, context): @@ -180,6 +182,7 @@ class PostfixAddressBackend(ServiceController): [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]] && { /etc/init.d/postfix reload; } """) % context ) + self.append('exit 0') def get_context_files(self): return { diff --git a/orchestra/apps/mailboxes/models.py b/orchestra/apps/mailboxes/models.py index 0740def0..6c437d91 100644 --- a/orchestra/apps/mailboxes/models.py +++ b/orchestra/apps/mailboxes/models.py @@ -111,12 +111,12 @@ class Address(models.Model): def email(self): return "%s@%s" % (self.name, self.domain) -# @property -# def destination(self): -# destinations = list(self.mailboxes.values_list('name', flat=True)) -# if self.forward: -# destinations.append(self.forward) -# return ' '.join(destinations) + @cached_property + def destination(self): + destinations = list(self.mailboxes.values_list('name', flat=True)) + if self.forward: + destinations += self.forward + return ' '.join(destinations) def clean(self): if self.account_id: diff --git a/orchestra/apps/websites/admin.py b/orchestra/apps/websites/admin.py index d486acc9..45b4fac1 100644 --- a/orchestra/apps/websites/admin.py +++ b/orchestra/apps/websites/admin.py @@ -1,13 +1,17 @@ from django import forms from django.contrib import admin +from django.core.urlresolvers import resolve +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ + from orchestra.admin import ExtendedModelAdmin from orchestra.admin.utils import admin_link, change_url from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin from orchestra.forms.widgets import DynamicHelpTextSelect from . import settings +from .forms import WebsiteAdminForm from .models import Content, Website, WebsiteOption @@ -65,6 +69,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): 'fields': ('account_link', 'name', 'port', 'domains', 'is_active'), }), ) + form = WebsiteAdminForm filter_by_account_fields = ['domains'] list_prefetch_related = ('domains', 'content_set__webapp') search_fields = ('name', 'account__username', 'domains__name') @@ -91,9 +96,21 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): display_webapps.short_description = _("Web apps") def formfield_for_dbfield(self, db_field, **kwargs): - if db_field.name == 'root': - kwargs['widget'] = forms.TextInput(attrs={'size':'100'}) - return super(WebsiteAdmin, self).formfield_for_dbfield(db_field, **kwargs) + """ + Exclude domains with exhausted ports + has to be done here, on the form doesn't work because of filter_by_account_fields + """ + formfield = super(WebsiteAdmin, self).formfield_for_dbfield(db_field, **kwargs) + if db_field.name == 'domains': + qset = Q() + for port, __ in settings.WEBSITES_PORT_CHOICES: + qset = qset & Q(websites__port=port) + args = resolve(kwargs['request'].path).args + if args: + object_id = args[0] + qset = Q(qset & ~Q(websites__pk=object_id)) + formfield.queryset = formfield.queryset.exclude(qset) + return formfield admin.site.register(Website, WebsiteAdmin) diff --git a/orchestra/apps/websites/forms.py b/orchestra/apps/websites/forms.py new file mode 100644 index 00000000..dd82cc42 --- /dev/null +++ b/orchestra/apps/websites/forms.py @@ -0,0 +1,20 @@ +from django import forms +from django.core.exceptions import ValidationError + + +class WebsiteAdminForm(forms.ModelForm): + def clean(self): + """ Prevent multiples domains on the same port """ + domains = self.cleaned_data.get('domains') + port = self.cleaned_data.get('port') + existing = [] + for domain in domains.all(): + if domain.websites.filter(port=port).exclude(pk=self.instance.pk).exists(): + existing.append(domain.name) + if existing: + context = (', '.join(existing), port) + raise ValidationError({ + 'domains': 'A website is already defined for "%s" on port %s' % context + }) + return self.cleaned_data + diff --git a/orchestra/apps/websites/models.py b/orchestra/apps/websites/models.py index 36e4e261..977952ed 100644 --- a/orchestra/apps/websites/models.py +++ b/orchestra/apps/websites/models.py @@ -32,12 +32,6 @@ class Website(models.Model): def unique_name(self): return "%s-%i" % (self.name, self.pk) - @cached - def get_options(self): - return { - opt.name: opt.value for opt in self.options.all() - } - @property def protocol(self): if self.port == 80: @@ -46,6 +40,12 @@ class Website(models.Model): return 'https' raise TypeError('No protocol for port "%s"' % self.port) + @cached + def get_options(self): + return { + opt.name: opt.value for opt in self.options.all() + } + def get_absolute_url(self): domain = self.domains.first() if domain: diff --git a/orchestra/apps/websites/serializers.py b/orchestra/apps/websites/serializers.py index 30ede633..87287133 100644 --- a/orchestra/apps/websites/serializers.py +++ b/orchestra/apps/websites/serializers.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ValidationError from django.shortcuts import get_object_or_404 from rest_framework import serializers @@ -49,3 +50,17 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): model = Website fields = ('url', 'name', 'port', 'domains', 'is_active', 'contents', 'options') postonly_fileds = ('name',) + + def full_clean(self, instance): + """ Prevent multiples domains on the same port """ + existing = [] + for domain in instance._m2m_data['domains']: + if domain.websites.filter(port=instance.port).exclude(pk=instance.pk).exists(): + existing.append(domain.name) + if existing: + context = (', '.join(existing), instance.port) + raise ValidationError({ + 'domains': 'A website is already defined for "%s" on port %s' % context + }) + return instance +