Improved bulk mailer detection and fixes on domain serializer validation
This commit is contained in:
parent
1223ed85e6
commit
7c5eff9a90
|
@ -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')
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
try:
|
||||||
os.unlink(zone_path)
|
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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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."),
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]"\\
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue