diff --git a/TODO.md b/TODO.md index df7df2d0..238c05a0 100644 --- a/TODO.md +++ b/TODO.md @@ -149,20 +149,16 @@ * Resource used_list_display=True, allocated_list_displat=True, allow resources to show up on list_display -* Move plugins back from apps to orchestra main app - * BackendLog.updated_at (tasks that run over several minutes when finished they do not appear first on the changelist) (like celery tasks.when) -* Validate a model path exists between resource.content_type and backend.model - * Periodic task for cleaning old monitoring data -* Generate reports of Account contracted services - * Create an admin service_view with icons (like SaaS app) * Fix ftp traffic * Resource graph for each related object -* contacts filter by email_usage fix exact for contains +* Rename apache logs ending on .log in order to logrotate easily + +* SaaS wordpress multiple blogs per user? separate users from sites? diff --git a/orchestra/apps/contacts/actions.py b/orchestra/admin/actions.py similarity index 72% rename from orchestra/apps/contacts/actions.py rename to orchestra/admin/actions.py index 315afab2..75ac3d9e 100644 --- a/orchestra/apps/contacts/actions.py +++ b/orchestra/admin/actions.py @@ -3,8 +3,9 @@ from django.core.mail import send_mass_mail from django.shortcuts import render from django.utils.translation import ungettext, ugettext_lazy as _ -from orchestra.admin.utils import change_url +from .. import settings +from .utils import change_url from .forms import SendEmailForm @@ -13,18 +14,19 @@ class SendEmail(object): short_description = _("Send email") form = SendEmailForm template = 'admin/orchestra/generic_confirmation.html' + default_from = settings.ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL __name__ = 'semd_email' def __call__(self, modeladmin, request, queryset): """ make this monster behave like a function """ self.modeladmin = modeladmin self.queryset = queryset - opts = modeladmin.model._meta - app_label = opts.app_label + self.opts = modeladmin.model._meta + app_label = self.opts.app_label self.context = { 'action_name': _("Send email"), 'action_value': self.__name__, - 'opts': opts, + 'opts': self.opts, 'app_label': app_label, 'queryset': queryset, 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, @@ -34,14 +36,17 @@ class SendEmail(object): def write_email(self, request): if not request.user.is_superuser: raise PermissionDenied - form = self.form() + initial={ + 'email_from': self.default_from, + 'to': ' '.join(self.queryset.values_list('email', flat=True)) + } + form = self.form(initial=initial) if request.POST.get('post'): - form = self.form(request.POST) + form = self.form(request.POST, initial=initial) if form.is_valid(): options = { 'email_from': form.cleaned_data['email_from'], - 'cc': form.cleaned_data['cc'], - 'bcc': form.cleaned_data['bcc'], + 'extra_to': form.cleaned_data['extra_to'], 'subject': form.cleaned_data['subject'], 'message': form.cleaned_data['message'], @@ -50,7 +55,7 @@ class SendEmail(object): opts = self.modeladmin.model._meta app_label = opts.app_label self.context.update({ - 'title': _("Send e-mail to contacts"), + 'title': _("Send e-mail to %s") % self.opts.verbose_name_plural, 'content_title': "", 'form': form, 'submit_value': _("Continue"), @@ -61,31 +66,36 @@ class SendEmail(object): def confirm_email(self, request, **options): num = len(self.queryset) email_from = options['email_from'] - bcc = options['bcc'] - to = options['cc'] + extra_to = options['extra_to'] subject = options['subject'] message = options['message'] # The user has already confirmed if request.POST.get('post') == 'email_confirmation': + emails = [] for contact in self.queryset.all(): - to.append(contact.email) - send_mass_mail(subject, message, email_from, to, bcc) + emails.append((subject, message, email_from, [contact.email])) + if extra_to: + emails.append((subject, message, email_from, extra_to)) + send_mass_mail(emails) msg = ungettext( _("Message has been sent to %s.") % str(contact), - _("Message has been sent to %i contacts.") % num, + _("Message has been sent to %i %s.") % (num, self.opts.verbose_name_plural), num ) self.modeladmin.message_user(request, msg) return None form = self.form(initial={ + 'email_from': email_from, + 'extra_to': ', '.join(extra_to), 'subject': subject, 'message': message }) self.context.update({ 'title': _("Are you sure?"), 'content_message': _( - "Are you sure you want to send the following message to the following contacts?"), + "Are you sure you want to send the following message to the following %s?" + ) % self.opts.verbose_name_plural, 'display_objects': ["%s (%s)" % (contact, contact.email) for contact in self.queryset], 'form': form, 'subject': subject, diff --git a/orchestra/admin/forms.py b/orchestra/admin/forms.py index ef00dfa3..5d84fe1d 100644 --- a/orchestra/admin/forms.py +++ b/orchestra/admin/forms.py @@ -2,10 +2,13 @@ from functools import partial from django import forms from django.contrib.admin import helpers +from django.core import validators from django.forms.models import modelformset_factory, BaseModelFormSet from django.template import Template, Context from django.utils.translation import ugettext_lazy as _ +from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget + from ..core.validators import validate_password @@ -129,3 +132,39 @@ class AdminPasswordChangeForm(forms.Form): return ['password'] changed_data = property(_get_changed_data) + +class SendEmailForm(forms.Form): + email_from = forms.EmailField(label=_("From"), + widget=forms.TextInput(attrs={'size':'118'})) + to = forms.CharField(label="To", required=False, + widget=ShowTextWidget()) + extra_to = forms.CharField(label="To (extra)", required=False, + widget=forms.TextInput(attrs={'size':'118'})) + subject = forms.CharField(label=_("Subject"), + widget=forms.TextInput(attrs={'size':'118'})) + message = forms.CharField(label=_("Message"), + widget=forms.Textarea(attrs={'cols': 118, 'rows': 15})) + + def __init__(self, *args, **kwargs): + super(SendEmailForm, self).__init__(*args, **kwargs) + initial = kwargs.get('initial') + if 'to' in initial: + self.fields['to'].widget = ReadOnlyWidget(initial['to']) + else: + self.fields.pop('to') + + def clean_comma_separated_emails(self, value): + clean_value = [] + for email in value.split(','): + email = email.strip() + if email: + try: + validators.validate_email(email) + except validators.ValidationError: + raise validators.ValidationError("Comma separated email addresses.") + clean_value.append(email) + return clean_value + + def clean_extra_to(self): + extra_to = self.cleaned_data['extra_to'] + return self.clean_comma_separated_emails(extra_to) diff --git a/orchestra/apps/accounts/actions.py b/orchestra/apps/accounts/actions.py index 57d9856a..7a6bd349 100644 --- a/orchestra/apps/accounts/actions.py +++ b/orchestra/apps/accounts/actions.py @@ -39,13 +39,22 @@ list_contacts.verbose_name = _("List contacts") def service_report(modeladmin, request, queryset): accounts = [] - for account in queryset: + fields = [] + # First we get related manager names to fire a prefetch related + for name, field in queryset.model._meta._name_map.iteritems(): + model = field[0].model + if model in services.get() and model != queryset.model: + fields.append((model, name)) + sorted(fields, key=lambda i: i[0]._meta.verbose_name_plural.lower()) + fields = [field for model, field in fields] + + for account in queryset.prefetch_related(*fields): items = [] - for service in services.get(): - if service != type(account): - items.append((service._meta, service.objects.filter(account=account))) - sorted(items, key=lambda i: i[0].verbose_name_plural.lower()) + for field in fields: + related_manager = getattr(account, field) + items.append((related_manager.model._meta, related_manager.all())) accounts.append((account, items)) + context = { 'accounts': accounts, 'date': timezone.now().today() diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py index 4b126704..9ceda689 100644 --- a/orchestra/apps/accounts/admin.py +++ b/orchestra/apps/accounts/admin.py @@ -13,6 +13,7 @@ from django.utils.six.moves.urllib.parse import parse_qsl from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin +from orchestra.admin.actions import SendEmail from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query, change_url from orchestra.core import services, accounts from orchestra.forms import UserChangeForm @@ -61,7 +62,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin) filter_horizontal = () change_readonly_fields = ('username', 'main_systemuser_link') change_form_template = 'admin/accounts/account/change_form.html' - actions = [disable, list_contacts, service_report] + actions = [disable, list_contacts, service_report, SendEmail()] change_view_actions = [disable, service_report] list_select_related = ('billcontact',) ordering = () diff --git a/orchestra/apps/accounts/templates/admin/accounts/account/service_report.html b/orchestra/apps/accounts/templates/admin/accounts/account/service_report.html index d59fac6a..3e6c84d3 100644 --- a/orchestra/apps/accounts/templates/admin/accounts/account/service_report.html +++ b/orchestra/apps/accounts/templates/admin/accounts/account/service_report.html @@ -1,7 +1,7 @@ -{% load utils %} +{% load utils i18n %} - {% block title %}Service Report{% endblock %} + {% block title %}Account service report{% endblock %} {% block head %}{% endblock %} -
Service report generated on {{ date | date }}
+
{% trans "Service report generated on" %} {{ date | date }}
{% for account, items in accounts %} -

{{ account.get_full_name }} ({{ account.username }})

+

{{ account.get_full_name }} - {{ account.username }}


- {{ account.get_type_display }} account registered on {{ account.date_joined | date }}
+ {{ account.get_type_display }} {% trans "account registered on" %} {{ account.date_joined | date }}