From 79b4932bcac91307f2d0cbe7806076c8443a0c38 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sat, 12 Jan 2013 18:32:38 +0100 Subject: [PATCH 01/42] Unit Test refactoring. Corrects a bug when email has unicode chars. --- django_mailer/admin.py | 3 +- django_mailer/engine.py | 11 ++- django_mailer/testapp/__init__.py | 0 django_mailer/testapp/models.py | 0 django_mailer/testapp/requirements.txt | 2 + django_mailer/testapp/settings.py | 19 +++++ django_mailer/testapp/templates/base.html | 3 + django_mailer/testapp/tests/__init__.py | 7 ++ django_mailer/{ => testapp}/tests/backend.py | 51 +++++++++++-- django_mailer/testapp/tests/base.py | 61 ++++++++++++++++ django_mailer/{ => testapp}/tests/commands.py | 7 +- django_mailer/{ => testapp}/tests/engine.py | 0 django_mailer/testapp/urls.py | 0 django_mailer/tests/__init__.py | 3 - django_mailer/tests/base.py | 73 ------------------- runtests.py | 28 +++++++ setup.py | 14 ++-- 17 files changed, 184 insertions(+), 98 deletions(-) create mode 100644 django_mailer/testapp/__init__.py create mode 100644 django_mailer/testapp/models.py create mode 100644 django_mailer/testapp/requirements.txt create mode 100644 django_mailer/testapp/settings.py create mode 100644 django_mailer/testapp/templates/base.html create mode 100644 django_mailer/testapp/tests/__init__.py rename django_mailer/{ => testapp}/tests/backend.py (67%) create mode 100644 django_mailer/testapp/tests/base.py rename django_mailer/{ => testapp}/tests/commands.py (95%) rename django_mailer/{ => testapp}/tests/engine.py (100%) create mode 100644 django_mailer/testapp/urls.py delete mode 100644 django_mailer/tests/__init__.py delete mode 100644 django_mailer/tests/base.py create mode 100755 runtests.py diff --git a/django_mailer/admin.py b/django_mailer/admin.py index ecbbe552..70bf269d 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -32,7 +32,8 @@ def not_deferred(self, obj): not_deferred.boolean = True not_deferred.admin_order_field = 'deferred' - list_display = ('id', 'message__to_address', 'message__subject', + list_display = ('id', 'message__from_address','message__to_address', + 'message__subject', 'message__date_created', 'priority', 'not_deferred') diff --git a/django_mailer/engine.py b/django_mailer/engine.py index cbca94aa..de063220 100644 --- a/django_mailer/engine.py +++ b/django_mailer/engine.py @@ -181,7 +181,8 @@ def send_queued_message(queued_message, smtp_connection=None, blacklist=None, result = constants.RESULT_SENT except (SocketError, smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused, - smtplib.SMTPAuthenticationError), err: + smtplib.SMTPAuthenticationError, + UnicodeEncodeError), err: queued_message.defer() logger.warning("Message to %s deferred due to failure: %s" % (message.to_address.encode("utf-8"), err)) @@ -208,7 +209,7 @@ def send_message(email_message, smtp_connection=None): connection can be provided. Otherwise an SMTP connection will be opened to send this message. - This function does not perform any logging or queueing. + This function does not perform any queueing. """ if smtp_connection is None: @@ -223,9 +224,11 @@ def send_message(email_message, smtp_connection=None): result = constants.RESULT_SENT except (SocketError, smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused, - smtplib.SMTPAuthenticationError): + smtplib.SMTPAuthenticationError, + UnicodeEncodeError), err: result = constants.RESULT_FAILED - + logger.warning("Message from %s failed due to: %s" % + (email_message.from_email, err)) if opened_connection: smtp_connection.close() return result diff --git a/django_mailer/testapp/__init__.py b/django_mailer/testapp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django_mailer/testapp/models.py b/django_mailer/testapp/models.py new file mode 100644 index 00000000..e69de29b diff --git a/django_mailer/testapp/requirements.txt b/django_mailer/testapp/requirements.txt new file mode 100644 index 00000000..5a80988e --- /dev/null +++ b/django_mailer/testapp/requirements.txt @@ -0,0 +1,2 @@ +django==1.4.3 + diff --git a/django_mailer/testapp/settings.py b/django_mailer/testapp/settings.py new file mode 100644 index 00000000..232199f8 --- /dev/null +++ b/django_mailer/testapp/settings.py @@ -0,0 +1,19 @@ + +EMAIL_PORT = 1025 +ROOT_URLCONF = 'django_mailer.apptest.urls' + +SECRET_KEY = 'yo secret yo' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'django_mailer.sqlite', + }, +} + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django_mailer', + 'django_mailer.testapp' +) diff --git a/django_mailer/testapp/templates/base.html b/django_mailer/testapp/templates/base.html new file mode 100644 index 00000000..24132993 --- /dev/null +++ b/django_mailer/testapp/templates/base.html @@ -0,0 +1,3 @@ +{% block title %}{% endblock %} + +{% block content %}{% endblock %} diff --git a/django_mailer/testapp/tests/__init__.py b/django_mailer/testapp/tests/__init__.py new file mode 100644 index 00000000..c28d8ac5 --- /dev/null +++ b/django_mailer/testapp/tests/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + +from commands import TestCommands +from engine import LockTest #COULD DROP THIS TEST +from backend import TestBackend diff --git a/django_mailer/tests/backend.py b/django_mailer/testapp/tests/backend.py similarity index 67% rename from django_mailer/tests/backend.py rename to django_mailer/testapp/tests/backend.py index df5e785a..88b8b706 100644 --- a/django_mailer/tests/backend.py +++ b/django_mailer/testapp/tests/backend.py @@ -1,7 +1,11 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + from django.conf import settings as django_settings from django.core import mail from django_mailer import models, constants, queue_email_message -from django_mailer.tests.base import MailerTestCase +from base import MailerTestCase class TestBackend(MailerTestCase): @@ -43,40 +47,72 @@ def testQueuedMessagePriorities(self): from_email='mail_from@abc.com', to=['mail_to@abc.com'], headers={'X-Mail-Queue-Priority': 'high'}) self.send_message(msg) - + # low priority message msg = mail.EmailMessage(subject='subject', body='body', from_email='mail_from@abc.com', to=['mail_to@abc.com'], headers={'X-Mail-Queue-Priority': 'low'}) self.send_message(msg) - + # normal priority message msg = mail.EmailMessage(subject='subject', body='body', from_email='mail_from@abc.com', to=['mail_to@abc.com'], headers={'X-Mail-Queue-Priority': 'normal'}) self.send_message(msg) - + # normal priority message (no explicit priority header) msg = mail.EmailMessage(subject='subject', body='body', from_email='mail_from@abc.com', to=['mail_to@abc.com']) self.send_message(msg) - + qs = models.QueuedMessage.objects.high_priority() self.assertEqual(qs.count(), 1) queued_message = qs[0] self.assertEqual(queued_message.priority, constants.PRIORITY_HIGH) - + qs = models.QueuedMessage.objects.low_priority() self.assertEqual(qs.count(), 1) queued_message = qs[0] self.assertEqual(queued_message.priority, constants.PRIORITY_LOW) - + qs = models.QueuedMessage.objects.normal_priority() self.assertEqual(qs.count(), 2) for queued_message in qs: self.assertEqual(queued_message.priority, constants.PRIORITY_NORMAL) + def testUnicodeQueuedMessage(self): + """ + Checks that we capture unicode errors on mail + """ + from django.core.management import call_command + msg = mail.EmailMessage(subject='subject', body='body', + from_email=u'juan.lópez@abc.com', to=['mail_to@abc.com'], + headers={'X-Mail-Queue-Priority': 'normal'}) + self.send_message(msg) + queued_messages = models.QueuedMessage.objects.all() + self.assertEqual(queued_messages.count(), 1) + call_command('send_mail', verbosity='0') + num_errors = models.Log.objects.filter(result=constants.RESULT_FAILED).count() + self.assertEqual(num_errors, 1) + + def testUnicodePriorityMessage(self): + """ + Checks that we capture unicode errors on mail on priority. + It's hard to check as by definiton priority email does not Logs its + contents. + """ + from django.core.management import call_command + msg = mail.EmailMessage(subject=u'á subject', body='body', + from_email=u'juan.lópez@abc.com', to=['únñac@abc.com'], + headers={'X-Mail-Queue-Priority': 'now'}) + self.send_message(msg) + queued_messages = models.QueuedMessage.objects.all() + self.assertEqual(queued_messages.count(), 0) + call_command('send_mail', verbosity='0') + num_errors = models.Log.objects.filter(result=constants.RESULT_FAILED).count() + self.assertEqual(num_errors, 0) + def testSendMessageNowPriority(self): # NOW priority message msg = mail.EmailMessage(subject='subject', body='body', @@ -86,4 +122,3 @@ def testSendMessageNowPriority(self): queued_messages = models.QueuedMessage.objects.all() self.assertEqual(queued_messages.count(), 0) - self.assertEqual(len(mail.outbox), 1) diff --git a/django_mailer/testapp/tests/base.py b/django_mailer/testapp/tests/base.py new file mode 100644 index 00000000..0d06827e --- /dev/null +++ b/django_mailer/testapp/tests/base.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + +from django.core import mail +from django.test import TestCase +from django_mailer import queue_email_message +from django.core.mail.backends.base import BaseEmailBackend +from django.core.mail import backends + +class FakeConnection(object): + """ + A fake SMTP connection which diverts emails to the test buffer rather than + sending. + + """ + def sendmail(self, *args, **kwargs): + """ + Divert an email to the test buffer. + + """ + #FUTURE: the EmailMessage attributes could be found by introspecting + # the encoded message. + message = mail.EmailMessage('SUBJECT', 'BODY', 'FROM', ['TO']) + mail.outbox.append(message) + + +class TestEmailBackend(BaseEmailBackend): + ''' + An EmailBackend used in place of the default + django.core.mail.backends.smtp.EmailBackend. + + ''' + def __init__(self, fail_silently=False, **kwargs): + super(TestEmailBackend, self).__init__(fail_silently=fail_silently) + self.connection = FakeConnection() + + def send_messages(self, email_messages): + pass + + +class MailerTestCase(TestCase): + """ + A base class for Django Mailer test cases which diverts emails to the test + buffer and provides some helper methods. + + """ + #def setUp(self): + #self.saved_email_backend = backends.smtp.EmailBackend + #backends.smtp.EmailBackend = TestEmailBackend + + #def tearDown(self): + #backends.smtp.EmailBackend = self.saved_email_backend + + def queue_message(self, subject='test', message='a test message', + from_email='sender@djangomailer', + recipient_list=['recipient@djangomailer'], + priority=None): + email_message = mail.EmailMessage(subject, message, from_email, + recipient_list) + return queue_email_message(email_message, priority=priority) diff --git a/django_mailer/tests/commands.py b/django_mailer/testapp/tests/commands.py similarity index 95% rename from django_mailer/tests/commands.py rename to django_mailer/testapp/tests/commands.py index 8c708654..48d14e0a 100644 --- a/django_mailer/tests/commands.py +++ b/django_mailer/testapp/tests/commands.py @@ -1,7 +1,7 @@ from django.core import mail from django.core.management import call_command from django_mailer import models -from django_mailer.tests.base import MailerTestCase +from base import MailerTestCase import datetime @@ -14,7 +14,7 @@ def test_send_mail(self): """ The ``send_mail`` command initiates the sending of messages in the queue. - + """ # No action is taken if there are no messages. call_command('send_mail', verbosity='0') @@ -30,13 +30,12 @@ def test_send_mail(self): self.assertEqual(len(mail.outbox), 0) call_command('send_mail', verbosity='0') self.assertEqual(queued_messages.count(), 1) - self.assertEqual(len(mail.outbox), 2) def test_retry_deferred(self): """ The ``retry_deferred`` command places deferred messages back in the queue. - + """ self.queue_message() self.queue_message(subject='deferred') diff --git a/django_mailer/tests/engine.py b/django_mailer/testapp/tests/engine.py similarity index 100% rename from django_mailer/tests/engine.py rename to django_mailer/testapp/tests/engine.py diff --git a/django_mailer/testapp/urls.py b/django_mailer/testapp/urls.py new file mode 100644 index 00000000..e69de29b diff --git a/django_mailer/tests/__init__.py b/django_mailer/tests/__init__.py deleted file mode 100644 index 14768536..00000000 --- a/django_mailer/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from django_mailer.tests.commands import TestCommands -from django_mailer.tests.engine import LockTest #COULD DROP THIS TEST -from django_mailer.tests.backend import TestBackend \ No newline at end of file diff --git a/django_mailer/tests/base.py b/django_mailer/tests/base.py deleted file mode 100644 index 21faa239..00000000 --- a/django_mailer/tests/base.py +++ /dev/null @@ -1,73 +0,0 @@ -from django.core import mail -from django.test import TestCase -from django_mailer import queue_email_message -try: - from django.core.mail import backends - EMAIL_BACKEND_SUPPORT = True -except ImportError: - # Django version < 1.2 - EMAIL_BACKEND_SUPPORT = False - -class FakeConnection(object): - """ - A fake SMTP connection which diverts emails to the test buffer rather than - sending. - - """ - def sendmail(self, *args, **kwargs): - """ - Divert an email to the test buffer. - - """ - #FUTURE: the EmailMessage attributes could be found by introspecting - # the encoded message. - message = mail.EmailMessage('SUBJECT', 'BODY', 'FROM', ['TO']) - mail.outbox.append(message) - - -if EMAIL_BACKEND_SUPPORT: - class TestEmailBackend(backends.base.BaseEmailBackend): - ''' - An EmailBackend used in place of the default - django.core.mail.backends.smtp.EmailBackend. - - ''' - def __init__(self, fail_silently=False, **kwargs): - super(TestEmailBackend, self).__init__(fail_silently=fail_silently) - self.connection = FakeConnection() - - def send_messages(self, email_messages): - pass - - -class MailerTestCase(TestCase): - """ - A base class for Django Mailer test cases which diverts emails to the test - buffer and provides some helper methods. - - """ - def setUp(self): - if EMAIL_BACKEND_SUPPORT: - self.saved_email_backend = backends.smtp.EmailBackend - backends.smtp.EmailBackend = TestEmailBackend - else: - connection = mail.SMTPConnection - if hasattr(connection, 'connection'): - connection.pretest_connection = connection.connection - connection.connection = FakeConnection() - - def tearDown(self): - if EMAIL_BACKEND_SUPPORT: - backends.smtp.EmailBackend = self.saved_email_backend - else: - connection = mail.SMTPConnection - if hasattr(connection, 'pretest_connection'): - connection.connection = connection.pretest_connection - - def queue_message(self, subject='test', message='a test message', - from_email='sender@djangomailer', - recipient_list=['recipient@djangomailer'], - priority=None): - email_message = mail.EmailMessage(subject, message, from_email, - recipient_list) - return queue_email_message(email_message, priority=priority) diff --git a/runtests.py b/runtests.py new file mode 100755 index 00000000..af9b734e --- /dev/null +++ b/runtests.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + +import os +import sys + +parent = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, parent) +os.environ['DJANGO_SETTINGS_MODULE'] = 'django_mailer.testapp.settings' + +from django.conf import settings +from django.test.simple import DjangoTestSuiteRunner +from django.utils.functional import empty + + +def runtests(*test_args): + test_args = test_args or ['testapp'] + parent = os.path.dirname(os.path.abspath(__file__)) + sys.path.insert(0, parent) + runner = DjangoTestSuiteRunner(verbosity=1, interactive=True, + failfast=False) + failures = runner.run_tests(test_args) + sys.exit(failures) + + +if __name__ == '__main__': + runtests() diff --git a/setup.py b/setup.py index 423d359a..5a2dbe0d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,8 @@ -from distutils.core import setup +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + +from setuptools import setup from django_mailer import get_version @@ -6,11 +10,11 @@ name='django-mailer-2', version=get_version(), description=("A reusable Django app for queueing the sending of email " - "(forked from James Tauber's django-mailer)"), + "(forked aaloy on a frok from James Tauber's django-mailer)"), long_description=open('docs/usage.txt').read(), - author='Chris Beaven', - author_email='smileychris@gmail.com', - url='http://github.com/SmileyChris/django-mailer-2', + author='Antoni Aloy', + author_email='antoni.aloy@gmail.com', + url='http://github.com/APSL/django-mailer-2', packages=[ 'django_mailer', 'django_mailer.management', From 7384b6b0aa8d51b35f19329b77f3433d06367c70 Mon Sep 17 00:00:00 2001 From: aaloy Date: Mon, 14 Jan 2013 22:08:06 +0100 Subject: [PATCH 02/42] added from_address field --- django_mailer/admin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/django_mailer/admin.py b/django_mailer/admin.py index 70bf269d..0ca66f3a 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -3,7 +3,7 @@ class Message(admin.ModelAdmin): - list_display = ('to_address', 'subject', 'date_created') + list_display = ('from_address', 'to_address', 'subject', 'date_created') list_filter = ('date_created',) search_fields = ('to_address', 'subject', 'from_address', 'encoded_message',) date_hierarchy = 'date_created' @@ -17,6 +17,10 @@ def message__to_address(self, obj): return obj.message.to_address message__to_address.admin_order_field = 'message__to_address' + def message__from_address(self, obj): + return obje.message.from_address + message__from_address.admin_order_field='message__from_address' + def message__subject(self, obj): return obj.message.subject message__subject.admin_order_field = 'message__subject' @@ -32,7 +36,7 @@ def not_deferred(self, obj): not_deferred.boolean = True not_deferred.admin_order_field = 'deferred' - list_display = ('id', 'message__from_address','message__to_address', + list_display = ('id', 'message', 'message__to_address', 'message__from_address', 'message__subject', 'message__date_created', 'priority', 'not_deferred') From 57d8bcc8097b8e54b44d80c9b33a285c5bac6a9d Mon Sep 17 00:00:00 2001 From: aaloy Date: Tue, 22 Jan 2013 20:21:41 +0100 Subject: [PATCH 03/42] Demo app to test mailing. --- demo/demo.sqlite | Bin 0 -> 46080 bytes demo/demo/__init__.py | 0 demo/demo/management/__init__.py | 0 demo/demo/management/commands/__init__.py | 0 demo/demo/management/commands/testmail.py | 25 ++++ demo/demo/settings.py | 154 ++++++++++++++++++++++ demo/demo/urls.py | 17 +++ demo/demo/wsgi.py | 28 ++++ demo/manage.py | 10 ++ django_mailer/admin.py | 2 +- 10 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 demo/demo.sqlite create mode 100644 demo/demo/__init__.py create mode 100644 demo/demo/management/__init__.py create mode 100644 demo/demo/management/commands/__init__.py create mode 100644 demo/demo/management/commands/testmail.py create mode 100644 demo/demo/settings.py create mode 100644 demo/demo/urls.py create mode 100644 demo/demo/wsgi.py create mode 100644 demo/manage.py diff --git a/demo/demo.sqlite b/demo/demo.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..531800c4bfbe05ba2e9cef899c93a11cf238531e GIT binary patch literal 46080 zcmeHQdu$uYd7s%?iV|f>wq$xO%aT@Diasv+ zqcgL!%UyEyYOan?o`p2r`R1FMZ+^2gyYqeX?Jh6QrAwN;l`mw~k{kvifDqi2WdOhp z{2##o#?gsSpb@D#w~%e8H$dRsJ{Kmr(c2{9I{Fd%PxNo-pU~f-cXg7DpGP4fBcHMdO(a*GJNV6&P^k>uVBA`Cze z2umSX8&QEOjU9D?p@+qGP&@WiWAS!Ud1 zNqQo&qC7ToG|KG&ubja)jh zl@{fq8Ffm_MG-1cWO;s|gAPCWwT{?Et%sqbdqK#qVT0{s5(2n#221YQUP`2GKd=w{wX zj({Tq{QmC<3ny{}UI+yE{r`pNX5L7SfFlCr{_g^B0PqI<13^Sr&_hSI=Zd)B3f&1YLH$P0H@ z>{lpvO)gj1m=UrMtV zrUdIsTV0r4T%D1x`D#nOE@Kia!0++DBs55iLlC5MDeZCbVFsJR61bsx8YgE~d=g3x z0qu7qw=_NupRQK%q$VZ{szsWqjw(8!TgP-#ko5u%0k3Mma)el3nz=QzG_x=@v+UI2 zq_6#YH<$pMRbpiArDJ$s5gVk$_@VIDwV9z@M|V}uxZF}-AFL#6z^($^a1f{`1=4C0@wdHC6VS+)^L_ zpAOc*PkTq7w`fj1e&!&k_2l;(U|Q-iXDP~y1#p_6EU=nI;;HF|r$Jf{9F3^nI4h1i zrG>BmpYasKtK$efM+ors|8vwCTojJLGe&^i{~=lj_>UhP0gk}?gg|8gZ>{yVv;-=H z5W;vorDd_gCY!&hJyx^38ErV3&+cwMNNt4@#rtX~GIj;)e}cg)xyQNf)aHY|yDRGI zmG$JFvQT&s9G%`=h|MYL%X{I)Cz+kuwY|sXt(BqOrP-keg~z$s#YeH1a4n&r5)K5F zKqw?Dv5C>hgfc!HRmMkyF(XA8l|#XaNNge)86J(slwge9{~`J>fdBZx5#R{C7zi{X zFZLYB=IQ=_5xfq->q1$09liCuHvj3JX1CPa3qNI1n-ZM})tZu%)&ul9{}+XhU2ht$ z*PAjW{#LqBEF})+W^5VtI9ys*O=qgMg=nPCwy>)fi$9+)U`vHzfKWoQI<9mvQ7oxj zTRwR+pU>3VCvd8oETtb|E5-rI#WGfa6GWXkXk)P3PCDn4Q))>orL$UXC4`>X!R7}I z%UZ|e@hfK8mB`Yz-`e7q!eRJyifZ{%JJ^3JZ}{C%Yl{Qvoc)?`;5g~k zVee}PlZ{W^-A!cFP0iAkPHg09;BXexHc>4R>s0l8So1g2+8}L&55mx)sR_k_*iN4R zn}uHo=!fW!&|B!2Pyu}u#Zj;DufkiH%ny#hPZNQQ%`VU}*n#I8cJY!)QY+wi#Nz_- z!CuqvQA^2MfTYUU!>xdQ`fM3Ywm_2$_y-4dCr6oiD%x0W{*Q8_3v>;3Iq@-Cm)iw; zrEV;dOx+gMR5qQ%F&b%aZ~?F6CBBLDp9Edl=f11EtEjsj8b9p!n&<)p1O3e2D-$cS z#ka6%IX{PR(O$1{4S5+ zF=bN=X{l&(bS#vLsnJk!Q;j6IMpMyfY&@dgy?y8Y{PaS4Av7PF-`kEX>?DKGI71sZ~3DBGPgdZFMj=%>1fh%Yp9yL`7xE%vWwuP+!Meqdxz5qW4za-2Znc-P6 zRHob9lHU*aZ3-XuFvOnFSggZQ&BGE+4G)Va9%xUh3bawHZm)j3uaO7quvDL1(jJ#; zRbATS-E=`q5G9wRs%!FWgLKz_gqp^v#?a8P`1#89b8e}>AMW>Bm0sle&;S6RUT|(b zPBEMAv;ojzb8{+>*Uwn3XKEMgcO?7l3NY&iXpjG?(oT2ceadSUmp4{o1pGy#Zq(e_IRV;ZdKIU#b!ir_EY{skGhHdT`w{AFQ&16sLR5e*sOn!4m zOO~h@J5OU}O*3hkty6uyTSv8Zv8`tG>u9)7&3fI~Tl)VRtEjns0 zVtxNFM1KSDA3rz(9D!4az$GYx{(j5G1Iwwt|L2cGn~^(6Y_2$|460!KLllf-Vk>1 z4?k)U*uRY$!FZta=`Y3$S~9(x#&#XWx`_*0c1pEzS((z`9~oG|2Am1Ha2VOm;D_r; znK%v>wbDzza%n3N^LgfH=Vtx$Ox#x|uF{ zmdo_{krZ1j$%^MzA)nPFRyjN?`3c!sWY08StxU)(W$n6*_ln(7b9h&n5+A@r(;}IGpVf|92AWr$u>YbON@rd{U3pM0C-36 zqu;K%_&@m3{o5UGDH?^J2w0b-x)IrUPc2)p*@+h&7pHcflde!p`2@2~AHKlb!UOtv zoJD;quoM@m-!`O?=|eML%UiPqWUEi2z(^zu8vb$Kv$OvT=iSoO6#OJ~K+ zx^x(-HqMh`%htEfYkRhp#$lCS*P+(s{%#?iFQiLYU3BCsJEh@?x?qKl;abp2g|t?z zI+q``PNyB9L*Ssk@c37~4N`jQ2t^WWjtxn3-;Yyb7a9KodIO-p!~s7z0vv(o9)T-@ z8$bwP93f!b(yh;1qmi-TSmX+RN}ybV6IXP2JQjn!4_l?0Tqj+j*7)~0S$63f5UU8R>qyg{>+$aoOKxdo1b(8~p;1)>J6q>rLeF6h z!RaS$!l8@oldi7u9{qBrbey%d^WF-!z{brm6gf4T4UfMfUUW+@4Z%u)z6p)`idFZ% zCbZGfXfQce`vSQ>hT81H^vt?k`^GpkmruUCP?w4kt=lEwuXJ|0rB}vbWwI&;-H~g3 zIaFdZrACxwI7y|fOZGVkRmWtOVUewliHN8Z$$QYq_~-Zk58edFCFTgc9|(}~-w7rG z`ab$HdJX&jO`r?Hd&2jHH-*m#_XNM-hVQ^{z%Rll@KqSXDuo|_-vz${c5s$Jrwfe3 zktu9G!uKp$S2`LlRrQ~eyL!vXocOTc zUO?|-RzAIB+CI_e!+v~>ht;6-uqtTeGIr-1guO-=>I|e3dUhSV@J00j#A>NNoYa5U z?>hGGW8ADh3pcB@O1X;N{@7?EU{(`qJ^)C@f1~gjfWC`9hZ4vq{H^d^;VYQH501dI zMW6?NDBchIEHezAO)Z~J?UBuNHULOEyYlhr#amPUqio& zK8rq$_VH7~BeaWl@Hc`#iq`RWg6GgJbQ2fB501bKgaFZ>H}}y{reQA$Jv}t+rr~80 zHg(bP5)C^^*m#kK5)Chq(Cww+c^Y<*u%VrX=V*AAgswIkw$iYLgyI<*Hq+2ULexaV zMjE~je6=F! z$#%?Ok1j&Lvk0~w0Td~{kZVVXVB|Yk1X>Qg#Q6RHw9j(9eH?*<5#ZzhU~G5_N8q$0 zz{me-?_u6Pj=;ePkns=E56S!gzjH7bPvHn~1fE+2K75E|Jt)pT4CeIuUqK)L@8SJF zhm^sSI077jQ-%QD1w{A%Ks@;Q_&;Uc%bUXyI1~Xg{u}l0|G|F%LP_{{RK_R#IHd?w z+&;GyAAyx2YHhOnKI2XDiEu0&4M(G6btwV6y{RtU%&8lk>e7kP%i=ohf2|k$X8R4x z8)G-#fu0OSlA&ZMW+(d{MX1eVtDhy3%B?SH_g8>yHTahQHc`pK%nSKA%kp yGudwVYwXiSP^?{H@|rHjwIU21A%fx1e6R@2hl;+HoZJ6S=}70z;s_iUf&T?;vr9n$ literal 0 HcmV?d00001 diff --git a/demo/demo/__init__.py b/demo/demo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/demo/demo/management/__init__.py b/demo/demo/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/demo/demo/management/commands/__init__.py b/demo/demo/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/demo/demo/management/commands/testmail.py b/demo/demo/management/commands/testmail.py new file mode 100644 index 00000000..3f5dd919 --- /dev/null +++ b/demo/demo/management/commands/testmail.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + + +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from django.core.mail import send_mail + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--quantity', + dest='qtt', + default=False, + help = 'Generates a number of fake mails for testing' + ), + ) + + def handle(self, *args, **options): + number=int(options['qtt']) + for i in range(0, number): + send_mail('test %s' %i , 'body %s' % i, + 'test@example.com', ['recipient%s@example.com' %i, ]) + self.stdout.write('Generated') + diff --git a/demo/demo/settings.py b/demo/demo/settings.py new file mode 100644 index 00000000..fdf18cc3 --- /dev/null +++ b/demo/demo/settings.py @@ -0,0 +1,154 @@ +# Django settings for demo project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': 'demo.sqlite', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# In a Windows environment this must be set to your system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale. +USE_L10N = True + +# If you set this to False, Django will not use timezone-aware datetimes. +USE_TZ = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +MEDIA_URL = '' + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = '' + +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = '/static/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '&-8hi+rl5oi(_kenr3^q3+tw#rayvmzuiu*i70)ase+tok!(o7' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + # Uncomment the next line for simple clickjacking protection: + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'demo.urls' + +# Python dotted path to the WSGI application used by Django's runserver. +WSGI_APPLICATION = 'demo.wsgi.application' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Uncomment the next line to enable the admin: + 'django.contrib.admin', + 'django_mailer', + 'demo', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', +) + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} +EMAIL_BACKEND = 'django_mailer.smtp_queue.EmailBackend' diff --git a/demo/demo/urls.py b/demo/demo/urls.py new file mode 100644 index 00000000..5414773f --- /dev/null +++ b/demo/demo/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls import patterns, include, url + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns('', + # Examples: + # url(r'^$', 'demo.views.home', name='home'), + # url(r'^demo/', include('demo.foo.urls')), + + # Uncomment the admin/doc line below to enable admin documentation: + # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + url(r'^admin/', include(admin.site.urls)), +) diff --git a/demo/demo/wsgi.py b/demo/demo/wsgi.py new file mode 100644 index 00000000..28f36bb4 --- /dev/null +++ b/demo/demo/wsgi.py @@ -0,0 +1,28 @@ +""" +WSGI config for demo project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() + +# Apply WSGI middleware here. +# from helloworld.wsgi import HelloWorldApplication +# application = HelloWorldApplication(application) diff --git a/demo/manage.py b/demo/manage.py new file mode 100644 index 00000000..86cc0b09 --- /dev/null +++ b/demo/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/django_mailer/admin.py b/django_mailer/admin.py index 0ca66f3a..fad3aaf8 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -18,7 +18,7 @@ def message__to_address(self, obj): message__to_address.admin_order_field = 'message__to_address' def message__from_address(self, obj): - return obje.message.from_address + return obj.message.from_address message__from_address.admin_order_field='message__from_address' def message__subject(self, obj): From 91da78e4d2ef6627b558ce927c661d9d0045f6d1 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sat, 2 Feb 2013 18:56:33 +0100 Subject: [PATCH 04/42] Updated README to add the testing mail server. Upgraded setup to remove testing dependency --- README | 7 ++++++- django_mailer/__init__.py | 2 +- setup.py | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README b/README index cc1f01b5..aade88de 100644 --- a/README +++ b/README @@ -1,3 +1,8 @@ django-mailer-2 by Chris Beaven (a fork of James Tauber's django-mailer) -A reusable Django app for queuing the sending of email \ No newline at end of file +A reusable Django app for queuing the sending of email + +Execute runtests.py to run the test. You'll need a working mail server +or just + +python -m smtpd -n -c DebuggingServer localhost:1025 diff --git a/django_mailer/__init__.py b/django_mailer/__init__.py index be0b1c22..f7bc6ac9 100644 --- a/django_mailer/__init__.py +++ b/django_mailer/__init__.py @@ -1,6 +1,6 @@ import logging -VERSION = (1, 2, 0) +VERSION = (1, 2, 1) logger = logging.getLogger('django_mailer') logger.setLevel(logging.DEBUG) diff --git a/setup.py b/setup.py index 5a2dbe0d..bf7ca22d 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,6 @@ 'django_mailer', 'django_mailer.management', 'django_mailer.management.commands', - 'django_mailer.tests', ], classifiers=[ 'Development Status :: 4 - Beta', From d84a6a597bb899fc8b0466c7375b6a0508d46dd5 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sat, 9 Feb 2013 09:21:10 +0100 Subject: [PATCH 05/42] remove uneed configuration --- runtests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/runtests.py b/runtests.py index af9b734e..0522d0de 100755 --- a/runtests.py +++ b/runtests.py @@ -9,9 +9,7 @@ sys.path.insert(0, parent) os.environ['DJANGO_SETTINGS_MODULE'] = 'django_mailer.testapp.settings' -from django.conf import settings from django.test.simple import DjangoTestSuiteRunner -from django.utils.functional import empty def runtests(*test_args): From d56025ab44b08b1ef7cccf5079c4d845f48b64ce Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Sat, 23 Feb 2013 11:59:08 +0100 Subject: [PATCH 06/42] integration with https://github.com/selwin/django-mailer/tree/cleanup_mail --- .../management/commands/cleanup_mail.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 django_mailer/management/commands/cleanup_mail.py diff --git a/django_mailer/management/commands/cleanup_mail.py b/django_mailer/management/commands/cleanup_mail.py new file mode 100644 index 00000000..ef35af08 --- /dev/null +++ b/django_mailer/management/commands/cleanup_mail.py @@ -0,0 +1,29 @@ +import datetime +import logging +from optparse import make_option + +from django.core.management.base import BaseCommand + +from django_mailer.management.commands import create_handler +from django_mailer.models import Message + + +class Command(BaseCommand): + help = 'Place deferred messages back in the queue.' + option_list = BaseCommand.option_list + ( + make_option('-d', '--days', type='int', default=90, + help="Cleanup mails older than this many days, defaults to 90."), + ) + + def handle(self, verbosity, days, **options): + # Delete mails and their related logs and queued created before X days + logger = logging.getLogger('django_mailer') + handler = create_handler(verbosity) + logger.addHandler(handler) + + today = datetime.date.today() + cutoff_date = today - datetime.timedelta(days) + count = Message.objects.filter(date_created__lt=cutoff_date).count() + Message.objects.filter(date_created__lt=cutoff_date).delete() + logger.warning("Deleted %s mails created before %s " % + (count, cutoff_date)) From 714bea887d345e07fb9b02d35c22deb7cc88824f Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Sat, 23 Feb 2013 11:59:32 +0100 Subject: [PATCH 07/42] Test command --- django_mailer/testapp/tests/commands.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/django_mailer/testapp/tests/commands.py b/django_mailer/testapp/tests/commands.py index 48d14e0a..b125f2cc 100644 --- a/django_mailer/testapp/tests/commands.py +++ b/django_mailer/testapp/tests/commands.py @@ -63,3 +63,16 @@ def test_retry_deferred(self): self.assertEqual(non_deferred_messages.count(), 1) call_command('retry_deferred', verbosity='0', max_retries=3) self.assertEqual(non_deferred_messages.count(), 3) + + def test_cleanup_mail(self): + """ + The ``cleanup_mail`` command deletes mails older than a specified + amount of days + """ + today = datetime.date.today() + self.assertEqual(models.Message.objects.count(), 0) + models.Message.objects.create() + prev = today - datetime.timedelta(31) + models.Message.objects.create(date_created=prev) + call_command('cleanup_mail', days=30) + self.assertEqual(models.Message.objects.count(), 1) From 8e2a8a7528d979dfa002a4ade4750afe1e193911 Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Sat, 23 Feb 2013 18:01:58 +0100 Subject: [PATCH 08/42] Added bin cron samples. Merged documentation. --- bin/cleanup_mail | 13 ++++++++++++ bin/fake-server | 4 ++++ bin/retry_deferred | 13 ++++++++++++ bin/send_mails | 13 ++++++++++++ django_mailer/constants.py | 4 ++++ django_mailer/engine.py | 4 ++++ django_mailer/lockfile.py | 21 ++++++++++++++++--- .../management/commands/cleanup_mail.py | 6 +++++- .../management/commands/retry_deferred.py | 4 ++++ django_mailer/managers.py | 4 ++++ django_mailer/models.py | 18 +++++++++------- django_mailer/testapp/tests/commands.py | 2 ++ docs/usage.txt | 10 +++++++-- 13 files changed, 103 insertions(+), 13 deletions(-) create mode 100755 bin/cleanup_mail create mode 100755 bin/fake-server create mode 100755 bin/retry_deferred create mode 100755 bin/send_mails diff --git a/bin/cleanup_mail b/bin/cleanup_mail new file mode 100755 index 00000000..d80ab833 --- /dev/null +++ b/bin/cleanup_mail @@ -0,0 +1,13 @@ +#!/bin/bash +# cron job example to run cleanup_mail and delete old mails +# replace {{username}} with the user who is going to execute the task +# the sample assumes you have virtualenv and virtualenvwrapper installed +# and that the applicacion is installed in src + +USER={{username}} +export WORKON_HOME=/$HOME/.virtualenvs +source /usr/local/bin/virtualenvwrapper.sh +VIRTUALENV=$USER +workon $VIRTUALENV +cd $HOME/src +python manage.py cleanup_mail --days=90 diff --git a/bin/fake-server b/bin/fake-server new file mode 100755 index 00000000..35649e97 --- /dev/null +++ b/bin/fake-server @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Runing a fake smptp server on localhost:1020" + +python -m smtpd -n -c DebuggingServer localhost:1025 diff --git a/bin/retry_deferred b/bin/retry_deferred new file mode 100755 index 00000000..52e6f1d8 --- /dev/null +++ b/bin/retry_deferred @@ -0,0 +1,13 @@ +#!/bin/bash +# cron job example to run retry_deferred +# replace {{username}} with the user who is going to execute the task +# the sample assumes you have virtualenv and virtualenvwrapper installed +# and that the applicacion is installed in src + +USER={{username}} +export WORKON_HOME=/$HOME/.virtualenvs +source /usr/local/bin/virtualenvwrapper.sh +VIRTUALENV=$USER +workon $VIRTUALENV +cd $HOME/src +python manage.py retry_deferred diff --git a/bin/send_mails b/bin/send_mails new file mode 100755 index 00000000..4a59326e --- /dev/null +++ b/bin/send_mails @@ -0,0 +1,13 @@ +#!/bin/bash +# replace {{username}} with the user who is going to execute the task +# the sample assumes you have virtualenv and virtualenvwrapper installed +# and that the applicacion is installed in src + +USER={{usern{{username}} +export WORKON_HOME=/$HOME/.virtualenvs +source /usr/local/bin/virtualenvwrapper.sh +VIRTUALENV=$USER + +workon $VIRTUALENV +cd $HOME/src +python manage.py send_mail diff --git a/django_mailer/constants.py b/django_mailer/constants.py index 74b8394e..c5cdfb1c 100644 --- a/django_mailer/constants.py +++ b/django_mailer/constants.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + PRIORITY_EMAIL_NOW = 0 PRIORITY_HIGH = 1 PRIORITY_NORMAL = 3 diff --git a/django_mailer/engine.py b/django_mailer/engine.py index de063220..063c8432 100644 --- a/django_mailer/engine.py +++ b/django_mailer/engine.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + """ The "engine room" of django mailer. diff --git a/django_mailer/lockfile.py b/django_mailer/lockfile.py index bca686cf..80af2227 100644 --- a/django_mailer/lockfile.py +++ b/django_mailer/lockfile.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- """ lockfile.py - Platform-independent advisory file locks. @@ -69,6 +72,7 @@ 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock', 'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock'] + class Error(Exception): """ Base class for other exceptions. @@ -80,6 +84,7 @@ class Error(Exception): """ pass + class LockError(Error): """ Base class for error arising from attempts to acquire the lock. @@ -91,6 +96,7 @@ class LockError(Error): """ pass + class LockTimeout(LockError): """Raised when lock creation fails within a user-defined period of time. @@ -101,6 +107,7 @@ class LockTimeout(LockError): """ pass + class AlreadyLocked(LockError): """Some other thread/process is locking the file. @@ -111,6 +118,7 @@ class AlreadyLocked(LockError): """ pass + class LockFailed(LockError): """Lock file creation failed for some other reason. @@ -121,6 +129,7 @@ class LockFailed(LockError): """ pass + class UnlockError(Error): """ Base class for errors arising from attempts to release the lock. @@ -132,6 +141,7 @@ class UnlockError(Error): """ pass + class NotLocked(UnlockError): """Raised when an attempt is made to unlock an unlocked file. @@ -142,6 +152,7 @@ class NotLocked(UnlockError): """ pass + class NotMyLock(UnlockError): """Raised when an attempt is made to unlock a file someone else locked. @@ -152,6 +163,7 @@ class NotMyLock(UnlockError): """ pass + class LockBase: """Base class for platform-specific lock classes.""" def __init__(self, path, threaded=True): @@ -229,6 +241,7 @@ def __exit__(self, *_exc): """ self.release() + class LinkFileLock(LockBase): """Lock access to a file using atomic property of link(2).""" @@ -286,6 +299,7 @@ def break_lock(self): if os.path.exists(self.lock_file): os.unlink(self.lock_file) + class MkdirFileLock(LockBase): """Lock file by creating a directory.""" def __init__(self, path, threaded=True): @@ -360,6 +374,7 @@ def break_lock(self): os.unlink(os.path.join(self.lock_file, name)) os.rmdir(self.lock_file) + class SQLiteFileLock(LockBase): "Demonstration of using same SQL-based locking." @@ -376,7 +391,7 @@ def __init__(self, path, threaded=True): import sqlite3 self.connection = sqlite3.connect(SQLiteFileLock.testdb) - + c = self.connection.cursor() try: c.execute("create table locks" @@ -438,7 +453,7 @@ def acquire(self, timeout=None): if len(rows) == 1: # We're the locker, so go home. return - + # Maybe we should wait a bit longer. if timeout is not None and time.time() > end_time: if timeout > 0: @@ -468,7 +483,7 @@ def _who_is_locking(self): " where lock_file = ?", (self.lock_file,)) return cursor.fetchone()[0] - + def is_locked(self): cursor = self.connection.cursor() cursor.execute("select * from locks" diff --git a/django_mailer/management/commands/cleanup_mail.py b/django_mailer/management/commands/cleanup_mail.py index ef35af08..84554ee7 100644 --- a/django_mailer/management/commands/cleanup_mail.py +++ b/django_mailer/management/commands/cleanup_mail.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + import datetime import logging from optparse import make_option @@ -9,7 +13,7 @@ class Command(BaseCommand): - help = 'Place deferred messages back in the queue.' + help = 'Delete the mails created before -d days (default 90)' option_list = BaseCommand.option_list + ( make_option('-d', '--days', type='int', default=90, help="Cleanup mails older than this many days, defaults to 90."), diff --git a/django_mailer/management/commands/retry_deferred.py b/django_mailer/management/commands/retry_deferred.py index a4820650..0692a91a 100644 --- a/django_mailer/management/commands/retry_deferred.py +++ b/django_mailer/management/commands/retry_deferred.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + from django.core.management.base import NoArgsCommand from django_mailer import models from django_mailer.management.commands import create_handler diff --git a/django_mailer/managers.py b/django_mailer/managers.py index c8a6ead9..9778e4da 100644 --- a/django_mailer/managers.py +++ b/django_mailer/managers.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + import datetime from django.db import models from django_mailer import constants diff --git a/django_mailer/models.py b/django_mailer/models.py index b50897db..dbf1c83a 100644 --- a/django_mailer/models.py +++ b/django_mailer/models.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + from django.db import models from django_mailer import constants, managers import datetime @@ -19,12 +23,12 @@ class Message(models.Model): """ An email message. - + The ``to_address``, ``from_address`` and ``subject`` fields are merely for easy of access for these common values. The ``encoded_message`` field contains the entire encoded email message ready to be sent to an SMTP connection. - + """ to_address = models.CharField(max_length=200) from_address = models.CharField(max_length=200) @@ -43,10 +47,10 @@ def __unicode__(self): class QueuedMessage(models.Model): """ A queued message. - + Messages in the queue can be prioritised so that the higher priority messages are sent first (secondarily sorted by the oldest message). - + """ message = models.OneToOneField(Message, editable=False) priority = models.PositiveSmallIntegerField(choices=PRIORITIES, @@ -68,10 +72,10 @@ def defer(self): class Blacklist(models.Model): """ A blacklisted email address. - + Messages attempted to be sent to e-mail addresses which appear on this blacklist will be skipped entirely. - + """ email = models.EmailField(max_length=200) date_added = models.DateTimeField(default=datetime.datetime.now) @@ -85,7 +89,7 @@ class Meta: class Log(models.Model): """ A log used to record the activity of a queued message. - + """ message = models.ForeignKey(Message, editable=False) result = models.PositiveSmallIntegerField(choices=RESULT_CODES) diff --git a/django_mailer/testapp/tests/commands.py b/django_mailer/testapp/tests/commands.py index b125f2cc..eb62d2e1 100644 --- a/django_mailer/testapp/tests/commands.py +++ b/django_mailer/testapp/tests/commands.py @@ -71,8 +71,10 @@ def test_cleanup_mail(self): """ today = datetime.date.today() self.assertEqual(models.Message.objects.count(), 0) + #new message (not to be deleted) models.Message.objects.create() prev = today - datetime.timedelta(31) + # new message (old) models.Message.objects.create(date_created=prev) call_command('cleanup_mail', days=30) self.assertEqual(models.Message.objects.count(), 1) diff --git a/docs/usage.txt b/docs/usage.txt index 5d719dcc..ce17640f 100644 --- a/docs/usage.txt +++ b/docs/usage.txt @@ -73,16 +73,22 @@ you can run: * ``retry_deferred`` will move any deferred mail back into the normal queue (so it will be attempted again on the next ``send_mail``). + * ``cleanup_mail`` will delete mails created before an X number of days + (defaults to 90). + You may want to set these up via cron to run regularly:: * * * * * (cd $PROJECT; python manage.py send_mail >> $PROJECT/cron_mail.log 2>&1) 0,20,40 * * * * (cd $PROJECT; python manage.py retry_deferred >> $PROJECT/cron_mail_deferred.log 2>&1) + 0 1 * * * (cd $PROJECT; python manage.py cleanup_mail --days=30 >> $PROJECT/cron_mail_cleanup.log 2>&1) -This attempts to send mail every minute with a retry on failure every 20 minutes. +This attempts to send mail every minute with a retry on failure every 20 minutes +and will run a cleanup task every day cleaning all the messaged created before +30 days. ``manage.py send_mail`` uses a lock file in case clearing the queue takes longer than the interval between calling ``manage.py send_mail``. Note that if your project lives inside a virtualenv, you also have to execute this command from the virtualenv. The same, naturally, applies also if you're -executing it with cron. \ No newline at end of file +executing it with cron. From 214983834df1f1c40be6b6b3f8143c66600f361c Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Sat, 23 Feb 2013 18:06:48 +0100 Subject: [PATCH 09/42] updated version number --- django_mailer/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/django_mailer/__init__.py b/django_mailer/__init__.py index f7bc6ac9..3cf1a0a3 100644 --- a/django_mailer/__init__.py +++ b/django_mailer/__init__.py @@ -1,6 +1,10 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + import logging -VERSION = (1, 2, 1) +VERSION = (1, 2, 2) logger = logging.getLogger('django_mailer') logger.setLevel(logging.DEBUG) @@ -49,7 +53,7 @@ def mail_admins(subject, message, fail_silently=False, priority=None): """ from django.conf import settings as django_settings from django.utils.encoding import force_unicode - from django_mailer import constants, settings + from django_mailer import settings if priority is None: settings.MAIL_ADMINS_PRIORITY From 7e4f88da1083d8b2343e28bd64d889c17b47013e Mon Sep 17 00:00:00 2001 From: aaloy Date: Sun, 24 Feb 2013 15:32:52 +0100 Subject: [PATCH 10/42] Created a basic mail viewer for the demo applications. Allows you to download attachments and see how the e-mail is displayed. --- .gitignore | 4 ++ demo/demo/mail_utils.py | 47 +++++++++++++++ demo/demo/templates/base.html | 58 +++++++++++++++++++ .../django_mailer/message_detail.html | 26 +++++++++ .../templates/django_mailer/message_list.html | 30 ++++++++++ demo/demo/urls.py | 14 +++-- demo/demo/views.py | 56 ++++++++++++++++++ 7 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 demo/demo/mail_utils.py create mode 100644 demo/demo/templates/base.html create mode 100644 demo/demo/templates/django_mailer/message_detail.html create mode 100644 demo/demo/templates/django_mailer/message_list.html create mode 100644 demo/demo/views.py diff --git a/.gitignore b/.gitignore index 2f78cf5b..f3d25f25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ *.pyc +/django_mailer_2.egg-info/dependency_links.txt +/django_mailer_2.egg-info/PKG-INFO +/django_mailer_2.egg-info/SOURCES.txt +/django_mailer_2.egg-info/top_level.txt diff --git a/demo/demo/mail_utils.py b/demo/demo/mail_utils.py new file mode 100644 index 00000000..cca9892f --- /dev/null +++ b/demo/demo/mail_utils.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + +import hashlib + + +class Attachment(object): + """ + Utility class to contain the attachment information + """ + + def __init__(self, mailpart): + self.filename = mailpart.sanitized_filename + self.tipus = mailpart.type + self.charset = mailpart.charset + self.content_description = mailpart.part.get('Content-Description') + self.payload = mailpart.get_payload() + self.length = len(self.payload) + self.firma = hashlib.md5(self.payload).hexdigest() + + +def get_attachments(msg): + """ + Returns a list with all the mail attachments + """ + + attachments = [] + for mailpart in msg.mailparts: + if not mailpart.is_body and mailpart.disposition == 'attachment': + attachment = Attachment(mailpart) + attachments.append(attachment) + return attachments + + +def get_attachment(msg, key): + """ + Given a msg returns the attachment who's signature (md5 key) matches + the key value + """ + + for mailpart in msg.mailparts: + if not mailpart.is_body and mailpart.disposition == 'attachment': + attachment = Attachment(mailpart) + if attachment.firma == key: + return attachment + return None diff --git a/demo/demo/templates/base.html b/demo/demo/templates/base.html new file mode 100644 index 00000000..131ae2fa --- /dev/null +++ b/demo/demo/templates/base.html @@ -0,0 +1,58 @@ +{% load static i18n %}{% load url from future %} + + + + + + + {% block head_title_base %}{% block head_title %}{% endblock %} | {% trans "Project Name" %}{% endblock %} + + + + {% block style_base %} + {% block extra_style %}{% endblock %} + {% endblock %} + + {% block extra_head_base %} + {% block extra_head %}{% endblock %} + {% block analytics %} + {% endblock analytics %} + {% endblock %} + + +
+ {% block topbar_base %} + {% endblock %} + +
+ {% block ieprompt %}{% endblock %} + + + {% block page_header_base %} + {% block page_header %}{% endblock %} + {% endblock %} + +
+ {% block content %}{% endblock %} +
+
+ +
{# sticky footer push #} +
{# /wrap #} + + {% block footer_base %} + {% endblock %} + + {% block script_base %} + {% block extra_script %}{% endblock %} + {% endblock %} + + {% block extra_body_base %} + {% block extra_body %}{% endblock %} + {% endblock %} + + {% block modal_base %}{% endblock %} + + diff --git a/demo/demo/templates/django_mailer/message_detail.html b/demo/demo/templates/django_mailer/message_detail.html new file mode 100644 index 00000000..cb49223e --- /dev/null +++ b/demo/demo/templates/django_mailer/message_detail.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block content %} +

Message {{object.id}}

+ Subject: {{subject}}
+ From: <{{from.0}}>{{from.1}}
+ To: {% for x in to %}<{{x.0}}>{{x.1}} {% endfor %}
+ CC: {% for x in cc %}<{{x.0}}>{{x.1}} {% endfor %}
+
+ {{parts}} + {% if msg_html %} +

HTML

+ {{msg_html|safe}} + {% endif %} + {% if msg_text %} +

Text

+ {{msg_text|linebreaks}} + {% endif %} + {% if attachments %} +

Attachments

+ {% for file in attachments %} + {{file.filename}} {{file.tipus}} {{file.length}} +
+ {% endfor %} + {% endif %} +

Back to list +{% endblock content %} diff --git a/demo/demo/templates/django_mailer/message_list.html b/demo/demo/templates/django_mailer/message_list.html new file mode 100644 index 00000000..e5a29e51 --- /dev/null +++ b/demo/demo/templates/django_mailer/message_list.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% block content %} +

Message List

+ + {% for mail in object_list %} + + + + + + {% endfor %} +
{{mail.pk}} + {{mail.from_address}}{{mail.to_address}}{{mail.subject}} + {{mail.date_created}}
+ +{% if is_paginated %} + {% if page_obj.has_previous %} + + Previous + {% endif %} + + Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. + + {% if page_obj.has_next %} + + Next + {% endif %} +{% endif %} + + {% endblock content %} diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 5414773f..ef701f64 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -4,14 +4,16 @@ from django.contrib import admin admin.autodiscover() +from demo.views import MailListView, MailDetailView, DownloadView + urlpatterns = patterns('', # Examples: - # url(r'^$', 'demo.views.home', name='home'), - # url(r'^demo/', include('demo.foo.urls')), - - # Uncomment the admin/doc line below to enable admin documentation: - # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - + url(r'^$', MailListView.as_view(), name='home'), + url(r'^mail/(?P\d+)/$', MailDetailView.as_view(), + name='mail_detail'), + url('^mail/attachment/(?P\d+)/(?P[0-9a-f]{32})/$', + DownloadView.as_view(), + name="mail_download"), # Uncomment the next line to enable the admin: url(r'^admin/', include(admin.site.urls)), ) diff --git a/demo/demo/views.py b/demo/demo/views.py new file mode 100644 index 00000000..019d6caf --- /dev/null +++ b/demo/demo/views.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + +from django.views.generic.list import ListView +from django.views.generic import DetailView +from django.views.generic.base import View +from django_mailer.models import Message +from pyzmail.parse import message_from_string +from django.http import HttpResponse +from mail_utils import get_attachments, get_attachment + + +class MailListView(ListView): + model = Message + paginate_by = 10 + +class MailDetailView(DetailView): + model = Message + + def get_context_data(self, **kwargs): + context = super(MailDetailView, self).get_context_data(**kwargs) + payload_str = self.object.encoded_message.encode('utf-8') + msg = message_from_string(payload_str) + context['subject'] = msg.get_subject() + context['from'] = msg.get_address('from') + context['to'] = msg.get_addresses('to') + context['cc'] = msg.get_addresses('cc') + msg_text = msg.text_part.get_payload() if msg.text_part else None + msg_html = msg.html_part.get_payload() if msg.html_part else None + context['msg_html'] = msg_html + context['msg_text'] = msg_text + context['attachments'] = get_attachments(msg) + return context + + +class DownloadView(View): + """ + Given a Message and an attachment signature returns the file + """ + + model = Message + + def get_file(self, request, *args, **kwargs): + pk = self.kwargs['pk'] + firma = self.kwargs['firma'] + payload_str = self.model.objects.get(pk=pk).encoded_message.encode('utf-8') + msg = message_from_string(payload_str) + return get_attachment(msg, key=firma) + + def get(self, request, *args, **kwargs): + arx = self.get_file(request, *args, **kwargs) + response = HttpResponse(mimetype=arx.tipus) + response['Content-Disposition'] = 'filename=' + arx.filename + response.write(arx.payload) + return response From b196d2e8261749f230a71a2a3228c70a018ddae8 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sun, 24 Feb 2013 15:46:48 +0100 Subject: [PATCH 11/42] Convert html display in a iframe to avoid possible clashes between page sytles and e-mails styles. --- demo/demo/templates/django_mailer/html_detail.html | 1 + .../templates/django_mailer/message_detail.html | 3 ++- demo/demo/urls.py | 4 ++++ demo/demo/views.py | 13 +++++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 demo/demo/templates/django_mailer/html_detail.html diff --git a/demo/demo/templates/django_mailer/html_detail.html b/demo/demo/templates/django_mailer/html_detail.html new file mode 100644 index 00000000..fca47b0e --- /dev/null +++ b/demo/demo/templates/django_mailer/html_detail.html @@ -0,0 +1 @@ +{{msg_html|safe}} diff --git a/demo/demo/templates/django_mailer/message_detail.html b/demo/demo/templates/django_mailer/message_detail.html index cb49223e..a8cc250e 100644 --- a/demo/demo/templates/django_mailer/message_detail.html +++ b/demo/demo/templates/django_mailer/message_detail.html @@ -9,7 +9,8 @@

Message {{object.id}}

{{parts}} {% if msg_html %}

HTML

- {{msg_html|safe}} + {% endif %} {% if msg_text %}

Text

diff --git a/demo/demo/urls.py b/demo/demo/urls.py index ef701f64..271e9681 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -5,6 +5,7 @@ admin.autodiscover() from demo.views import MailListView, MailDetailView, DownloadView +from demo.views import MailHtmlDetailView urlpatterns = patterns('', # Examples: @@ -14,6 +15,9 @@ url('^mail/attachment/(?P\d+)/(?P[0-9a-f]{32})/$', DownloadView.as_view(), name="mail_download"), + url('^mail/html/(?P\d+)/$', + MailHtmlDetailView.as_view(), + name="mail_html"), # Uncomment the next line to enable the admin: url(r'^admin/', include(admin.site.urls)), ) diff --git a/demo/demo/views.py b/demo/demo/views.py index 019d6caf..9d97d25f 100644 --- a/demo/demo/views.py +++ b/demo/demo/views.py @@ -34,6 +34,19 @@ def get_context_data(self, **kwargs): return context +class MailHtmlDetailView(DetailView): + model = Message + template_name = 'django_mailer/html_detail.html' + + def get_context_data(self, **kwargs): + context = super(MailHtmlDetailView, self).get_context_data(**kwargs) + payload_str = self.object.encoded_message.encode('utf-8') + msg = message_from_string(payload_str) + msg_html = msg.html_part.get_payload() if msg.html_part else None + context['msg_html'] = msg_html + return context + + class DownloadView(View): """ Given a Message and an attachment signature returns the file From a9ff561f25fe3026f62663f63411fed38f8f6f14 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sun, 24 Feb 2013 15:49:52 +0100 Subject: [PATCH 12/42] demo requirements --- demo/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 demo/requirements.txt diff --git a/demo/requirements.txt b/demo/requirements.txt new file mode 100644 index 00000000..cba14f49 --- /dev/null +++ b/demo/requirements.txt @@ -0,0 +1,2 @@ +django +pyzmail From b66a6e441706af697d7717c4dd4a5b0a4ebe41ab Mon Sep 17 00:00:00 2001 From: aaloy Date: Sun, 24 Feb 2013 15:50:54 +0100 Subject: [PATCH 13/42] Updated requirements --- demo/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/requirements.txt b/demo/requirements.txt index cba14f49..5899fdad 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,2 +1,3 @@ django pyzmail +#and django_mailer2 of course From fac30e71bd3491b3c58ff0d3278b7ad5980e8074 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sun, 24 Feb 2013 23:58:55 +0100 Subject: [PATCH 14/42] Docs ad minor changes on template --- README | 5 +++-- .../templates/django_mailer/message_list.html | 9 +++++---- demo/demo/views.py | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README b/README index aade88de..896cc2fb 100644 --- a/README +++ b/README @@ -1,6 +1,7 @@ -django-mailer-2 by Chris Beaven (a fork of James Tauber's django-mailer) +django-mailer-2 by APSL is a Chris Beaven form from a fork of +James Tauber's django-mailer. -A reusable Django app for queuing the sending of email +A reusable Django app for queuing the sending of email. Execute runtests.py to run the test. You'll need a working mail server or just diff --git a/demo/demo/templates/django_mailer/message_list.html b/demo/demo/templates/django_mailer/message_list.html index e5a29e51..a8983045 100644 --- a/demo/demo/templates/django_mailer/message_list.html +++ b/demo/demo/templates/django_mailer/message_list.html @@ -1,13 +1,14 @@ {% extends "base.html" %} {% block content %} -

Message List

- +

Message List

+
+ {% for mail in object_list %} - - {% endfor %} diff --git a/demo/demo/views.py b/demo/demo/views.py index 9d97d25f..9cf43cae 100644 --- a/demo/demo/views.py +++ b/demo/demo/views.py @@ -12,9 +12,20 @@ class MailListView(ListView): + """ + Displays the mail list + """ + model = Message paginate_by = 10 + def get_queryset(self): + """ + Sort the emails in reverse order + """ + return super(MailListView, self).get_queryset().order_by('-id') + + class MailDetailView(DetailView): model = Message @@ -35,6 +46,12 @@ def get_context_data(self, **kwargs): class MailHtmlDetailView(DetailView): + """ + Shows just the HTML mail for a message, so we can use it to diplay the + message in an iframe and avoid style classhes with the original + content. + """ + model = Message template_name = 'django_mailer/html_detail.html' From 0274684857f1f0fb99c5cdd611a3d89fbf7e58ab Mon Sep 17 00:00:00 2001 From: aaloy Date: Mon, 25 Feb 2013 00:17:00 +0100 Subject: [PATCH 15/42] Create sphinx doc to move to readthedocs --- .gitignore | 2 +- docs/Makefile | 153 ++++++++++++++++++ docs/conf.py | 242 ++++++++++++++++++++++++++++ docs/{index.txt => index.rst} | 5 +- docs/{install.txt => install.rst} | 0 docs/{settings.txt => settings.rst} | 0 docs/{usage.txt => usage.rst} | 0 7 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/conf.py rename docs/{index.txt => index.rst} (98%) rename docs/{install.txt => install.rst} (100%) rename docs/{settings.txt => settings.rst} (100%) rename docs/{usage.txt => usage.rst} (100%) diff --git a/.gitignore b/.gitignore index f3d25f25..b165bdab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *.pyc - +_* /django_mailer_2.egg-info/dependency_links.txt /django_mailer_2.egg-info/PKG-INFO /django_mailer_2.egg-info/SOURCES.txt diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..35c11ffb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-mailer2.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-mailer2.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/django-mailer2" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-mailer2" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..3507c8b1 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# +# django-mailer2 documentation build configuration file, created by +# sphinx-quickstart on Mon Feb 25 00:09:36 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-mailer2' +copyright = u'2013, Tauber, Beaven & APSL' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = 'master' +# The full version, including alpha/beta/rc tags. +release = 'master' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-mailer2doc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'django-mailer2.tex', u'django-mailer2 Documentation', + u'Tauber, Beaven \\& APSL', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'django-mailer2', u'django-mailer2 Documentation', + [u'Tauber, Beaven & APSL'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'django-mailer2', u'django-mailer2 Documentation', + u'Tauber, Beaven & APSL', 'django-mailer2', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/docs/index.txt b/docs/index.rst similarity index 98% rename from docs/index.txt rename to docs/index.rst index dd9700fa..a5bd6cda 100644 --- a/docs/index.txt +++ b/docs/index.rst @@ -56,4 +56,7 @@ Credit At the time of the fork, the primary authors of django-mailer were James Tauber and Brian Rosner. The additional contributors included Michael Trier, Doug -Napoleone and Jannis Leidel. \ No newline at end of file +Napoleone and Jannis Leidel. + + + diff --git a/docs/install.txt b/docs/install.rst similarity index 100% rename from docs/install.txt rename to docs/install.rst diff --git a/docs/settings.txt b/docs/settings.rst similarity index 100% rename from docs/settings.txt rename to docs/settings.rst diff --git a/docs/usage.txt b/docs/usage.rst similarity index 100% rename from docs/usage.txt rename to docs/usage.rst From 953274456fb1cb5a6e9a3e2e8ce4f9f9e9c100ab Mon Sep 17 00:00:00 2001 From: aaloy Date: Mon, 25 Feb 2013 00:20:43 +0100 Subject: [PATCH 16/42] added readthedocs link --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index 896cc2fb..79916d1d 100644 --- a/README +++ b/README @@ -7,3 +7,5 @@ Execute runtests.py to run the test. You'll need a working mail server or just python -m smtpd -n -c DebuggingServer localhost:1025 + +Find documentation about configuration an use on: https://django-mailer2.readthedocs.org/en/latest/ From 7f5031bff19677dacc3798beba8c3787e6fa28cc Mon Sep 17 00:00:00 2001 From: aaloy Date: Tue, 26 Feb 2013 00:29:59 +0100 Subject: [PATCH 17/42] Changed code to allow access to mails to just superuser users --- demo/demo/templates/registration/login.html | 25 ++++++++ demo/demo/urls.py | 1 + demo/demo/views.py | 63 +++++++++++++++++++-- demo/manage.py | 0 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 demo/demo/templates/registration/login.html mode change 100644 => 100755 demo/manage.py diff --git a/demo/demo/templates/registration/login.html b/demo/demo/templates/registration/login.html new file mode 100644 index 00000000..001ee0ff --- /dev/null +++ b/demo/demo/templates/registration/login.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block content %} +
+

Login

+ +
+ +{% if form.errors %} +

Sorry, that's not a valid username or password

+{% endif %} + +
{% csrf_token %} +
+
+
+
+
+
+ + + +
+
+{% endblock %} diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 271e9681..cf71721b 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -19,5 +19,6 @@ MailHtmlDetailView.as_view(), name="mail_html"), # Uncomment the next line to enable the admin: + (r'^accounts/login/$', 'django.contrib.auth.views.login'), url(r'^admin/', include(admin.site.urls)), ) diff --git a/demo/demo/views.py b/demo/demo/views.py index 9cf43cae..9bece8ae 100644 --- a/demo/demo/views.py +++ b/demo/demo/views.py @@ -9,9 +9,63 @@ from pyzmail.parse import message_from_string from django.http import HttpResponse from mail_utils import get_attachments, get_attachment +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.contrib.auth.views import redirect_to_login +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.utils.decorators import method_decorator +from django.conf import settings +""" +Shows mail info with attachemnts from django-mailer +Superuser rights are required to acces to the information. +""" -class MailListView(ListView): + +class LoginRequiredMixin(object): + """ + View mixin which verifies that the user has authenticated. + + NOTE: + This should be the left-most mixin of a view. + + Code from django_braces + https://github.com/brack3t/django-braces/blob/master/braces/views.py + """ + + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + return super(LoginRequiredMixin, self).dispatch(request, + *args, **kwargs) + + +class SuperuserRequiredMixin(object): + """ + Mixin allows you to require a user with `is_superuser` set to True. + Code from django_braces + https://github.com/brack3t/django-braces/blob/master/braces/views.py + """ + + login_url = settings.LOGIN_URL # LOGIN_URL from project settings + raise_exception = False # Default whether to raise an exception to none + redirect_field_name = REDIRECT_FIELD_NAME # Set by django.contrib.auth + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_superuser: # If the user is a standard user, + if self.raise_exception: # *and* if an exception was desired + raise PermissionDenied # return a forbidden response. + else: + return redirect_to_login(request.get_full_path(), + self.login_url, + self.redirect_field_name) + + return super(SuperuserRequiredMixin, self).dispatch(request, + *args, **kwargs) + + + + +class MailListView(LoginRequiredMixin, SuperuserRequiredMixin, ListView): """ Displays the mail list """ @@ -26,7 +80,7 @@ def get_queryset(self): return super(MailListView, self).get_queryset().order_by('-id') -class MailDetailView(DetailView): +class MailDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): model = Message def get_context_data(self, **kwargs): @@ -45,7 +99,8 @@ def get_context_data(self, **kwargs): return context -class MailHtmlDetailView(DetailView): +class MailHtmlDetailView(LoginRequiredMixin, SuperuserRequiredMixin, + DetailView): """ Shows just the HTML mail for a message, so we can use it to diplay the message in an iframe and avoid style classhes with the original @@ -64,7 +119,7 @@ def get_context_data(self, **kwargs): return context -class DownloadView(View): +class DownloadView(LoginRequiredMixin, SuperuserRequiredMixin, View): """ Given a Message and an attachment signature returns the file """ diff --git a/demo/manage.py b/demo/manage.py old mode 100644 new mode 100755 From 22e92c1d93c1a7deda9bb982f0adc24b2c391607 Mon Sep 17 00:00:00 2001 From: aaloy Date: Fri, 1 Mar 2013 11:06:01 +0100 Subject: [PATCH 18/42] Explains history --- docs/index.rst | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index a5bd6cda..f59d246b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,13 @@ Contents: Django Mailer fork ================== -django-mailer-2 is a fork of James Tauber's `django-mailer`__. +django-mailer-2 is a fork form Chris Beaven fort to of James Tauber's +`django-mailer`.__ + +This document is readthedocs version of the fork that Chris and James made +the original document with some additional information. + +comments. .. __: http://github.com/jtauber/django-mailer @@ -37,6 +43,11 @@ technically live side by side in harmony. One of the motivations in doing this was to make the transition simpler for projects which are using django-mailer (or to transition back, if someone doesn't like this one). +I made an additional fork as I need to correct some bugs related to unicode +mail and add some interesting patches as the one which allows you to remove +mails. + + Differences ----------- @@ -51,6 +62,20 @@ Some of the larger differences in django-mailer-2: * It provides a hook to override (aka "monkey patch") the Django ``send_mail``, ``mail_admins`` and ``mail_manager`` functions. +* Added a management command to remove old e-mails, so the database does not + increase so much. + +* Added a new testing procedure, so you can run the tests without having to + install and configure a Django application. + +* Added some cron templates ein `bin` folder to help you to configure the + cron. + +* Improved admin configuration. + +* Added a demo project, which shows how we can retrieve an email stored in + the database and shows django-mailer in the admin. + Credit ------ @@ -58,5 +83,6 @@ At the time of the fork, the primary authors of django-mailer were James Tauber and Brian Rosner. The additional contributors included Michael Trier, Doug Napoleone and Jannis Leidel. +Original branch and the django-mailer-2 hard work comes from Chris Beaven. From 7857beac82c999be9456fa8ad38a49dce63aebe9 Mon Sep 17 00:00:00 2001 From: aaloy Date: Thu, 7 Mar 2013 20:47:48 +0100 Subject: [PATCH 19/42] bug on setup --- django_mailer/admin.py | 11 ++++++----- setup.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/django_mailer/admin.py b/django_mailer/admin.py index fad3aaf8..2291afbc 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -5,7 +5,8 @@ class Message(admin.ModelAdmin): list_display = ('from_address', 'to_address', 'subject', 'date_created') list_filter = ('date_created',) - search_fields = ('to_address', 'subject', 'from_address', 'encoded_message',) + search_fields = ('to_address', 'subject', 'from_address', + 'encoded_message',) date_hierarchy = 'date_created' ordering = ('-date_created',) @@ -19,7 +20,7 @@ def message__to_address(self, obj): def message__from_address(self, obj): return obj.message.from_address - message__from_address.admin_order_field='message__from_address' + message__from_address.admin_order_field = 'message__from_address' def message__subject(self, obj): return obj.message.subject @@ -36,9 +37,9 @@ def not_deferred(self, obj): not_deferred.boolean = True not_deferred.admin_order_field = 'deferred' - list_display = ('id', 'message', 'message__to_address', 'message__from_address', - 'message__subject', - 'message__date_created', 'priority', 'not_deferred') + list_display = ('id', 'message', 'message__to_address', + 'message__from_address', 'message__subject', + 'message__date_created', 'priority', 'not_deferred') class Blacklist(admin.ModelAdmin): diff --git a/setup.py b/setup.py index bf7ca22d..2b9f48b2 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ version=get_version(), description=("A reusable Django app for queueing the sending of email " "(forked aaloy on a frok from James Tauber's django-mailer)"), - long_description=open('docs/usage.txt').read(), + long_description=open('docs/usage.rst').read(), author='Antoni Aloy', author_email='antoni.aloy@gmail.com', url='http://github.com/APSL/django-mailer-2', From facb18f9a210a680146c86b3e74da5f5dc601c87 Mon Sep 17 00:00:00 2001 From: aaloy Date: Fri, 15 Mar 2013 00:56:32 +0100 Subject: [PATCH 20/42] Now we can show e-mail in the admin --- demo/demo.sqlite | Bin 46080 -> 46080 bytes .../django_mailer/message_detail.html | 27 ---- demo/demo/templates/index.html | 1 + demo/demo/templates/registration/login.html | 25 ---- demo/demo/urls.py | 15 +- demo/demo/views.py | 139 +----------------- django_mailer/admin.py | 62 +++++++- {demo/demo => django_mailer}/mail_utils.py | 0 .../admin/django_mailer/change_form.html | 4 + .../templates/django_mailer/html_detail.html | 0 .../django_mailer/message_detail.html | 43 ++++++ .../templates/django_mailer/message_list.html | 0 12 files changed, 113 insertions(+), 203 deletions(-) delete mode 100644 demo/demo/templates/django_mailer/message_detail.html create mode 100644 demo/demo/templates/index.html delete mode 100644 demo/demo/templates/registration/login.html rename {demo/demo => django_mailer}/mail_utils.py (100%) create mode 100644 django_mailer/templates/admin/django_mailer/change_form.html rename {demo/demo => django_mailer}/templates/django_mailer/html_detail.html (100%) create mode 100644 django_mailer/templates/django_mailer/message_detail.html rename {demo/demo => django_mailer}/templates/django_mailer/message_list.html (100%) diff --git a/demo/demo.sqlite b/demo/demo.sqlite index 531800c4bfbe05ba2e9cef899c93a11cf238531e..6a3d85a962179f0f7139eaf16d3544a35170457c 100644 GIT binary patch delta 246 zcmZp8!PM}AX@WGP`$QRMM)!>gMdl*Lx`rkSMg~?uWMH6YXkcz(YP$KExtstO6B8c; zGdmL>VfAgQb~G|Ewn$7&OHNBROR-2aGqN-_H#AN*v`9;~NKP^| zHJYsNy%%V%kp;qBV^afT6NAa@-ha86m?ttYPh{T4e0#GXOEa^k3bQXGAro0ZiYD`W oD{Q{yuN43^;t&J#L*_%wi-AVWV4i$*v6?70`&c$VTr4jD0RQ1aPXGV_ delta 117 zcmZp8!PM}AX@WGP{X`jOM*EEkMdl)gx<*C{h89*PrdEcQdgg|fCI%LppP9=Ea4<5n zGcdC=?b+D4iD~jtPw&Z`-oJr@+ZdR)G2h-S$g+x=MTOayaWcQR!sc84S^+@0hYZXQ UnGXTwb}~<1wpeZR>&2o10KbhRbN~PV diff --git a/demo/demo/templates/django_mailer/message_detail.html b/demo/demo/templates/django_mailer/message_detail.html deleted file mode 100644 index a8cc250e..00000000 --- a/demo/demo/templates/django_mailer/message_detail.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Message {{object.id}}

- Subject: {{subject}}
- From: <{{from.0}}>{{from.1}}
- To: {% for x in to %}<{{x.0}}>{{x.1}} {% endfor %}
- CC: {% for x in cc %}<{{x.0}}>{{x.1}} {% endfor %}
-
- {{parts}} - {% if msg_html %} -

HTML

- - {% endif %} - {% if msg_text %} -

Text

- {{msg_text|linebreaks}} - {% endif %} - {% if attachments %} -

Attachments

- {% for file in attachments %} - {{file.filename}} {{file.tipus}} {{file.length}} -
- {% endfor %} - {% endif %} -

Back to list -{% endblock content %} diff --git a/demo/demo/templates/index.html b/demo/demo/templates/index.html new file mode 100644 index 00000000..94d9808c --- /dev/null +++ b/demo/demo/templates/index.html @@ -0,0 +1 @@ +{% extends "base.html" %} diff --git a/demo/demo/templates/registration/login.html b/demo/demo/templates/registration/login.html deleted file mode 100644 index 001ee0ff..00000000 --- a/demo/demo/templates/registration/login.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -

-

Login

- -
- -{% if form.errors %} -

Sorry, that's not a valid username or password

-{% endif %} - -{% csrf_token %} -
-
-
-
-
-
- - - -
-
-{% endblock %} diff --git a/demo/demo/urls.py b/demo/demo/urls.py index cf71721b..de022736 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -4,21 +4,10 @@ from django.contrib import admin admin.autodiscover() -from demo.views import MailListView, MailDetailView, DownloadView -from demo.views import MailHtmlDetailView +from demo.views import IndexView urlpatterns = patterns('', # Examples: - url(r'^$', MailListView.as_view(), name='home'), - url(r'^mail/(?P\d+)/$', MailDetailView.as_view(), - name='mail_detail'), - url('^mail/attachment/(?P\d+)/(?P[0-9a-f]{32})/$', - DownloadView.as_view(), - name="mail_download"), - url('^mail/html/(?P\d+)/$', - MailHtmlDetailView.as_view(), - name="mail_html"), - # Uncomment the next line to enable the admin: - (r'^accounts/login/$', 'django.contrib.auth.views.login'), + url(r'^$', IndexView.as_view(), name='home'), url(r'^admin/', include(admin.site.urls)), ) diff --git a/demo/demo/views.py b/demo/demo/views.py index 9bece8ae..72dfa014 100644 --- a/demo/demo/views.py +++ b/demo/demo/views.py @@ -2,140 +2,7 @@ # encoding: utf-8 # ---------------------------------------------------------------------------- -from django.views.generic.list import ListView -from django.views.generic import DetailView -from django.views.generic.base import View -from django_mailer.models import Message -from pyzmail.parse import message_from_string -from django.http import HttpResponse -from mail_utils import get_attachments, get_attachment -from django.contrib.auth import REDIRECT_FIELD_NAME -from django.contrib.auth.views import redirect_to_login -from django.contrib.auth.decorators import login_required -from django.core.exceptions import PermissionDenied -from django.utils.decorators import method_decorator -from django.conf import settings +from django.views.generic import TemplateView -""" -Shows mail info with attachemnts from django-mailer -Superuser rights are required to acces to the information. -""" - - -class LoginRequiredMixin(object): - """ - View mixin which verifies that the user has authenticated. - - NOTE: - This should be the left-most mixin of a view. - - Code from django_braces - https://github.com/brack3t/django-braces/blob/master/braces/views.py - """ - - @method_decorator(login_required) - def dispatch(self, request, *args, **kwargs): - return super(LoginRequiredMixin, self).dispatch(request, - *args, **kwargs) - - -class SuperuserRequiredMixin(object): - """ - Mixin allows you to require a user with `is_superuser` set to True. - Code from django_braces - https://github.com/brack3t/django-braces/blob/master/braces/views.py - """ - - login_url = settings.LOGIN_URL # LOGIN_URL from project settings - raise_exception = False # Default whether to raise an exception to none - redirect_field_name = REDIRECT_FIELD_NAME # Set by django.contrib.auth - - def dispatch(self, request, *args, **kwargs): - if not request.user.is_superuser: # If the user is a standard user, - if self.raise_exception: # *and* if an exception was desired - raise PermissionDenied # return a forbidden response. - else: - return redirect_to_login(request.get_full_path(), - self.login_url, - self.redirect_field_name) - - return super(SuperuserRequiredMixin, self).dispatch(request, - *args, **kwargs) - - - - -class MailListView(LoginRequiredMixin, SuperuserRequiredMixin, ListView): - """ - Displays the mail list - """ - - model = Message - paginate_by = 10 - - def get_queryset(self): - """ - Sort the emails in reverse order - """ - return super(MailListView, self).get_queryset().order_by('-id') - - -class MailDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): - model = Message - - def get_context_data(self, **kwargs): - context = super(MailDetailView, self).get_context_data(**kwargs) - payload_str = self.object.encoded_message.encode('utf-8') - msg = message_from_string(payload_str) - context['subject'] = msg.get_subject() - context['from'] = msg.get_address('from') - context['to'] = msg.get_addresses('to') - context['cc'] = msg.get_addresses('cc') - msg_text = msg.text_part.get_payload() if msg.text_part else None - msg_html = msg.html_part.get_payload() if msg.html_part else None - context['msg_html'] = msg_html - context['msg_text'] = msg_text - context['attachments'] = get_attachments(msg) - return context - - -class MailHtmlDetailView(LoginRequiredMixin, SuperuserRequiredMixin, - DetailView): - """ - Shows just the HTML mail for a message, so we can use it to diplay the - message in an iframe and avoid style classhes with the original - content. - """ - - model = Message - template_name = 'django_mailer/html_detail.html' - - def get_context_data(self, **kwargs): - context = super(MailHtmlDetailView, self).get_context_data(**kwargs) - payload_str = self.object.encoded_message.encode('utf-8') - msg = message_from_string(payload_str) - msg_html = msg.html_part.get_payload() if msg.html_part else None - context['msg_html'] = msg_html - return context - - -class DownloadView(LoginRequiredMixin, SuperuserRequiredMixin, View): - """ - Given a Message and an attachment signature returns the file - """ - - model = Message - - def get_file(self, request, *args, **kwargs): - pk = self.kwargs['pk'] - firma = self.kwargs['firma'] - payload_str = self.model.objects.get(pk=pk).encoded_message.encode('utf-8') - msg = message_from_string(payload_str) - return get_attachment(msg, key=firma) - - def get(self, request, *args, **kwargs): - arx = self.get_file(request, *args, **kwargs) - response = HttpResponse(mimetype=arx.tipus) - response['Content-Disposition'] = 'filename=' + arx.filename - response.write(arx.payload) - return response +class IndexView(TemplateView): + template_name = 'index.html' diff --git a/django_mailer/admin.py b/django_mailer/admin.py index 2291afbc..754aab0a 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -1,15 +1,73 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ---------------------------------------------------------------------------- + from django.contrib import admin from django_mailer import models - +from django.conf.urls import patterns, url +from mail_utils import get_attachments, get_attachment +from pyzmail.parse import message_from_string +from django.shortcuts import render +from django.http import HttpResponse class Message(admin.ModelAdmin): list_display = ('from_address', 'to_address', 'subject', 'date_created') list_filter = ('date_created',) search_fields = ('to_address', 'subject', 'from_address', - 'encoded_message',) + 'encoded_message',) date_hierarchy = 'date_created' ordering = ('-date_created',) + def get_urls(self): + urls = super(Message, self).get_urls() + custom_urls = patterns('', + url(r'^mail/(?P\d+)/$', + self.admin_site.admin_view(self.detail_view), + name='mail_detail'), + url('^mail/attachment/(?P\d+)/(?P[0-9a-f]{32})/$', + self.admin_site.admin_view(self.download_view), + name="mail_download"), + url('^mail/html/(?P\d+)/$', + self.admin_site.admin_view(self.html_view), + name="mail_html")) + return custom_urls + urls + + def detail_view(self, request, pk): + instance = models.Message.objects.get(pk=pk) + payload_str = instance.encoded_message.encode('utf-8') + msg = message_from_string(payload_str) + context = {} + context['subject'] = msg.get_subject() + context['from'] = msg.get_address('from') + context['to'] = msg.get_addresses('to') + context['cc'] = msg.get_addresses('cc') + msg_text = msg.text_part.get_payload() if msg.text_part else None + msg_html = msg.html_part.get_payload() if msg.html_part else None + context['msg_html'] = msg_html + context['msg_text'] = msg_text + context['attachments'] = get_attachments(msg) + context['is_popup'] = True + context['object'] = instance + return render(request, 'django_mailer/message_detail.html', context) + + def download_view(self, request, pk, firma): + payload_str = models.Message.objects.get( + pk=pk).encoded_message.encode('utf-8') + msg = message_from_string(payload_str) + arx = get_attachment(msg, key=firma) + response = HttpResponse(mimetype=arx.tipus) + response['Content-Disposition'] = 'filename=' + arx.filename + response.write(arx.payload) + return response + + def html_view(self, request, pk): + msg = models.Message.objects.get(pk=pk) + payload_str = msg.encoded_message.encode('utf-8') + msg = message_from_string(payload_str) + msg_html = msg.html_part.get_payload() if msg.html_part else None + context = {} + context['msg_html'] = msg_html + return render(request, 'django_mailer/html_detail.html', context) class MessageRelatedModelAdmin(admin.ModelAdmin): list_select_related = True diff --git a/demo/demo/mail_utils.py b/django_mailer/mail_utils.py similarity index 100% rename from demo/demo/mail_utils.py rename to django_mailer/mail_utils.py diff --git a/django_mailer/templates/admin/django_mailer/change_form.html b/django_mailer/templates/admin/django_mailer/change_form.html new file mode 100644 index 00000000..0d213f5e --- /dev/null +++ b/django_mailer/templates/admin/django_mailer/change_form.html @@ -0,0 +1,4 @@ +{% extends "admin/change_form.html" %} +{% load i18n admin_urls %} +{% block object-tools-items %}
  • show{{block.super}}
  • {% endblock %} + diff --git a/demo/demo/templates/django_mailer/html_detail.html b/django_mailer/templates/django_mailer/html_detail.html similarity index 100% rename from demo/demo/templates/django_mailer/html_detail.html rename to django_mailer/templates/django_mailer/html_detail.html diff --git a/django_mailer/templates/django_mailer/message_detail.html b/django_mailer/templates/django_mailer/message_detail.html new file mode 100644 index 00000000..0689b105 --- /dev/null +++ b/django_mailer/templates/django_mailer/message_detail.html @@ -0,0 +1,43 @@ +{% extends "admin/change_form.html" %} +{% block breadcrumbs %}{% endblock breadcrumbs %} +{% block content_title %} +

    Message {{object.id}}

    +{% endblock content_title %} +{% block content %} +
    + {{subject}} +
    +
    + <{{from.0}}>{{from.1}}
    +
    +
    + {% for x in to %}<{{x.0}}>{{x.1}} {% endfor %}
    +
    +
    + {% for x in cc %}<{{x.0}}>{{x.1}} {% endfor %}
    +
    + + {% if msg_html %} +
    +

    HTML

    + +
    + {% endif %} + {% if msg_text %} +
    +

    Text

    + {{msg_text|linebreaks}} +
    + {% endif %} + {% if attachments %} +
    +

    Attachments

    + {% for file in attachments %} + + {% endfor %} +
    + {% endif %} +{% endblock content %} diff --git a/demo/demo/templates/django_mailer/message_list.html b/django_mailer/templates/django_mailer/message_list.html similarity index 100% rename from demo/demo/templates/django_mailer/message_list.html rename to django_mailer/templates/django_mailer/message_list.html From 838cf4b11e11b2429c4430bb80b8117f7341e57d Mon Sep 17 00:00:00 2001 From: aaloy Date: Fri, 15 Mar 2013 00:59:33 +0100 Subject: [PATCH 21/42] added requirements --- demo/demo.sqlite | Bin 46080 -> 46080 bytes requirements.txt | 1 + 2 files changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/demo/demo.sqlite b/demo/demo.sqlite index 6a3d85a962179f0f7139eaf16d3544a35170457c..1fb8bbaaed0eb26ca9c96810d90aedb429a42d34 100644 GIT binary patch delta 239 zcmZp8!PM}AX@WGP??f4AM&FGI73O@#R;K1w1{Qjzh88BKMw_3R%L(u>Gm0`WvorAl zsZ$dh_e?(O>8-}b>f2Q9Xq;%4W|*34Y+z|@WNd6;mXc~{X_07VX=s^hU}~OXFGUqUFW8Sv0F@{-Hh1r*pkeO_oC;Ar$ s0QD|rV1CGah Date: Fri, 15 Mar 2013 01:00:10 +0100 Subject: [PATCH 22/42] Upgraded version number --- django_mailer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_mailer/__init__.py b/django_mailer/__init__.py index 3cf1a0a3..e1cbd8f1 100644 --- a/django_mailer/__init__.py +++ b/django_mailer/__init__.py @@ -4,7 +4,7 @@ import logging -VERSION = (1, 2, 2) +VERSION = (1, 3, 0) logger = logging.getLogger('django_mailer') logger.setLevel(logging.DEBUG) From cdc9427422fe8aeb74de2092938959adbdd76d6e Mon Sep 17 00:00:00 2001 From: aaloy Date: Tue, 19 Mar 2013 20:46:26 +0100 Subject: [PATCH 23/42] Added dependencies --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2b9f48b2..8f9868de 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ from setuptools import setup from django_mailer import get_version - setup( name='django-mailer-2', version=get_version(), @@ -15,6 +14,7 @@ author='Antoni Aloy', author_email='antoni.aloy@gmail.com', url='http://github.com/APSL/django-mailer-2', + install_requires = ["pyzmail", ], packages=[ 'django_mailer', 'django_mailer.management', From a05e53c1bed3c4c03c7a31ae15c22f91ad7ebcc1 Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Mon, 13 May 2013 16:01:02 +0200 Subject: [PATCH 24/42] django 1.5 compatibility --- django_mailer/templates/admin/django_mailer/change_form.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_mailer/templates/admin/django_mailer/change_form.html b/django_mailer/templates/admin/django_mailer/change_form.html index 0d213f5e..6c6fcf1b 100644 --- a/django_mailer/templates/admin/django_mailer/change_form.html +++ b/django_mailer/templates/admin/django_mailer/change_form.html @@ -1,4 +1,5 @@ {% extends "admin/change_form.html" %} {% load i18n admin_urls %} -{% block object-tools-items %}
  • show{{block.super}}
  • {% endblock %} +{% load url from future %} +{% block object-tools-items %}
  • show{{block.super}}
  • {% endblock %} From a7eb344564cea4ccb9afd34834dc70c0c677bf3c Mon Sep 17 00:00:00 2001 From: aaloy Date: Wed, 15 May 2013 19:17:29 +0200 Subject: [PATCH 25/42] added from future in templates --- django_mailer/templates/django_mailer/message_detail.html | 5 +++-- django_mailer/templates/django_mailer/message_list.html | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/django_mailer/templates/django_mailer/message_detail.html b/django_mailer/templates/django_mailer/message_detail.html index 0689b105..c3adac48 100644 --- a/django_mailer/templates/django_mailer/message_detail.html +++ b/django_mailer/templates/django_mailer/message_detail.html @@ -1,4 +1,5 @@ {% extends "admin/change_form.html" %} +{% load url from future %} {% block breadcrumbs %}{% endblock breadcrumbs %} {% block content_title %}

    Message {{object.id}}

    @@ -20,7 +21,7 @@

    Message {{object.id}}

    {% if msg_html %}

    HTML

    -
    {% endif %} @@ -35,7 +36,7 @@

    Text

    Attachments

    {% for file in attachments %} {% endfor %} diff --git a/django_mailer/templates/django_mailer/message_list.html b/django_mailer/templates/django_mailer/message_list.html index a8983045..af0a3613 100644 --- a/django_mailer/templates/django_mailer/message_list.html +++ b/django_mailer/templates/django_mailer/message_list.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load url from future %} {% block content %}

    Message List

    idFromToSubjectCreated
    {{mail.pk}} + {{mail.pk}} {{mail.from_address}} {{mail.to_address}}{{mail.subject}} + {{mail.subject}} {{mail.date_created}}
    @@ -16,14 +17,14 @@

    Message List

    {% if is_paginated %} {% if page_obj.has_previous %} - + Previous {% endif %} Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. {% if page_obj.has_next %} - + Next {% endif %} {% endif %} From b8fc7dc3ae24202b055daad138008595afaa58f3 Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Tue, 4 Jun 2013 15:05:53 +0200 Subject: [PATCH 26/42] corrijo admin para funciona con 1.3 --- django_mailer/admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/django_mailer/admin.py b/django_mailer/admin.py index 754aab0a..ca7b000a 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -4,7 +4,10 @@ from django.contrib import admin from django_mailer import models -from django.conf.urls import patterns, url +try: + from django.conf.urls import patterns, url +except ImportError: + from django.conf.urls.defaults import * from mail_utils import get_attachments, get_attachment from pyzmail.parse import message_from_string from django.shortcuts import render From 49853e9ce1e9ddeccffc23c2a83aa824eeb18396 Mon Sep 17 00:00:00 2001 From: Antoni Aloy Date: Sat, 1 Feb 2014 12:13:30 +0100 Subject: [PATCH 27/42] Added a management commant to allow monitoring --- demo/demo.sqlite | Bin 46080 -> 48128 bytes django_mailer/__init__.py | 2 +- .../management/commands/send_mail.py | 2 +- .../management/commands/status_mail.py | 41 ++++++++++++++++++ docs/usage.rst | 4 ++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 django_mailer/management/commands/status_mail.py diff --git a/demo/demo.sqlite b/demo/demo.sqlite index 1fb8bbaaed0eb26ca9c96810d90aedb429a42d34..48f6ab535a8cb54b2a817dbaf5413189494b1d17 100644 GIT binary patch delta 311 zcmZp8!PM}BX@WFs5Ca2){zL_PAVc?&`o@%Ob8!=010!7nLj?m%D^p`DBXd1NGYb<_ z^Ucr9r3Cm`f$ABVe*;Mdedgbr1$o{xi!$i3b1~m$U}1J=V4ldF!@P~zo!Nb3qbakh z3bQX`s=0BJp|M$NqH&_RfvI7ViDj~Bs$rUWVzRkGiit%k$L1XW83BAO%qym)b}z$6hCZe~VN24;39J|Hz&P$iXd t$;8G5n?*feGjZ{PoyrcB-Prh-Nu#lhgVndG+L4fZChL3ef`kF4wEzH-O-29! delta 140 zcmZqp!PM}AX@WGXF9QRE?nDK9AVc?&;>MJ0b8%x`17lr769pq69qHo9*X-TInIfQ$Jy12c0D1M@`Y9OiAz r+cq}FFmKNBpAo>r%)FR^`62Tm=Eck{n*~|QnI~r~R^Gg7ahL!A;R+^{ diff --git a/django_mailer/__init__.py b/django_mailer/__init__.py index e1cbd8f1..151b2c36 100644 --- a/django_mailer/__init__.py +++ b/django_mailer/__init__.py @@ -4,7 +4,7 @@ import logging -VERSION = (1, 3, 0) +VERSION = (1, 3, 1) logger = logging.getLogger('django_mailer') logger.setLevel(logging.DEBUG) diff --git a/django_mailer/management/commands/send_mail.py b/django_mailer/management/commands/send_mail.py index d609360a..0faebb6d 100644 --- a/django_mailer/management/commands/send_mail.py +++ b/django_mailer/management/commands/send_mail.py @@ -30,7 +30,7 @@ def handle_noargs(self, verbosity, block_size, count, **options): # If this is just a count request the just calculate, report and exit. if count: queued = models.QueuedMessage.objects.non_deferred().count() - deferred = models.QueuedMessage.objects.non_deferred().count() + deferred = models.QueuedMessage.objects.deferred().count() sys.stdout.write('%s queued message%s (and %s deferred message%s).' '\n' % (queued, queued != 1 and 's' or '', deferred, deferred != 1 and 's' or '')) diff --git a/django_mailer/management/commands/status_mail.py b/django_mailer/management/commands/status_mail.py new file mode 100644 index 00000000..49dd3fd1 --- /dev/null +++ b/django_mailer/management/commands/status_mail.py @@ -0,0 +1,41 @@ +""" +This command returns the status of the queue in the format: + queued/deferred/seconds + +where: + queued is the number of queued messages we have actually + deferred number of deferred messages we have actually + seconds age in seconds of the oldest messages + +Example: + + 2/0/10 + +means we have 2 queued messages, 0 defered messaged and than the oldest message +in the queue is just 2 seconds old. +""" + +from django.core.management.base import NoArgsCommand +from django_mailer.models import QueuedMessage +import sys +import datetime +try: + from django.core.mail import get_connection + EMAIL_BACKEND_SUPPORT = True +except ImportError: + # Django version < 1.2 + EMAIL_BACKEND_SUPPORT = False + + +class Command(NoArgsCommand): + help = "Returns a strig with the queue status as queued/deferred/seconds" + + def handle_noargs(self, *args, **kwargs): + # If this is just a count request the just calculate, report and exit. + queued = QueuedMessage.objects.non_deferred().count() + deferred = QueuedMessage.objects.deferred().count() + oldest = QueuedMessage.objects.non_deferred().order_by('date_queued')[0] + queue_time = datetime.datetime.now() - oldest.date_queued.replace(tzinfo=None) + seconds = (datetime.datetime.now() - oldest.date_queued.replace(tzinfo=None)).seconds + sys.stdout.write('%s/%s/%s\n"' % (queued, deferred, seconds)) + sys.exit() diff --git a/docs/usage.rst b/docs/usage.rst index ce17640f..e69d278c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -76,6 +76,10 @@ you can run: * ``cleanup_mail`` will delete mails created before an X number of days (defaults to 90). + * ``status_mail`` the intent of this commant is to allow systems as nagios to + be able to ask the queue about its status. It returns as string with than + can be parses as (?P\d+)/(?P\d+)/(?P\d+) + You may want to set these up via cron to run regularly:: * * * * * (cd $PROJECT; python manage.py send_mail >> $PROJECT/cron_mail.log 2>&1) From f9cfc34e4796e45636872679e5964caf7656d5cf Mon Sep 17 00:00:00 2001 From: aaloy Date: Sat, 1 Feb 2014 17:37:36 +0100 Subject: [PATCH 28/42] Added unit test to theck the status_mail command and see it honours the re match --- django_mailer/testapp/tests/backend.py | 2 +- django_mailer/testapp/tests/commands.py | 36 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/django_mailer/testapp/tests/backend.py b/django_mailer/testapp/tests/backend.py index 88b8b706..c747ee5c 100644 --- a/django_mailer/testapp/tests/backend.py +++ b/django_mailer/testapp/tests/backend.py @@ -104,7 +104,7 @@ def testUnicodePriorityMessage(self): """ from django.core.management import call_command msg = mail.EmailMessage(subject=u'á subject', body='body', - from_email=u'juan.lópez@abc.com', to=['únñac@abc.com'], + from_email=u'juan.lópez@abc.com', to=[u'únñac@abc.com'], headers={'X-Mail-Queue-Priority': 'now'}) self.send_message(msg) queued_messages = models.QueuedMessage.objects.all() diff --git a/django_mailer/testapp/tests/commands.py b/django_mailer/testapp/tests/commands.py index eb62d2e1..f12bd4df 100644 --- a/django_mailer/testapp/tests/commands.py +++ b/django_mailer/testapp/tests/commands.py @@ -64,6 +64,42 @@ def test_retry_deferred(self): call_command('retry_deferred', verbosity='0', max_retries=3) self.assertEqual(non_deferred_messages.count(), 3) + def test_status_mail(self): + """ + The ``status_mail`` should return a string that matches: + (?P\d+)/(?P\d+)/(?P\d+) + """ + import re + import sys + from cStringIO import StringIO + import time + + re_string = r"(?P\d+)/(?P\d+)/(?P\d+)" + p = re.compile(re_string) + + self.queue_message(subject="test") + self.queue_message(subject='deferred') + self.queue_message(subject='deferred 2') + self.queue_message(subject='deferred 3') + models.QueuedMessage.objects\ + .filter(message__subject__startswith='deferred')\ + .update(deferred=datetime.datetime.now()) + non_deferred_messages = models.QueuedMessage.objects.non_deferred() + time.sleep(1) + # Deferred messages are returned to the queue (nothing is sent). + out, sys.stdout = sys.stdout, StringIO() + with self.assertRaises(SystemExit) as cm: + call_command('status_mail', verbosity='0') + sys.stdout.seek(0) + result = sys.stdout.read() + m = p.match(result) + sys.stdout = out + self.assertTrue(m, "Output does not include expected r.e.") + v = m.groupdict() + self.assertTrue(v['queued'], "1") + self.assertEqual(v['deferred'], "3") + self.assertTrue(int(v['seconds'])>=1) + def test_cleanup_mail(self): """ The ``cleanup_mail`` command deletes mails older than a specified From 44acd316370e9038121b7c1077aa325750345925 Mon Sep 17 00:00:00 2001 From: aaloy Date: Sat, 1 Feb 2014 17:48:41 +0100 Subject: [PATCH 29/42] Modified to have a better look --- docs/usage.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index e69d278c..70f25533 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -60,10 +60,10 @@ or all managers as defined in the ``MANAGERS`` setting by calling:: mail_managers(subject, message_body) -Clear Queue With Command Extensions +Command Extensions =================================== -With mailer in your INSTALLED_APPS, there will be two new manage.py commands +With mailer in your INSTALLED_APPS, there will be four new manage.py commands you can run: * ``send_mail`` will clear the current message queue. If there are any @@ -78,7 +78,7 @@ you can run: * ``status_mail`` the intent of this commant is to allow systems as nagios to be able to ask the queue about its status. It returns as string with than - can be parses as (?P\d+)/(?P\d+)/(?P\d+) + can be parses as ``(?P\d+)/(?P\d+)/(?P\d+)`` You may want to set these up via cron to run regularly:: From cbf50a661548ce17e1236d9af196340e7e09dc85 Mon Sep 17 00:00:00 2001 From: aaloy Date: Tue, 11 Feb 2014 00:09:44 +0100 Subject: [PATCH 30/42] Modified the template so the show button is only displayed in the mail form. Added links to the message in the list views. --- demo/demo.sqlite | Bin 48128 -> 48128 bytes django_mailer/admin.py | 18 ++++++++++++++++-- .../admin/django_mailer/change_form.html | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/demo/demo.sqlite b/demo/demo.sqlite index 48f6ab535a8cb54b2a817dbaf5413189494b1d17..4fe660fd1481b4de038d6c4027b62b9a06115071 100644 GIT binary patch delta 622 zcmaixO-~bH5XX0)X|-ttHF{GFX?nmA;Cc3an{e1v`hsp-o0ffPOoZJn+wHD`u(Vwd zw1y8L>B)1iUP_H2;pW%Six)qD@jyIT5{V`pm}G9h%*^jU{d3emNADldjpK(|x-ova zC^i_3ak}Rex{4?9l{H;STa^y$tPp2&T?s2gu)P@K zRhp(5Ot>Y?ED=1v%8=kJF&i0w*_n zQ6%h#oFL*Jv7|kMMNYJdCk3Rvm%M<6x6kG$?ed3o0>LQ--{5p|H}(+fNdjNk|y$c+a)4BgSfJ%;<|#0!L%gyiIC)W0=} zxxXMmqc}_hpie%sgmd;#o gZqwFFzU9k17xPpduU`KAYCeO5#8ma|aQ5uyAK%`o_W%F@ delta 208 zcmZqp!PM}BX@WFk&_o$$#-NP}+UC&)K%kOWk(Zv5lwIW?k{GHIkz8fySCnmF;*#WN z;bWNSQDq!hnUm!iR#j1&7NT7c?i(9TnORhreHkb7?^f8H?LSjxF^d4p zVwMUGpu!dg=7-FOm=`m*FsE*83}c?`xmaWKgvBL15H(v^*ch4F8JO8OHvVOr%)a|4 E0D5*kM*si- diff --git a/django_mailer/admin.py b/django_mailer/admin.py index ca7b000a..1e4fb538 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -12,6 +12,8 @@ from pyzmail.parse import message_from_string from django.shortcuts import render from django.http import HttpResponse +from django.core.urlresolvers import reverse + class Message(admin.ModelAdmin): list_display = ('from_address', 'to_address', 'subject', 'date_created') @@ -98,7 +100,13 @@ def not_deferred(self, obj): not_deferred.boolean = True not_deferred.admin_order_field = 'deferred' - list_display = ('id', 'message', 'message__to_address', + def message_link(self, obj): + url = reverse('admin:mail_detail', args=(obj.message.id,)) + return """%s""" % (url, obj.message) + message_link.allow_tags = True + message_link.short_description = u'Message' + + list_display = ('id', 'message_link', 'message__to_address', 'message__from_address', 'message__subject', 'message__date_created', 'priority', 'not_deferred') @@ -108,8 +116,14 @@ class Blacklist(admin.ModelAdmin): class Log(MessageRelatedModelAdmin): + def message_link(self, obj): + url = reverse('admin:mail_detail', args=(obj.message.id,)) + return """show""" % url + message_link.allow_tags = True + message_link.short_description = u'Message' + list_display = ('id', 'result', 'message__to_address', 'message__subject', - 'date') + 'date', 'message_link') list_filter = ('result',) list_display_links = ('id', 'result') diff --git a/django_mailer/templates/admin/django_mailer/change_form.html b/django_mailer/templates/admin/django_mailer/change_form.html index 6c6fcf1b..86796b57 100644 --- a/django_mailer/templates/admin/django_mailer/change_form.html +++ b/django_mailer/templates/admin/django_mailer/change_form.html @@ -1,5 +1,5 @@ {% extends "admin/change_form.html" %} {% load i18n admin_urls %} {% load url from future %} -{% block object-tools-items %}
  • show{{block.super}}
  • {% endblock %} +{% block object-tools-items %}{% if opts.model_name = 'message'%}
  • show{% endif %}
  • {{block.super}}{% endblock %} From bedcc0a5bc5905d131fac1dea849b9fc5fe03c37 Mon Sep 17 00:00:00 2001 From: aaloy Date: Wed, 19 Feb 2014 00:00:35 +0100 Subject: [PATCH 31/42] Added a direct link in message list to show a message --- django_mailer/__init__.py | 2 +- django_mailer/admin.py | 8 +++++++- .../templates/admin/django_mailer/change_form.html | 5 ----- .../admin/django_mailer/message/change_form.html | 5 +++++ .../templates/django_mailer/message_list.html | 11 ++++++----- 5 files changed, 19 insertions(+), 12 deletions(-) delete mode 100644 django_mailer/templates/admin/django_mailer/change_form.html create mode 100644 django_mailer/templates/admin/django_mailer/message/change_form.html diff --git a/django_mailer/__init__.py b/django_mailer/__init__.py index 151b2c36..63b19328 100644 --- a/django_mailer/__init__.py +++ b/django_mailer/__init__.py @@ -4,7 +4,7 @@ import logging -VERSION = (1, 3, 1) +VERSION = (1, 3, 2) logger = logging.getLogger('django_mailer') logger.setLevel(logging.DEBUG) diff --git a/django_mailer/admin.py b/django_mailer/admin.py index 1e4fb538..e9f56d80 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -16,7 +16,13 @@ class Message(admin.ModelAdmin): - list_display = ('from_address', 'to_address', 'subject', 'date_created') + def message_link(self, obj): + url = reverse('admin:mail_detail', args=(obj.id,)) + return """show""" % url + message_link.allow_tags = True + message_link.short_description = u'Show' + + list_display = ('from_address', 'to_address', 'subject', 'date_created', 'message_link') list_filter = ('date_created',) search_fields = ('to_address', 'subject', 'from_address', 'encoded_message',) diff --git a/django_mailer/templates/admin/django_mailer/change_form.html b/django_mailer/templates/admin/django_mailer/change_form.html deleted file mode 100644 index 86796b57..00000000 --- a/django_mailer/templates/admin/django_mailer/change_form.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "admin/change_form.html" %} -{% load i18n admin_urls %} -{% load url from future %} -{% block object-tools-items %}{% if opts.model_name = 'message'%}
  • show{% endif %}
  • {{block.super}}{% endblock %} - diff --git a/django_mailer/templates/admin/django_mailer/message/change_form.html b/django_mailer/templates/admin/django_mailer/message/change_form.html new file mode 100644 index 00000000..653805c3 --- /dev/null +++ b/django_mailer/templates/admin/django_mailer/message/change_form.html @@ -0,0 +1,5 @@ +{% extends "admin/change_form.html" %} +{% load i18n admin_urls %} +{% load url from future %} +{% block object-tools-items %}
  • show
  • {{block.super}}{% endblock %} + diff --git a/django_mailer/templates/django_mailer/message_list.html b/django_mailer/templates/django_mailer/message_list.html index af0a3613..50f1749f 100644 --- a/django_mailer/templates/django_mailer/message_list.html +++ b/django_mailer/templates/django_mailer/message_list.html @@ -1,9 +1,10 @@ {% extends "base.html" %} {% load url from future %} +{% load i18n %} {% block content %} -

    Message List

    +

    {% trans "Message List" %}

    - + {% for mail in object_list %} @@ -18,14 +19,14 @@

    Message List

    {% if is_paginated %} {% if page_obj.has_previous %} - Previous + {% trans "Previous" %} {% endif %} - Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. + {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}. {% if page_obj.has_next %} - Next + {% trans "Next" %} {% endif %} {% endif %} From ed8cd889d028ac24192332149459c2423a62721c Mon Sep 17 00:00:00 2001 From: Cesc Date: Wed, 16 Apr 2014 12:42:46 +0200 Subject: [PATCH 32/42] Merge con master --- .../admin/django_mailer/message/change_form.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/django_mailer/templates/admin/django_mailer/message/change_form.html b/django_mailer/templates/admin/django_mailer/message/change_form.html index 653805c3..16959814 100644 --- a/django_mailer/templates/admin/django_mailer/message/change_form.html +++ b/django_mailer/templates/admin/django_mailer/message/change_form.html @@ -1,5 +1,20 @@ {% extends "admin/change_form.html" %} {% load i18n admin_urls %} {% load url from future %} +<<<<<<< HEAD:django_mailer/templates/admin/django_mailer/message/change_form.html {% block object-tools-items %}
  • show
  • {{block.super}}{% endblock %} +======= + +{% block object-tools-items %} +
  • + + + + Show + +{{block.super}} +
  • {% endblock %} +>>>>>>> e68d906... Compatibilidad con yawd admin:django_mailer/templates/admin/django_mailer/change_form.html From c6086c8be375bfb72f30ce28def999be9debe6c3 Mon Sep 17 00:00:00 2001 From: Cesc Date: Wed, 16 Apr 2014 17:13:04 +0200 Subject: [PATCH 33/42] =?UTF-8?q?Hab=C3=ADa=20un=20conflicto.=20Resulto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/admin/django_mailer/message/change_form.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/django_mailer/templates/admin/django_mailer/message/change_form.html b/django_mailer/templates/admin/django_mailer/message/change_form.html index 16959814..53a1ab75 100644 --- a/django_mailer/templates/admin/django_mailer/message/change_form.html +++ b/django_mailer/templates/admin/django_mailer/message/change_form.html @@ -1,9 +1,6 @@ {% extends "admin/change_form.html" %} {% load i18n admin_urls %} {% load url from future %} -<<<<<<< HEAD:django_mailer/templates/admin/django_mailer/message/change_form.html -{% block object-tools-items %}
  • show
  • {{block.super}}{% endblock %} -======= {% block object-tools-items %}
  • @@ -16,5 +13,4 @@ {{block.super}}
  • {% endblock %} ->>>>>>> e68d906... Compatibilidad con yawd admin:django_mailer/templates/admin/django_mailer/change_form.html From c330314eff85f690ec0630421b9cc9d7ba51947e Mon Sep 17 00:00:00 2001 From: Cesc Date: Tue, 22 Apr 2014 10:29:53 +0200 Subject: [PATCH 34/42] Activo fancybox para los enlaces de 'show' que se muestran en los listados --- django_mailer/admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django_mailer/admin.py b/django_mailer/admin.py index e9f56d80..3a5309fe 100644 --- a/django_mailer/admin.py +++ b/django_mailer/admin.py @@ -18,7 +18,7 @@ class Message(admin.ModelAdmin): def message_link(self, obj): url = reverse('admin:mail_detail', args=(obj.id,)) - return """show""" % url + return """show""" % url message_link.allow_tags = True message_link.short_description = u'Show' @@ -108,7 +108,7 @@ def not_deferred(self, obj): def message_link(self, obj): url = reverse('admin:mail_detail', args=(obj.message.id,)) - return """%s""" % (url, obj.message) + return """%s""" % (url, obj.message) message_link.allow_tags = True message_link.short_description = u'Message' @@ -124,7 +124,7 @@ class Blacklist(admin.ModelAdmin): class Log(MessageRelatedModelAdmin): def message_link(self, obj): url = reverse('admin:mail_detail', args=(obj.message.id,)) - return """show""" % url + return """show""" % url message_link.allow_tags = True message_link.short_description = u'Message' From 184cefb099225d35031a829968a72635b88f557a Mon Sep 17 00:00:00 2001 From: Daniel Sastre Date: Fri, 24 Oct 2014 12:04:02 +0200 Subject: [PATCH 35/42] Add templates dir in MANIFEST.in Fixes TemplateDoesNotExist when trying to see the email in the admin. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index f68c3674..645fcc9c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include LICENSE recursive-include docs * +recursive-include django_mailer/templates * From e5e56d91dfbdc822246ae706af7c5f52140d5d4d Mon Sep 17 00:00:00 2001 From: Daniel Sastre Date: Fri, 24 Oct 2014 12:21:10 +0200 Subject: [PATCH 36/42] Added include_package_data=True to setup.py Fixes TemplateDoesNotExist when trying to see the email in the admin. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 8f9868de..01d47268 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ 'django_mailer.management', 'django_mailer.management.commands', ], + include_package_data=True, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', From 1837c9a04ed03c06bc16942b9aff9756c9e8a731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20T=C3=B6rnqvist?= Date: Mon, 17 Nov 2014 14:19:05 +0200 Subject: [PATCH 37/42] The ability to run tests on django 1.7 --- runtests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/runtests.py b/runtests.py index 0522d0de..eab2ce8b 100755 --- a/runtests.py +++ b/runtests.py @@ -9,6 +9,13 @@ sys.path.insert(0, parent) os.environ['DJANGO_SETTINGS_MODULE'] = 'django_mailer.testapp.settings' +# Django 1.7 and later requires a separate .setup() call +import django +try: + django.setup() +except AttributeError: + pass + from django.test.simple import DjangoTestSuiteRunner From c2594156af9c428bf22a1a42c0c31d91a5b341c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20T=C3=B6rnqvist?= Date: Mon, 17 Nov 2014 14:40:55 +0200 Subject: [PATCH 38/42] Use timezone.now() where available --- django_mailer/management/commands/status_mail.py | 11 ++++++++--- django_mailer/managers.py | 9 +++++++-- django_mailer/models.py | 16 ++++++++++------ django_mailer/testapp/tests/commands.py | 16 ++++++++++------ 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/django_mailer/management/commands/status_mail.py b/django_mailer/management/commands/status_mail.py index 49dd3fd1..799c7ee8 100644 --- a/django_mailer/management/commands/status_mail.py +++ b/django_mailer/management/commands/status_mail.py @@ -18,7 +18,12 @@ from django.core.management.base import NoArgsCommand from django_mailer.models import QueuedMessage import sys -import datetime +try: + from django.utils.timezone import now +except ImportError: + import datetime + now = datetime.datetime.now + try: from django.core.mail import get_connection EMAIL_BACKEND_SUPPORT = True @@ -35,7 +40,7 @@ def handle_noargs(self, *args, **kwargs): queued = QueuedMessage.objects.non_deferred().count() deferred = QueuedMessage.objects.deferred().count() oldest = QueuedMessage.objects.non_deferred().order_by('date_queued')[0] - queue_time = datetime.datetime.now() - oldest.date_queued.replace(tzinfo=None) - seconds = (datetime.datetime.now() - oldest.date_queued.replace(tzinfo=None)).seconds + queue_time = now() - oldest.date_queued.replace(tzinfo=None) + seconds = (now() - oldest.date_queued.replace(tzinfo=None)).seconds sys.stdout.write('%s/%s/%s\n"' % (queued, deferred, seconds)) sys.exit() diff --git a/django_mailer/managers.py b/django_mailer/managers.py index 9778e4da..93f6dda7 100644 --- a/django_mailer/managers.py +++ b/django_mailer/managers.py @@ -2,7 +2,12 @@ # encoding: utf-8 # ---------------------------------------------------------------------------- -import datetime +try: + from django.utils.timezone import now +except ImportError: + import datetime + now = datetime.datetime.now + from django.db import models from django_mailer import constants @@ -18,7 +23,7 @@ def exclude_future(self): Exclude future time-delayed messages. """ - return self.exclude(date_queued__gt=datetime.datetime.now) + return self.exclude(date_queued__gt=now) def high_priority(self): """ diff --git a/django_mailer/models.py b/django_mailer/models.py index dbf1c83a..e2a36e72 100644 --- a/django_mailer/models.py +++ b/django_mailer/models.py @@ -4,7 +4,11 @@ from django.db import models from django_mailer import constants, managers -import datetime +try: + from django.utils.timezone import now +except ImportError: + import datetime + now = datetime.datetime.now PRIORITIES = ( @@ -35,7 +39,7 @@ class Message(models.Model): subject = models.CharField(max_length=255) encoded_message = models.TextField() - date_created = models.DateTimeField(default=datetime.datetime.now) + date_created = models.DateTimeField(default=now) class Meta: ordering = ('date_created',) @@ -57,7 +61,7 @@ class QueuedMessage(models.Model): default=constants.PRIORITY_NORMAL) deferred = models.DateTimeField(null=True, blank=True) retries = models.PositiveIntegerField(default=0) - date_queued = models.DateTimeField(default=datetime.datetime.now) + date_queued = models.DateTimeField(default=now) objects = managers.QueueManager() @@ -65,7 +69,7 @@ class Meta: ordering = ('priority', 'date_queued') def defer(self): - self.deferred = datetime.datetime.now() + self.deferred = now() self.save() @@ -78,7 +82,7 @@ class Blacklist(models.Model): """ email = models.EmailField(max_length=200) - date_added = models.DateTimeField(default=datetime.datetime.now) + date_added = models.DateTimeField(default=now) class Meta: ordering = ('-date_added',) @@ -93,7 +97,7 @@ class Log(models.Model): """ message = models.ForeignKey(Message, editable=False) result = models.PositiveSmallIntegerField(choices=RESULT_CODES) - date = models.DateTimeField(default=datetime.datetime.now) + date = models.DateTimeField(default=now) log_message = models.TextField() class Meta: diff --git a/django_mailer/testapp/tests/commands.py b/django_mailer/testapp/tests/commands.py index f12bd4df..ada2e2b9 100644 --- a/django_mailer/testapp/tests/commands.py +++ b/django_mailer/testapp/tests/commands.py @@ -3,6 +3,10 @@ from django_mailer import models from base import MailerTestCase import datetime +try: + from django.utils.timezone import now +except ImportError: + now = datetime.datetime.now class TestCommands(MailerTestCase): @@ -24,7 +28,7 @@ def test_send_mail(self): self.queue_message(subject='deferred') models.QueuedMessage.objects\ .filter(message__subject__startswith='deferred')\ - .update(deferred=datetime.datetime.now()) + .update(deferred=now()) queued_messages = models.QueuedMessage.objects.all() self.assertEqual(queued_messages.count(), 3) self.assertEqual(len(mail.outbox), 0) @@ -43,7 +47,7 @@ def test_retry_deferred(self): self.queue_message(subject='deferred 3') models.QueuedMessage.objects\ .filter(message__subject__startswith='deferred')\ - .update(deferred=datetime.datetime.now()) + .update(deferred=now()) non_deferred_messages = models.QueuedMessage.objects.non_deferred() # Deferred messages are returned to the queue (nothing is sent). self.assertEqual(non_deferred_messages.count(), 1) @@ -53,13 +57,13 @@ def test_retry_deferred(self): # Check the --max-retries logic. models.QueuedMessage.objects\ .filter(message__subject='deferred')\ - .update(deferred=datetime.datetime.now(), retries=2) + .update(deferred=now(), retries=2) models.QueuedMessage.objects\ .filter(message__subject='deferred 2')\ - .update(deferred=datetime.datetime.now(), retries=3) + .update(deferred=now(), retries=3) models.QueuedMessage.objects\ .filter(message__subject='deferred 3')\ - .update(deferred=datetime.datetime.now(), retries=4) + .update(deferred=now(), retries=4) self.assertEqual(non_deferred_messages.count(), 1) call_command('retry_deferred', verbosity='0', max_retries=3) self.assertEqual(non_deferred_messages.count(), 3) @@ -83,7 +87,7 @@ def test_status_mail(self): self.queue_message(subject='deferred 3') models.QueuedMessage.objects\ .filter(message__subject__startswith='deferred')\ - .update(deferred=datetime.datetime.now()) + .update(deferred=now()) non_deferred_messages = models.QueuedMessage.objects.non_deferred() time.sleep(1) # Deferred messages are returned to the queue (nothing is sent). From f494cb8be5f86539b6e0ce3940825d68925dc58c Mon Sep 17 00:00:00 2001 From: Cesc Date: Wed, 19 Nov 2014 09:46:30 +0100 Subject: [PATCH 39/42] Ampliamos ancho de iframe --- django_mailer/templates/django_mailer/message_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_mailer/templates/django_mailer/message_detail.html b/django_mailer/templates/django_mailer/message_detail.html index c3adac48..7ccd87e2 100644 --- a/django_mailer/templates/django_mailer/message_detail.html +++ b/django_mailer/templates/django_mailer/message_detail.html @@ -21,7 +21,7 @@

    Message {{object.id}}

    {% if msg_html %}

    HTML

    -
    {% endif %} From b72826f3cb63dfc52fe9242afc0cdfd70de8b8f7 Mon Sep 17 00:00:00 2001 From: Bernardo Cabezas Serra Date: Mon, 13 Jul 2015 14:25:56 +0200 Subject: [PATCH 40/42] Remove forcing DEBUG logging devel --- django_mailer/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django_mailer/__init__.py b/django_mailer/__init__.py index 63b19328..79cb4409 100644 --- a/django_mailer/__init__.py +++ b/django_mailer/__init__.py @@ -7,7 +7,6 @@ VERSION = (1, 3, 2) logger = logging.getLogger('django_mailer') -logger.setLevel(logging.DEBUG) def get_version(): From 0650d30e8f1a72f009d1cf98505c723a6becd653 Mon Sep 17 00:00:00 2001 From: mikkki Date: Thu, 12 Nov 2015 16:14:32 -0700 Subject: [PATCH 41/42] Update __init__.py I was getting the following error after running './manage.py send_mail': Traceback (most recent call last): File "./manage.py", line 10, in execute_from_command_line(sys.argv) File "/env/lib/python2.7/site-packages/django/core/management/__init__.py", line 351, in execute_from_command_line utility.execute() File "/env/lib/python2.7/site-packages/django/core/management/__init__.py", line 343, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/env/lib/python2.7/site-packages/django/core/management/base.py", line 394, in run_from_argv self.execute(*args, **cmd_options) File "/env/lib/python2.7/site-packages/django/core/management/base.py", line 445, in execute output = self.handle(*args, **options) File "/env/lib/python2.7/site-packages/django/core/management/base.py", line 661, in handle return self.handle_noargs(**options) File "/env/src/django-mailer/django_mailer/management/commands/send_mail.py", line 41, in handle_noargs handler = create_handler(verbosity) File "/env/src/django-mailer/django_mailer/management/commands/__init__.py", line 13, in create_handler handler.setLevel(LOGGING_LEVEL[verbosity]) KeyError: 1 ======================== Changing handler.setLevel(LOGGING_LEVEL[verbosity]) to handler.setLevel(LOGGING_LEVEL[str(verbosity)]) fixes it. --- django_mailer/management/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_mailer/management/commands/__init__.py b/django_mailer/management/commands/__init__.py index 91440c71..0e27c87f 100644 --- a/django_mailer/management/commands/__init__.py +++ b/django_mailer/management/commands/__init__.py @@ -10,7 +10,7 @@ def create_handler(verbosity, message='%(message)s'): level output depends on the verbosity level). """ handler = logging.StreamHandler() - handler.setLevel(LOGGING_LEVEL[verbosity]) + handler.setLevel(LOGGING_LEVEL[str(verbosity)]) formatter = logging.Formatter(message) handler.setFormatter(formatter) return handler From 594b3fb1597e28b22e135ac9bc751cde500664ce Mon Sep 17 00:00:00 2001 From: Daniel Sastre Date: Fri, 13 Nov 2015 13:35:32 +0100 Subject: [PATCH 42/42] Add not maintained warning --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index 79916d1d..719d4b10 100644 --- a/README +++ b/README @@ -1,3 +1,5 @@ +WARNING: This fork is no longer maintained, use https://github.com/APSL/django-yubin. + django-mailer-2 by APSL is a Chris Beaven form from a fork of James Tauber's django-mailer.
    idFromToSubjectCreated
    {% trans "id" %}{% trans "From" %}{% trans "To" %}{% trans "Subject" %}{% trans "Created" %}
    {{mail.pk}}