Split Operation and BackendOperation

This commit is contained in:
Marc Aymerich 2015-04-07 15:14:49 +00:00
parent 4a6d29ebd7
commit bac2b94d70
34 changed files with 173 additions and 114 deletions

16
TODO.md
View file

@ -11,8 +11,6 @@
* env vars instead of multiple settings files: https://devcenter.heroku.com/articles/config-vars ?
# TODO Log changes from rest api (serialized objects)
* backend logs with hal logo
* LAST version of this shit http://wkhtmltopdf.org/downloads.h otml
@ -175,9 +173,8 @@ require_once(/etc/moodles/.$moodle_host.config.php);``` moodle/drupl
* autoexpand mailbox.filter according to filtering options (js)
* allow empty metric pack for default rates? changes on rating algo
# IMPORTANT make sure no order is created for mailboxes that include disk? or just don't produce lines with cost == 0 or quantity 0 ? maybe minimal quantity for billing? like 0.1 ? or minimal price? per line or per bill?
# don't produce lines with cost == 0 or quantity 0 ? maybe minimal quantity for billing? like 0.1 ? or minimal price? per line or per bill?
* Improve performance of admin change lists with debug toolbar and prefech_related
# DOMINI REGISTRE MIGRATION SCRIPTS
# lines too long on invoice, double lines or cut, and make margin wider
@ -191,7 +188,7 @@ require_once(/etc/moodles/.$moodle_host.config.php);``` moodle/drupl
# display subline links on billlines, to show that they exists.
* update service orders on a celery task? because it take alot
# billline quantity eval('10x100') instead of miningless description '(10*100)'
# billline quantity eval('10x100') instead of miningless description '(10*100)' line.verbose_quantity
# FIXME do more test, make sure billed until doesn't get uodated whhen services are billed with les metric, and don't upgrade billed_until when undoing under this circumstances
* line 513: change threshold and one time service metric change should update last value if not billed, only record for recurring invoicing. postpay services should store the last metric for pricing period.
@ -211,12 +208,12 @@ require_once(/etc/moodles/.$moodle_host.config.php);``` moodle/drupl
* modeladmin Default filter + search isn't working, prepend filter when searching
# IMPORTANT do all modles.py TODOs and create migrations for finished apps
* create service help templates based on urlqwargs with the most basic services.
# TDOO Base price: domini propi (all domains) + extra for other domains
# IMPORTANT op.instance = copy.deepcopy(instance) ValueError: Cannot assign "<SaaS: blog@WordPressService>": "SaaS" instance isn't saved in the database.
# Separate operation from models !! BackendOperation and Operation
Translation
-----------
@ -258,7 +255,7 @@ https://code.djangoproject.com/ticket/24576
# FIXME what to do when deleting accounts? set fk null and fill a username charfield? issues, invoices.. we whant all this to go away?
* implement delete All related services
# FIXME address name change does not remove old one :P
# FIXME address name change does not remove old one :P, readonly, perhaps we can regenerate all addresses using backend.prepare()?
* read https://docs.djangoproject.com/en/dev/releases/1.8/ and fix deprecation warnings
* remove admin object display_links , like contents webapps
@ -277,4 +274,5 @@ https://code.djangoproject.com/ticket/24576
* migrate to DRF3.x
* move all tests on django-orchestra/tests
* move all tests to django-orchestra/tests
* *natural keys: those fields that uniquely identify a service, list.name, website.name, webapp.name+account, make sure rest api can not edit thos things

View file

@ -244,7 +244,7 @@ class ChangePasswordAdminMixin(object):
'save_as': False,
'show_save': True,
}
context.update(admin.site.each_context())
context.update(admin.site.each_context(request))
return TemplateResponse(request,
self.change_user_password_template,
context, current_app=self.admin_site.name)

View file

@ -6,7 +6,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
from orchestra.contrib.orchestration.models import BackendOperation as Operation
from orchestra.contrib.orchestration import Operation
from orchestra.core import services, accounts
from orchestra.utils import send_email_template

View file

@ -38,7 +38,7 @@ class MySQLBackend(ServiceController):
if database.type != database.MYSQL:
return
context = self.get_context(database)
self.append("mysql -e 'DROP DATABASE `%(database)s`;' || exit_code=1" % context)
self.append("mysql -e 'DROP DATABASE `%(database)s`;' || exit_code=$?" % context)
self.append("mysql mysql -e 'DELETE FROM db WHERE db = \"%(database)s\";'" % context)
def commit(self):
@ -76,7 +76,7 @@ class MySQLUserBackend(ServiceController):
return
context = self.get_context(user)
self.append(textwrap.dedent("""\
mysql -e 'DROP USER "%(username)s"@"%(host)s";' \
mysql -e 'DROP USER "%(username)s"@"%(host)s";' || exit_code=$? \
""") % context
)

View file

@ -4,7 +4,7 @@ import textwrap
from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.orchestration import ServiceController, replace
from orchestra.contrib.orchestration.models import BackendOperation as Operation
from orchestra.contrib.orchestration import Operation
from . import settings
@ -41,10 +41,11 @@ class Bind9MasterDomainBackend(ServiceController):
def update_conf(self, context):
self.append(textwrap.dedent("""\
sed '/zone "%(name)s".*/,/^\s*};\s*$/!d' %(conf_path)s | diff -B -I"^\s*//" - <(echo '%(conf)s') || {
conf='%(conf)s'
sed '/zone "%(name)s".*/,/^\s*};\s*$/!d' %(conf_path)s | diff -B -I"^\s*//" - <(echo "${conf}") || {
sed -i -e '/zone\s\s*"%(name)s".*/,/^\s*};/d' \\
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s
echo '%(conf)s' >> %(conf_path)s
echo "${conf}" >> %(conf_path)s
UPDATED=1
}""") % context
)
@ -80,7 +81,7 @@ class Bind9MasterDomainBackend(ServiceController):
def get_servers(self, domain, backend):
""" Get related server IPs from registered backend routes """
from orchestra.contrib.orchestration.manager import router
operation = Operation.create(backend, domain, Operation.SAVE)
operation = Operation(backend, domain, Operation.SAVE)
servers = []
for server in router.get_servers(operation):
servers.append(server.get_ip())

View file

@ -107,7 +107,8 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
'email', 'account_link', 'domain_link', 'display_mailboxes', 'display_forward',
)
list_filter = (HasMailboxListFilter, HasForwardListFilter)
fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
fields = ('account_link', 'email_link', 'mailboxes', 'forward')
add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
inlines = [AutoresponseInline]
search_fields = ('name', 'domain__name', 'forward', 'mailboxes__name', 'account__username')
readonly_fields = ('account_link', 'domain_link', 'email_link')

View file

@ -17,16 +17,14 @@ class Mailbox(models.Model):
name = models.CharField(_("name"), max_length=64, unique=True,
help_text=_("Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."),
validators=[
RegexValidator(r'^[\w.@+-]+$', _("Enter a valid mailbox name."))
RegexValidator(r'^[\w.@+-]+$', _("Enter a valid mailbox name.")),
])
password = models.CharField(_("password"), max_length=128)
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='mailboxes')
filtering = models.CharField(max_length=16,
default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING,
choices=[
(k, v[0]) for k,v in settings.MAILBOXES_MAILBOX_FILTERINGS.items()
])
choices=[(k, v[0]) for k,v in settings.MAILBOXES_MAILBOX_FILTERINGS.items()])
custom_filtering = models.TextField(_("filtering"), blank=True,
validators=[validators.validate_sieve],
help_text=_("Arbitrary email filtering in sieve language. "
@ -80,7 +78,7 @@ class Mailbox(models.Model):
def get_local_address(self):
if not settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN:
raise AttributeError("Mailboxes do not have a defined local address domain")
raise AttributeError("Mailboxes do not have a defined local address domain.")
return '@'.join((self.name, settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN))

View file

@ -42,7 +42,7 @@ class MiscServiceAdmin(ExtendedModelAdmin):
num_instances.admin_order_field = 'instances__count'
def get_queryset(self, request):
qs = super(MiscServiceAdmin, self).queryset(request)
qs = super(MiscServiceAdmin, self).get_queryset(request)
return qs.annotate(models.Count('instances', distinct=True))
def formfield_for_dbfield(self, db_field, **kwargs):

View file

@ -1 +1,60 @@
import copy
from .backends import ServiceBackend, ServiceController, replace
class Operation():
DELETE = 'delete'
SAVE = 'save'
MONITOR = 'monitor'
EXCEEDED = 'exceeded'
RECOVERY = 'recovery'
def __str__(self):
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
def __hash__(self):
""" set() """
return hash(self.backend) + hash(self.instance) + hash(self.action)
def __eq__(self, operation):
""" set() """
return hash(self) == hash(operation)
def __init__(self, backend, instance, action, servers=None):
self.backend = backend
# instance should maintain any dynamic attribute until backend execution
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
self.instance = copy.deepcopy(instance)
self.action = action
self.servers = servers
@classmethod
def execute(cls, operations, async=False):
from . import manager
scripts, block = manager.generate(operations)
return manager.execute(scripts, block=block, async=async)
@classmethod
def execute_action(cls, instance, action):
backends = ServiceBackend.get_backends(instance=instance, action=action)
operations = [cls(backend_cls, instance, action) for backend_cls in backends]
return cls.execute(operations)
def preload_context(self):
"""
Heuristic
Running get_context will prevent most of related objects do not exist errors
"""
if self.action == self.DELETE:
if hasattr(self.backend, 'get_context'):
self.backend().get_context(self.instance)
def create(self, log):
from .models import BackendOperation
return BackendOperation.objects.create(
log=log,
backend=self.backend.get_name(),
instance=self.instance,
action=self.action,
)

View file

@ -1,10 +1,12 @@
from django.contrib import admin
from django.contrib import admin, messages
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.html import monospace_format
from orchestra.admin.utils import admin_link, admin_date, admin_colored
from . import settings
from .backends import ServiceBackend
from .models import Server, Route, BackendLog, BackendOperation
from .widgets import RouteBackendSelect
@ -66,6 +68,19 @@ class RouteAdmin(admin.ModelAdmin):
if obj:
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
return form
def show_orchestration_disabled(self, request):
if settings.ORCHESTRATION_DISABLE_EXECUTION:
msg = _("Orchestration execution is disabled by <tt>ORCHESTRATION_DISABLE_EXECUTION</tt> setting.")
self.message_user(request, mark_safe(msg), messages.WARNING)
def changelist_view(self, request, extra_context=None):
self.show_orchestration_disabled(request)
return super(RouteAdmin, self).changelist_view(request, extra_context)
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
self.show_orchestration_disabled(request)
return super(RouteAdmin, self).changeform_view(request, object_id, form_url, extra_context)
class BackendOperationInline(admin.TabularInline):

View file

@ -10,13 +10,13 @@ class Command(BaseCommand):
help = 'Runs orchestration backends.'
def add_arguments(self, parser):
parser.add_argument('model', nargs='+',
help='App label of an application to synchronize the
parser.add_argument('query', nargs='?',
parser.add_argument('model',
help='Label of a model to execute the orchestration.')
parser.add_argument('query', nargs='*',
help='Query arguments for filter().')
parser.add_argument('--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.')
parser.add_argument('--action', action='store', dest='database',
parser.add_argument('--action', action='store', dest='action',
default='save', help='Executes action. Defaults to "save".')
def handle(self, *args, **options):

View file

@ -9,10 +9,10 @@ from django.core.mail import mail_admins
from orchestra.utils.python import import_class
from . import settings
from . import settings, Operation
from .backends import ServiceBackend
from .helpers import send_report
from .models import BackendLog, BackendOperation as Operation
from .models import BackendLog
from .signals import pre_action, post_action
@ -95,6 +95,9 @@ def generate(operations):
def execute(scripts, block=False, async=False):
""" executes the operations on the servers """
if settings.ORCHESTRATION_DISABLE_EXECUTION:
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION settings.')
return []
# Execute scripts on each server
threads = []
executions = []
@ -120,10 +123,9 @@ def execute(scripts, block=False, async=False):
if hasattr(execution, 'log'):
for operation in operations:
logger.info("Executed %s" % str(operation))
operation.log = execution.log
if operation.object_id:
# Not all backends are call with objects saved on the database
operation.save()
if operation.instance.pk:
# Not all backends are called with objects saved on the database
operation.create(execution.log)
stdout = execution.log.stdout.strip()
stdout and logger.debug('STDOUT %s', stdout)
stderr = execution.log.stderr.strip()
@ -157,7 +159,7 @@ def collect(instance, action, **kwargs):
candidates = [candidate]
for candidate in candidates:
# Check if a delete for candidate is in operations
delete_mock = Operation.create(backend_cls, candidate, Operation.DELETE)
delete_mock = Operation(backend_cls, candidate, Operation.DELETE)
if delete_mock not in operations:
# related objects with backend.model trigger save()
instances.append((candidate, Operation.SAVE))
@ -165,7 +167,7 @@ def collect(instance, action, **kwargs):
# Maintain consistent state of operations based on save/delete behaviour
# Prevent creating a deleted selected by deleting existing saves
if iaction == Operation.DELETE:
save_mock = Operation.create(backend_cls, selected, Operation.SAVE)
save_mock = Operation(backend_cls, selected, Operation.SAVE)
try:
operations.remove(save_mock)
except KeyError:
@ -185,7 +187,7 @@ def collect(instance, action, **kwargs):
break
if not execute:
continue
operation = Operation.create(backend_cls, selected, iaction)
operation = Operation(backend_cls, selected, iaction)
# Only schedule operations if the router gives servers to execute into
servers = router.get_servers(operation, cache=route_cache)
if servers:

View file

@ -8,9 +8,9 @@ from django.http.response import HttpResponseServerError
from orchestra.utils.python import OrderedSet
from . import manager
from . import manager, Operation
from .helpers import message_user
from .models import BackendLog, BackendOperation as Operation
from .models import BackendLog
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')

View file

@ -1,9 +1,9 @@
import copy
import socket
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.functional import cached_property
from django.utils.module_loading import autodiscover_modules
from django.utils.translation import ugettext_lazy as _
@ -98,12 +98,6 @@ class BackendOperation(models.Model):
"""
Encapsulates an operation, storing its related object, the action and the backend.
"""
DELETE = 'delete'
SAVE = 'save'
MONITOR = 'monitor'
EXCEEDED = 'exceeded'
RECOVERY = 'recovery'
log = models.ForeignKey('orchestration.BackendLog', related_name='operations')
backend = models.CharField(_("backend"), max_length=256)
action = models.CharField(_("action"), max_length=64)
@ -119,46 +113,7 @@ class BackendOperation(models.Model):
def __str__(self):
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
def __hash__(self):
""" set() """
backend = getattr(self, 'backend', self.backend)
return hash(backend) + hash(self.instance) + hash(self.action)
def __eq__(self, operation):
""" set() """
return hash(self) == hash(operation)
@classmethod
def create(cls, backend, instance, action, servers=None):
op = cls(backend=backend.get_name(), instance=instance, action=action)
op.backend = backend
# instance should maintain any dynamic attribute until backend execution
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
op.instance = copy.deepcopy(instance)
op.servers = servers
return op
@classmethod
def execute(cls, operations, async=False):
from . import manager
scripts, block = manager.generate(operations)
return manager.execute(scripts, block=block, async=async)
@classmethod
def execute_action(cls, instance, action):
backends = ServiceBackend.get_backends(instance=instance, action=action)
operations = [cls.create(backend_cls, instance, action) for backend_cls in backends]
return cls.execute(operations)
def preload_context(self):
"""
Heuristic
Running get_context will prevent most of related objects do not exist errors
"""
if self.action == self.DELETE:
if hasattr(self.backend, 'get_context'):
self.backend().get_context(self.instance)
@cached_property
def backend_class(self):
return ServiceBackend.get_backend(self.backend)
@ -187,7 +142,7 @@ class Route(models.Model):
def __str__(self):
return "%s@%s" % (self.backend, self.host)
@property
@cached_property
def backend_class(self):
return ServiceBackend.get_backend(self.backend)

View file

@ -23,3 +23,8 @@ ORCHESTRATION_ROUTER = getattr(settings, 'ORCHESTRATION_ROUTER',
ORCHESTRATION_TEMP_SCRIPT_PATH = getattr(settings, 'ORCHESTRATION_TEMP_SCRIPT_PATH',
'/dev/shm'
)
ORCHESTRATION_DISABLE_EXECUTION = getattr(settings, 'ORCHESTRATION_DISABLE_EXECUTION',
False
)

View file

@ -1,7 +1,7 @@
from orchestra.utils.tests import BaseTestCase
from .. import backends
from ..models import Route, Server, BackendOperation as Operation
from .. import backends, Operation
from ..models import Route, Server
class RouterTests(BaseTestCase):

View file

@ -241,6 +241,7 @@ class MetricStorage(models.Model):
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
created_on = models.DateField(_("created"), auto_now_add=True)
# default=lambda: timezone.now())
# TODO time field?
updated_on = models.DateTimeField(_("updated"))
class Meta:

View file

@ -1,6 +1,7 @@
from django.core.validators import ValidationError
from django.db import models
from django.db.models import Q
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services, accounts
@ -14,6 +15,9 @@ from . import rating
class Plan(models.Model):
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name])
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
# TODO is_active = models.BooleanField(_("active"), default=True,
# help_text=_("Designates whether this account should be treated as active. "
# "Unselect this instead of deleting accounts."))
is_default = models.BooleanField(_("default"), default=False,
help_text=_("Designates whether this plan is used by default or not."))
is_combinable = models.BooleanField(_("combinable"), default=True,
@ -42,6 +46,10 @@ class ContractedPlan(models.Model):
def __str__(self):
return str(self.plan)
@cached_property
def active(self):
return self.plan.is_active and self.account.is_active
def clean(self):
if not self.pk and not self.plan.allow_multiple:
if ContractedPlan.objects.filter(plan=self.plan, account=self.account).exists():

View file

@ -1,6 +1,6 @@
from celery import shared_task
from orchestra.contrib.orchestration.models import BackendOperation as Operation
from orchestra.contrib.orchestration import Operation
from orchestra.models.utils import get_model_field_path
from .backends import ServiceMonitor

View file

@ -12,6 +12,7 @@ from .services import SoftwareService
class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
list_display = ('name', 'service', 'display_site_domain', 'account_link', 'is_active')
list_filter = ('service', 'is_active')
search_fields = ('name', 'account__username')
change_readonly_fields = ('service',)
plugin = SoftwareService
plugin_field = 'service'

View file

@ -18,8 +18,9 @@ class BSCWBackend(ServiceController):
self.append(textwrap.dedent("""\
if [[ $(%(bsadmin)s register %(email)s) ]]; then
echo 'ValidationError: email-exists'
elif [[ $(%(bsadmin)s users -n %(username)s) ]]; then
echo 'ValidationError: username-exists'
fi
if [[ $(%(bsadmin)s users -n %(username)s) ]]; then
echo 'ValidationError: user-exists'
fi""") % context
)

View file

@ -52,6 +52,8 @@ class SaaS(models.Model):
return self.is_active and self.account.is_active
def clean(self):
if not self.pk:
self.name = self.name.lower()
self.data = self.service_instance.clean_data()
def get_site_domain(self):

View file

@ -4,7 +4,7 @@ from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
from orchestra import plugins
from orchestra.contrib.orchestration.models import BackendOperation as Operation
from orchestra.contrib.orchestration import Operation
from orchestra.core import validators
from orchestra.forms import widgets
from orchestra.plugins.forms import PluginDataForm
@ -109,7 +109,7 @@ class SoftwareService(plugins.Plugin):
errors = {}
if 'user-exists' in log.stdout:
errors['name'] = _("User with this username already exists.")
elif 'email-exists' in log.stdout:
if 'email-exists' in log.stdout:
errors['email'] = _("User with this email address already exists.")
if errors:
raise ValidationError(errors)

View file

@ -7,6 +7,10 @@ from .options import SoftwareService, SoftwareServiceForm
class WordPressForm(SoftwareServiceForm):
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}))
def __init__(self, *args, **kwargs):
super(WordPressForm, self).__init__(*args, **kwargs)
self.fields['name'].label = _("Site name")
class WordPressDataSerializer(serializers.Serializer):
@ -18,5 +22,5 @@ class WordPressService(SoftwareService):
form = WordPressForm
serializer = WordPressDataSerializer
icon = 'orchestra/icons/apps/WordPress.png'
site_name_base_domain = 'blogs.orchestra.lan'
site_base_domain = 'blogs.orchestra.lan'
change_readonly_fileds = ('email',)

View file

@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.admin.utils import get_object_from_url
@transaction.atomic
def update_orders(modeladmin, request, queryset, extra_context=None):
if not queryset:

View file

@ -53,7 +53,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
bool(self.matches(obj))
except Exception as exception:
name = type(exception).__name__
raise ValidationError(': '.join((name, exception)))
raise ValidationError(': '.join((name, str(exception))))
def validate_metric(self, service):
try:
@ -64,7 +64,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
bool(self.get_metric(obj))
except Exception as exception:
name = type(exception).__name__
raise ValidationError(': '.join((name, exception)))
raise ValidationError(': '.join((name, str(exception))))
def get_content_type(self):
if not self.model:

View file

@ -4,7 +4,7 @@ from django.core.exceptions import PermissionDenied
from django.utils.translation import ungettext, ugettext_lazy as _
from orchestra.admin.decorators import action_with_confirmation
from orchestra.contrib.orchestration.models import BackendOperation as Operation
from orchestra.contrib.orchestration import Operation
class GrantPermissionForm(forms.Form):

View file

@ -74,7 +74,7 @@ class UNIXUserBackend(ServiceController):
'shell': user.shell,
'mainuser': user.username if user.is_main else user.account.username,
'home': user.get_home(),
'base_home': self.get_base_home(),
'base_home': user.get_base_home(),
}
return replace(context, "'", '"')
@ -111,7 +111,7 @@ class Exim4Traffic(ServiceMonitor):
script_executable = '/usr/bin/python'
def prepare(self):
mainlog = settings.LISTS_MAILMAN_POST_LOG_PATH
mainlog = settings.SYSTEMUSERS_MAIL_LOG_PATH
context = {
'current_date': self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
'mainlogs': str((mainlog, mainlog+'.1')),

View file

@ -40,7 +40,6 @@ class SystemUser(models.Model):
groups = models.ManyToManyField('self', blank=True, symmetrical=False,
help_text=_("A new group will be created for the user. "
"Which additional groups would you like them to be a member of?"))
# is_main = models.BooleanField(_("is main"), default=False)
is_active = models.BooleanField(_("active"), default=True,
help_text=_("Designates whether this account should be treated as active. "
"Unselect this instead of deleting accounts."))

View file

@ -32,6 +32,10 @@ SYSTEMUSERS_FTP_LOG_PATH = getattr(settings, 'SYSTEMUSERS_FTP_LOG_PATH',
)
SYSTEMUSERS_MAIL_LOG_PATH = getattr(settings, 'SYSTEMUSERS_MAIL_LOG_PATH',
'/var/log/exim4/mainlog'
)
SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = getattr(settings, 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
('www-data',)
)

View file

@ -55,7 +55,7 @@ class PHPAppOption(AppOption):
def validate(self):
super(PHPAppOption, self).validate()
if self.deprecated:
php_version = self.instance.webapp.type_instance.get_php_version()
php_version = self.instance.webapp.type_instance.get_php_version_number()
if php_version and php_version > self.deprecated:
raise ValidationError(
_("This option is deprecated since PHP version %s.") % str(self.deprecated)

View file

@ -163,9 +163,9 @@ class Apache2Backend(ServiceController):
return [(location, directives)]
def get_ssl(self, directives):
cert = directives.get('ssl_cert')
key = directives.get('ssl_key')
ca = directives.get('ssl_ca')
cert = directives.get('ssl-cert')
key = directives.get('ssl-key')
ca = directives.get('ssl-ca')
if not (cert and key):
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
key = [settings.WEBSITES_DEFAULT_SSL_KEY]
@ -181,11 +181,11 @@ class Apache2Backend(ServiceController):
def get_security(self, directives):
security = []
for rules in directives.get('sec_rule_remove', []):
for rules in directives.get('sec-rule-remove', []):
for rule in rules.value.split():
sec_rule = "SecRuleRemoveById %i" % int(rule)
security.append(('', sec_rule))
for location in directives.get('sec_engine', []):
for location in directives.get('sec-engine', []):
sec_rule = textwrap.dedent("""\
<Location %s>
SecRuleEngine off

View file

@ -80,7 +80,7 @@ class Proxy(SiteDirective):
class ErrorDocument(SiteDirective):
name = 'error_document'
name = 'error-document'
verbose_name = _("ErrorDocumentRoot")
help_text = _("&lt;error code&gt; &lt;URL/path/message&gt;<br>"
"<tt>&nbsp;500 http://foo.example.com/cgi-bin/tester</tt><br>"
@ -93,7 +93,7 @@ class ErrorDocument(SiteDirective):
class SSLCA(SiteDirective):
name = 'ssl_ca'
name = 'ssl-ca'
verbose_name = _("SSL CA")
help_text = _("Filesystem path of the CA certificate file.")
regex = r'^[^ ]+$'
@ -102,7 +102,7 @@ class SSLCA(SiteDirective):
class SSLCert(SiteDirective):
name = 'ssl_cert'
name = 'ssl-cert'
verbose_name = _("SSL cert")
help_text = _("Filesystem path of the certificate file.")
regex = r'^[^ ]+$'
@ -111,7 +111,7 @@ class SSLCert(SiteDirective):
class SSLKey(SiteDirective):
name = 'ssl_key'
name = 'ssl-key'
verbose_name = _("SSL key")
help_text = _("Filesystem path of the key file.")
regex = r'^[^ ]+$'
@ -120,7 +120,7 @@ class SSLKey(SiteDirective):
class SecRuleRemove(SiteDirective):
name = 'sec_rule_remove'
name = 'sec-rule-remove'
verbose_name = _("SecRuleRemoveById")
help_text = _("Space separated ModSecurity rule IDs.")
regex = r'^[0-9\s]+$'
@ -128,7 +128,7 @@ class SecRuleRemove(SiteDirective):
class SecEngine(SiteDirective):
name = 'sec_engine'
name = 'sec-engine'
verbose_name = _("SecRuleEngine Off")
help_text = _("URL path with disabled modsecurity engine.")
regex = r'^/[^ ]*$'

View file

@ -46,6 +46,10 @@ class Website(models.Model):
context = self.get_settings_context()
return settings.WEBSITES_UNIQUE_NAME_FORMAT % context
@cached_property
def active(self):
return self.is_active and self.account.is_active
def get_settings_context(self):
""" format settings strings """
return {