Added support from domain SOA record massive editing and phplist password changing
This commit is contained in:
parent
0d9058d266
commit
708758880d
3
TODO.md
3
TODO.md
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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', '')
|
||||||
|
if not tool_description:
|
||||||
|
tool_description = getattr(action, 'short_description',
|
||||||
view.url_name.capitalize().replace('_', ' '))
|
view.url_name.capitalize().replace('_', ' '))
|
||||||
if hasattr(verbose_name, '__call__'):
|
if hasattr(tool_description, '__call__'):
|
||||||
verbose_name = verbose_name(obj)
|
tool_description = tool_description(obj)
|
||||||
view.verbose_name = verbose_name
|
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 ' '*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")
|
||||||
|
|
|
@ -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:
|
||||||
|
if obj.is_top:
|
||||||
fieldsets += (
|
fieldsets += (
|
||||||
(_("SOA"), {
|
(_("SOA"), {
|
||||||
'classes': ('collapse',),
|
'classes': ('collapse',),
|
||||||
'fields': ('serial', 'refresh', 'retry', 'expire', 'min_ttl'),
|
'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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
|
@ -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.'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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 "
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in New Issue