From 7c5eff9a902c775c5709b8e1b463f30f8adf869b Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Thu, 28 May 2015 10:55:48 +0000 Subject: [PATCH] Improved bulk mailer detection and fixes on domain serializer validation --- orchestra/contrib/accounts/serializers.py | 2 +- .../templates/bills/microspective-fee.html | 2 +- orchestra/contrib/domains/serializers.py | 14 +++++++++----- orchestra/contrib/domains/validators.py | 5 ++++- orchestra/contrib/mailer/backends.py | 19 +++++++++++++++++-- orchestra/contrib/mailer/models.py | 2 +- orchestra/contrib/mailer/settings.py | 8 ++++++++ orchestra/core/caches.py | 16 ++++++++++++---- orchestra/utils/html.py | 1 - orchestra/utils/mail.py | 12 +++++++----- 10 files changed, 60 insertions(+), 21 deletions(-) diff --git a/orchestra/contrib/accounts/serializers.py b/orchestra/contrib/accounts/serializers.py index e58bb268..37edfdf8 100644 --- a/orchestra/contrib/accounts/serializers.py +++ b/orchestra/contrib/accounts/serializers.py @@ -15,7 +15,7 @@ class AccountSerializer(serializers.HyperlinkedModelSerializer): class AccountSerializerMixin(object): def __init__(self, *args, **kwargs): super(AccountSerializerMixin, self).__init__(*args, **kwargs) - self.account = None + self.account = self.get_account() def get_account(self): request = self.context.get('request') diff --git a/orchestra/contrib/bills/templates/bills/microspective-fee.html b/orchestra/contrib/bills/templates/bills/microspective-fee.html index 5825feec..33cf7de4 100644 --- a/orchestra/contrib/bills/templates/bills/microspective-fee.html +++ b/orchestra/contrib/bills/templates/bills/microspective-fee.html @@ -93,7 +93,7 @@ hr { {{ buyer.vat }}
{{ buyer.address }}
{{ buyer.zipcode }} - {{ buyer.city }}
- {{ buyer.get_country_display }}
+ {% trans buyer.get_country_display %}
diff --git a/orchestra/contrib/domains/serializers.py b/orchestra/contrib/domains/serializers.py index cc4d22cf..5eb1471a 100644 --- a/orchestra/contrib/domains/serializers.py +++ b/orchestra/contrib/domains/serializers.py @@ -39,9 +39,13 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): def validate(self, data): """ Checks if everything is consistent """ data = super(DomainSerializer, self).validate(data) - if self.instance and data.get('name'): + name = data.get('name') + if name: + instance = self.instance + if instance is None: + instance = Domain(name=name, account=self.account) records = data['records'] - domain = domain_for_validation(self.instance, records) + domain = domain_for_validation(instance, records) validators.validate_zone(domain.render_zone()) return data @@ -52,9 +56,9 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): domain.records.create(type=record['type'], value=record['value']) return domain - def update(self, validated_data): + def update(self, instance, validated_data): precords = validated_data.pop('records') - domain = super(DomainSerializer, self).update(validated_data) + domain = super(DomainSerializer, self).update(instance, validated_data) to_delete = [] for erecord in domain.records.all(): match = False @@ -63,7 +67,7 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): match = True break if match: - precords.remove(ix) + precords.pop(ix) else: to_delete.append(erecord) for precord in precords: diff --git a/orchestra/contrib/domains/validators.py b/orchestra/contrib/domains/validators.py index e3e8114e..d8aeee6a 100644 --- a/orchestra/contrib/domains/validators.py +++ b/orchestra/contrib/domains/validators.py @@ -120,7 +120,10 @@ def validate_zone(zone): # Don't use /dev/stdin becuase the 'argument list is too long' error check = run(' '.join([checkzone, zone_name, zone_path]), valid_codes=(0,1,127), display=False) finally: - os.unlink(zone_path) + try: + os.unlink(zone_path) + except FileNotFoundError: + pass if check.exit_code == 127: logger.error("Cannot validate domain zone: %s not installed." % checkzone) elif check.exit_code == 1: diff --git a/orchestra/contrib/mailer/backends.py b/orchestra/contrib/mailer/backends.py index 65de5cdf..f078e7ef 100644 --- a/orchestra/contrib/mailer/backends.py +++ b/orchestra/contrib/mailer/backends.py @@ -1,6 +1,9 @@ from django.conf import settings as djsettings from django.core.mail.backends.base import BaseEmailBackend +from orchestra.core.caches import get_request_cache + +from . import settings from .models import Message from .tasks import send_message @@ -9,17 +12,27 @@ class EmailBackend(BaseEmailBackend): """ A wrapper that manages a queued SMTP system. """ + messages = 0 + def send_messages(self, email_messages): if not email_messages: return - num_sent = 0 + cache = get_request_cache() + key = 'mailer.sent_messages' + sent_messages = cache.get(key) or 0 + sent_messages += 1 + cache.set(key, sent_messages) + is_bulk = len(email_messages) > 1 + if sent_messages > settings.MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD: + is_bulk = True default_priority = Message.NORMAL if is_bulk else Message.CRITICAL + num_sent = 0 for message in email_messages: priority = message.extra_headers.get('X-Mail-Priority', default_priority) content = message.message().as_string() for to_email in message.recipients(): - message = Message.objects.create( + message = Message( priority=priority, to_address=to_email, from_address=getattr(message, 'from_email', djsettings.DEFAULT_FROM_EMAIL), @@ -29,5 +42,7 @@ class EmailBackend(BaseEmailBackend): if priority == Message.CRITICAL: # send immidiately send_message.apply_async(message) + else: + message.save() num_sent += 1 return num_sent diff --git a/orchestra/contrib/mailer/models.py b/orchestra/contrib/mailer/models.py index e0e3c3b8..be952cb8 100644 --- a/orchestra/contrib/mailer/models.py +++ b/orchestra/contrib/mailer/models.py @@ -50,7 +50,7 @@ class Message(models.Model): def sent(self): self.state = self.SENT - self.save(update_fields=('state',)) + self.save() def log(self, error): result = SMTPLog.SUCCESS diff --git a/orchestra/contrib/mailer/settings.py b/orchestra/contrib/mailer/settings.py index 1eb9cb1e..9e0d42e5 100644 --- a/orchestra/contrib/mailer/settings.py +++ b/orchestra/contrib/mailer/settings.py @@ -1,3 +1,5 @@ +from django.utils.translation import ugettext_lazy as _ + from orchestra.contrib.settings import Setting @@ -9,3 +11,9 @@ MAILER_DEFERE_SECONDS = Setting('MAILER_DEFERE_SECONDS', MAILER_MESSAGES_CLEANUP_DAYS = Setting('MAILER_MESSAGES_CLEANUP_DAYS', 7 ) + + +MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD = Setting('MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD', + 2, + help_text=_("Number of emails that will be sent directly before starting to queue them."), +) diff --git a/orchestra/core/caches.py b/orchestra/core/caches.py index 4550d827..9c5e86fb 100644 --- a/orchestra/core/caches.py +++ b/orchestra/core/caches.py @@ -27,11 +27,19 @@ def get_request_cache(): class RequestCacheMiddleware(object): def process_request(self, request): - cache = _request_cache.get(currentThread(), RequestCache()) - _request_cache[currentThread()] = cache + current_thread = currentThread() + cache = _request_cache.get(current_thread, RequestCache()) + _request_cache[current_thread] = cache cache.clear() - def process_response(self, request, response): + def clear_cache(self): + current_thread = currentThread() if currentThread() in _request_cache: - _request_cache[currentThread()].clear() + _request_cache[current_thread].clear() + + def process_exception(self, request, exception): + self.clear_cache() + + def process_response(self, request, response): + self.clear_cache() return response diff --git a/orchestra/utils/html.py b/orchestra/utils/html.py index 7e51df9e..6d84a52d 100644 --- a/orchestra/utils/html.py +++ b/orchestra/utils/html.py @@ -5,7 +5,6 @@ from orchestra.utils.sys import run def html_to_pdf(html, pagination=False): """ converts HTL to PDF using wkhtmltopdf """ - print(pagination) context = { 'pagination': textwrap.dedent("""\ --footer-center "Page [page] of [topage]"\\ diff --git a/orchestra/utils/mail.py b/orchestra/utils/mail.py index fd189075..21028fb9 100644 --- a/orchestra/utils/mail.py +++ b/orchestra/utils/mail.py @@ -5,7 +5,7 @@ from django.template.loader import render_to_string from django.template import Context -def send_email_template(template, context, to, email_from=None, html=None, attachments=[]): +def render_email_template(template, context): """ Renders an email template with this format: {% if subject %}Subject{% endif %} @@ -13,7 +13,6 @@ def send_email_template(template, context, to, email_from=None, html=None, attac context can be a dictionary or a template.Context instance """ - if isinstance(context, dict): context = Context(context) if isinstance(to, str): @@ -27,13 +26,16 @@ def send_email_template(template, context, to, email_from=None, html=None, attac 'scheme': url.scheme, 'domain': url.netloc, } - - #subject cannot have new lines subject = render_to_string(template, {'subject': True}, context).strip() message = render_to_string(template, {'message': True}, context).strip() + return subject, message + + +def send_email_template(template, context, to, email_from=None, html=None, attachments=[]): + subject, message = render_email_template(template, context) msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments) if html: - html_message = render_to_string(html, {'message': True}, context) + subject, html_message = render_email_template(html, context) msg.attach_alternative(html_message, "text/html") msg.send()