diff --git a/postmark/__init__.py b/postmark/__init__.py index 8b72993..49ad45d 100755 --- a/postmark/__init__.py +++ b/postmark/__init__.py @@ -1,4 +1,4 @@ -VERSION = (0, 1, 6, "final", 0) +VERSION = (0, 2, 1, "final", 0) def get_version(): diff --git a/postmark/migrations/0001_initial.py b/postmark/migrations/0001_initial.py index cd990e1..24c11ef 100644 --- a/postmark/migrations/0001_initial.py +++ b/postmark/migrations/0001_initial.py @@ -1,86 +1,68 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models +# -*- coding: utf-8 -*- +from __future__ import unicode_literals -class Migration(SchemaMigration): +from django.db import models, migrations - def forwards(self, orm): - - # Adding model 'EmailMessage' - db.create_table('postmark_emailmessage', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('message_id', self.gf('django.db.models.fields.CharField')(max_length=40)), - ('submitted_at', self.gf('django.db.models.fields.DateTimeField')()), - ('status', self.gf('django.db.models.fields.CharField')(max_length=150)), - ('to', self.gf('django.db.models.fields.CharField')(max_length=150)), - ('to_type', self.gf('django.db.models.fields.CharField')(max_length=3)), - ('sender', self.gf('django.db.models.fields.CharField')(max_length=150)), - ('reply_to', self.gf('django.db.models.fields.CharField')(max_length=150)), - ('subject', self.gf('django.db.models.fields.CharField')(max_length=150)), - ('tag', self.gf('django.db.models.fields.CharField')(max_length=25)), - ('text_body', self.gf('django.db.models.fields.TextField')()), - ('html_body', self.gf('django.db.models.fields.TextField')()), - ('headers', self.gf('django.db.models.fields.TextField')()), - ('attachments', self.gf('django.db.models.fields.TextField')()), - )) - db.send_create_signal('postmark', ['EmailMessage']) - # Adding model 'EmailBounce' - db.create_table('postmark_emailbounce', ( - ('id', self.gf('django.db.models.fields.PositiveIntegerField')(primary_key=True)), - ('message', self.gf('django.db.models.fields.related.ForeignKey')(related_name='bounces', to=orm['postmark.EmailMessage'])), - ('inactive', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('can_activate', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('type', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('description', self.gf('django.db.models.fields.TextField')()), - ('details', self.gf('django.db.models.fields.TextField')()), - ('bounced_at', self.gf('django.db.models.fields.DateTimeField')()), - ('_order', self.gf('django.db.models.fields.IntegerField')(default=0)), - )) - db.send_create_signal('postmark', ['EmailBounce']) +class Migration(migrations.Migration): + dependencies = [ + ] - def backwards(self, orm): - - # Deleting model 'EmailMessage' - db.delete_table('postmark_emailmessage') - - # Deleting model 'EmailBounce' - db.delete_table('postmark_emailbounce') - - - models = { - 'postmark.emailbounce': { - 'Meta': {'ordering': "('_order',)", 'object_name': 'EmailBounce'}, - '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), - 'bounced_at': ('django.db.models.fields.DateTimeField', [], {}), - 'can_activate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'details': ('django.db.models.fields.TextField', [], {}), - 'id': ('django.db.models.fields.PositiveIntegerField', [], {'primary_key': 'True'}), - 'inactive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'message': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bounces'", 'to': "orm['postmark.EmailMessage']"}), - 'type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'postmark.emailmessage': { - 'Meta': {'ordering': "['-submitted_at']", 'object_name': 'EmailMessage'}, - 'attachments': ('django.db.models.fields.TextField', [], {}), - 'headers': ('django.db.models.fields.TextField', [], {}), - 'html_body': ('django.db.models.fields.TextField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '40'}), - 'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '150'}), - 'sender': ('django.db.models.fields.CharField', [], {'max_length': '150'}), - 'status': ('django.db.models.fields.CharField', [], {'max_length': '150'}), - 'subject': ('django.db.models.fields.CharField', [], {'max_length': '150'}), - 'submitted_at': ('django.db.models.fields.DateTimeField', [], {}), - 'tag': ('django.db.models.fields.CharField', [], {'max_length': '25'}), - 'text_body': ('django.db.models.fields.TextField', [], {}), - 'to': ('django.db.models.fields.CharField', [], {'max_length': '150'}), - 'to_type': ('django.db.models.fields.CharField', [], {'max_length': '3'}) - } - } - - complete_apps = ['postmark'] + operations = [ + migrations.CreateModel( + name='EmailBounce', + fields=[ + ('id', models.PositiveIntegerField(serialize=False, primary_key=True)), + ('inactive', models.BooleanField(verbose_name='Inactive')), + ('can_activate', models.BooleanField(verbose_name='Can Activate')), + ('type', models.CharField(max_length=100, verbose_name='Type', choices=[(b'HardBounce', 'Hard Bounce'), (b'Transient', 'Transient'), (b'Unsubscribe', 'Unsubscribe'), (b'Subscribe', 'Subscribe'), (b'AutoResponder', 'Auto Responder'), (b'AddressChange', 'Address Change'), (b'DnsError', 'DNS Error'), (b'SpamNotification', 'Spam Notification'), (b'OpenRelayTest', 'Open Relay Test'), (b'Unknown', 'Unknown'), (b'SoftBounce', 'Soft Bounce'), (b'VirusNotification', 'Virus Notification'), (b'ChallengeVerification', 'Challenge Verification'), (b'BadEmailAddress', 'Bad Email Address'), (b'SpamComplaint', 'Spam Complaint'), (b'ManuallyDeactivated', 'Manually Deactivated'), (b'Unconfirmed', 'Unconfirmed'), (b'Blocked', 'Blocked')])), + ('description', models.TextField(verbose_name='Description')), + ('details', models.TextField(verbose_name='Details')), + ('bounced_at', models.DateTimeField(verbose_name='Bounced At')), + ], + options={ + 'ordering': ['-bounced_at'], + 'get_latest_by': 'bounced_at', + 'verbose_name': 'email bounce', + 'verbose_name_plural': 'email bounces', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='EmailMessage', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('message_id', models.CharField(max_length=40, verbose_name='Message ID')), + ('submitted_at', models.DateTimeField(verbose_name='Submitted At')), + ('status', models.CharField(max_length=150, verbose_name='Status')), + ('to', models.CharField(max_length=150, verbose_name='To')), + ('to_type', models.CharField(max_length=3, verbose_name='Type', choices=[(b'to', 'Recipient'), (b'cc', 'Carbon Copy'), (b'bcc', 'Blind Carbon Copy')])), + ('sender', models.CharField(max_length=150, verbose_name='Sender')), + ('reply_to', models.CharField(max_length=150, verbose_name='Reply To')), + ('subject', models.CharField(max_length=150, verbose_name='Subject')), + ('tag', models.CharField(max_length=150, verbose_name='Tag')), + ('text_body', models.TextField(verbose_name='Text Body')), + ('html_body', models.TextField(verbose_name='HTML Body')), + ('headers', models.TextField(verbose_name='Headers')), + ('attachments', models.TextField(verbose_name='Attachments')), + ], + options={ + 'ordering': ['-submitted_at'], + 'get_latest_by': 'submitted_at', + 'verbose_name': 'email message', + 'verbose_name_plural': 'email messages', + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='emailbounce', + name='message', + field=models.ForeignKey(related_name='bounces', verbose_name='Message', to='postmark.EmailMessage'), + preserve_default=True, + ), + migrations.AlterOrderWithRespectTo( + name='emailbounce', + order_with_respect_to='message', + ), + ] diff --git a/postmark/migrations/0002_auto_20141028_2212.py b/postmark/migrations/0002_auto_20141028_2212.py new file mode 100644 index 0000000..fbcc8be --- /dev/null +++ b/postmark/migrations/0002_auto_20141028_2212.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('postmark', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='emailbounce', + name='can_activate', + field=models.BooleanField(default=False, verbose_name='Can Activate'), + preserve_default=True, + ), + migrations.AlterField( + model_name='emailbounce', + name='inactive', + field=models.BooleanField(default=False, verbose_name='Inactive'), + preserve_default=True, + ), + ] diff --git a/postmark/models.py b/postmark/models.py index 23997b6..b1eab94 100755 --- a/postmark/models.py +++ b/postmark/models.py @@ -1,10 +1,8 @@ -from django.utils.translation import ugettext_lazy as _ -from django.dispatch import receiver +from dateutil.parser import parse as parsedatetime from django.db import models +from django.dispatch import receiver +from django.utils.translation import ugettext_lazy as _ from itertools import izip_longest -from datetime import datetime -from pytz import timezone -import pytz from postmark.signals import post_send @@ -37,77 +35,78 @@ ("Blocked", _("Blocked")), ) + class EmailMessage(models.Model): message_id = models.CharField(_("Message ID"), max_length=40) submitted_at = models.DateTimeField(_("Submitted At")) status = models.CharField(_("Status"), max_length=150) - + to = models.CharField(_("To"), max_length=150) to_type = models.CharField(_("Type"), max_length=3, choices=TO_CHOICES) - + sender = models.CharField(_("Sender"), max_length=150) reply_to = models.CharField(_("Reply To"), max_length=150) subject = models.CharField(_("Subject"), max_length=150) tag = models.CharField(_("Tag"), max_length=150) text_body = models.TextField(_("Text Body")) html_body = models.TextField(_("HTML Body")) - + headers = models.TextField(_("Headers")) attachments = models.TextField(_("Attachments")) - + def __unicode__(self): return u"%s" % (self.message_id,) - + class Meta: verbose_name = _("email message") verbose_name_plural = _("email messages") - + get_latest_by = "submitted_at" ordering = ["-submitted_at"] + class EmailBounce(models.Model): id = models.PositiveIntegerField(primary_key=True) - message = models.ForeignKey(EmailMessage, related_name="bounces", verbose_name=_("Message")) - - inactive = models.BooleanField(_("Inactive")) - can_activate = models.BooleanField(_("Can Activate")) - + message = models.ForeignKey( + EmailMessage, related_name="bounces", verbose_name=_("Message")) + + inactive = models.BooleanField(_("Inactive"), default=False) + can_activate = models.BooleanField(_("Can Activate"), default=False) + type = models.CharField(_("Type"), max_length=100, choices=BOUNCE_TYPES) description = models.TextField(_("Description")) details = models.TextField(_("Details")) - + bounced_at = models.DateTimeField(_("Bounced At")) - + def __unicode__(self): return u"Bounce: %s" % (self.message.to,) - + class Meta: verbose_name = _("email bounce") verbose_name_plural = _("email bounces") - + order_with_respect_to = "message" get_latest_by = "bounced_at" ordering = ["-bounced_at"] + @receiver(post_send) def sent_message(sender, **kwargs): msg = kwargs["message"] resp = kwargs["response"] - + for recipient in ( list(izip_longest(msg["To"].split(","), [], fillvalue='to')) + list(izip_longest(msg.get("Cc", "").split(","), [], fillvalue='cc')) + - list(izip_longest(msg.get("Bcc", "").split(","), [], fillvalue='bcc'))): - + list(izip_longest(msg.get("Bcc", "").split(","), [], fillvalue='bcc')) + ): + if not recipient[0]: continue - - timestamp, tz = resp["SubmittedAt"].rsplit("+", 1) - tz_offset = int(tz.split(":", 1)[0]) - tz = timezone("Etc/GMT%s%d" % ("+" if tz_offset >= 0 else "-", tz_offset)) - submitted_at = tz.localize(datetime.strptime(timestamp[:26], POSTMARK_DATETIME_STRING)).astimezone(pytz.utc) - - + + submitted_at = parsedatetime(resp['SubmittedAt']) + emsg = EmailMessage( message_id=resp["MessageID"], submitted_at=submitted_at, @@ -123,4 +122,4 @@ def sent_message(sender, **kwargs): headers=msg.get("Headers", ""), attachments=msg.get("Attachments", "") ) - emsg.save() \ No newline at end of file + emsg.save() diff --git a/postmark/south_migrations/0001_initial.py b/postmark/south_migrations/0001_initial.py new file mode 100644 index 0000000..cd990e1 --- /dev/null +++ b/postmark/south_migrations/0001_initial.py @@ -0,0 +1,86 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'EmailMessage' + db.create_table('postmark_emailmessage', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('message_id', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('submitted_at', self.gf('django.db.models.fields.DateTimeField')()), + ('status', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('to', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('to_type', self.gf('django.db.models.fields.CharField')(max_length=3)), + ('sender', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('reply_to', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('subject', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('tag', self.gf('django.db.models.fields.CharField')(max_length=25)), + ('text_body', self.gf('django.db.models.fields.TextField')()), + ('html_body', self.gf('django.db.models.fields.TextField')()), + ('headers', self.gf('django.db.models.fields.TextField')()), + ('attachments', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('postmark', ['EmailMessage']) + + # Adding model 'EmailBounce' + db.create_table('postmark_emailbounce', ( + ('id', self.gf('django.db.models.fields.PositiveIntegerField')(primary_key=True)), + ('message', self.gf('django.db.models.fields.related.ForeignKey')(related_name='bounces', to=orm['postmark.EmailMessage'])), + ('inactive', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('can_activate', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('type', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('description', self.gf('django.db.models.fields.TextField')()), + ('details', self.gf('django.db.models.fields.TextField')()), + ('bounced_at', self.gf('django.db.models.fields.DateTimeField')()), + ('_order', self.gf('django.db.models.fields.IntegerField')(default=0)), + )) + db.send_create_signal('postmark', ['EmailBounce']) + + + def backwards(self, orm): + + # Deleting model 'EmailMessage' + db.delete_table('postmark_emailmessage') + + # Deleting model 'EmailBounce' + db.delete_table('postmark_emailbounce') + + + models = { + 'postmark.emailbounce': { + 'Meta': {'ordering': "('_order',)", 'object_name': 'EmailBounce'}, + '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'bounced_at': ('django.db.models.fields.DateTimeField', [], {}), + 'can_activate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'details': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.PositiveIntegerField', [], {'primary_key': 'True'}), + 'inactive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bounces'", 'to': "orm['postmark.EmailMessage']"}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'postmark.emailmessage': { + 'Meta': {'ordering': "['-submitted_at']", 'object_name': 'EmailMessage'}, + 'attachments': ('django.db.models.fields.TextField', [], {}), + 'headers': ('django.db.models.fields.TextField', [], {}), + 'html_body': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'sender': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'status': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'submitted_at': ('django.db.models.fields.DateTimeField', [], {}), + 'tag': ('django.db.models.fields.CharField', [], {'max_length': '25'}), + 'text_body': ('django.db.models.fields.TextField', [], {}), + 'to': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'to_type': ('django.db.models.fields.CharField', [], {'max_length': '3'}) + } + } + + complete_apps = ['postmark'] diff --git a/postmark/migrations/0002_auto__chg_field_emailmessage_tag.py b/postmark/south_migrations/0002_auto__chg_field_emailmessage_tag.py similarity index 100% rename from postmark/migrations/0002_auto__chg_field_emailmessage_tag.py rename to postmark/south_migrations/0002_auto__chg_field_emailmessage_tag.py diff --git a/postmark/south_migrations/__init__.py b/postmark/south_migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index 32eb52f..7ee39a0 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,25 @@ from setuptools import setup setup( - name = "django-postmark", - version = __import__("postmark").__version__, - author = "Donald Stufft", - author_email = "donald@e.vilgeni.us", - description = "A Django reusable app to send email with postmark, as well as models and views to handle bounce integration.", - long_description = open("README.rst").read(), - url = "http://github.com/dstufft/django-postmark/", - license = "BSD", - install_requires = [ + name="django-postmark", + version=__import__("postmark").__version__, + author="Donald Stufft", + author_email="donald@e.vilgeni.us", + description="A Django reusable app to send email with postmark, as well as models and views to handle bounce integration.", + long_description=open("README.rst").read(), + url="http://github.com/dstufft/django-postmark/", + license="BSD", + install_requires=[ "httplib2", "pytz", + "python-dateutil", ], - packages = [ + packages=[ "postmark", "postmark.migrations", + "postmark.south_migrations", ], - classifiers = [ + classifiers=[ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers",