Split Operation and BackendOperation
This commit is contained in:
parent
4a6d29ebd7
commit
bac2b94d70
16
TODO.md
16
TODO.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')),
|
||||
|
|
|
@ -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."))
|
||||
|
|
|
@ -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',)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -80,7 +80,7 @@ class Proxy(SiteDirective):
|
|||
|
||||
|
||||
class ErrorDocument(SiteDirective):
|
||||
name = 'error_document'
|
||||
name = 'error-document'
|
||||
verbose_name = _("ErrorDocumentRoot")
|
||||
help_text = _("<error code> <URL/path/message><br>"
|
||||
"<tt> 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'^/[^ ]*$'
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue