diff --git a/TODO.md b/TODO.md index bc86b844..d2170e32 100644 --- a/TODO.md +++ b/TODO.md @@ -158,3 +158,6 @@ textwrap.dedent( \\) * better modeling of the interdependency between webapps and websites (settings) * webapp options cfig agnostic + +* Disable menu on tests, fucking overlapping +* service.name / verbose_name instead of .description ? diff --git a/orchestra/apps/accounts/models.py b/orchestra/apps/accounts/models.py index ba9abbec..1104857d 100644 --- a/orchestra/apps/accounts/models.py +++ b/orchestra/apps/accounts/models.py @@ -58,6 +58,10 @@ class Account(auth.AbstractBaseUser): def get_main(cls): return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK) + def clean(self): + self.first_name = self.first_name.strip() + self.last_name = self.last_name.strip() + def disable(self): self.is_active = False self.save(update_fields=['is_active']) diff --git a/orchestra/apps/contacts/models.py b/orchestra/apps/contacts/models.py index 16824c6e..5a0482c2 100644 --- a/orchestra/apps/contacts/models.py +++ b/orchestra/apps/contacts/models.py @@ -48,6 +48,15 @@ class Contact(models.Model): def __unicode__(self): return self.short_name + + def clean(self): + self.short_name = self.short_name.strip() + self.full_name = self.full_name.strip() + self.phone = self.phone.strip() + self.phone2 = self.phone2.strip() + self.address = self.address.strip() + self.city = self.city.strip() + self.country = self.country.strip() accounts.register(Contact) diff --git a/orchestra/apps/lists/backends.py b/orchestra/apps/lists/backends.py index 3c607f64..e94b87ec 100644 --- a/orchestra/apps/lists/backends.py +++ b/orchestra/apps/lists/backends.py @@ -2,6 +2,7 @@ import re import textwrap from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ from orchestra.apps.orchestration import ServiceController from orchestra.apps.resources import ServiceMonitor @@ -135,6 +136,7 @@ class MailmanBackend(ServiceController): class MailmanTraffic(ServiceMonitor): model = 'lists.List' resource = ServiceMonitor.TRAFFIC + verbose_name = _("Mailman traffic") def prepare(self): current_date = timezone.localtime(self.current_date) @@ -168,3 +170,18 @@ class MailmanTraffic(ServiceMonitor): 'object_id': mail_list.pk, 'last_date': last_date.strftime("%b %d %H:%M:%S"), } + + +class MailmanTraffic(ServiceMonitor): + model = 'lists.List' + verbose_name = _("Mailman subscribers") + + def monitor(self, mail_list): + context = self.get_context(mail_list) + self.append('echo %(object_id)i $(list_members %(list_name)s | wc -l)' % context) + + def get_context(self, mail_list): + return { + 'list_name': mail_list.name, + 'object_id': mail_list.pk, + } diff --git a/orchestra/apps/mailboxes/forms.py b/orchestra/apps/mailboxes/forms.py index 7d9c376e..a9709b0d 100644 --- a/orchestra/apps/mailboxes/forms.py +++ b/orchestra/apps/mailboxes/forms.py @@ -11,11 +11,14 @@ from .models import Address, Mailbox class MailboxForm(forms.ModelForm): """ hacky form for adding reverse M2M form field for Mailbox.addresses """ + # TODO keep track of this ticket for future reimplementation + # https://code.djangoproject.com/ticket/897 addresses = forms.ModelMultipleChoiceField(queryset=Address.objects, required=False, widget=widgets.FilteredSelectMultiple(verbose_name=_('Pizzas'), is_stacked=False)) def __init__(self, *args, **kwargs): super(MailboxForm, self).__init__(*args, **kwargs) + # Hack the widget in order to display add button field = AttrDict(**{ 'to': Address, 'get_related_field': lambda: AttrDict(name='id'), @@ -23,6 +26,8 @@ class MailboxForm(forms.ModelForm): widget = self.fields['addresses'].widget self.fields['addresses'].widget = widgets.RelatedFieldWidgetWrapper(widget, field, self.modeladmin.admin_site, can_add_related=True) + + # Filter related addresses by account old_render = self.fields['addresses'].widget.render def render(*args, **kwargs): output = old_render(*args, **kwargs) @@ -44,7 +49,6 @@ class MailboxForm(forms.ModelForm): return custom_filtering - class MailboxChangeForm(UserChangeForm, MailboxForm): pass @@ -66,4 +70,3 @@ class AddressForm(forms.ModelForm): cleaned_data = super(AddressForm, self).clean() if not cleaned_data.get('mailboxes', True) and not cleaned_data['forward']: raise forms.ValidationError(_("Mailboxes or forward address should be provided")) - diff --git a/orchestra/apps/payments/methods/sepadirectdebit.py b/orchestra/apps/payments/methods/sepadirectdebit.py index f364b0df..7bd55c3b 100644 --- a/orchestra/apps/payments/methods/sepadirectdebit.py +++ b/orchestra/apps/payments/methods/sepadirectdebit.py @@ -23,12 +23,24 @@ class SEPADirectDebitForm(PluginDataForm): widget=forms.TextInput(attrs={'size': '50'})) name = forms.CharField(max_length=128, label=_("Name"), widget=forms.TextInput(attrs={'size': '50'})) + + def clean_iban(self): + return self.cleaned_data['iban'].strip() + + def clean_name(self): + return self.cleaned_data['name'].strip() class SEPADirectDebitSerializer(serializers.Serializer): iban = serializers.CharField(label='IBAN', validators=[IBANValidator()], min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34) name = serializers.CharField(label=_("Name"), max_length=128) + + def clean_iban(self, attrs, source): + return attrs[source].strip() + + def clean_name(self, attrs, source): + return attrs[source].strip() class SEPADirectDebit(PaymentMethod): diff --git a/orchestra/apps/resources/backends.py b/orchestra/apps/resources/backends.py index 754ccebf..5ddcf97c 100644 --- a/orchestra/apps/resources/backends.py +++ b/orchestra/apps/resources/backends.py @@ -18,7 +18,7 @@ class ServiceMonitor(ServiceBackend): abstract = True @classmethod - def get_backends(cls): + def get_plugins(cls): """ filter controller classes """ return [ plugin for plugin in cls.plugins if ServiceMonitor in plugin.__mro__ diff --git a/orchestra/apps/resources/helpers.py b/orchestra/apps/resources/helpers.py index c4da0d9d..c404f8de 100644 --- a/orchestra/apps/resources/helpers.py +++ b/orchestra/apps/resources/helpers.py @@ -61,4 +61,4 @@ def compute_resource_usage(data): has_result = True else: raise NotImplementedError("%s support not implemented" % data.period) - return result/resource.scale if has_result else None + return result/resource.get_scale() if has_result else None diff --git a/orchestra/apps/resources/models.py b/orchestra/apps/resources/models.py index 19fac85a..2a5e907f 100644 --- a/orchestra/apps/resources/models.py +++ b/orchestra/apps/resources/models.py @@ -1,12 +1,12 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.apps import apps -from django.core import validators from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from djcelery.models import PeriodicTask, CrontabSchedule +from orchestra.core import validators from orchestra.models import queryset, fields from . import helpers @@ -36,8 +36,7 @@ class Resource(models.Model): name = models.CharField(_("name"), max_length=32, help_text=_('Required. 32 characters or fewer. Lowercase letters, ' 'digits and hyphen only.'), - validators=[validators.RegexValidator(r'^[a-z0-9_\-]+$', - _('Enter a valid name.'), 'invalid')]) + validators=[validators.validate_name]) verbose_name = models.CharField(_("verbose name"), max_length=256) content_type = models.ForeignKey(ContentType, help_text=_("Model where this resource will be hooked.")) @@ -57,7 +56,7 @@ class Resource(models.Model): "For example GB, KB or subscribers")) scale = models.PositiveIntegerField(_("scale"), help_text=_("Scale in which this resource monitoring resoults should " - "be prorcessed to match with unit.")) + "be prorcessed to match with unit. e.g. 10**9")) disable_trigger = models.BooleanField(_("disable trigger"), default=False, help_text=_("Disables monitors exeeded and recovery triggers")) crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"), @@ -113,6 +112,9 @@ class Resource(models.Model): task='resources.Monitor', args=[self.pk] ).delete() + + def get_scale(self): + return eval(self.scale) class ResourceData(models.Model): diff --git a/orchestra/apps/services/models.py b/orchestra/apps/services/models.py index 256ef131..8e9f9c02 100644 --- a/orchestra/apps/services/models.py +++ b/orchestra/apps/services/models.py @@ -27,6 +27,9 @@ class Plan(models.Model): def __unicode__(self): return self.name + + def clean(self): + self.name = self.name.strip() class ContractedPlan(models.Model): @@ -215,6 +218,7 @@ class Service(models.Model): return ServiceHandler(self) def clean(self): + self.description = self.description.strip() content_type = self.handler.get_content_type() if self.content_type != content_type: ct = str(content_type) diff --git a/orchestra/apps/webapps/models.py b/orchestra/apps/webapps/models.py index e8ca6518..79ad75a4 100644 --- a/orchestra/apps/webapps/models.py +++ b/orchestra/apps/webapps/models.py @@ -19,8 +19,7 @@ def settings_to_choices(choices): class WebApp(models.Model): """ Represents a web application """ - name = models.CharField(_("name"), max_length=128, - validators=[validators.validate_name]) + name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) type = models.CharField(_("type"), max_length=32, choices=settings_to_choices(settings.WEBAPPS_TYPES), default=settings.WEBAPPS_DEFAULT_TYPE) diff --git a/orchestra/apps/websites/models.py b/orchestra/apps/websites/models.py index 76756c72..01e46a3f 100644 --- a/orchestra/apps/websites/models.py +++ b/orchestra/apps/websites/models.py @@ -78,7 +78,8 @@ class WebsiteOption(models.Model): class Content(models.Model): webapp = models.ForeignKey('webapps.WebApp', verbose_name=_("web application")) website = models.ForeignKey('websites.Website', verbose_name=_("web site")) - path = models.CharField(_("path"), max_length=256, blank=True) + path = models.CharField(_("path"), max_length=256, blank=True, + validators=[validators.validate_url_path]) class Meta: unique_together = ('website', 'path') diff --git a/orchestra/core/validators.py b/orchestra/core/validators.py index 5dc363a9..9848c8f6 100644 --- a/orchestra/core/validators.py +++ b/orchestra/core/validators.py @@ -74,3 +74,7 @@ def validate_password(value): except ValueError, message: raise ValidationError("Password %s." % str(message)[3:]) + +def validate_url_path(value): + if not re.match(r'^\/[/.a-zA-Z0-9-]*$', value): + raise ValidationError(_('"%s" is not a valid URL path.') % value)