Added support from domain SOA record massive editing and phplist password changing

This commit is contained in:
Marc Aymerich 2015-07-20 12:51:30 +00:00
parent 0d9058d266
commit 708758880d
21 changed files with 290 additions and 68 deletions

View File

@ -426,7 +426,4 @@ Case
# bill changelist: dates: (closed_on, created_on, updated_on) # 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 # Resource data inline show info: link to monitor data, and history chart: link to monitor data of each item

View File

@ -129,4 +129,4 @@ def disable(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
disable.url_name = 'disable' disable.url_name = 'disable'
disable.verbose_name = _("Disable") disable.short_description = _("Disable")

View File

@ -17,7 +17,9 @@ class AdminFormMixin(object):
def as_admin(self): def as_admin(self):
prepopulated_fields = {} prepopulated_fields = {}
fieldsets = [ fieldsets = [
(None, {'fields': list(self.fields.keys())}) (None, {
'fields': list(self.fields.keys())
}),
] ]
adminform = helpers.AdminForm(self, fieldsets, prepopulated_fields) adminform = helpers.AdminForm(self, fieldsets, prepopulated_fields)
template = Template( template = Template(
@ -25,7 +27,10 @@ class AdminFormMixin(object):
' {% include "admin/includes/fieldset.html" %}' ' {% include "admin/includes/fieldset.html" %}'
'{% endfor %}' '{% endfor %}'
) )
return template.render(Context({'adminform': adminform})) context = Context({
'adminform': adminform
})
return template.render(context)
class AdminFormSet(BaseModelFormSet): class AdminFormSet(BaseModelFormSet):
@ -43,7 +48,10 @@ class AdminFormSet(BaseModelFormSet):
template = Template( template = Template(
'{% include "admin/edit_inline/tabular.html" %}' '{% 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): def adminmodelformset_factory(modeladmin, form, formset=AdminFormSet, **kwargs):

View File

@ -98,11 +98,13 @@ class ChangeViewActionsMixin(object):
action = getattr(self, action) action = getattr(self, action)
view = action_to_view(action, self) view = action_to_view(action, self)
view.url_name = getattr(action, 'url_name', action.__name__) view.url_name = getattr(action, 'url_name', action.__name__)
verbose_name = getattr(action, 'verbose_name', tool_description = getattr(action, 'tool_description', '')
view.url_name.capitalize().replace('_', ' ')) if not tool_description:
if hasattr(verbose_name, '__call__'): tool_description = getattr(action, 'short_description',
verbose_name = verbose_name(obj) view.url_name.capitalize().replace('_', ' '))
view.verbose_name = verbose_name if hasattr(tool_description, '__call__'):
tool_description = tool_description(obj)
view.tool_description = tool_description
view.css_class = getattr(action, 'css_class', 'historylink') view.css_class = getattr(action, 'css_class', 'historylink')
view.help_text = getattr(action, 'help_text', '') view.help_text = getattr(action, 'help_text', '')
views.append(view) views.append(view)

View File

@ -98,7 +98,7 @@ def change_url(obj):
@admin_field @admin_field
def admin_link(*args, **kwargs): def admin_link(*args, **kwargs):
instance = args[-1] instance = args[-1]
if kwargs['field'] in ['id', 'pk', '__str__']: if kwargs['field'] in ('id', 'pk', '__str__'):
obj = instance obj = instance
else: else:
try: try:
@ -120,7 +120,7 @@ def admin_link(*args, **kwargs):
extra = '' extra = ''
if kwargs['popup']: if kwargs['popup']:
extra = 'onclick="return showAddAnotherPopup(this);"' extra = 'onclick="return showAddAnotherPopup(this);"'
return '<a href="%s" %s>%s</a>' % (url, extra, display) return mark_safe('<a href="%s" %s>%s</a>' % (url, extra, display))
@admin_field @admin_field

View File

@ -25,7 +25,7 @@ def list_contacts(modeladmin, request, queryset):
url = reverse('admin:contacts_contact_changelist') url = reverse('admin:contacts_contact_changelist')
url += '?account__in=%s' % ','.join(map(str, ids)) url += '?account__in=%s' % ','.join(map(str, ids))
return redirect(url) return redirect(url)
list_contacts.verbose_name = _("List contacts") list_contacts.short_description = _("List contacts")
def service_report(modeladmin, request, queryset): def service_report(modeladmin, request, queryset):

View File

@ -31,7 +31,7 @@ def view_bill(modeladmin, request, queryset):
return return
html = bill.html or bill.render() html = bill.html or bill.render()
return HttpResponse(html) return HttpResponse(html)
view_bill.verbose_name = _("View") view_bill.tool_description = _("View")
view_bill.url_name = '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), 'obj': get_object_from_url(modeladmin, request),
} }
return render(request, 'admin/orchestra/generic_confirmation.html', context) return render(request, 'admin/orchestra/generic_confirmation.html', context)
close_bills.verbose_name = _("Close") close_bills.tool_description = _("Close")
close_bills.url_name = 'close' close_bills.url_name = 'close'
@ -137,7 +137,7 @@ def download_bills(modeladmin, request, queryset):
response = HttpResponse(pdf, content_type='application/pdf') response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % bill.number response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % bill.number
return response return response
download_bills.verbose_name = _("Download") download_bills.tool_description = _("Download")
download_bills.url_name = 'download' download_bills.url_name = 'download'
@ -149,7 +149,7 @@ def close_send_download_bills(modeladmin, request, queryset):
return return
return download_bills(modeladmin, request, queryset) return download_bills(modeladmin, request, queryset)
return response 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.url_name = 'close-send-download'
close_send_download_bills.help_text = _("Close, send and download bills in one shot.") close_send_download_bills.help_text = _("Close, send and download bills in one shot.")
@ -288,7 +288,7 @@ def amend_bills(modeladmin, request, queryset):
_('<a href="%(url)s">%(num)i amendment bills</a> have been generated.') % context, _('<a href="%(url)s">%(num)i amendment bills</a> have been generated.') % context,
num num
))) )))
amend_bills.verbose_name = _("Amend") amend_bills.tool_description = _("Amend")
amend_bills.url_name = 'amend' amend_bills.url_name = 'amend'

View File

@ -1,16 +1,19 @@
import copy import copy
from django.contrib import messages
from django.contrib.admin import helpers 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.shortcuts import render
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ungettext, ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from orchestra.admin.forms import adminmodelformset_factory 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 orchestra.utils.python import AttrDict
from .forms import RecordForm, RecordEditFormSet from .forms import RecordForm, RecordEditFormSet, SOAForm
from .models import Record from .models import Record
@ -23,15 +26,33 @@ def view_zone(modeladmin, request, queryset):
} }
return TemplateResponse(request, 'admin/domains/domain/view_zone.html', context) return TemplateResponse(request, 'admin/domains/domain/view_zone.html', context)
view_zone.url_name = 'view-zone' view_zone.url_name = 'view-zone'
view_zone.verbose_name = _("View zone") view_zone.short_description = _("View zone")
def edit_records(modeladmin, request, queryset): 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 = [] formsets = []
for domain in queryset.prefetch_related('records'): for domain in queryset.prefetch_related('records'):
modeladmin_copy = copy.copy(modeladmin) modeladmin_copy = copy.copy(modeladmin)
modeladmin_copy.model = Record modeladmin_copy.model = Record
link = '<a href="%s">%s</a>' % (change_url(domain), domain.name) prefix = '' if domain.is_top else '&nbsp;'*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 = '<a href="%(url)s" title="%(title)s">%(name)s</a>' % context
modeladmin_copy.verbose_name_plural = mark_safe(link) modeladmin_copy.verbose_name_plural = mark_safe(link)
RecordFormSet = adminmodelformset_factory( RecordFormSet = adminmodelformset_factory(
modeladmin_copy, RecordForm, formset=RecordEditFormSet, extra=1, can_delete=True) modeladmin_copy, RecordForm, formset=RecordEditFormSet, extra=1, can_delete=True)
@ -73,7 +94,7 @@ def edit_records(modeladmin, request, queryset):
opts = modeladmin.model._meta opts = modeladmin.model._meta
context = { context = {
'title': _("Edit records"), 'title': _("Edit records"),
'action_name': 'Edit records', 'action_name': _("Edit records"),
'action_value': 'edit_records', 'action_value': 'edit_records',
'display_objects': [], 'display_objects': [],
'queryset': queryset, 'queryset': queryset,
@ -86,6 +107,46 @@ def edit_records(modeladmin, request, queryset):
return render(request, 'admin/domains/domain/edit_records.html', context) return render(request, 'admin/domains/domain/edit_records.html', context)
def add_records(modeladmin, request, queryset): def set_soa(modeladmin, request, queryset):
# TODO if queryset.filter(top__isnull=False).exists():
pass 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")

View File

@ -8,7 +8,7 @@ from orchestra.admin.utils import admin_link, change_url
from orchestra.contrib.accounts.admin import AccountAdminMixin from orchestra.contrib.accounts.admin import AccountAdminMixin
from orchestra.utils import apps 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 .filters import TopDomainListFilter
from .forms import RecordForm, RecordInlineFormSet, BatchDomainCreationAdminForm from .forms import RecordForm, RecordInlineFormSet, BatchDomainCreationAdminForm
from .models import Domain, Record from .models import Domain, Record
@ -51,13 +51,16 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
) )
add_fields = ('name', 'account') add_fields = ('name', 'account')
fields = ('name', 'account_link') fields = ('name', 'account_link')
inlines = [RecordInline, DomainInline] readonly_fields = ('account_link', 'top_link',)
list_filter = [TopDomainListFilter] inlines = (RecordInline, DomainInline)
list_filter = (TopDomainListFilter,)
change_readonly_fields = ('name', 'serial') change_readonly_fields = ('name', 'serial')
search_fields = ('name', 'account__username') search_fields = ('name', 'account__username')
add_form = BatchDomainCreationAdminForm add_form = BatchDomainCreationAdminForm
actions = (edit_records,) actions = (edit_records, set_soa)
change_view_actions = [view_zone] change_view_actions = (view_zone, edit_records)
top_link = admin_link('top')
def structured_name(self, domain): def structured_name(self, domain):
if domain.is_top: if domain.is_top:
@ -90,15 +93,26 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
""" Add SOA fields when domain is top """ """ Add SOA fields when domain is top """
fieldsets = super(DomainAdmin, self).get_fieldsets(request, obj) fieldsets = super(DomainAdmin, self).get_fieldsets(request, obj)
if obj and obj.is_top: if obj:
fieldsets += ( if obj.is_top:
(_("SOA"), { fieldsets += (
'classes': ('collapse',), (_("SOA"), {
'fields': ('serial', 'refresh', 'retry', 'expire', 'min_ttl'), '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 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): def get_queryset(self, request):
""" Order by structured name and imporve performance """ """ Order by structured name and imporve performance """
qs = super(DomainAdmin, self).get_queryset(request) 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) domain = Domain.objects.create(name=name, account_id=obj.account_id)
self.extra_domains.append(domain) 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): def save_related(self, request, form, formsets, change):
""" batch domain creation support """ """ batch domain creation support """
super(DomainAdmin, self).save_related(request, form, formsets, change) super(DomainAdmin, self).save_related(request, form, formsets, change)
@ -142,7 +150,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
record.pk = None record.pk = None
formset.instance = domain formset.instance = domain
form.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) admin.site.register(Domain, DomainAdmin)

View File

@ -174,7 +174,8 @@ class Bind9MasterDomainBackend(ServiceController):
class Bind9SlaveDomainBackend(Bind9MasterDomainBackend): class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
""" """
Generate the configuartion for slave servers 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 CONF_PATH = settings.DOMAINS_SLAVES_PATH

View File

@ -1,8 +1,9 @@
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _ 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 . import validators
from .helpers import domain_for_validation from .helpers import domain_for_validation
@ -71,8 +72,8 @@ class RecordForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RecordForm, self).__init__(*args, **kwargs) super(RecordForm, self).__init__(*args, **kwargs)
self.fields['ttl'].widget = forms.TextInput(attrs={'size':'10'}) self.fields['ttl'].widget = forms.TextInput(attrs={'size': '10'})
self.fields['value'].widget = forms.TextInput(attrs={'size':'100'}) self.fields['value'].widget = forms.TextInput(attrs={'size': '100'})
class ValidateZoneMixin(object): class ValidateZoneMixin(object):
@ -97,3 +98,30 @@ class RecordEditFormSet(ValidateZoneMixin, AdminFormSet):
class RecordInlineFormSet(ValidateZoneMixin, forms.models.BaseInlineFormSet): class RecordInlineFormSet(ValidateZoneMixin, forms.models.BaseInlineFormSet):
pass 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,
)

View File

@ -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.'),
),
]

View File

@ -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 <tt>4w</tt>.', 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 <tt>1h</tt>.', 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 <tt>1d</tt>.", 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 <tt>2h</tt>.', verbose_name='retry', max_length=16),
),
]

View File

@ -21,7 +21,7 @@ class Domain(models.Model):
editable=False) editable=False)
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, 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.")) 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], validators=[validators.validate_zone_interval],
help_text=_("The time a secondary DNS server waits before querying the primary DNS " 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, " "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 " "If they are different, the secondary DNS server will request a zone "
"transfer from the primary DNS server. " "transfer from the primary DNS server. "
"The default value is <tt>%s</tt>.") % settings.DOMAINS_DEFAULT_REFRESH) "The default value is <tt>%s</tt>.") % 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], validators=[validators.validate_zone_interval],
help_text=_("The time a secondary server waits before retrying a failed zone transfer. " help_text=_("The time a secondary server waits before retrying a failed zone transfer. "
"Normally, the retry time is less than the refresh time. " "Normally, the retry time is less than the refresh time. "
"The default value is <tt>%s</tt>.") % settings.DOMAINS_DEFAULT_RETRY) "The default value is <tt>%s</tt>.") % 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], validators=[validators.validate_zone_interval],
help_text=_("The time that a secondary server will keep trying to complete a zone " 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, " "transfer. If this time expires prior to a successful zone transfer, "
"the secondary server will expire its zone file. This means the secondary " "the secondary server will expire its zone file. This means the secondary "
"will stop answering queries. " "will stop answering queries. "
"The default value is <tt>%s</tt>.") % settings.DOMAINS_DEFAULT_EXPIRE) "The default value is <tt>%s</tt>.") % 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], validators=[validators.validate_zone_interval],
help_text=_("The minimum time-to-live value applies to all resource records in the " 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 " "zone file. This value is supplied in query responses to inform other "

View File

@ -68,7 +68,7 @@ def mark_as_executed(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
mark_as_executed.url_name = 'execute' mark_as_executed.url_name = 'execute'
mark_as_executed.verbose_name = _("Mark as executed") mark_as_executed.short_description = _("Mark as executed")
@transaction.atomic @transaction.atomic
@ -84,7 +84,7 @@ def mark_as_secured(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
mark_as_secured.url_name = 'secure' mark_as_secured.url_name = 'secure'
mark_as_secured.verbose_name = _("Mark as secured") mark_as_secured.short_description = _("Mark as secured")
@transaction.atomic @transaction.atomic
@ -100,7 +100,7 @@ def mark_as_rejected(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
mark_as_rejected.url_name = 'reject' 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): def _format_display_objects(modeladmin, request, queryset, related):
@ -139,7 +139,7 @@ def mark_process_as_executed(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
mark_process_as_executed.url_name = 'executed' 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 @transaction.atomic
@ -155,7 +155,7 @@ def abort(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
abort.url_name = 'abort' abort.url_name = 'abort'
abort.verbose_name = _("Abort") abort.short_description = _("Abort")
@transaction.atomic @transaction.atomic
@ -171,7 +171,7 @@ def commit(modeladmin, request, queryset):
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
commit.url_name = 'commit' commit.url_name = 'commit'
commit.verbose_name = _("Commit") commit.short_description = _("Commit")
def delete_selected(modeladmin, request, queryset): def delete_selected(modeladmin, request, queryset):

View File

@ -1,9 +1,12 @@
import hashlib
import re import re
import textwrap
import requests import requests
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.orchestration import ServiceController from orchestra.contrib.orchestration import ServiceController
from orchestra.utils.sys import sshrun
from .. import settings from .. import settings
@ -45,7 +48,28 @@ class PhpListSaaSBackend(ServiceController):
if response.status_code != 200: if response.status_code != 200:
raise RuntimeError("Bad status code %i" % response.status_code) raise RuntimeError("Bad status code %i" % response.status_code)
else: 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): def save(self, saas):
if hasattr(saas, 'password'): if hasattr(saas, 'password'):

View File

@ -54,7 +54,7 @@ class PHPListService(SoftwareService):
return db_name[:65] return db_name[:65]
def get_db_user(self): def get_db_user(self):
return settings.SAAS_PHPLIST_DB_NAME return settings.SAAS_PHPLIST_DB_USER
def get_account(self): def get_account(self):
return self.instance.account.get_main() return self.instance.account.get_main()

View File

@ -1,3 +1,5 @@
from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.settings import Setting from orchestra.contrib.settings import Setting
from orchestra.settings import ORCHESTRA_BASE_DOMAIN 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', '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', SAAS_PHPLIST_BASE_DOMAIN = Setting('SAAS_PHPLIST_BASE_DOMAIN',
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN), 'lists.{}'.format(ORCHESTRA_BASE_DOMAIN),
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default.", help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default.",

View File

@ -47,7 +47,7 @@ def update_orders(modeladmin, request, queryset, extra_context=None):
} }
return render(request, 'admin/services/service/update_orders.html', context) return render(request, 'admin/services/service/update_orders.html', context)
update_orders.url_name = 'update-orders' update_orders.url_name = 'update-orders'
update_orders.verbose_name = _("Update orders") update_orders.short_description = _("Update orders")
def view_help(modeladmin, request, queryset): 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) return TemplateResponse(request, 'admin/services/service/help.html', context)
view_help.url_name = 'help' view_help.url_name = 'help'
view_help.verbose_name = _("Help") view_help.tool_description = _("Help")
def clone(modeladmin, request, queryset): def clone(modeladmin, request, queryset):

View File

@ -67,7 +67,7 @@ def set_permission(modeladmin, request, queryset):
return TemplateResponse(request, 'admin/systemusers/systemuser/set_permission.html', return TemplateResponse(request, 'admin/systemusers/systemuser/set_permission.html',
context, current_app=modeladmin.admin_site.name) context, current_app=modeladmin.admin_site.name)
set_permission.url_name = 'set-permission' set_permission.url_name = 'set-permission'
set_permission.verbose_name = _("Set permission") set_permission.tool_description = _("Set permission")
def delete_selected(modeladmin, request, queryset): def delete_selected(modeladmin, request, queryset):

View File

@ -4,7 +4,7 @@
{% block object-tools-items %} {% block object-tools-items %}
{% for item in object_tools_items %} {% for item in object_tools_items %}
<li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.help_text }}">{{ item.verbose_name }}</a></li> <li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.help_text }}">{{ item.tool_description }}</a></li>
{% endfor %} {% endfor %}
{{ block.super }} {{ block.super }}
{% endblock %} {% endblock %}