Fixed bug with m2m without intermediary model backends not being executed
This commit is contained in:
parent
f2dbc0ed42
commit
66fa3bb4c6
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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("""
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,12 +93,16 @@ 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:
|
||||
if active and backend.get_name() not in active_backends:
|
||||
continue
|
||||
include = True
|
||||
if instance:
|
||||
opts = instance._meta
|
||||
|
@ -109,8 +113,7 @@ class ServiceBackend(plugins.Plugin):
|
|||
include = False
|
||||
if include:
|
||||
included.append(backend)
|
||||
backends = included
|
||||
return backends
|
||||
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):
|
||||
|
|
|
@ -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:
|
||||
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:
|
||||
instance = candidate
|
||||
# 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
|
||||
if instance is not None:
|
||||
# Prevent creating a deleted instance by deleting existing saves
|
||||
if action == Operation.DELETE:
|
||||
save = Operation.create(backend, instance, Operation.SAVE)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue