diff --git a/TODO.md b/TODO.md index 97d25523..a4c63f66 100644 --- a/TODO.md +++ b/TODO.md @@ -206,6 +206,12 @@ Php binaries should have this format: /usr/bin/php5.2-cgi * better validate options and directives (url locations, filesystem paths, etc..) * filter php deprecated options out based on version -* order virtualhost locations /hola / including directive * make sure that you understand the risks + + +* full support for deactivation of services/accounts + * Display admin.is_active (disabled account/order by) + + +* show details data on webapp changelist diff --git a/orchestra/apps/accounts/models.py b/orchestra/apps/accounts/models.py index 14d472ab..d20849ef 100644 --- a/orchestra/apps/accounts/models.py +++ b/orchestra/apps/accounts/models.py @@ -79,16 +79,10 @@ class Account(auth.AbstractBaseUser): # Trigger save() on related objects that depend on this account for rel in self._meta.get_all_related_objects(): source = getattr(rel, 'related_model', rel.model) - if not source in services: - continue - try: - source._meta.get_field_by_name('is_active') - except models.FieldDoesNotExist: - continue - else: + if source in services and hasattr(source, 'active'): for obj in getattr(self, rel.get_accessor_name()).all(): obj.save(update_fields=[]) - + def send_email(self, template, context, contacts=[], attachments=[], html=None): contacts = self.contacts.filter(email_usages=contacts) email_to = contacts.values_list('email', flat=True) diff --git a/orchestra/apps/lists/backends.py b/orchestra/apps/lists/backends.py index d969c912..6baa4322 100644 --- a/orchestra/apps/lists/backends.py +++ b/orchestra/apps/lists/backends.py @@ -46,7 +46,7 @@ class MailmanBackend(ServiceController): self.append('sed -i "/^%(address_domain)s\s*$/d" %(virtual_alias_domains)s' % context) def get_virtual_aliases(self, context): - aliases = [] + aliases = ['# %(banner)s' % context] for address in self.addresses: context['address'] = address aliases.append("%(address_name)s%(address)s@%(domain)s\t%(name)s%(address)s" % context) @@ -65,15 +65,13 @@ class MailmanBackend(ServiceController): # Preserve indentation self.append(textwrap.dedent("""\ if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then - echo '# %(banner)s\n%(aliases)s - ' >> %(virtual_alias)s + echo '%(aliases)s' >> %(virtual_alias)s UPDATED_VIRTUAL_ALIAS=1 else if [[ ! $(grep '^\s*%(address_name)s@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\ -e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s - echo '# %(banner)s\n%(aliases)s - ' >> %(virtual_alias)s + echo '%(aliases)s' >> %(virtual_alias)s UPDATED_VIRTUAL_ALIAS=1 fi fi""") % context @@ -99,15 +97,19 @@ class MailmanBackend(ServiceController): '%(mailman_root)s/bin/change_pw --listname="%(name)s" --password="%(password)s"' % context ) self.include_virtual_alias_domain(context) + if mail_list.active: + self.append('chmod 775 %(mailman_root)s/lists/%(name)s' % context) + else: + self.append('chmod 000 %(mailman_root)s/lists/%(name)s' % context) def delete(self, mail_list): context = self.get_context(mail_list) self.exclude_virtual_alias_domain(context) - self.append(textwrap.dedent("""\ + self.append(textwrap.dedent(""" sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\ -e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s""") % context ) - self.append(textwrap.dedent("""\ + self.append(textwrap.dedent(""" # Non-existent list archives produce exit code 1 exit_code=0 rmlist -a %(name)s || exit_code=$? @@ -119,9 +121,12 @@ class MailmanBackend(ServiceController): def commit(self): context = self.get_context_files() self.append(textwrap.dedent(""" - [[ $UPDATED_VIRTUAL_ALIAS == 1 ]] && { postmap %(virtual_alias)s; } - [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]] && { /etc/init.d/postfix reload; } - """) % context + if [[ $UPDATED_VIRTUAL_ALIAS == 1 ]]; then + postmap %(virtual_alias)s + fi + if [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]]; then + /etc/init.d/postfix reload + fi""") % context ) def get_context_files(self): diff --git a/orchestra/apps/lists/models.py b/orchestra/apps/lists/models.py index 0706f6e2..372c560f 100644 --- a/orchestra/apps/lists/models.py +++ b/orchestra/apps/lists/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from orchestra.core import services @@ -20,7 +21,10 @@ class List(models.Model): help_text=_("Administration email address")) account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='lists') - + # TODO also admin + # 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.")) password = None class Meta: @@ -35,6 +39,10 @@ class List(models.Model): return "%s@%s" % (self.address_name, self.address_domain) return '' + @cached_property + def active(self): + return self.is_active and self.account.is_active + def get_address_name(self): return self.address_name or self.name diff --git a/orchestra/apps/resources/helpers.py b/orchestra/apps/resources/helpers.py index 0131d18c..6cd67d10 100644 --- a/orchestra/apps/resources/helpers.py +++ b/orchestra/apps/resources/helpers.py @@ -21,16 +21,13 @@ def compute_resource_usage(data): slot = (data.created_at-ini).total_seconds() result += data.value * slot/total ini = data.created_at - elif resource.period == resource.MONTHLY_SUM: + elif resource.period in (resource.MONTHLY_SUM, resource.LAST): # FIXME Aggregation of 0s returns None! django bug? # value = dataset.aggregate(models.Sum('value'))['value__sum'] values = dataset.values_list('value', flat=True) if values: has_result = True result += sum(values) - elif resource.period == resource.LAST: - result += dataset.value - has_result = True else: raise NotImplementedError("%s support not implemented" % data.period) 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 525d7f25..b4ea8bc7 100644 --- a/orchestra/apps/resources/models.py +++ b/orchestra/apps/resources/models.py @@ -239,8 +239,11 @@ class ResourceData(models.Model): ) ) elif resource.period == resource.LAST: + # Get last monitoring data per object_id try: - datasets.append(dataset.latest()) + datasets.append( + dataset.order_by('object_id', '-id').distinct('object_id') + ) except MonitorData.DoesNotExist: continue else: diff --git a/orchestra/apps/resources/tasks.py b/orchestra/apps/resources/tasks.py index 6d3d2b02..6ea9a4e5 100644 --- a/orchestra/apps/resources/tasks.py +++ b/orchestra/apps/resources/tasks.py @@ -30,7 +30,7 @@ def monitor(resource_id, ids=None, async=True): op = Operation.create(backend, obj, Operation.MONITOR) operations.append(op) monitorings.append(op) - # TODO async=TRue only when running with celery + # TODO async=True only when running with celery Operation.execute(monitorings, async=async) kwargs = {'id__in': ids} if ids else {} diff --git a/orchestra/apps/webapps/models.py b/orchestra/apps/webapps/models.py index ccb23005..d35e9017 100644 --- a/orchestra/apps/webapps/models.py +++ b/orchestra/apps/webapps/models.py @@ -24,7 +24,8 @@ class WebApp(models.Model): choices=AppType.get_plugin_choices()) account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='webapps') - data = JSONField(_("data"), help_text=_("Extra information dependent of each service.")) + data = JSONField(_("data"), blank=True, + help_text=_("Extra information dependent of each service.")) class Meta: unique_together = ('name', 'account') @@ -122,4 +123,7 @@ def type_save(sender, *args, **kwargs): @receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete') def type_delete(sender, *args, **kwargs): instance = kwargs['instance'] - instance.type_instance.delete() + try: + instance.type_instance.delete() + except KeyError: + pass diff --git a/orchestra/apps/webapps/options.py b/orchestra/apps/webapps/options.py index ff9cf4dc..9a0ab84c 100644 --- a/orchestra/apps/webapps/options.py +++ b/orchestra/apps/webapps/options.py @@ -336,8 +336,8 @@ class PHPUploadMaxFileSize(AppOption): group = AppOption.PHP -class PHPPostMaxSize(AppOption): - name = 'post_max_size' - verbose_name = _("zend_extension") +class PHPZendExtension(AppOption): + name = 'zend_extension' + verbose_name = _("Zend extension") regex = r'^[^ ]+$' group = AppOption.PHP diff --git a/orchestra/apps/webapps/settings.py b/orchestra/apps/webapps/settings.py index 1b9de49f..fa4e77e0 100644 --- a/orchestra/apps/webapps/settings.py +++ b/orchestra/apps/webapps/settings.py @@ -142,7 +142,7 @@ WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', ( 'orchestra.apps.webapps.options.PHPSuhosinSimulation', 'orchestra.apps.webapps.options.PHPSuhosinExecutorIncludeWhitelist', 'orchestra.apps.webapps.options.PHPUploadMaxFileSize', - 'orchestra.apps.webapps.options.PHPPostMaxSize', + 'orchestra.apps.webapps.options.PHPZendExtension', )) diff --git a/orchestra/apps/websites/admin.py b/orchestra/apps/websites/admin.py index 3cc0f82c..eacb8740 100644 --- a/orchestra/apps/websites/admin.py +++ b/orchestra/apps/websites/admin.py @@ -13,11 +13,11 @@ from orchestra.forms.widgets import DynamicHelpTextSelect from . import settings from .directives import SiteDirective from .forms import WebsiteAdminForm -from .models import Content, Website, Directive +from .models import Content, Website, WebsiteDirective -class DirectiveInline(admin.TabularInline): - model = Directive +class WebsiteDirectiveInline(admin.TabularInline): + model = WebsiteDirective extra = 1 DIRECTIVES_HELP_TEXT = { @@ -37,7 +37,7 @@ class DirectiveInline(admin.TabularInline): kwargs['widget'] = DynamicHelpTextSelect( 'this.id.replace("name", "value")', self.DIRECTIVES_HELP_TEXT ) - return super(DirectiveInline, self).formfield_for_dbfield(db_field, **kwargs) + return super(WebsiteDirectiveInline, self).formfield_for_dbfield(db_field, **kwargs) class ContentInline(AccountAdminMixin, admin.TabularInline): @@ -61,7 +61,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ('name', 'display_domains', 'display_webapps', 'account_link') list_filter = ('protocol', 'is_active',) change_readonly_fields = ('name',) - inlines = [ContentInline, DirectiveInline] + inlines = [ContentInline, WebsiteDirectiveInline] filter_horizontal = ['domains'] fieldsets = ( (None, { diff --git a/orchestra/apps/websites/backends/apache.py b/orchestra/apps/websites/backends/apache.py index 8bc1edfd..0d65b626 100644 --- a/orchestra/apps/websites/backends/apache.py +++ b/orchestra/apps/websites/backends/apache.py @@ -245,14 +245,14 @@ class Apache2Backend(ServiceController): option = site.get_directives().get('user_group') if option: return option[0] - return site.account.username + return site.get_username() def get_groupname(self, site): option = site.get_directives().get('user_group') if option and ' ' in option: user, group = option.split() return group - return site.account.username + return site.get_groupname() def get_context(self, site): base_apache_conf = settings.WEBSITES_BASE_APACHE_CONF diff --git a/orchestra/apps/websites/backends/webalizer.py b/orchestra/apps/websites/backends/webalizer.py index 335fb790..f447930d 100644 --- a/orchestra/apps/websites/backends/webalizer.py +++ b/orchestra/apps/websites/backends/webalizer.py @@ -21,7 +21,9 @@ class WebalizerBackend(ServiceController): echo 'Webstats are coming soon' > %(webalizer_path)s/index.html fi echo '%(webalizer_conf)s' > %(webalizer_conf_path)s - chown %(user)s:www-data %(webalizer_path)s""") % context + chown %(user)s:www-data %(webalizer_path)s + chmod g+xr %(webalizer_path)s + """) % context ) def delete(self, content): diff --git a/orchestra/apps/websites/directives.py b/orchestra/apps/websites/directives.py index 2a80d1c5..8f1da48c 100644 --- a/orchestra/apps/websites/directives.py +++ b/orchestra/apps/websites/directives.py @@ -84,13 +84,13 @@ class UserGroup(SiteDirective): group = SiteDirective.HTTPD def validate(self, directive): - super(UserGroupDirective, self).validate(directive) - options = directive.split() - syetmusers = [options[0]] + super(UserGroup, self).validate(directive) + options = directive.value.split() + systemusers = [options[0]] if len(options) > 1: systemusers.append(options[1]) # TODO not sure about this dependency maybe make it part of pangea only - from orchestra.apps.users.models import SystemUser + from orchestra.apps.systemusers.models import SystemUser errors = [] for user in systemusers: if not SystemUser.objects.filter(username=user).exists(): diff --git a/orchestra/apps/websites/models.py b/orchestra/apps/websites/models.py index 8542402c..441f6b7f 100644 --- a/orchestra/apps/websites/models.py +++ b/orchestra/apps/websites/models.py @@ -46,12 +46,19 @@ class Website(models.Model): @property def unique_name(self): - return settings.WEBSITES_UNIQUE_NAME_FORMAT % { + context = self.get_settings_context() + return settings.WEBSITES_UNIQUE_NAME_FORMAT % context + + def get_settings_context(self): + """ format settings strings """ + return { 'id': self.id, 'pk': self.pk, - 'account': self.account.username, + 'home': self.get_user().get_home(), + 'user': self.get_username(), + 'group': self.get_groupname(), + 'site_name': self.name, 'protocol': self.protocol, - 'name': self.name, } def get_protocol(self): @@ -74,27 +81,27 @@ class Website(models.Model): if domain: return '%s://%s' % (self.get_protocol(), domain) - def get_www_log_context(self): - return { - 'home': self.account.main_systemuser.get_home(), - 'account': self.account.username, - 'user': self.account.username, - 'site_name': self.name, - 'unique_name': self.unique_name - } + def get_user(self): + return self.account.main_systemuser + + def get_username(self): + return self.get_user().username + + def get_groupname(self): + return self.get_username() def get_www_access_log_path(self): - context = self.get_www_log_context() + context = self.get_settings_context() path = settings.WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH % context return os.path.normpath(path.replace('//', '/')) def get_www_error_log_path(self): - context = self.get_www_log_context() + context = self.get_settings_context() path = settings.WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH % context return os.path.normpath(path.replace('//', '/')) -class Directive(models.Model): +class WebsiteDirective(models.Model): website = models.ForeignKey(Website, verbose_name=_("web site"), related_name='directives') name = models.CharField(_("name"), max_length=128, diff --git a/orchestra/apps/websites/settings.py b/orchestra/apps/websites/settings.py index 7229450d..52f360b0 100644 --- a/orchestra/apps/websites/settings.py +++ b/orchestra/apps/websites/settings.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT', - '%(account)s-%(name)s') + '%(user)s-%(site_name)s') # TODO 'http', 'https', 'https-only', 'http and https' and rename to PROTOCOL @@ -72,3 +72,4 @@ WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS #WEBSITES_DEFAULT_SSl_KEY = getattr(settings, 'WEBSITES_DEFAULT_SSl_KEY', # '') +