Improved bulk mailer detection and fixes on domain serializer validation

This commit is contained in:
Marc Aymerich 2015-05-28 10:55:48 +00:00
parent 1223ed85e6
commit 7c5eff9a90
10 changed files with 60 additions and 21 deletions

View File

@ -15,7 +15,7 @@ class AccountSerializer(serializers.HyperlinkedModelSerializer):
class AccountSerializerMixin(object): class AccountSerializerMixin(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AccountSerializerMixin, self).__init__(*args, **kwargs) super(AccountSerializerMixin, self).__init__(*args, **kwargs)
self.account = None self.account = self.get_account()
def get_account(self): def get_account(self):
request = self.context.get('request') request = self.context.get('request')

View File

@ -93,7 +93,7 @@ hr {
{{ buyer.vat }}<br> {{ buyer.vat }}<br>
{{ buyer.address }}<br> {{ buyer.address }}<br>
{{ buyer.zipcode }} - {{ buyer.city }}<br> {{ buyer.zipcode }} - {{ buyer.city }}<br>
{{ buyer.get_country_display }}<br> {% trans buyer.get_country_display %}<br>
</div> </div>
<div id="number" class="column-1"> <div id="number" class="column-1">

View File

@ -39,9 +39,13 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
def validate(self, data): def validate(self, data):
""" Checks if everything is consistent """ """ Checks if everything is consistent """
data = super(DomainSerializer, self).validate(data) 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'] records = data['records']
domain = domain_for_validation(self.instance, records) domain = domain_for_validation(instance, records)
validators.validate_zone(domain.render_zone()) validators.validate_zone(domain.render_zone())
return data return data
@ -52,9 +56,9 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
domain.records.create(type=record['type'], value=record['value']) domain.records.create(type=record['type'], value=record['value'])
return domain return domain
def update(self, validated_data): def update(self, instance, validated_data):
precords = validated_data.pop('records') precords = validated_data.pop('records')
domain = super(DomainSerializer, self).update(validated_data) domain = super(DomainSerializer, self).update(instance, validated_data)
to_delete = [] to_delete = []
for erecord in domain.records.all(): for erecord in domain.records.all():
match = False match = False
@ -63,7 +67,7 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
match = True match = True
break break
if match: if match:
precords.remove(ix) precords.pop(ix)
else: else:
to_delete.append(erecord) to_delete.append(erecord)
for precord in precords: for precord in precords:

View File

@ -120,7 +120,10 @@ def validate_zone(zone):
# Don't use /dev/stdin becuase the 'argument list is too long' error # 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) check = run(' '.join([checkzone, zone_name, zone_path]), valid_codes=(0,1,127), display=False)
finally: finally:
os.unlink(zone_path) try:
os.unlink(zone_path)
except FileNotFoundError:
pass
if check.exit_code == 127: if check.exit_code == 127:
logger.error("Cannot validate domain zone: %s not installed." % checkzone) logger.error("Cannot validate domain zone: %s not installed." % checkzone)
elif check.exit_code == 1: elif check.exit_code == 1:

View File

@ -1,6 +1,9 @@
from django.conf import settings as djsettings from django.conf import settings as djsettings
from django.core.mail.backends.base import BaseEmailBackend from django.core.mail.backends.base import BaseEmailBackend
from orchestra.core.caches import get_request_cache
from . import settings
from .models import Message from .models import Message
from .tasks import send_message from .tasks import send_message
@ -9,17 +12,27 @@ class EmailBackend(BaseEmailBackend):
""" """
A wrapper that manages a queued SMTP system. A wrapper that manages a queued SMTP system.
""" """
messages = 0
def send_messages(self, email_messages): def send_messages(self, email_messages):
if not email_messages: if not email_messages:
return 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 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 default_priority = Message.NORMAL if is_bulk else Message.CRITICAL
num_sent = 0
for message in email_messages: for message in email_messages:
priority = message.extra_headers.get('X-Mail-Priority', default_priority) priority = message.extra_headers.get('X-Mail-Priority', default_priority)
content = message.message().as_string() content = message.message().as_string()
for to_email in message.recipients(): for to_email in message.recipients():
message = Message.objects.create( message = Message(
priority=priority, priority=priority,
to_address=to_email, to_address=to_email,
from_address=getattr(message, 'from_email', djsettings.DEFAULT_FROM_EMAIL), from_address=getattr(message, 'from_email', djsettings.DEFAULT_FROM_EMAIL),
@ -29,5 +42,7 @@ class EmailBackend(BaseEmailBackend):
if priority == Message.CRITICAL: if priority == Message.CRITICAL:
# send immidiately # send immidiately
send_message.apply_async(message) send_message.apply_async(message)
else:
message.save()
num_sent += 1 num_sent += 1
return num_sent return num_sent

View File

@ -50,7 +50,7 @@ class Message(models.Model):
def sent(self): def sent(self):
self.state = self.SENT self.state = self.SENT
self.save(update_fields=('state',)) self.save()
def log(self, error): def log(self, error):
result = SMTPLog.SUCCESS result = SMTPLog.SUCCESS

View File

@ -1,3 +1,5 @@
from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.settings import Setting 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', MAILER_MESSAGES_CLEANUP_DAYS = Setting('MAILER_MESSAGES_CLEANUP_DAYS',
7 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."),
)

View File

@ -27,11 +27,19 @@ def get_request_cache():
class RequestCacheMiddleware(object): class RequestCacheMiddleware(object):
def process_request(self, request): def process_request(self, request):
cache = _request_cache.get(currentThread(), RequestCache()) current_thread = currentThread()
_request_cache[currentThread()] = cache cache = _request_cache.get(current_thread, RequestCache())
_request_cache[current_thread] = cache
cache.clear() cache.clear()
def process_response(self, request, response): def clear_cache(self):
current_thread = currentThread()
if currentThread() in _request_cache: 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 return response

View File

@ -5,7 +5,6 @@ from orchestra.utils.sys import run
def html_to_pdf(html, pagination=False): def html_to_pdf(html, pagination=False):
""" converts HTL to PDF using wkhtmltopdf """ """ converts HTL to PDF using wkhtmltopdf """
print(pagination)
context = { context = {
'pagination': textwrap.dedent("""\ 'pagination': textwrap.dedent("""\
--footer-center "Page [page] of [topage]"\\ --footer-center "Page [page] of [topage]"\\

View File

@ -5,7 +5,7 @@ from django.template.loader import render_to_string
from django.template import Context 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: Renders an email template with this format:
{% if subject %}Subject{% endif %} {% 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 context can be a dictionary or a template.Context instance
""" """
if isinstance(context, dict): if isinstance(context, dict):
context = Context(context) context = Context(context)
if isinstance(to, str): if isinstance(to, str):
@ -27,13 +26,16 @@ def send_email_template(template, context, to, email_from=None, html=None, attac
'scheme': url.scheme, 'scheme': url.scheme,
'domain': url.netloc, 'domain': url.netloc,
} }
#subject cannot have new lines
subject = render_to_string(template, {'subject': True}, context).strip() subject = render_to_string(template, {'subject': True}, context).strip()
message = render_to_string(template, {'message': 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) msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments)
if html: 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.attach_alternative(html_message, "text/html")
msg.send() msg.send()