Fixed bug with m2m without intermediary model backends not being executed

This commit is contained in:
Marc Aymerich 2015-02-27 16:57:39 +00:00
parent f2dbc0ed42
commit 66fa3bb4c6
11 changed files with 78 additions and 40 deletions

View file

@ -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:

View file

@ -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))

View file

@ -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("""

View file

@ -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):

View file

@ -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):

View file

@ -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)

View file

@ -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:

View file

@ -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)

View file

@ -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
}

View file

@ -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',

View file

@ -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',