diff --git a/TODO.md b/TODO.md index 97ee72c8..3dc05241 100644 --- a/TODO.md +++ b/TODO.md @@ -427,3 +427,5 @@ Case # bill changelist: dates: (closed_on, created_on, updated_on) # Resource data inline show info: link to monitor data, and history chart: link to monitor data of each item + +# DIsplay message email content nicelly diff --git a/orchestra/contrib/accounts/actions.py b/orchestra/contrib/accounts/actions.py index b6bf042b..9c93ff07 100644 --- a/orchestra/contrib/accounts/actions.py +++ b/orchestra/contrib/accounts/actions.py @@ -18,7 +18,7 @@ from . import settings def list_contacts(modeladmin, request, queryset): - ids = queryset.values_list('id', flat=True) + ids = queryset.order_by().values_list('id', flat=True).distinct() if not ids: messages.warning(request, "Select at least one account.") return @@ -28,6 +28,17 @@ def list_contacts(modeladmin, request, queryset): list_contacts.short_description = _("List contacts") +def list_accounts(modeladmin, request, queryset): + accounts = queryset.order_by().values_list('account_id', flat=True).distinct() + if not accounts: + messages.warning(request, "Select at least one instance.") + return + url = reverse('admin:contacts_contact_changelist') + url += '?id__in=%s' % ','.join(map(str, accounts)) + return redirect(url) +list_accounts.short_description = _("List accounts") + + def service_report(modeladmin, request, queryset): # TODO resources accounts = [] diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py index 347ad6ba..fb70ef14 100644 --- a/orchestra/contrib/bills/admin.py +++ b/orchestra/contrib/bills/admin.py @@ -13,6 +13,7 @@ from django.shortcuts import redirect from orchestra.admin import ExtendedModelAdmin from orchestra.admin.utils import admin_date, insertattr, admin_link +from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import AccountAdminMixin, AccountAdmin from orchestra.forms.widgets import paddingCheckboxSelectMultiple @@ -212,7 +213,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): actions = [ actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills, actions.amend_bills, actions.bill_report, actions.service_report, - actions.close_send_download_bills, + actions.close_send_download_bills, list_accounts, ] change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link', 'amend_links') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') diff --git a/orchestra/contrib/contacts/admin.py b/orchestra/contrib/contacts/admin.py index 965e4732..f761fc28 100644 --- a/orchestra/contrib/contacts/admin.py +++ b/orchestra/contrib/contacts/admin.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.admin import AtLeastOneRequiredInlineFormSet, ExtendedModelAdmin from orchestra.admin.actions import SendEmail from orchestra.admin.utils import insertattr, change_url +from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import AccountAdmin, AccountAdminMixin from orchestra.forms.widgets import paddingCheckboxSelectMultiple @@ -59,7 +60,7 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin): 'fields': ('address', ('zipcode', 'city'), 'country') }), ) - actions = [SendEmail(),] + actions = (SendEmail(), list_accounts) def dispaly_name(self, contact): return str(contact) diff --git a/orchestra/contrib/databases/admin.py b/orchestra/contrib/databases/admin.py index 184968e4..a0cc213d 100644 --- a/orchestra/contrib/databases/admin.py +++ b/orchestra/contrib/databases/admin.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin.utils import change_url +from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import SelectAccountAdminMixin from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm @@ -42,6 +43,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): filter_horizontal = ['users'] filter_by_account_fields = ('users',) list_prefetch_related = ('users',) + actions = (list_accounts,) def display_users(self, db): links = [] @@ -90,6 +92,7 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten readonly_fields = ('account_link', 'display_databases',) filter_by_account_fields = ('databases',) list_prefetch_related = ('databases',) + actions = (list_accounts,) def display_databases(self, user): links = [] diff --git a/orchestra/contrib/domains/admin.py b/orchestra/contrib/domains/admin.py index efc05559..db328b1e 100644 --- a/orchestra/contrib/domains/admin.py +++ b/orchestra/contrib/domains/admin.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin from orchestra.admin.utils import admin_link, change_url +from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import AccountAdminMixin from orchestra.utils import apps @@ -57,7 +58,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): change_readonly_fields = ('name', 'serial') search_fields = ('name', 'account__username') add_form = BatchDomainCreationAdminForm - actions = (edit_records, set_soa) + actions = (edit_records, set_soa, list_accounts) change_view_actions = (view_zone, edit_records) top_link = admin_link('top') diff --git a/orchestra/contrib/domains/forms.py b/orchestra/contrib/domains/forms.py index 66c0bf10..50bbd312 100644 --- a/orchestra/contrib/domains/forms.py +++ b/orchestra/contrib/domains/forms.py @@ -80,15 +80,15 @@ class ValidateZoneMixin(object): def clean(self): """ Checks if everything is consistent """ super(ValidateZoneMixin, self).clean() - if any(formset.errors): + if any(self.errors): return - if formset.instance.name: + if self.instance.name: records = [] - for form in formset.forms: + for form in self.forms: data = form.cleaned_data if data and not data['DELETE']: records.append(data) - domain = domain_for_validation(formset.instance, records) + domain = domain_for_validation(self.instance, records) validators.validate_zone(domain.render_zone()) diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py index 3e4890e8..b1053311 100644 --- a/orchestra/contrib/domains/models.py +++ b/orchestra/contrib/domains/models.py @@ -273,14 +273,14 @@ class Record(models.Model): ) VALIDATORS = { - MX: validators.validate_mx_record, - NS: validators.validate_zone_label, - A: validate_ipv4_address, - AAAA: validate_ipv6_address, - CNAME: validators.validate_zone_label, - TXT: validate_ascii, - SRV: validators.validate_srv_record, - SOA: validators.validate_soa_record, + MX: (validators.validate_mx_record,), + NS: (validators.validate_zone_label,), + A: (validate_ipv4_address,), + AAAA: (validate_ipv6_address,), + CNAME: (validators.validate_zone_label,), + TXT: (validate_ascii, validators.validate_quoted_record), + SRV: (validators.validate_srv_record,), + SOA: (validators.validate_soa_record,), } domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records') @@ -300,12 +300,13 @@ class Record(models.Model): if self.type != self.TXT: self.value = self.value.lower().strip() if self.type: - try: - self.VALIDATORS[self.type](self.value) - except ValidationError as error: - raise ValidationError({ - 'value': error, - }) + for validator in self.VALIDATORS.get(self.type, []): + try: + validator(self.value) + except ValidationError as error: + raise ValidationError({ + 'value': error, + }) def get_ttl(self): return self.ttl or settings.DOMAINS_DEFAULT_TTL diff --git a/orchestra/contrib/domains/validators.py b/orchestra/contrib/domains/validators.py index 569b81e1..b1c02610 100644 --- a/orchestra/contrib/domains/validators.py +++ b/orchestra/contrib/domains/validators.py @@ -107,6 +107,16 @@ def validate_soa_record(value): raise ValidationError(msg) +def validate_quoted_record(value): + value = value.strip() + if ' ' in value and (value[0] != '"' or value[-1] != '"'): + raise ValidationError( + _("This record value contains spaces, you must enclose the string in double quotes; " + "otherwise, individual words will be separately quoted and break up the record " + "into multiple parts.") + ) + + def validate_zone(zone): """ Ultimate zone file validation using named-checkzone """ zone_name = zone.split()[0][:-1] diff --git a/orchestra/contrib/lists/admin.py b/orchestra/contrib/lists/admin.py index f4861270..9f5e8e90 100644 --- a/orchestra/contrib/lists/admin.py +++ b/orchestra/contrib/lists/admin.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin.actions import disable from orchestra.admin.utils import admin_link +from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import SelectAccountAdminMixin from orchestra.contrib.accounts.filters import IsActiveListFilter @@ -53,7 +54,7 @@ class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModel add_form = ListCreationForm list_select_related = ('account', 'address_domain',) filter_by_account_fields = ['address_domain'] - actions = (disable,) + actions = (disable, list_accounts) address_domain_link = admin_link('address_domain', order='address_domain__name') diff --git a/orchestra/contrib/mailboxes/admin.py b/orchestra/contrib/mailboxes/admin.py index c31c41dd..4398945d 100644 --- a/orchestra/contrib/mailboxes/admin.py +++ b/orchestra/contrib/mailboxes/admin.py @@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin.actions import disable from orchestra.admin.utils import admin_link, change_url +from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import SelectAccountAdminMixin from orchestra.contrib.accounts.filters import IsActiveListFilter @@ -69,7 +70,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo add_form = MailboxCreationForm form = MailboxChangeForm list_prefetch_related = ('addresses__domain',) - actions = (disable,) + actions = (disable, list_accounts) def __init__(self, *args, **kwargs): super(MailboxAdmin, self).__init__(*args, **kwargs) diff --git a/orchestra/contrib/mailer/admin.py b/orchestra/contrib/mailer/admin.py index 78483270..c95bcb93 100644 --- a/orchestra/contrib/mailer/admin.py +++ b/orchestra/contrib/mailer/admin.py @@ -1,3 +1,6 @@ +import base64 +import email + from django import forms from django.contrib import admin from django.core.urlresolvers import reverse @@ -12,7 +15,7 @@ from .engine import send_pending from .models import Message, SMTPLog -COLORS = { +COLORS = { Message.QUEUED: 'purple', Message.SENT: 'green', Message.DEFERRED: 'darkorange', @@ -29,6 +32,21 @@ class MessageAdmin(admin.ModelAdmin): ) list_filter = ('state', 'priority', 'retries') list_prefetch_related = ('logs__id') + fieldsets = ( + (None, { + 'fields': ('state', 'priority', ('retries', 'last_retry_delta'), + 'display_full_subject', 'display_to_address', 'display_from_address', + 'display_content'), + }), + (_("Edit"), { + 'classes': ('collapse',), + 'fields': ('subject', 'to_address', 'from_address', 'content'), + }), + ) + readonly_fields = ( + 'retries', 'last_retry_delta', 'display_full_subject', 'display_to_address', + 'display_from_address', 'display_content', + ) colored_state = admin_colored('state', colors=COLORS) created_at_delta = admin_date('created_at') @@ -52,6 +70,37 @@ class MessageAdmin(admin.ModelAdmin): num_logs.admin_order_field = 'logs__count' num_logs.allow_tags = True + def display_content(self, instance): + part = email.message_from_string(instance.content) + payload = part.get_payload() + if isinstance(payload, list): + for part in payload: + payload = part.get_payload() + if part.get_content_type() == 'text/html': + # prioritize HTML + payload = '