From 708758880dc302269d705cfd6b8ab0faff011a4c Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Mon, 20 Jul 2015 12:51:30 +0000 Subject: [PATCH] Added support from domain SOA record massive editing and phplist password changing --- TODO.md | 3 - orchestra/admin/actions.py | 2 +- orchestra/admin/forms.py | 14 +++- orchestra/admin/options.py | 12 +-- orchestra/admin/utils.py | 4 +- orchestra/contrib/accounts/actions.py | 2 +- orchestra/contrib/bills/actions.py | 10 +-- orchestra/contrib/domains/actions.py | 77 +++++++++++++++++-- orchestra/contrib/domains/admin.py | 46 ++++++----- orchestra/contrib/domains/backends.py | 3 +- orchestra/contrib/domains/forms.py | 34 +++++++- .../migrations/0003_auto_20150720_1121.py | 36 +++++++++ .../migrations/0004_auto_20150720_1121.py | 35 +++++++++ orchestra/contrib/domains/models.py | 8 +- orchestra/contrib/payments/actions.py | 12 +-- orchestra/contrib/saas/backends/phplist.py | 26 ++++++- orchestra/contrib/saas/services/phplist.py | 2 +- orchestra/contrib/saas/settings.py | 24 +++++- orchestra/contrib/services/actions.py | 4 +- orchestra/contrib/systemusers/actions.py | 2 +- .../orchestra/admin/change_form.html | 2 +- 21 files changed, 290 insertions(+), 68 deletions(-) create mode 100644 orchestra/contrib/domains/migrations/0003_auto_20150720_1121.py create mode 100644 orchestra/contrib/domains/migrations/0004_auto_20150720_1121.py diff --git a/TODO.md b/TODO.md index fc38504a..97ee72c8 100644 --- a/TODO.md +++ b/TODO.md @@ -426,7 +426,4 @@ Case # bill changelist: dates: (closed_on, created_on, updated_on) - -# Add record modeladmin action: select domains + add records (formset) to selected domains - # Resource data inline show info: link to monitor data, and history chart: link to monitor data of each item diff --git a/orchestra/admin/actions.py b/orchestra/admin/actions.py index 3d9440cb..0f47ab48 100644 --- a/orchestra/admin/actions.py +++ b/orchestra/admin/actions.py @@ -129,4 +129,4 @@ def disable(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) disable.url_name = 'disable' -disable.verbose_name = _("Disable") +disable.short_description = _("Disable") diff --git a/orchestra/admin/forms.py b/orchestra/admin/forms.py index 4d82480f..e19f00f4 100644 --- a/orchestra/admin/forms.py +++ b/orchestra/admin/forms.py @@ -17,7 +17,9 @@ class AdminFormMixin(object): def as_admin(self): prepopulated_fields = {} fieldsets = [ - (None, {'fields': list(self.fields.keys())}) + (None, { + 'fields': list(self.fields.keys()) + }), ] adminform = helpers.AdminForm(self, fieldsets, prepopulated_fields) template = Template( @@ -25,7 +27,10 @@ class AdminFormMixin(object): ' {% include "admin/includes/fieldset.html" %}' '{% endfor %}' ) - return template.render(Context({'adminform': adminform})) + context = Context({ + 'adminform': adminform + }) + return template.render(context) class AdminFormSet(BaseModelFormSet): @@ -43,7 +48,10 @@ class AdminFormSet(BaseModelFormSet): template = Template( '{% include "admin/edit_inline/tabular.html" %}' ) - return template.render(Context({'inline_admin_formset': inline_admin_formset})) + context = Context({ + 'inline_admin_formset': inline_admin_formset + }) + return template.render(context) def adminmodelformset_factory(modeladmin, form, formset=AdminFormSet, **kwargs): diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index 1c1aebfb..6eac7fb1 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -98,11 +98,13 @@ class ChangeViewActionsMixin(object): action = getattr(self, action) view = action_to_view(action, self) view.url_name = getattr(action, 'url_name', action.__name__) - verbose_name = getattr(action, 'verbose_name', - view.url_name.capitalize().replace('_', ' ')) - if hasattr(verbose_name, '__call__'): - verbose_name = verbose_name(obj) - view.verbose_name = verbose_name + tool_description = getattr(action, 'tool_description', '') + if not tool_description: + tool_description = getattr(action, 'short_description', + view.url_name.capitalize().replace('_', ' ')) + if hasattr(tool_description, '__call__'): + tool_description = tool_description(obj) + view.tool_description = tool_description view.css_class = getattr(action, 'css_class', 'historylink') view.help_text = getattr(action, 'help_text', '') views.append(view) diff --git a/orchestra/admin/utils.py b/orchestra/admin/utils.py index b5b01b7c..0fa7aee5 100644 --- a/orchestra/admin/utils.py +++ b/orchestra/admin/utils.py @@ -98,7 +98,7 @@ def change_url(obj): @admin_field def admin_link(*args, **kwargs): instance = args[-1] - if kwargs['field'] in ['id', 'pk', '__str__']: + if kwargs['field'] in ('id', 'pk', '__str__'): obj = instance else: try: @@ -120,7 +120,7 @@ def admin_link(*args, **kwargs): extra = '' if kwargs['popup']: extra = 'onclick="return showAddAnotherPopup(this);"' - return '%s' % (url, extra, display) + return mark_safe('%s' % (url, extra, display)) @admin_field diff --git a/orchestra/contrib/accounts/actions.py b/orchestra/contrib/accounts/actions.py index 983aa80e..b6bf042b 100644 --- a/orchestra/contrib/accounts/actions.py +++ b/orchestra/contrib/accounts/actions.py @@ -25,7 +25,7 @@ def list_contacts(modeladmin, request, queryset): url = reverse('admin:contacts_contact_changelist') url += '?account__in=%s' % ','.join(map(str, ids)) return redirect(url) -list_contacts.verbose_name = _("List contacts") +list_contacts.short_description = _("List contacts") def service_report(modeladmin, request, queryset): diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py index 729279a5..21486fc7 100644 --- a/orchestra/contrib/bills/actions.py +++ b/orchestra/contrib/bills/actions.py @@ -31,7 +31,7 @@ def view_bill(modeladmin, request, queryset): return html = bill.html or bill.render() return HttpResponse(html) -view_bill.verbose_name = _("View") +view_bill.tool_description = _("View") view_bill.url_name = 'view' @@ -91,7 +91,7 @@ def close_bills(modeladmin, request, queryset, action='close_bills'): 'obj': get_object_from_url(modeladmin, request), } return render(request, 'admin/orchestra/generic_confirmation.html', context) -close_bills.verbose_name = _("Close") +close_bills.tool_description = _("Close") close_bills.url_name = 'close' @@ -137,7 +137,7 @@ def download_bills(modeladmin, request, queryset): response = HttpResponse(pdf, content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % bill.number return response -download_bills.verbose_name = _("Download") +download_bills.tool_description = _("Download") download_bills.url_name = 'download' @@ -149,7 +149,7 @@ def close_send_download_bills(modeladmin, request, queryset): return return download_bills(modeladmin, request, queryset) return response -close_send_download_bills.verbose_name = _("C.S.D.") +close_send_download_bills.tool_description = _("C.S.D.") close_send_download_bills.url_name = 'close-send-download' close_send_download_bills.help_text = _("Close, send and download bills in one shot.") @@ -288,7 +288,7 @@ def amend_bills(modeladmin, request, queryset): _('%(num)i amendment bills have been generated.') % context, num ))) -amend_bills.verbose_name = _("Amend") +amend_bills.tool_description = _("Amend") amend_bills.url_name = 'amend' diff --git a/orchestra/contrib/domains/actions.py b/orchestra/contrib/domains/actions.py index 48297766..62081dc7 100644 --- a/orchestra/contrib/domains/actions.py +++ b/orchestra/contrib/domains/actions.py @@ -1,16 +1,19 @@ import copy +from django.contrib import messages from django.contrib.admin import helpers +from django.db.models import Q +from django.db.models.functions import Concat, Coalesce from django.shortcuts import render from django.utils.safestring import mark_safe from django.utils.translation import ungettext, ugettext_lazy as _ from django.template.response import TemplateResponse from orchestra.admin.forms import adminmodelformset_factory -from orchestra.admin.utils import get_object_from_url, change_url +from orchestra.admin.utils import get_object_from_url, change_url, admin_link from orchestra.utils.python import AttrDict -from .forms import RecordForm, RecordEditFormSet +from .forms import RecordForm, RecordEditFormSet, SOAForm from .models import Record @@ -23,15 +26,33 @@ def view_zone(modeladmin, request, queryset): } return TemplateResponse(request, 'admin/domains/domain/view_zone.html', context) view_zone.url_name = 'view-zone' -view_zone.verbose_name = _("View zone") +view_zone.short_description = _("View zone") def edit_records(modeladmin, request, queryset): + selected_ids = queryset.values_list('id', flat=True) + # Include subodmains + queryset = queryset.model.objects.filter( + Q(top__id__in=selected_ids) | Q(id__in=selected_ids) + ).annotate( + structured_id=Coalesce('top__id', 'id'), + structured_name=Concat('top__name', 'name') + ).order_by('-structured_id', 'structured_name') formsets = [] for domain in queryset.prefetch_related('records'): modeladmin_copy = copy.copy(modeladmin) modeladmin_copy.model = Record - link = '%s' % (change_url(domain), domain.name) + prefix = '' if domain.is_top else ' '*8 + context = { + 'url': change_url(domain), + 'name': prefix+domain.name, + 'title': '', + } + if domain.id not in selected_ids: + context['name'] += '*' + context['title'] = _("This subdomain was not explicitly selected " + "but has been automatically added to this list.") + link = '%(name)s' % context modeladmin_copy.verbose_name_plural = mark_safe(link) RecordFormSet = adminmodelformset_factory( modeladmin_copy, RecordForm, formset=RecordEditFormSet, extra=1, can_delete=True) @@ -73,7 +94,7 @@ def edit_records(modeladmin, request, queryset): opts = modeladmin.model._meta context = { 'title': _("Edit records"), - 'action_name': 'Edit records', + 'action_name': _("Edit records"), 'action_value': 'edit_records', 'display_objects': [], 'queryset': queryset, @@ -86,6 +107,46 @@ def edit_records(modeladmin, request, queryset): return render(request, 'admin/domains/domain/edit_records.html', context) -def add_records(modeladmin, request, queryset): - # TODO - pass +def set_soa(modeladmin, request, queryset): + if queryset.filter(top__isnull=False).exists(): + msg = _("Set SOA on subdomains is not possible.") + modeladmin.message_user(request, msg, messages.ERROR) + return + form = SOAForm() + if request.POST.get('post') == 'generic_confirmation': + form = SOAForm(request.POST) + if form.is_valid(): + updates = {name: value for name, value in form.cleaned_data.items() if value} + change_message = _("SOA set %s") % str(updates)[1:-1] + for domain in queryset: + for name, value in updates.items(): + if name.startswith('clear_'): + name = name.replace('clear_', '') + value = '' + setattr(domain, name, value) + modeladmin.log_change(request, domain, change_message) + domain.save() + num = len(queryset) + msg = ungettext( + _("SOA record for one domain has been updated."), + _("SOA record for %s domains has been updated.") % num, + num + ) + modeladmin.message_user(request, msg) + return + opts = modeladmin.model._meta + context = { + 'title': _("Set SOA for selected domains"), + 'content_message': '', + 'action_name': _("Set SOA"), + 'action_value': 'set_soa', + 'display_objects': [admin_link('__str__')(domain) for domain in queryset], + 'queryset': queryset, + 'opts': opts, + 'app_label': opts.app_label, + 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, + 'form': form, + 'obj': get_object_from_url(modeladmin, request), + } + return render(request, 'admin/orchestra/generic_confirmation.html', context) +set_soa.short_description = _("Set SOA for selected domains") diff --git a/orchestra/contrib/domains/admin.py b/orchestra/contrib/domains/admin.py index 6ae92199..efc05559 100644 --- a/orchestra/contrib/domains/admin.py +++ b/orchestra/contrib/domains/admin.py @@ -8,7 +8,7 @@ from orchestra.admin.utils import admin_link, change_url from orchestra.contrib.accounts.admin import AccountAdminMixin from orchestra.utils import apps -from .actions import view_zone, edit_records +from .actions import view_zone, edit_records, set_soa from .filters import TopDomainListFilter from .forms import RecordForm, RecordInlineFormSet, BatchDomainCreationAdminForm from .models import Domain, Record @@ -51,13 +51,16 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): ) add_fields = ('name', 'account') fields = ('name', 'account_link') - inlines = [RecordInline, DomainInline] - list_filter = [TopDomainListFilter] + readonly_fields = ('account_link', 'top_link',) + inlines = (RecordInline, DomainInline) + list_filter = (TopDomainListFilter,) change_readonly_fields = ('name', 'serial') search_fields = ('name', 'account__username') add_form = BatchDomainCreationAdminForm - actions = (edit_records,) - change_view_actions = [view_zone] + actions = (edit_records, set_soa) + change_view_actions = (view_zone, edit_records) + + top_link = admin_link('top') def structured_name(self, domain): if domain.is_top: @@ -90,15 +93,26 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): def get_fieldsets(self, request, obj=None): """ Add SOA fields when domain is top """ fieldsets = super(DomainAdmin, self).get_fieldsets(request, obj) - if obj and obj.is_top: - fieldsets += ( - (_("SOA"), { - 'classes': ('collapse',), - 'fields': ('serial', 'refresh', 'retry', 'expire', 'min_ttl'), - }), - ) + if obj: + if obj.is_top: + fieldsets += ( + (_("SOA"), { + 'classes': ('collapse',), + 'fields': ('serial', 'refresh', 'retry', 'expire', 'min_ttl'), + }), + ) + else: + existing = fieldsets[0][1]['fields'] + if 'top_link' not in existing: + fieldsets[0][1]['fields'].insert(2, 'top_link') return fieldsets + def get_inline_instances(self, request, obj=None): + inlines = super(DomainAdmin, self).get_inline_instances(request, obj) + if not obj or not obj.is_top: + return [inline for inline in inlines if type(inline) != DomainInline] + return inlines + def get_queryset(self, request): """ Order by structured name and imporve performance """ qs = super(DomainAdmin, self).get_queryset(request) @@ -121,12 +135,6 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): domain = Domain.objects.create(name=name, account_id=obj.account_id) self.extra_domains.append(domain) - def save_formset(self, request, form, formset, change): - """ - Given an inline formset save it to the database. - """ - formset.save() - def save_related(self, request, form, formsets, change): """ batch domain creation support """ super(DomainAdmin, self).save_related(request, form, formsets, change) @@ -142,7 +150,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): record.pk = None formset.instance = domain form.instance = domain - self.save_formset(request, form, formset, change=change) + self.save_formset(request, form, formset, change) admin.site.register(Domain, DomainAdmin) diff --git a/orchestra/contrib/domains/backends.py b/orchestra/contrib/domains/backends.py index f6f7789d..32ae40b9 100644 --- a/orchestra/contrib/domains/backends.py +++ b/orchestra/contrib/domains/backends.py @@ -174,7 +174,8 @@ class Bind9MasterDomainBackend(ServiceController): class Bind9SlaveDomainBackend(Bind9MasterDomainBackend): """ Generate the configuartion for slave servers - It auto-discover the master server based on your routing configuration or you can use DOMAINS_MASTERS to explicitly configure the master. + It auto-discover the master server based on your routing configuration or you can use + DOMAINS_MASTERS to explicitly configure the master. """ CONF_PATH = settings.DOMAINS_SLAVES_PATH diff --git a/orchestra/contrib/domains/forms.py b/orchestra/contrib/domains/forms.py index b63b0e47..66c0bf10 100644 --- a/orchestra/contrib/domains/forms.py +++ b/orchestra/contrib/domains/forms.py @@ -1,8 +1,9 @@ from django import forms from django.core.exceptions import ValidationError +from django.utils.text import capfirst from django.utils.translation import ugettext_lazy as _ -from orchestra.admin.forms import AdminFormSet +from orchestra.admin.forms import AdminFormSet, AdminFormMixin from . import validators from .helpers import domain_for_validation @@ -71,8 +72,8 @@ class RecordForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(RecordForm, self).__init__(*args, **kwargs) - self.fields['ttl'].widget = forms.TextInput(attrs={'size':'10'}) - self.fields['value'].widget = forms.TextInput(attrs={'size':'100'}) + self.fields['ttl'].widget = forms.TextInput(attrs={'size': '10'}) + self.fields['value'].widget = forms.TextInput(attrs={'size': '100'}) class ValidateZoneMixin(object): @@ -97,3 +98,30 @@ class RecordEditFormSet(ValidateZoneMixin, AdminFormSet): class RecordInlineFormSet(ValidateZoneMixin, forms.models.BaseInlineFormSet): pass + + +class SOAForm(AdminFormMixin, forms.Form): + refresh = forms.CharField() + clear_refresh = forms.BooleanField(label=_("Clear refresh"), required=False, + help_text=_("Remove custom refresh value for all selected domains.")) + retry = forms.CharField() + clear_retry = forms.BooleanField(label=_("Clear retry"), required=False, + help_text=_("Remove custom retry value for all selected domains.")) + expire = forms.CharField() + clear_expire = forms.BooleanField(label=_("Clear expire"), required=False, + help_text=_("Remove custom expire value for all selected domains.")) + min_ttl = forms.CharField() + clear_min_ttl = forms.BooleanField(label=_("Clear min TTL"), required=False, + help_text=_("Remove custom min TTL value for all selected domains.")) + + def __init__(self, *args, **kwargs): + super(SOAForm, self).__init__(*args, **kwargs) + for name in self.fields: + if not name.startswith('clear_'): + field = Domain._meta.get_field_by_name(name)[0] + self.fields[name] = forms.CharField( + label=capfirst(field.verbose_name), + help_text=field.help_text, + validators=field.validators, + required=False, + ) diff --git a/orchestra/contrib/domains/migrations/0003_auto_20150720_1121.py b/orchestra/contrib/domains/migrations/0003_auto_20150720_1121.py new file mode 100644 index 00000000..d240ea0b --- /dev/null +++ b/orchestra/contrib/domains/migrations/0003_auto_20150720_1121.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import orchestra.contrib.domains.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('domains', '0002_auto_20150715_1017'), + ] + + operations = [ + migrations.RemoveField( + model_name='domain', + name='expire', + ), + migrations.RemoveField( + model_name='domain', + name='min_ttl', + ), + migrations.RemoveField( + model_name='domain', + name='refresh', + ), + migrations.RemoveField( + model_name='domain', + name='retry', + ), + migrations.AlterField( + model_name='domain', + name='serial', + field=models.IntegerField(editable=False, verbose_name='serial', default=orchestra.contrib.domains.utils.generate_zone_serial, help_text='A revision number that changes whenever this domain is updated.'), + ), + ] diff --git a/orchestra/contrib/domains/migrations/0004_auto_20150720_1121.py b/orchestra/contrib/domains/migrations/0004_auto_20150720_1121.py new file mode 100644 index 00000000..e133d415 --- /dev/null +++ b/orchestra/contrib/domains/migrations/0004_auto_20150720_1121.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import orchestra.contrib.domains.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('domains', '0003_auto_20150720_1121'), + ] + + operations = [ + migrations.AddField( + model_name='domain', + name='expire', + field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text='The time that a secondary server will keep trying to complete a zone transfer. If this time expires prior to a successful zone transfer, the secondary server will expire its zone file. This means the secondary will stop answering queries. The default value is 4w.', verbose_name='expire', max_length=16), + ), + migrations.AddField( + model_name='domain', + name='min_ttl', + field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text='The minimum time-to-live value applies to all resource records in the zone file. This value is supplied in query responses to inform other servers how long they should keep the data in cache. The default value is 1h.', verbose_name='min TTL', max_length=16), + ), + migrations.AddField( + model_name='domain', + name='refresh', + field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text="The time a secondary DNS server waits before querying the primary DNS server's SOA record to check for changes. When the refresh time expires, the secondary DNS server requests a copy of the current SOA record from the primary. The primary DNS server complies with this request. The secondary DNS server compares the serial number of the primary DNS server's current SOA record and the serial number in it's own SOA record. If they are different, the secondary DNS server will request a zone transfer from the primary DNS server. The default value is 1d.", verbose_name='refresh', max_length=16), + ), + migrations.AddField( + model_name='domain', + name='retry', + field=models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], blank=True, help_text='The time a secondary server waits before retrying a failed zone transfer. Normally, the retry time is less than the refresh time. The default value is 2h.', verbose_name='retry', max_length=16), + ), + ] diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py index e34fe531..3e4890e8 100644 --- a/orchestra/contrib/domains/models.py +++ b/orchestra/contrib/domains/models.py @@ -21,7 +21,7 @@ class Domain(models.Model): editable=False) serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, editable=False, help_text=_("A revision number that changes whenever this domain is updated.")) - refresh = models.IntegerField(_("refresh"), null=True, blank=True, + refresh = models.CharField(_("refresh"), max_length=16, blank=True, validators=[validators.validate_zone_interval], help_text=_("The time a secondary DNS server waits before querying the primary DNS " "server's SOA record to check for changes. When the refresh time expires, " @@ -32,19 +32,19 @@ class Domain(models.Model): "If they are different, the secondary DNS server will request a zone " "transfer from the primary DNS server. " "The default value is %s.") % settings.DOMAINS_DEFAULT_REFRESH) - retry = models.IntegerField(_("retry"), null=True, blank=True, + retry = models.CharField(_("retry"), max_length=16, blank=True, validators=[validators.validate_zone_interval], help_text=_("The time a secondary server waits before retrying a failed zone transfer. " "Normally, the retry time is less than the refresh time. " "The default value is %s.") % settings.DOMAINS_DEFAULT_RETRY) - expire = models.IntegerField(_("expire"), null=True, blank=True, + expire = models.CharField(_("expire"), max_length=16, blank=True, validators=[validators.validate_zone_interval], help_text=_("The time that a secondary server will keep trying to complete a zone " "transfer. If this time expires prior to a successful zone transfer, " "the secondary server will expire its zone file. This means the secondary " "will stop answering queries. " "The default value is %s.") % settings.DOMAINS_DEFAULT_EXPIRE) - min_ttl = models.IntegerField(_("min TTL"), null=True, blank=True, + min_ttl = models.CharField(_("min TTL"), max_length=16, blank=True, validators=[validators.validate_zone_interval], help_text=_("The minimum time-to-live value applies to all resource records in the " "zone file. This value is supplied in query responses to inform other " diff --git a/orchestra/contrib/payments/actions.py b/orchestra/contrib/payments/actions.py index affe04ca..d2d0652a 100644 --- a/orchestra/contrib/payments/actions.py +++ b/orchestra/contrib/payments/actions.py @@ -68,7 +68,7 @@ def mark_as_executed(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) mark_as_executed.url_name = 'execute' -mark_as_executed.verbose_name = _("Mark as executed") +mark_as_executed.short_description = _("Mark as executed") @transaction.atomic @@ -84,7 +84,7 @@ def mark_as_secured(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) mark_as_secured.url_name = 'secure' -mark_as_secured.verbose_name = _("Mark as secured") +mark_as_secured.short_description = _("Mark as secured") @transaction.atomic @@ -100,7 +100,7 @@ def mark_as_rejected(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) mark_as_rejected.url_name = 'reject' -mark_as_rejected.verbose_name = _("Mark as rejected") +mark_as_rejected.short_description = _("Mark as rejected") def _format_display_objects(modeladmin, request, queryset, related): @@ -139,7 +139,7 @@ def mark_process_as_executed(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) mark_process_as_executed.url_name = 'executed' -mark_process_as_executed.verbose_name = _("Mark as executed") +mark_process_as_executed.short_description = _("Mark as executed") @transaction.atomic @@ -155,7 +155,7 @@ def abort(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) abort.url_name = 'abort' -abort.verbose_name = _("Abort") +abort.short_description = _("Abort") @transaction.atomic @@ -171,7 +171,7 @@ def commit(modeladmin, request, queryset): num) modeladmin.message_user(request, msg) commit.url_name = 'commit' -commit.verbose_name = _("Commit") +commit.short_description = _("Commit") def delete_selected(modeladmin, request, queryset): diff --git a/orchestra/contrib/saas/backends/phplist.py b/orchestra/contrib/saas/backends/phplist.py index 2e7215af..9e2a2299 100644 --- a/orchestra/contrib/saas/backends/phplist.py +++ b/orchestra/contrib/saas/backends/phplist.py @@ -1,9 +1,12 @@ +import hashlib import re +import textwrap import requests from django.utils.translation import ugettext_lazy as _ from orchestra.contrib.orchestration import ServiceController +from orchestra.utils.sys import sshrun from .. import settings @@ -45,7 +48,28 @@ class PhpListSaaSBackend(ServiceController): if response.status_code != 200: raise RuntimeError("Bad status code %i" % response.status_code) else: - raise NotImplementedError("Change password not implemented") + md5_password = hashlib.md5() + md5_password.update(saas.password.encode('ascii')) + context = { + 'name': saas.name, + 'site_name': saas.name, + 'db_user': settings.SAAS_PHPLIST_DB_USER, + 'db_pass': settings.SAAS_PHPLIST_DB_PASS, + 'db_name': settings.SAAS_PHPLIST_DB_NAME, + 'db_host': settings.SAAS_PHPLIST_DB_HOST, + 'digest': md5_password.hexdigest(), + } + context['db_name'] = context['db_name'] % context + cmd = textwrap.dedent("""\ + mysql \\ + --host=%(db_host)s \\ + --user=%(db_user)s \\ + --password=%(db_pass)s \\ + --execute='UPDATE phplist_admin SET password="%(digest)s" where ID=1; \\ + UPDATE phplist_user_user SET password="%(digest)s" where ID=1;' \\ + %(db_name)s""") % context + print(cmd) + sshrun(server.get_address(), cmd) def save(self, saas): if hasattr(saas, 'password'): diff --git a/orchestra/contrib/saas/services/phplist.py b/orchestra/contrib/saas/services/phplist.py index 24cf2c92..ca74b10b 100644 --- a/orchestra/contrib/saas/services/phplist.py +++ b/orchestra/contrib/saas/services/phplist.py @@ -54,7 +54,7 @@ class PHPListService(SoftwareService): return db_name[:65] def get_db_user(self): - return settings.SAAS_PHPLIST_DB_NAME + return settings.SAAS_PHPLIST_DB_USER def get_account(self): return self.instance.account.get_main() diff --git a/orchestra/contrib/saas/settings.py b/orchestra/contrib/saas/settings.py index 263d2ac6..ac15c90b 100644 --- a/orchestra/contrib/saas/settings.py +++ b/orchestra/contrib/saas/settings.py @@ -1,3 +1,5 @@ +from django.utils.translation import ugettext_lazy as _ + from orchestra.contrib.settings import Setting from orchestra.settings import ORCHESTRA_BASE_DOMAIN @@ -76,10 +78,30 @@ SAAS_DRUPAL_SITES_PATH = Setting('WEBSITES_DRUPAL_SITES_PATH', ) -SAAS_PHPLIST_DB_NAME = Setting('SAAS_PHPLIST_DB_NAME', +SAAS_PHPLIST_DB_USER = Setting('SAAS_PHPLIST_DB_USER', 'phplist_mu', + help_text=_("Needed for password changing support."), ) + +SAAS_PHPLIST_DB_PASS = Setting('SAAS_PHPLIST_DB_PASS', + 'secret', + help_text=_("Needed for password changing support."), +) + + +SAAS_PHPLIST_DB_NAME = Setting('SAAS_PHPLIST_DB_NAME', + 'phplist_mu_%(site_name)s', + help_text=_("Needed for password changing support."), +) + + +SAAS_PHPLIST_DB_HOST = Setting('SAAS_PHPLIST_DB_HOST', + 'loclahost', + help_text=_("Needed for password changing support."), +) + + SAAS_PHPLIST_BASE_DOMAIN = Setting('SAAS_PHPLIST_BASE_DOMAIN', 'lists.{}'.format(ORCHESTRA_BASE_DOMAIN), help_text="Uses ORCHESTRA_BASE_DOMAIN by default.", diff --git a/orchestra/contrib/services/actions.py b/orchestra/contrib/services/actions.py index 9d9556fd..7bc59b80 100644 --- a/orchestra/contrib/services/actions.py +++ b/orchestra/contrib/services/actions.py @@ -47,7 +47,7 @@ def update_orders(modeladmin, request, queryset, extra_context=None): } return render(request, 'admin/services/service/update_orders.html', context) update_orders.url_name = 'update-orders' -update_orders.verbose_name = _("Update orders") +update_orders.short_description = _("Update orders") def view_help(modeladmin, request, queryset): @@ -62,7 +62,7 @@ def view_help(modeladmin, request, queryset): } return TemplateResponse(request, 'admin/services/service/help.html', context) view_help.url_name = 'help' -view_help.verbose_name = _("Help") +view_help.tool_description = _("Help") def clone(modeladmin, request, queryset): diff --git a/orchestra/contrib/systemusers/actions.py b/orchestra/contrib/systemusers/actions.py index 0fb90eb4..102d73ad 100644 --- a/orchestra/contrib/systemusers/actions.py +++ b/orchestra/contrib/systemusers/actions.py @@ -67,7 +67,7 @@ def set_permission(modeladmin, request, queryset): return TemplateResponse(request, 'admin/systemusers/systemuser/set_permission.html', context, current_app=modeladmin.admin_site.name) set_permission.url_name = 'set-permission' -set_permission.verbose_name = _("Set permission") +set_permission.tool_description = _("Set permission") def delete_selected(modeladmin, request, queryset): diff --git a/orchestra/templates/orchestra/admin/change_form.html b/orchestra/templates/orchestra/admin/change_form.html index 05b47f98..0191f584 100644 --- a/orchestra/templates/orchestra/admin/change_form.html +++ b/orchestra/templates/orchestra/admin/change_form.html @@ -4,7 +4,7 @@ {% block object-tools-items %} {% for item in object_tools_items %} -
  • {{ item.verbose_name }}
  • +
  • {{ item.tool_description }}
  • {% endfor %} {{ block.super }} {% endblock %}