From 7382018f94447e330bf97627dd91c96e765e2dc6 Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Fri, 21 Nov 2014 15:39:41 +0000 Subject: [PATCH] Added support for sending emails to contacts --- orchestra/apps/accounts/actions.py | 13 +++ orchestra/apps/accounts/admin.py | 6 +- orchestra/apps/contacts/actions.py | 96 +++++++++++++++++++ orchestra/apps/contacts/admin.py | 13 ++- orchestra/apps/contacts/forms.py | 34 +++++++ orchestra/apps/contacts/settings.py | 3 + orchestra/apps/orders/actions.py | 3 +- .../admin/orchestra/generic_confirmation.html | 4 +- 8 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 orchestra/apps/contacts/actions.py create mode 100644 orchestra/apps/contacts/forms.py diff --git a/orchestra/apps/accounts/actions.py b/orchestra/apps/accounts/actions.py index f6ee4286..037e1c66 100644 --- a/orchestra/apps/accounts/actions.py +++ b/orchestra/apps/accounts/actions.py @@ -1,5 +1,7 @@ from django.contrib import messages +from django.core.urlresolvers import reverse from django.db import transaction +from django.shortcuts import redirect from django.utils.translation import ungettext, ugettext_lazy as _ from orchestra.admin.decorators import action_with_confirmation @@ -20,3 +22,14 @@ def disable(modeladmin, request, queryset): modeladmin.message_user(request, msg) disable.url_name = 'disable' disable.verbose_name = _("Disable") + + +def list_contacts(modeladmin, request, queryset): + ids = queryset.values_list('id', flat=True) + if not ids: + message.warning(request, "Select at least one account.") + return + url = reverse('admin:contacts_contact_changelist') + url += '?account__in=%s' % ','.join(map(str, ids)) + return redirect(url) +list_contacts.verbose_name = _("List contacts") diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py index 3d0b3ef9..b4e7c196 100644 --- a/orchestra/apps/accounts/admin.py +++ b/orchestra/apps/accounts/admin.py @@ -18,7 +18,7 @@ from orchestra.core import services, accounts from orchestra.forms import UserChangeForm from . import settings -from .actions import disable +from .actions import disable, list_contacts from .filters import HasMainUserListFilter from .forms import AccountCreationForm from .models import Account @@ -61,8 +61,8 @@ 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] - change_view_actions = actions + actions = [disable, list_contacts] + change_view_actions = [disable] list_select_related = ('billcontact',) ordering = () diff --git a/orchestra/apps/contacts/actions.py b/orchestra/apps/contacts/actions.py new file mode 100644 index 00000000..315afab2 --- /dev/null +++ b/orchestra/apps/contacts/actions.py @@ -0,0 +1,96 @@ +from django.contrib import admin, messages +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 .forms import SendEmailForm + + +class SendEmail(object): + """ Form wizard for billing orders admin action """ + short_description = _("Send email") + form = SendEmailForm + template = 'admin/orchestra/generic_confirmation.html' + __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.context = { + 'action_name': _("Send email"), + 'action_value': self.__name__, + 'opts': opts, + 'app_label': app_label, + 'queryset': queryset, + 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, + } + return self.write_email(request) + + def write_email(self, request): + if not request.user.is_superuser: + raise PermissionDenied + form = self.form() + if request.POST.get('post'): + form = self.form(request.POST) + if form.is_valid(): + options = { + 'email_from': form.cleaned_data['email_from'], + 'cc': form.cleaned_data['cc'], + 'bcc': form.cleaned_data['bcc'], + 'subject': form.cleaned_data['subject'], + 'message': form.cleaned_data['message'], + + } + return self.confirm_email(request, **options) + opts = self.modeladmin.model._meta + app_label = opts.app_label + self.context.update({ + 'title': _("Send e-mail to contacts"), + 'content_title': "", + 'form': form, + 'submit_value': _("Continue"), + }) + # Display confirmation page + return render(request, self.template, self.context) + + def confirm_email(self, request, **options): + num = len(self.queryset) + email_from = options['email_from'] + bcc = options['bcc'] + to = options['cc'] + subject = options['subject'] + message = options['message'] + # The user has already confirmed + if request.POST.get('post') == 'email_confirmation': + for contact in self.queryset.all(): + to.append(contact.email) + send_mass_mail(subject, message, email_from, to, bcc) + msg = ungettext( + _("Message has been sent to %s.") % str(contact), + _("Message has been sent to %i contacts.") % num, + num + ) + self.modeladmin.message_user(request, msg) + return None + + form = self.form(initial={ + '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?"), + 'display_objects': ["%s (%s)" % (contact, contact.email) for contact in self.queryset], + 'form': form, + 'subject': subject, + 'message': message, + 'post_value': 'email_confirmation', + }) + # Display the confirmation page + return render(request, self.template, self.context) diff --git a/orchestra/apps/contacts/admin.py b/orchestra/apps/contacts/admin.py index eb1d2f9b..2ce161ef 100644 --- a/orchestra/apps/contacts/admin.py +++ b/orchestra/apps/contacts/admin.py @@ -2,22 +2,23 @@ from django import forms from django.contrib import admin from django.utils.translation import ugettext, ugettext_lazy as _ -from orchestra.admin import AtLeastOneRequiredInlineFormSet +from orchestra.admin import AtLeastOneRequiredInlineFormSet, ExtendedModelAdmin from orchestra.admin.utils import insertattr, admin_link, change_url from orchestra.apps.accounts.admin import AccountAdmin, AccountAdminMixin from orchestra.forms.widgets import paddingCheckboxSelectMultiple +from .actions import SendEmail from .models import Contact -class ContactAdmin(AccountAdminMixin, admin.ModelAdmin): +class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin): list_display = ( 'dispaly_name', 'email', 'phone', 'phone2', 'country', 'account_link' ) # TODO email usage custom filter contains list_filter = ('email_usage',) search_fields = ( - 'contact__account__name', 'short_name', 'full_name', 'phone', 'phone2', + 'account__username', 'account__full_name', 'short_name', 'full_name', 'phone', 'phone2', 'email' ) fieldsets = ( @@ -38,6 +39,7 @@ class ContactAdmin(AccountAdminMixin, admin.ModelAdmin): 'fields': ('address', ('zipcode', 'city'), 'country') }), ) + # TODO don't repeat all only for account_link do it on accountadmin add_fieldsets = ( (None, { 'classes': ('wide',), @@ -49,13 +51,14 @@ class ContactAdmin(AccountAdminMixin, admin.ModelAdmin): }), (_("Phone"), { 'classes': ('wide',), - 'fields': ('phone', 'phone_alternative'), + 'fields': ('phone', 'phone2'), }), (_("Postal address"), { 'classes': ('wide',), - 'fields': ('address', ('zip_code', 'city'), 'country') + 'fields': ('address', ('zipcode', 'city'), 'country') }), ) + actions = [SendEmail(),] def dispaly_name(self, contact): return unicode(contact) diff --git a/orchestra/apps/contacts/forms.py b/orchestra/apps/contacts/forms.py new file mode 100644 index 00000000..fecf1a1e --- /dev/null +++ b/orchestra/apps/contacts/forms.py @@ -0,0 +1,34 @@ +from django import forms +from django.core import validators +from django.utils.translation import ungettext, ugettext_lazy as _ + +from . import settings + + +class SendEmailForm(forms.Form): + email_from = forms.EmailField(label=_("From"), + initial=settings.CONTACTS_DEFAULT_FROM_EMAIL, + widget=forms.TextInput(attrs={'size':'118'})) + cc = forms.CharField(label="CC", required=False, + widget=forms.TextInput(attrs={'size':'118'})) + bcc = forms.CharField(label="BCC", 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 clean_space_separated_emails(self, value): + value = value.split() + for email in value: + try: + validators.validate_email(email) + except validators.ValidationError: + raise validators.ValidationError("Space separated emails.") + return value + + def clean_cc(self): + return self.clean_space_separated_emails(self.cleaned_data['cc']) + + def clean_bcc(self): + return self.clean_space_separated_emails(self.cleaned_data['bcc']) diff --git a/orchestra/apps/contacts/settings.py b/orchestra/apps/contacts/settings.py index 00ed01cb..737c8b48 100644 --- a/orchestra/apps/contacts/settings.py +++ b/orchestra/apps/contacts/settings.py @@ -14,3 +14,6 @@ CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', ((k,v) for k,v in d CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY', 'ES') + + +CONTACTS_DEFAULT_FROM_EMAIL = getattr(settings, 'CONTACTS_DEFAULT_FROM_EMAIL', 'support@orchestra.lan') diff --git a/orchestra/apps/orders/actions.py b/orchestra/apps/orders/actions.py index 31a3a084..bec49b99 100644 --- a/orchestra/apps/orders/actions.py +++ b/orchestra/apps/orders/actions.py @@ -7,8 +7,7 @@ from django.shortcuts import render from orchestra.admin.utils import change_url -from .forms import (BillSelectedOptionsForm, BillSelectConfirmationForm, - BillSelectRelatedForm) +from .forms import BillSelectedOptionsForm, BillSelectConfirmationForm, BillSelectRelatedForm class BillSelectedOrders(object): diff --git a/orchestra/templates/admin/orchestra/generic_confirmation.html b/orchestra/templates/admin/orchestra/generic_confirmation.html index acc04729..0b21e4d1 100644 --- a/orchestra/templates/admin/orchestra/generic_confirmation.html +++ b/orchestra/templates/admin/orchestra/generic_confirmation.html @@ -54,8 +54,8 @@ {% endfor %} - - + + {% endblock %}