diff --git a/TODO.md b/TODO.md index 50da2191..926ab0e8 100644 --- a/TODO.md +++ b/TODO.md @@ -180,4 +180,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im * Subdomain saving should not trigger bind slave -* Domain account change, unselected checkbox: migrate subdomains +* multiple files monitoring +* prevent adding local email addresses on account.contacts account.email + +* Resource monitoring without ROUTE alert or explicit error diff --git a/orchestra/apps/domains/admin.py b/orchestra/apps/domains/admin.py index 06efcff2..bea4093e 100644 --- a/orchestra/apps/domains/admin.py +++ b/orchestra/apps/domains/admin.py @@ -10,7 +10,7 @@ from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.utils import apps from .actions import view_zone -from .forms import RecordInlineFormSet, DomainAdminForm +from .forms import RecordInlineFormSet, CreateDomainAdminForm from .filters import TopDomainListFilter from .models import Domain, Record @@ -54,11 +54,11 @@ class DomainInline(admin.TabularInline): class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin): - # TODO name link - fields = ('name', ('account', 'migrate_subdomains'),) list_display = ( 'structured_name', 'display_is_top', 'websites', 'account_link' ) + add_fields = ('name', 'account') + fields = ('name', 'account_link') inlines = [RecordInline, DomainInline] list_filter = [TopDomainListFilter] change_readonly_fields = ('name',) @@ -66,7 +66,7 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin default_changelist_filters = ( ('top_domain', 'True'), ) - form = DomainAdminForm + add_form = CreateDomainAdminForm change_view_actions = [view_zone] def structured_name(self, domain): @@ -112,11 +112,11 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin qs = qs.prefetch_related('websites') return qs - def save_related(self, request, form, formsets, change): - super(DomainAdmin, self).save_related(request, form, formsets, change) - if form.cleaned_data['migrate_subdomains']: - domain = form.instance - domain.subdomains.update(account_id=domain.account_id) +# def save_related(self, request, form, formsets, change): +# super(DomainAdmin, self).save_related(request, form, formsets, change) +# if form.cleaned_data['migrate_subdomains']: +# domain = form.instance +# domain.subdomains.update(account_id=domain.account_id) admin.site.register(Domain, DomainAdmin) diff --git a/orchestra/apps/domains/backends.py b/orchestra/apps/domains/backends.py index c1b71568..c5a1f085 100644 --- a/orchestra/apps/domains/backends.py +++ b/orchestra/apps/domains/backends.py @@ -86,12 +86,14 @@ class Bind9MasterDomainBackend(ServiceController): return self.get_servers(domain, Bind9SlaveDomainBackend) def get_context(self, domain): + slaves = self.get_slaves(domain) context = { 'name': domain.name, 'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name}, 'subdomains': domain.subdomains.all(), 'banner': self.get_banner(), - 'slaves': '; '.join(self.get_slaves(domain)) or 'none', + 'slaves': '; '.join(slaves) or 'none', + 'also_notify': '; '.join(slaves) + ';' if slaves else '', } context.update({ 'conf_path': settings.DOMAINS_MASTERS_PATH, @@ -101,6 +103,7 @@ class Bind9MasterDomainBackend(ServiceController): type master; file "%(zone_path)s"; allow-transfer { %(slaves)s; }; + also-notify { %(also_notify)s }; notify yes; };""" % context) }) diff --git a/orchestra/apps/domains/forms.py b/orchestra/apps/domains/forms.py index b13429a6..212af8e9 100644 --- a/orchestra/apps/domains/forms.py +++ b/orchestra/apps/domains/forms.py @@ -7,13 +7,13 @@ from .helpers import domain_for_validation from .models import Domain -class DomainAdminForm(forms.ModelForm): - migrate_subdomains = forms.BooleanField(label=_("Migrate subdomains"), required=False, - initial=False, help_text=_("Propagate the account owner change to subdomains.")) +class CreateDomainAdminForm(forms.ModelForm): +# migrate_subdomains = forms.BooleanField(label=_("Migrate subdomains"), required=False, +# initial=False, help_text=_("Propagate the account owner change to subdomains.")) def clean(self): """ inherit related top domain account, when exists """ - cleaned_data = super(DomainAdminForm, self).clean() + cleaned_data = super(CreateDomainAdminForm, self).clean() if not cleaned_data['account']: domain = Domain(name=cleaned_data['name']) top = domain.get_top() diff --git a/orchestra/apps/domains/models.py b/orchestra/apps/domains/models.py index fc5c9a72..6ac12dda 100644 --- a/orchestra/apps/domains/models.py +++ b/orchestra/apps/domains/models.py @@ -182,6 +182,7 @@ class Record(models.Model): def clean(self): """ validates record value based on its type """ # validate value + self.value = self.value.lower().strip() mapp = { self.MX: validators.validate_mx_record, self.NS: validators.validate_zone_label, diff --git a/orchestra/apps/issues/admin.py b/orchestra/apps/issues/admin.py index 7f34fb98..1f9772c4 100644 --- a/orchestra/apps/issues/admin.py +++ b/orchestra/apps/issues/admin.py @@ -12,12 +12,11 @@ from django.utils.translation import ugettext_lazy as _ from markdown import markdown from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions -from orchestra.admin.utils import (admin_link, admin_colored, wrap_admin_view, - admin_date) -from orchestra.apps.contacts import settings as contacts_settings +from orchestra.admin.utils import admin_link, admin_colored, wrap_admin_view, admin_date +from orchestra.apps.contacts.models import Contact from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets, - mark_as_unread, mark_as_read, set_default_queue) + mark_as_unread, mark_as_read, set_default_queue) from .filters import MyTicketsListFilter, TicketStateListFilter from .forms import MessageInlineForm, TicketForm from .helpers import get_ticket_changes, markdown_formated_changes, filter_actions @@ -311,7 +310,7 @@ class QueueAdmin(admin.ModelAdmin): def get_list_display(self, request): """ show notifications """ list_display = list(self.list_display) - for value, verbose in contacts_settings.CONTACTS_EMAIL_USAGES: + for value, verbose in Contact.EMAIL_USAGES: def display_notify(queue, notify=value): return notify in queue.notify display_notify.short_description = verbose diff --git a/orchestra/apps/lists/backends.py b/orchestra/apps/lists/backends.py index 3a6c8fed..1b202b1a 100644 --- a/orchestra/apps/lists/backends.py +++ b/orchestra/apps/lists/backends.py @@ -102,7 +102,8 @@ class MailmanBackend(ServiceController): for address in self.addresses: context['address'] = address self.append('sed -i "s/^.*\s%(name)s%(address)s\s*$//" %(virtual_alias)s' % context) - self.append("rmlist -a %(name)s" % context) + # TODO remove + self.append("echo rmlist -a %(name)s" % context) def commit(self): context = self.get_context_files() @@ -172,7 +173,7 @@ class MailmanTraffic(ServiceMonitor): } -class MailmanTraffic(ServiceMonitor): +class MailmanSubscribers(ServiceMonitor): model = 'lists.List' verbose_name = _("Mailman subscribers") diff --git a/orchestra/apps/lists/forms.py b/orchestra/apps/lists/forms.py index dd37bd47..9b01aaa3 100644 --- a/orchestra/apps/lists/forms.py +++ b/orchestra/apps/lists/forms.py @@ -1,6 +1,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ +from orchestra import settings as orchestra_settings from orchestra.core.validators import validate_password from orchestra.forms.widgets import ReadOnlyWidget @@ -16,7 +17,7 @@ class CleanAddressMixin(object): class ListCreationForm(CleanAddressMixin, forms.ModelForm): - password1 = forms.CharField(label=_("Password"), + password1 = forms.CharField(label=_("Password"), validators=[validate_password], widget=forms.PasswordInput) password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput, @@ -24,7 +25,13 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm): def __init__(self, *args, **kwargs): super(ListCreationForm, self).__init__(*args, **kwargs) - self.fields['password1'].validators.append(validate_password) + if orchestra_settings.ORCHESTRA_MIGRATION_MODE: + self.fields['password1'].widget = forms.HiddenInput() + self.fields['password1'].required = False + self.fields['password2'].widget = forms.HiddenInput() + self.fields['password2'].required = False + self.fields['admin_email'].widget = forms.HiddenInput() + self.fields['admin_email'].required = False def clean_password2(self): password1 = self.cleaned_data.get("password1") @@ -36,7 +43,8 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm): def save(self, commit=True): obj = super(ListCreationForm, self).save(commit=commit) - obj.set_password(self.cleaned_data["password1"]) + if not orchestra_settings.ORCHESTRA_MIGRATION_MODE: + obj.set_password(self.cleaned_data["password1"]) return obj diff --git a/orchestra/apps/mailboxes/backends.py b/orchestra/apps/mailboxes/backends.py index 73e4436d..46ecbbeb 100644 --- a/orchestra/apps/mailboxes/backends.py +++ b/orchestra/apps/mailboxes/backends.py @@ -46,8 +46,8 @@ class PasswdVirtualUserBackend(ServiceController): fi""" % context)) def generate_filter(self, mailbox, context): - self.append("doveadm mailbox create -u %(username)s Spam" % context) # TODO override webmail filters??? - context['filtering_path'] = os.path.join(context['home'], '.dovecot.sieve') + self.append("doveadm mailbox create -u %(username)s Spam" % context) + context['filtering_path'] = settings.MAILBOXES_SIEVE_PATH % context filtering = mailbox.get_filtering() if filtering: context['filtering'] = '# %(banner)s\n' + filtering diff --git a/orchestra/apps/mailboxes/forms.py b/orchestra/apps/mailboxes/forms.py index a9709b0d..3ea407af 100644 --- a/orchestra/apps/mailboxes/forms.py +++ b/orchestra/apps/mailboxes/forms.py @@ -14,7 +14,7 @@ class MailboxForm(forms.ModelForm): # TODO keep track of this ticket for future reimplementation # https://code.djangoproject.com/ticket/897 addresses = forms.ModelMultipleChoiceField(queryset=Address.objects, required=False, - widget=widgets.FilteredSelectMultiple(verbose_name=_('Pizzas'), is_stacked=False)) + widget=widgets.FilteredSelectMultiple(verbose_name=_('addresses'), is_stacked=False)) def __init__(self, *args, **kwargs): super(MailboxForm, self).__init__(*args, **kwargs) diff --git a/orchestra/apps/mailboxes/models.py b/orchestra/apps/mailboxes/models.py index 05bcc208..88e16284 100644 --- a/orchestra/apps/mailboxes/models.py +++ b/orchestra/apps/mailboxes/models.py @@ -8,7 +8,6 @@ from orchestra.core import services from . import validators, settings -# TODO rename app to mailboxes class Mailbox(models.Model): CUSTOM = 'CUSTOM' diff --git a/orchestra/apps/mailboxes/settings.py b/orchestra/apps/mailboxes/settings.py index 391f7946..138649c0 100644 --- a/orchestra/apps/mailboxes/settings.py +++ b/orchestra/apps/mailboxes/settings.py @@ -1,3 +1,4 @@ +import os import textwrap from django.conf import settings @@ -10,6 +11,10 @@ MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL', 'domains.Do MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME', '/home/%(name)s/') +MAILBOXES_SIEVE_PATH = getattr(settings, 'MAILBOXES_SIEVE_PATH', + os.path.join(MAILBOXES_HOME, 'Maildir/sieve/orchestra.sieve')) + + MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH', '/dev/shm') diff --git a/orchestra/apps/resources/actions.py b/orchestra/apps/resources/actions.py new file mode 100644 index 00000000..12668833 --- /dev/null +++ b/orchestra/apps/resources/actions.py @@ -0,0 +1,21 @@ +from django.contrib import messages +from django.db import transaction +from django.shortcuts import redirect +from django.utils.translation import ungettext, ugettext_lazy as _ + + +@transaction.atomic +def run_monitor(modeladmin, request, queryset): + for resource in queryset: + resource.monitor() + modeladmin.log_change(request, resource, _("Run monitors")) + num = len(queryset) + msg = ungettext( + _("One selected resource has been monitored."), + _("%s selected resource have been monitored.") % num, + num) + modeladmin.message_user(request, msg) + referer = request.META.get('HTTP_REFERER') + if referer: + return redirect(referer) +run_monitor.url_name = 'monitor' diff --git a/orchestra/apps/resources/admin.py b/orchestra/apps/resources/admin.py index f5951ef3..4cc4555e 100644 --- a/orchestra/apps/resources/admin.py +++ b/orchestra/apps/resources/admin.py @@ -1,8 +1,9 @@ from django.contrib import admin, messages from django.contrib.contenttypes import generic +from django.core.urlresolvers import reverse from django.utils.functional import cached_property from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext, ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin from orchestra.admin.filters import UsedContentTypeFilter @@ -10,6 +11,7 @@ from orchestra.admin.utils import insertattr, get_modeladmin, admin_link, admin_ from orchestra.core import services from orchestra.utils import database_ready +from .actions import run_monitor from .forms import ResourceForm from .models import Resource, ResourceData, MonitorData @@ -66,27 +68,49 @@ class ResourceAdmin(ExtendedModelAdmin): return super(ResourceAdmin, self).formfield_for_dbfield(db_field, **kwargs) -class ResourceDataAdmin(admin.ModelAdmin): +class ResourceDataAdmin(ExtendedModelAdmin): list_display = ( - 'id', 'resource_link', 'used', 'allocated', 'updated_at', 'content_object_link' + 'id', 'resource_link', 'content_object_link', 'used', 'allocated', 'display_unit', + 'display_updated' ) list_filter = ('resource',) - readonly_fields = ('content_object_link',) + add_fields = ('resource', 'content_type', 'object_id', 'used', 'updated_at', 'allocated') + fields = ( + 'resource_link', 'content_type', 'content_object_link', 'used', 'display_updated', + 'allocated', 'display_unit' + ) + readonly_fields = ('display_unit',) + change_readonly_fields = ( + 'resource_link', 'content_type', 'content_object_link', 'used', 'display_updated', + 'display_unit' + ) + actions = (run_monitor,) + change_view_actions = actions + ordering = ('-updated_at',) resource_link = admin_link('resource') content_object_link = admin_link('content_object') + display_updated = admin_date('updated_at', short_description=_("Updated")) + + def display_unit(self, data): + return data.unit + display_unit.short_description = _("Unit") + display_unit.admin_order_field = 'resource__unit' def get_queryset(self, request): queryset = super(ResourceDataAdmin, self).get_queryset(request) return queryset.prefetch_related('content_object') -class MonitorDataAdmin(admin.ModelAdmin): - list_display = ('id', 'monitor', 'created_at', 'value', 'content_object_link') +class MonitorDataAdmin(ExtendedModelAdmin): + list_display = ('id', 'monitor', 'display_created', 'value', 'content_object_link') list_filter = ('monitor',) - readonly_fields = ('content_object_link',) + add_fields = ('monitor', 'content_type', 'object_id', 'created_at', 'value') + fields = ('monitor', 'content_type', 'content_object_link', 'display_created', 'value') + change_readonly_fields = fields content_object_link = admin_link('content_object') + display_created = admin_date('created_at', short_description=_("Created")) def get_queryset(self, request): queryset = super(MonitorDataAdmin, self).get_queryset(request) @@ -109,10 +133,21 @@ def resource_inline_factory(resources): def forms(self, resources=resources): forms = [] resources_copy = list(resources) - for i, data in enumerate(self.queryset): + queryset = self.queryset + if self.instance.pk: + # Create missing resource data + queryset = list(queryset) + queryset_resources = [data.resource for data in queryset] + for resource in resources: + if resource not in queryset_resources: + data = resource.dataset.create(content_object=self.instance) + queryset.append(data) + # Existing dataset + for i, data in enumerate(queryset): forms.append(self._construct_form(i, resource=data.resource)) resources_copy.remove(data.resource) - for i, resource in enumerate(resources_copy, len(self.queryset)): + # Missing dataset + for i, resource in enumerate(resources_copy, len(queryset)): forms.append(self._construct_form(i, resource=resource)) return forms @@ -123,9 +158,9 @@ def resource_inline_factory(resources): formset = ResourceInlineFormSet can_delete = False fields = ( - 'verbose_name', 'used', 'display_updated', 'allocated', 'unit' + 'verbose_name', 'display_used', 'display_updated', 'allocated', 'unit', ) - readonly_fields = ('used', 'display_updated') + readonly_fields = ('display_used', 'display_updated') class Media: css = { @@ -134,9 +169,20 @@ def resource_inline_factory(resources): display_updated = admin_date('updated_at', default=_("Never")) + def display_used(self, data): + update_link = '' + if data.pk: + url = reverse('admin:resources_resourcedata_monitor', args=(data.pk,)) + update_link = '%s' % (url, ugettext("Update")) + if data.used is not None: + return '%s %s %s' % (data.used, data.resource.unit, update_link) + return _("Unknonw %s") % update_link + display_used.short_description = _("Used") + def has_add_permission(self, *args, **kwargs): """ Hidde add another """ return False + return ResourceInline diff --git a/orchestra/apps/resources/backends.py b/orchestra/apps/resources/backends.py index 5ddcf97c..ba5bb77e 100644 --- a/orchestra/apps/resources/backends.py +++ b/orchestra/apps/resources/backends.py @@ -50,7 +50,7 @@ class ServiceMonitor(ServiceBackend): data = self.get_last_data(object_id) if data is None: return self.current_date - datetime.timedelta(days=1) - return data.date + return data.created_at def process(self, line): """ line -> object_id, value """ @@ -66,7 +66,7 @@ class ServiceMonitor(ServiceBackend): line = line.strip() object_id, value = self.process(line) MonitorData.objects.create(monitor=name, object_id=object_id, - content_type=ct, value=value, date=self.current_date) + content_type=ct, value=value, created_at=self.current_date) def execute(self, server): log = super(ServiceMonitor, self).execute(server) diff --git a/orchestra/apps/resources/forms.py b/orchestra/apps/resources/forms.py index abc4d149..74e7a916 100644 --- a/orchestra/apps/resources/forms.py +++ b/orchestra/apps/resources/forms.py @@ -26,11 +26,11 @@ class ResourceForm(forms.ModelForm): self.fields['allocated'].required = True self.fields['allocated'].initial = self.resource.default_allocation - def has_changed(self): - """ Make sure resourcedata objects are created for all resources """ - if not self.instance.pk: - return True - return super(ResourceForm, self).has_changed() +# def has_changed(self): +# """ Make sure resourcedata objects are created for all resources """ +# if not self.instance.pk: +# return True +# return super(ResourceForm, self).has_changed() def save(self, *args, **kwargs): self.instance.resource_id = self.resource.pk diff --git a/orchestra/apps/resources/helpers.py b/orchestra/apps/resources/helpers.py index c404f8de..682d19e9 100644 --- a/orchestra/apps/resources/helpers.py +++ b/orchestra/apps/resources/helpers.py @@ -61,4 +61,4 @@ def compute_resource_usage(data): has_result = True else: raise NotImplementedError("%s support not implemented" % data.period) - return result/resource.get_scale() if has_result else None + return float(result)/resource.get_scale() if has_result else None diff --git a/orchestra/apps/resources/models.py b/orchestra/apps/resources/models.py index 45d00ad9..5d55fb62 100644 --- a/orchestra/apps/resources/models.py +++ b/orchestra/apps/resources/models.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType from django.apps import apps from django.db import models from django.utils import timezone +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from djcelery.models import PeriodicTask, CrontabSchedule @@ -11,7 +12,7 @@ from orchestra.models import queryset, fields from orchestra.utils.paths import get_project_root from orchestra.utils.system import run -from . import helpers +from . import helpers, tasks from .backends import ServiceMonitor from .validators import validate_scale @@ -127,7 +128,7 @@ class ResourceData(models.Model): resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource")) content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) object_id = models.PositiveIntegerField(_("object id")) - used = models.PositiveIntegerField(_("used"), null=True) + used = models.DecimalField(_("used"), max_digits=16, decimal_places=2, null=True) updated_at = models.DateTimeField(_("updated"), null=True) allocated = models.PositiveIntegerField(_("allocated"), null=True, blank=True) @@ -159,6 +160,9 @@ class ResourceData(models.Model): self.used = current or 0 self.updated_at = timezone.now() self.save(update_fields=['used', 'updated_at']) + + def monitor(self): + tasks.monitor(self.resource_id, ids=(self.object_id,)) class MonitorData(models.Model): @@ -167,7 +171,7 @@ class MonitorData(models.Model): choices=ServiceMonitor.get_plugin_choices()) content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) object_id = models.PositiveIntegerField(_("object id")) - created_at = models.DateTimeField(_("created"), auto_now_add=True) + created_at = models.DateTimeField(_("created")) value = models.DecimalField(_("value"), max_digits=16, decimal_places=2) content_object = GenericForeignKey() @@ -178,6 +182,10 @@ class MonitorData(models.Model): def __unicode__(self): return str(self.monitor) + + @cached_property + def unit(self): + return self.resource.unit def create_resource_relation(): diff --git a/orchestra/apps/resources/tasks.py b/orchestra/apps/resources/tasks.py index 1e65d0e4..9b571c7f 100644 --- a/orchestra/apps/resources/tasks.py +++ b/orchestra/apps/resources/tasks.py @@ -2,29 +2,39 @@ from celery import shared_task from django.db.models.loading import get_model from orchestra.apps.orchestration.models import BackendOperation as Operation +from orchestra.models.utils import get_model_field_path from .backends import ServiceMonitor -from .models import ResourceData, Resource @shared_task(name='resources.Monitor') -def monitor(resource_id): - resource = Resource.objects.get(pk=resource_id) +def monitor(resource_id, ids=None): + from .models import ResourceData, Resource + resource = Resource.objects.get(pk=resource_id) + resource_model = resource.content_type.model_class() # Execute monitors for monitor_name in resource.monitors: backend = ServiceMonitor.get_backend(monitor_name) model = get_model(backend.model) + kwargs = {} + if ids: + path = get_model_field_path(model, resource_model) + path = '%s__in' % ('__'.join(path) or 'id') + kwargs = { + path: ids + } operations = [] # Execute monitor - for obj in model.objects.all(): + for obj in model.objects.filter(**kwargs): operations.append(Operation.create(backend, obj, Operation.MONITOR)) Operation.execute(operations) + kwargs = {'id__in': ids} if ids else {} # Update used resources and trigger resource exceeded and revovery operations = [] model = resource.content_type.model_class() - for obj in model.objects.all(): + for obj in model.objects.filter(**kwargs): data = ResourceData.get_or_create(obj, resource) data.update() if not resource.disable_trigger: diff --git a/orchestra/apps/services/actions.py b/orchestra/apps/services/actions.py index 526bfd2f..c4243885 100644 --- a/orchestra/apps/services/actions.py +++ b/orchestra/apps/services/actions.py @@ -66,15 +66,14 @@ view_help.verbose_name = _("Help") def clone(modeladmin, request, queryset): service = queryset.get() - fields = ( - 'content_type_id', 'match', 'handler_type', 'is_active', 'ignore_superusers', 'billing_period', - 'billing_point', 'is_fee', 'metric', 'nominal_price', 'tax', 'pricing_period', - 'rate_algorithm', 'on_cancel', 'payment_style', - ) + fields = modeladmin.get_fields(request) + fk_fields = ('content_type',) query = [] for field in fields: - value = getattr(service, field) - field = field.replace('_id', '') + if field in fk_fields: + value = getattr(service, field + '_id') + else: + value = getattr(service, field) query.append('%s=%s' % (field, value)) opts = service._meta url = reverse('admin:%s_%s_add' % (opts.app_label, opts.model_name)) diff --git a/orchestra/apps/services/models.py b/orchestra/apps/services/models.py index fc09fd49..eacc83f0 100644 --- a/orchestra/apps/services/models.py +++ b/orchestra/apps/services/models.py @@ -186,7 +186,7 @@ class Service(models.Model): nominal_price = models.DecimalField(_("nominal price"), max_digits=12, decimal_places=2) tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES, - default=settings.SERVICES_SERVICE_DEFAUL_TAX) + default=settings.SERVICES_SERVICE_DEFAULT_TAX) pricing_period = models.CharField(_("pricing period"), max_length=16, help_text=_("Time period that is used for computing the rate metric."), choices=( diff --git a/orchestra/apps/services/settings.py b/orchestra/apps/services/settings.py index 835b47a6..65ee8cc0 100644 --- a/orchestra/apps/services/settings.py +++ b/orchestra/apps/services/settings.py @@ -9,7 +9,7 @@ SERVICES_SERVICE_TAXES = getattr(settings, 'SERVICES_SERVICE_TAXES', ( (21, "21%"), )) -SERVICES_SERVICE_DEFAUL_TAX = getattr(settings, 'ORDERS_SERVICE_DFAULT_TAX', 0) +SERVICES_SERVICE_DEFAULT_TAX = getattr(settings, 'SERVICES_SERVICE_DEFAULT_TAX', 0) SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH', 1) diff --git a/orchestra/apps/systemusers/backends.py b/orchestra/apps/systemusers/backends.py index 8821ba67..4874d8db 100644 --- a/orchestra/apps/systemusers/backends.py +++ b/orchestra/apps/systemusers/backends.py @@ -68,7 +68,7 @@ class SystemUserDisk(ServiceMonitor): def monitor(self, user): context = self.get_context(user) - self.append("du -s %(home)s | xargs echo %(object_id)s" % context) + self.append("du -s %(home)s | cut -f1 | xargs echo %(object_id)s" % context) def get_context(self, user): context = SystemUserBackend().get_context(user) diff --git a/orchestra/apps/webapps/models.py b/orchestra/apps/webapps/models.py index 3449ef30..2e95d095 100644 --- a/orchestra/apps/webapps/models.py +++ b/orchestra/apps/webapps/models.py @@ -13,7 +13,8 @@ from . import settings class WebApp(models.Model): """ Represents a web application """ - name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) + name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name], + blank=settings.WEBAPPS_ALLOW_BLANK_NAME) type = models.CharField(_("type"), max_length=32, choices=dict_setting_to_choices(settings.WEBAPPS_TYPES), default=settings.WEBAPPS_DEFAULT_TYPE) @@ -26,7 +27,7 @@ class WebApp(models.Model): verbose_name_plural = _("Web Apps") def __unicode__(self): - return self.name + return self.name or settings.WEBAPPS_BLANK_NAME @cached def get_options(self): diff --git a/orchestra/apps/webapps/settings.py b/orchestra/apps/webapps/settings.py index 02df8a06..86b121dd 100644 --- a/orchestra/apps/webapps/settings.py +++ b/orchestra/apps/webapps/settings.py @@ -10,6 +10,12 @@ WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN', '127.0.0.1:%(fpm_port)s') +WEBAPPS_ALLOW_BLANK_NAME = getattr(settings, 'WEBAPPS_ALLOW_BLANK_NAME', False) + +# Default name when blank +WEBAPPS_BLANK_NAME = getattr(settings, 'WEBAPPS_BLANK_NAME', 'webapp') + + WEBAPPS_FPM_START_PORT = getattr(settings, 'WEBAPPS_FPM_START_PORT', 10000) diff --git a/orchestra/apps/websites/backends/apache.py b/orchestra/apps/websites/backends/apache.py index 1924fd69..6be3cbe5 100644 --- a/orchestra/apps/websites/backends/apache.py +++ b/orchestra/apps/websites/backends/apache.py @@ -3,7 +3,6 @@ import os import re from django.template import Template, Context -from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from orchestra.apps.orchestration import ServiceController @@ -191,17 +190,15 @@ class Apache2Traffic(ServiceMonitor): verbose_name = _("Apache 2 Traffic") def prepare(self): - current_date = timezone.localtime(self.current_date) - current_date = current_date.strftime("%Y%m%d%H%M%S") + current_date = self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z") self.append(textwrap.dedent("""\ function monitor () { OBJECT_ID=$1 INI_DATE=$2 LOG_FILE="$3" { - awk -v ini="${INI_DATE}" ' + awk -v ini="${INI_DATE}" -v end="$(date '+%%Y%%m%%d%%H%%M%%S' -d '%s')" ' BEGIN { - end = "%s" sum = 0 months["Jan"] = "01"; months["Feb"] = "02"; @@ -235,12 +232,12 @@ class Apache2Traffic(ServiceMonitor): def monitor(self, site): context = self.get_context(site) - self.append('monitor %(object_id)i %(last_date)s "%(log_file)s"' % context) + self.append('monitor {object_id} $(date "+%Y%m%d%H%M%S" -d "{last_date}") "{log_file}"'.format(**context)) def get_context(self, site): - last_date = timezone.localtime(self.get_last_date(site.pk)) + last_date = self.get_last_date(site.pk) return { 'log_file': os.path.join(settings.WEBSITES_BASE_APACHE_LOGS, site.unique_name), - 'last_date': last_date.strftime("%Y%m%d%H%M%S"), + 'last_date': last_date.strftime("%Y-%m-%d %H:%M:%S %Z"), 'object_id': site.pk, } diff --git a/orchestra/bin/orchestra-admin b/orchestra/bin/orchestra-admin index 10952210..86558885 100755 --- a/orchestra/bin/orchestra-admin +++ b/orchestra/bin/orchestra-admin @@ -133,7 +133,7 @@ function install_requirements () { xvfb \ ca-certificates" - PIP="django==1.7 \ + PIP="django==1.7.1 \ django-celery-email==1.0.4 \ django-fluent-dashboard==0.3.5 \ https://bitbucket.org/izi/django-admin-tools/get/a0abfffd76a0.zip \