From 66fa3bb4c6e6d22375fec2d61a8e4a924552d08e Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Fri, 27 Feb 2015 16:57:39 +0000 Subject: [PATCH] Fixed bug with m2m without intermediary model backends not being executed --- orchestra/apps/accounts/models.py | 5 +-- orchestra/apps/lists/backends.py | 2 +- orchestra/apps/mailboxes/backends.py | 4 +++ orchestra/apps/mailboxes/models.py | 2 +- orchestra/apps/orchestration/backends.py | 37 +++++++++++---------- orchestra/apps/orchestration/middlewares.py | 37 +++++++++++++++------ orchestra/apps/resources/forms.py | 2 +- orchestra/apps/systemusers/backends.py | 3 +- orchestra/apps/websites/models.py | 17 +++++++--- orchestra/apps/websites/settings.py | 7 ++-- orchestra/conf/base_settings.py | 2 +- 11 files changed, 78 insertions(+), 40 deletions(-) diff --git a/orchestra/apps/accounts/models.py b/orchestra/apps/accounts/models.py index 6bdbc799..e9bfa914 100644 --- a/orchestra/apps/accounts/models.py +++ b/orchestra/apps/accounts/models.py @@ -77,10 +77,11 @@ class Account(auth.AbstractBaseUser): self.save(update_fields=['is_active']) # Trigger save() on related objects that depend on this account for rel in self._meta.get_all_related_objects(): - if not rel.model in services: + source = getattr(rel, 'related_model', rel.model) + if not source in services: continue try: - rel.model._meta.get_field_by_name('is_active') + source._meta.get_field_by_name('is_active') except models.FieldDoesNotExist: continue else: diff --git a/orchestra/apps/lists/backends.py b/orchestra/apps/lists/backends.py index 9e2d9c0f..8e20b17e 100644 --- a/orchestra/apps/lists/backends.py +++ b/orchestra/apps/lists/backends.py @@ -171,7 +171,7 @@ class MailmanTraffic(ServiceMonitor): def monitor(self, mail_list): context = self.get_context(mail_list) self.append( - 'monitor %(object_id)i %(last_date)s "%(list_name)s" "%(log_file)s{,.1}"' % context) + 'monitor %(object_id)i %(last_date)s "%(list_name)s" "%(mailman_log)s{,.1}"' % context) def get_context(self, mail_list): last_date = timezone.localtime(self.get_last_date(mail_list.pk)) diff --git a/orchestra/apps/mailboxes/backends.py b/orchestra/apps/mailboxes/backends.py index 84f8cfd0..aaa8e059 100644 --- a/orchestra/apps/mailboxes/backends.py +++ b/orchestra/apps/mailboxes/backends.py @@ -117,6 +117,10 @@ class PasswdVirtualUserBackend(ServiceController): class PostfixAddressBackend(ServiceController): verbose_name = _("Postfix address") model = 'mailboxes.Address' + # TODO + related_models = ( + ('mailboxes.Mailbox', 'addresses'), + ) def include_virtual_alias_domain(self, context): self.append(textwrap.dedent(""" diff --git a/orchestra/apps/mailboxes/models.py b/orchestra/apps/mailboxes/models.py index 6c437d91..20463de0 100644 --- a/orchestra/apps/mailboxes/models.py +++ b/orchestra/apps/mailboxes/models.py @@ -115,7 +115,7 @@ class Address(models.Model): def destination(self): destinations = list(self.mailboxes.values_list('name', flat=True)) if self.forward: - destinations += self.forward + destinations += self.forward.split() return ' '.join(destinations) def clean(self): diff --git a/orchestra/apps/orchestration/backends.py b/orchestra/apps/orchestration/backends.py index ba4bd87c..0d2c81ad 100644 --- a/orchestra/apps/orchestration/backends.py +++ b/orchestra/apps/orchestration/backends.py @@ -1,6 +1,6 @@ from functools import partial -from django.db.models.loading import get_model +from django.apps import apps from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ @@ -93,24 +93,27 @@ class ServiceBackend(plugins.Plugin): return None @classmethod - def get_backends(cls, instance=None, action=None): + def get_backends(cls, instance=None, action=None, active=True): + from .models import Route backends = cls.get_plugins() included = [] + if active: + active_backends = Route.objects.filter(is_active=True).values_list('backend', flat=True) # Filter for instance or action - if instance or action: - for backend in backends: - include = True - if instance: - opts = instance._meta - if backend.model != '.'.join((opts.app_label, opts.object_name)): - include = False - if include and action: - if action not in backend.get_actions(): - include = False - if include: - included.append(backend) - backends = included - return backends + for backend in backends: + if active and backend.get_name() not in active_backends: + continue + include = True + if instance: + opts = instance._meta + if backend.model != '.'.join((opts.app_label, opts.object_name)): + include = False + if include and action: + if action not in backend.get_actions(): + include = False + if include: + included.append(backend) + return included @classmethod def get_backend(cls, name): @@ -118,7 +121,7 @@ class ServiceBackend(plugins.Plugin): @classmethod def model_class(cls): - return get_model(cls.model) + return apps.get_model(cls.model) @property def scripts(self): diff --git a/orchestra/apps/orchestration/middlewares.py b/orchestra/apps/orchestration/middlewares.py index 19be9b8d..081d7990 100644 --- a/orchestra/apps/orchestration/middlewares.py +++ b/orchestra/apps/orchestration/middlewares.py @@ -1,7 +1,7 @@ from threading import local from django.core.urlresolvers import resolve -from django.db.models.signals import pre_delete, post_save +from django.db.models.signals import pre_delete, post_save, m2m_changed from django.dispatch import receiver from django.http.response import HttpResponseServerError @@ -23,6 +23,17 @@ def pre_delete_collector(sender, *args, **kwargs): if sender not in [BackendLog, Operation]: OperationsMiddleware.collect(Operation.DELETE, **kwargs) +@receiver(m2m_changed, dispatch_uid='orchestration.m2m_collector') +def m2m_collector(sender, *args, **kwargs): + # m2m relations without intermediary models are shit + # model.post_save is not sent and by the time related.post_save is sent + # the objects are not accessible with RelatedManager.all() + # We have to use this inefficient technique of collecting the instances via m2m_changed.post_add + if kwargs.pop('action') == 'post_add': + for pk in kwargs['pk_set']: + kwargs['instance'] = kwargs['model'].objects.get(pk=pk) + OperationsMiddleware.collect(Operation.SAVE, **kwargs) + class OperationsMiddleware(object): """ @@ -51,19 +62,25 @@ class OperationsMiddleware(object): pending_operations = cls.get_pending_operations() for backend in ServiceBackend.get_backends(): # Check if there exists a related instance to be executed for this backend - instance = None + instances = [] if backend.is_main(kwargs['instance']): - instance = kwargs['instance'] + instances = [(kwargs['instance'], action)] else: candidate = backend.get_related(kwargs['instance']) if candidate: - delete = Operation.create(backend, candidate, Operation.DELETE) - if delete not in pending_operations: - instance = candidate - # related objects with backend.model trigger save() - action = Operation.SAVE - # Maintain consistent state of pending_operations based on save/delete behaviour - if instance is not None: + if candidate.__class__.__name__ == 'ManyRelatedManager': + candidates = candidate.all() + else: + candidates = [candidate] + for candidate in candidates: + # Check if a delete for candidate is in pending_operations + delete = Operation.create(backend, candidate, Operation.DELETE) + if delete not in pending_operations: + # related objects with backend.model trigger save() + action = Operation.SAVE + instances.append((candidate, action)) + for instance, action in instances: + # Maintain consistent state of pending_operations based on save/delete behaviour # Prevent creating a deleted instance by deleting existing saves if action == Operation.DELETE: save = Operation.create(backend, instance, Operation.SAVE) diff --git a/orchestra/apps/resources/forms.py b/orchestra/apps/resources/forms.py index 09bcdd07..d1815a72 100644 --- a/orchestra/apps/resources/forms.py +++ b/orchestra/apps/resources/forms.py @@ -7,7 +7,7 @@ from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget class ResourceForm(forms.ModelForm): verbose_name = forms.CharField(label=_("Name"), required=False, widget=ShowTextWidget(bold=True)) - allocated = forms.IntegerField(label=_("Allocated")) + allocated = forms.DecimalField(label=_("Allocated")) unit = forms.CharField(label=_("Unit"), widget=ShowTextWidget(), required=False) class Meta: diff --git a/orchestra/apps/systemusers/backends.py b/orchestra/apps/systemusers/backends.py index e8cbf872..e2152e5b 100644 --- a/orchestra/apps/systemusers/backends.py +++ b/orchestra/apps/systemusers/backends.py @@ -25,6 +25,7 @@ class SystemUserBackend(ServiceController): useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s fi mkdir -p %(home)s + chmod 750 %(home)s chown %(username)s:%(username)s %(home)s""" % context )) for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS: @@ -46,7 +47,7 @@ class SystemUserBackend(ServiceController): # TODO setacl def delete_home(self, context, user): - if user.is_main: + if user.home.rstrip('/') == user.get_base_home().rstrip('/'): # TODO delete instead of this shit context['deleted'] = context['home'].rstrip('/') + '.deleted' self.append("mv %(home)s %(deleted)s" % context) diff --git a/orchestra/apps/websites/models.py b/orchestra/apps/websites/models.py index 977952ed..22e065ce 100644 --- a/orchestra/apps/websites/models.py +++ b/orchestra/apps/websites/models.py @@ -13,7 +13,7 @@ from . import settings class Website(models.Model): """ Models a web site, also known as virtual host """ - name = models.CharField(_("name"), max_length=128, unique=True, + name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='websites') @@ -25,12 +25,21 @@ class Website(models.Model): contents = models.ManyToManyField('webapps.WebApp', through='websites.Content') is_active = models.BooleanField(_("active"), default=True) + class Meta: + unique_together = ('name', 'account') + def __unicode__(self): return self.name @property def unique_name(self): - return "%s-%i" % (self.name, self.pk) + return settings.WEBSITES_UNIQUE_NAME_FORMAT % { + 'id': self.id, + 'pk': self.pk, + 'account': self.account.username, + 'port': self.port, + 'name': self.name, + } @property def protocol(self): @@ -53,8 +62,8 @@ class Website(models.Model): def get_www_log_path(self): context = { - 'user_home': self.account.main_systemuser.get_home(), - 'username': self.account.username, + 'home': self.account.main_systemuser.get_home(), + 'account': self.account.username, 'name': self.name, 'unique_name': self.unique_name } diff --git a/orchestra/apps/websites/settings.py b/orchestra/apps/websites/settings.py index dac9862e..1b7903b6 100644 --- a/orchestra/apps/websites/settings.py +++ b/orchestra/apps/websites/settings.py @@ -2,6 +2,10 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ +WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT', + '%(account)s-%(name)s') + + WEBSITES_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', ( (80, 'HTTP'), (443, 'HTTPS'), @@ -87,8 +91,7 @@ WEBSITES_WEBALIZER_PATH = getattr(settings, 'WEBSITES_WEBALIZER_PATH', WEBSITES_WEBSITE_WWW_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_LOG_PATH', - # %(user_home)s %(name)s %(unique_name)s %(username)s - '/var/log/apache2/virtual/%(unique_name)s') + '/var/log/apache2/virtual/%(unique_name)s.log') WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS', diff --git a/orchestra/conf/base_settings.py b/orchestra/conf/base_settings.py index 4c594510..49b874b4 100644 --- a/orchestra/conf/base_settings.py +++ b/orchestra/conf/base_settings.py @@ -95,7 +95,7 @@ INSTALLED_APPS = ( 'admin_tools', 'admin_tools.theming', 'admin_tools.menu', - 'admin_tools.dashboard', +# 'admin_tools.dashboard', 'rest_framework', 'rest_framework.authtoken', 'passlib.ext.django',