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'])
|
self.save(update_fields=['is_active'])
|
||||||
# Trigger save() on related objects that depend on this account
|
# Trigger save() on related objects that depend on this account
|
||||||
for rel in self._meta.get_all_related_objects():
|
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
|
continue
|
||||||
try:
|
try:
|
||||||
rel.model._meta.get_field_by_name('is_active')
|
source._meta.get_field_by_name('is_active')
|
||||||
except models.FieldDoesNotExist:
|
except models.FieldDoesNotExist:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -171,7 +171,7 @@ class MailmanTraffic(ServiceMonitor):
|
||||||
def monitor(self, mail_list):
|
def monitor(self, mail_list):
|
||||||
context = self.get_context(mail_list)
|
context = self.get_context(mail_list)
|
||||||
self.append(
|
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):
|
def get_context(self, mail_list):
|
||||||
last_date = timezone.localtime(self.get_last_date(mail_list.pk))
|
last_date = timezone.localtime(self.get_last_date(mail_list.pk))
|
||||||
|
|
|
@ -117,6 +117,10 @@ class PasswdVirtualUserBackend(ServiceController):
|
||||||
class PostfixAddressBackend(ServiceController):
|
class PostfixAddressBackend(ServiceController):
|
||||||
verbose_name = _("Postfix address")
|
verbose_name = _("Postfix address")
|
||||||
model = 'mailboxes.Address'
|
model = 'mailboxes.Address'
|
||||||
|
# TODO
|
||||||
|
related_models = (
|
||||||
|
('mailboxes.Mailbox', 'addresses'),
|
||||||
|
)
|
||||||
|
|
||||||
def include_virtual_alias_domain(self, context):
|
def include_virtual_alias_domain(self, context):
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
|
|
|
@ -115,7 +115,7 @@ class Address(models.Model):
|
||||||
def destination(self):
|
def destination(self):
|
||||||
destinations = list(self.mailboxes.values_list('name', flat=True))
|
destinations = list(self.mailboxes.values_list('name', flat=True))
|
||||||
if self.forward:
|
if self.forward:
|
||||||
destinations += self.forward
|
destinations += self.forward.split()
|
||||||
return ' '.join(destinations)
|
return ' '.join(destinations)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from functools import partial
|
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 import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -93,12 +93,16 @@ class ServiceBackend(plugins.Plugin):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@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()
|
backends = cls.get_plugins()
|
||||||
included = []
|
included = []
|
||||||
|
if active:
|
||||||
|
active_backends = Route.objects.filter(is_active=True).values_list('backend', flat=True)
|
||||||
# Filter for instance or action
|
# Filter for instance or action
|
||||||
if instance or action:
|
|
||||||
for backend in backends:
|
for backend in backends:
|
||||||
|
if active and backend.get_name() not in active_backends:
|
||||||
|
continue
|
||||||
include = True
|
include = True
|
||||||
if instance:
|
if instance:
|
||||||
opts = instance._meta
|
opts = instance._meta
|
||||||
|
@ -109,8 +113,7 @@ class ServiceBackend(plugins.Plugin):
|
||||||
include = False
|
include = False
|
||||||
if include:
|
if include:
|
||||||
included.append(backend)
|
included.append(backend)
|
||||||
backends = included
|
return included
|
||||||
return backends
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_backend(cls, name):
|
def get_backend(cls, name):
|
||||||
|
@ -118,7 +121,7 @@ class ServiceBackend(plugins.Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def model_class(cls):
|
def model_class(cls):
|
||||||
return get_model(cls.model)
|
return apps.get_model(cls.model)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def scripts(self):
|
def scripts(self):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
from django.core.urlresolvers import resolve
|
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.dispatch import receiver
|
||||||
from django.http.response import HttpResponseServerError
|
from django.http.response import HttpResponseServerError
|
||||||
|
|
||||||
|
@ -23,6 +23,17 @@ def pre_delete_collector(sender, *args, **kwargs):
|
||||||
if sender not in [BackendLog, Operation]:
|
if sender not in [BackendLog, Operation]:
|
||||||
OperationsMiddleware.collect(Operation.DELETE, **kwargs)
|
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):
|
class OperationsMiddleware(object):
|
||||||
"""
|
"""
|
||||||
|
@ -51,19 +62,25 @@ class OperationsMiddleware(object):
|
||||||
pending_operations = cls.get_pending_operations()
|
pending_operations = cls.get_pending_operations()
|
||||||
for backend in ServiceBackend.get_backends():
|
for backend in ServiceBackend.get_backends():
|
||||||
# Check if there exists a related instance to be executed for this backend
|
# Check if there exists a related instance to be executed for this backend
|
||||||
instance = None
|
instances = []
|
||||||
if backend.is_main(kwargs['instance']):
|
if backend.is_main(kwargs['instance']):
|
||||||
instance = kwargs['instance']
|
instances = [(kwargs['instance'], action)]
|
||||||
else:
|
else:
|
||||||
candidate = backend.get_related(kwargs['instance'])
|
candidate = backend.get_related(kwargs['instance'])
|
||||||
if candidate:
|
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)
|
delete = Operation.create(backend, candidate, Operation.DELETE)
|
||||||
if delete not in pending_operations:
|
if delete not in pending_operations:
|
||||||
instance = candidate
|
|
||||||
# related objects with backend.model trigger save()
|
# related objects with backend.model trigger save()
|
||||||
action = Operation.SAVE
|
action = Operation.SAVE
|
||||||
|
instances.append((candidate, action))
|
||||||
|
for instance, action in instances:
|
||||||
# Maintain consistent state of pending_operations based on save/delete behaviour
|
# 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
|
# Prevent creating a deleted instance by deleting existing saves
|
||||||
if action == Operation.DELETE:
|
if action == Operation.DELETE:
|
||||||
save = Operation.create(backend, instance, Operation.SAVE)
|
save = Operation.create(backend, instance, Operation.SAVE)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget
|
||||||
class ResourceForm(forms.ModelForm):
|
class ResourceForm(forms.ModelForm):
|
||||||
verbose_name = forms.CharField(label=_("Name"), required=False,
|
verbose_name = forms.CharField(label=_("Name"), required=False,
|
||||||
widget=ShowTextWidget(bold=True))
|
widget=ShowTextWidget(bold=True))
|
||||||
allocated = forms.IntegerField(label=_("Allocated"))
|
allocated = forms.DecimalField(label=_("Allocated"))
|
||||||
unit = forms.CharField(label=_("Unit"), widget=ShowTextWidget(), required=False)
|
unit = forms.CharField(label=_("Unit"), widget=ShowTextWidget(), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -25,6 +25,7 @@ class SystemUserBackend(ServiceController):
|
||||||
useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||||
fi
|
fi
|
||||||
mkdir -p %(home)s
|
mkdir -p %(home)s
|
||||||
|
chmod 750 %(home)s
|
||||||
chown %(username)s:%(username)s %(home)s""" % context
|
chown %(username)s:%(username)s %(home)s""" % context
|
||||||
))
|
))
|
||||||
for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS:
|
for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS:
|
||||||
|
@ -46,7 +47,7 @@ class SystemUserBackend(ServiceController):
|
||||||
# TODO setacl
|
# TODO setacl
|
||||||
|
|
||||||
def delete_home(self, context, user):
|
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
|
# TODO delete instead of this shit
|
||||||
context['deleted'] = context['home'].rstrip('/') + '.deleted'
|
context['deleted'] = context['home'].rstrip('/') + '.deleted'
|
||||||
self.append("mv %(home)s %(deleted)s" % context)
|
self.append("mv %(home)s %(deleted)s" % context)
|
||||||
|
|
|
@ -13,7 +13,7 @@ from . import settings
|
||||||
|
|
||||||
class Website(models.Model):
|
class Website(models.Model):
|
||||||
""" Models a web site, also known as virtual host """
|
""" 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])
|
validators=[validators.validate_name])
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='websites')
|
related_name='websites')
|
||||||
|
@ -25,12 +25,21 @@ class Website(models.Model):
|
||||||
contents = models.ManyToManyField('webapps.WebApp', through='websites.Content')
|
contents = models.ManyToManyField('webapps.WebApp', through='websites.Content')
|
||||||
is_active = models.BooleanField(_("active"), default=True)
|
is_active = models.BooleanField(_("active"), default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('name', 'account')
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_name(self):
|
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
|
@property
|
||||||
def protocol(self):
|
def protocol(self):
|
||||||
|
@ -53,8 +62,8 @@ class Website(models.Model):
|
||||||
|
|
||||||
def get_www_log_path(self):
|
def get_www_log_path(self):
|
||||||
context = {
|
context = {
|
||||||
'user_home': self.account.main_systemuser.get_home(),
|
'home': self.account.main_systemuser.get_home(),
|
||||||
'username': self.account.username,
|
'account': self.account.username,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'unique_name': self.unique_name
|
'unique_name': self.unique_name
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,10 @@ from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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', (
|
WEBSITES_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', (
|
||||||
(80, 'HTTP'),
|
(80, 'HTTP'),
|
||||||
(443, 'HTTPS'),
|
(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',
|
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.log')
|
||||||
'/var/log/apache2/virtual/%(unique_name)s')
|
|
||||||
|
|
||||||
|
|
||||||
WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS',
|
WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS',
|
||||||
|
|
|
@ -95,7 +95,7 @@ INSTALLED_APPS = (
|
||||||
'admin_tools',
|
'admin_tools',
|
||||||
'admin_tools.theming',
|
'admin_tools.theming',
|
||||||
'admin_tools.menu',
|
'admin_tools.menu',
|
||||||
'admin_tools.dashboard',
|
# 'admin_tools.dashboard',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'passlib.ext.django',
|
'passlib.ext.django',
|
||||||
|
|
Loading…
Reference in New Issue