From 413b60e35fac5e24048ffbf04556ef8e0c1d776d Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Wed, 27 Jun 2012 20:45:01 -0700 Subject: [PATCH] Initial commit. --- .hgignore | 2 + django_mailbox/__init__.py | 0 django_mailbox/admin.py | 29 +++++ django_mailbox/management/__init__.py | 0 .../management/commands/__init__.py | 0 django_mailbox/management/commands/getmail.py | 18 +++ django_mailbox/migrations/0001_initial.py | 59 ++++++++++ django_mailbox/migrations/__init__.py | 0 django_mailbox/models.py | 105 ++++++++++++++++++ django_mailbox/signals.py | 3 + django_mailbox/transports.py | 50 +++++++++ readme.rst | 56 ++++++++++ setup.py | 18 +++ 13 files changed, 340 insertions(+) create mode 100644 .hgignore create mode 100644 django_mailbox/__init__.py create mode 100644 django_mailbox/admin.py create mode 100644 django_mailbox/management/__init__.py create mode 100644 django_mailbox/management/commands/__init__.py create mode 100644 django_mailbox/management/commands/getmail.py create mode 100644 django_mailbox/migrations/0001_initial.py create mode 100644 django_mailbox/migrations/__init__.py create mode 100644 django_mailbox/models.py create mode 100644 django_mailbox/signals.py create mode 100644 django_mailbox/transports.py create mode 100644 readme.rst create mode 100644 setup.py diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..5aab31a --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +.*\.pyc +.*egg-info.* diff --git a/django_mailbox/__init__.py b/django_mailbox/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_mailbox/admin.py b/django_mailbox/admin.py new file mode 100644 index 0000000..efe2a33 --- /dev/null +++ b/django_mailbox/admin.py @@ -0,0 +1,29 @@ +from django.conf import settings +from django.contrib import admin + +from django_mailbox.models import Message, Mailbox + +def get_new_mail(mailbox_admin, request, queryset): + for mailbox in queryset.all(): + mailbox.get_new_mail() +get_new_mail.short_description = 'Get new mail' + +class MailboxAdmin(admin.ModelAdmin): + list_display = ( + 'name', + 'uri', + ) + actions = [get_new_mail] + +class MessageAdmin(admin.ModelAdmin): + list_display = ( + 'subject', + 'from_address', + 'received', + 'mailbox', + ) + ordering = ['-received'] + +if getattr(settings, 'DJANGO_MAILBOX_ADMIN_ENABLED', True): + admin.site.register(Message, MessageAdmin) + admin.site.register(Mailbox, MailboxAdmin) diff --git a/django_mailbox/management/__init__.py b/django_mailbox/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_mailbox/management/commands/__init__.py b/django_mailbox/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_mailbox/management/commands/getmail.py b/django_mailbox/management/commands/getmail.py new file mode 100644 index 0000000..9cc7a81 --- /dev/null +++ b/django_mailbox/management/commands/getmail.py @@ -0,0 +1,18 @@ +from django.core.management.base import BaseCommand +from django.dispatch import receiver + +from django_mailbox.models import Mailbox +from django_mailbox.signals import message_received + +class Command(BaseCommand): + def handle(self, *args, **options): + mailboxes = Mailbox.objects.all() + if args: + mailboxes = mailboxes.filter(name = ' '.join(args)) + for mailbox in mailboxes: + self.stdout.write('Gathering messages for %s\n' % mailbox.name) + mailbox.get_new_mail() + + @receiver(message_received) + def incoming_message(self, sender, message, **kwargs): + self.stdout.write('Received %s\n' % message.subject) diff --git a/django_mailbox/migrations/0001_initial.py b/django_mailbox/migrations/0001_initial.py new file mode 100644 index 0000000..4faa4fa --- /dev/null +++ b/django_mailbox/migrations/0001_initial.py @@ -0,0 +1,59 @@ +# -*- coding: 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 'Mailbox' + db.create_table('django_mailbox_mailbox', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('uri', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('django_mailbox', ['Mailbox']) + + # Adding model 'Message' + db.create_table('django_mailbox_message', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('mailbox', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_mailbox.Mailbox'])), + ('subject', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('message_id', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('from_address', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('body', self.gf('django.db.models.fields.TextField')()), + ('received', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('django_mailbox', ['Message']) + + + def backwards(self, orm): + # Deleting model 'Mailbox' + db.delete_table('django_mailbox_mailbox') + + # Deleting model 'Message' + db.delete_table('django_mailbox_message') + + + models = { + 'django_mailbox.mailbox': { + 'Meta': {'object_name': 'Mailbox'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'django_mailbox.message': { + 'Meta': {'object_name': 'Message'}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'from_address': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_mailbox.Mailbox']"}), + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'received': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['django_mailbox'] \ No newline at end of file diff --git a/django_mailbox/migrations/__init__.py b/django_mailbox/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_mailbox/models.py b/django_mailbox/models.py new file mode 100644 index 0000000..5e81313 --- /dev/null +++ b/django_mailbox/models.py @@ -0,0 +1,105 @@ +import email +import rfc822 +import urllib +import urlparse + +from django.db import models +from django_mailbox.transports import PopMailEnumerator, ImapMailEnumerator +from django_mailbox.signals import message_received + +class Mailbox(models.Model): + name = models.CharField(max_length=255) + uri = models.CharField( + max_length=255, + help_text=""" + Start your URI with imap:// or pop3://. +
+
+ Example: imap://myusername:mypassword@someserver +
+
+ Be sure to urlencode your username and password should they + contain illegal characters (like @, :, etc). + """ + ) + + @property + def _protocol_info(self): + return urlparse.urlparse(self.uri) + + @property + def _domain(self): + return self._protocol_info.hostname + + @property + def port(self): + return self._protocol_info.port + + @property + def username(self): + return urllib.unquote(self._protocol_info.username) + + @property + def password(self): + return urllib.unquote(self._protocol_info.password) + + @property + def location(self): + return self._domain if self._domain else '' + self._protocol_info.path + + @property + def type(self): + return self._protocol_info.scheme.lower() + + def get_connection(self): + if self.type == 'imap': + conn = ImapMailEnumerator( + self.location, + self.port if self.port else 143 + ) + elif self.type == 'pop3': + conn = PopMailEnumerator( + self.location, + self.port if self.port else 110 + ) + conn.connect(self.username, self.password) + return conn + + def get_new_mail(self): + connection = self.get_connection() + new_mail = [] + for message in connection.get_message(): + msg = Message() + msg.mailbox = self + msg.subject = message['subject'] + msg.message_id = message['message-id'] + msg.from_address = rfc822.parseaddr(message['from'])[1] + msg.body = message.as_string() + msg.save() + new_mail.append(msg) + message_received.send(sender=self, message=msg) + return new_mail + + def __unicode__(self): + return self.name + + class Meta: + verbose_name_plural = "Mailboxes" + +class Message(models.Model): + mailbox = models.ForeignKey(Mailbox, related_name='messages') + subject = models.CharField(max_length=255) + message_id = models.CharField(max_length=255) + from_address = models.CharField(max_length=255) + + body = models.TextField() + + received = models.DateTimeField( + auto_now_add=True + ) + + def get_email_object(self): + return email.message_from_string(self.body) + + def __unicode__(self): + return self.subject diff --git a/django_mailbox/signals.py b/django_mailbox/signals.py new file mode 100644 index 0000000..b445183 --- /dev/null +++ b/django_mailbox/signals.py @@ -0,0 +1,3 @@ +from django.dispatch.dispatcher import Signal + +message_received = Signal(providing_args=['message']) diff --git a/django_mailbox/transports.py b/django_mailbox/transports.py new file mode 100644 index 0000000..05452a1 --- /dev/null +++ b/django_mailbox/transports.py @@ -0,0 +1,50 @@ +import email +from imaplib import IMAP4 +from poplib import POP3 + +class PopMailEnumerator(object): + def __init__(self, hostname, port): + self.hostname = hostname + self.port = port + + def connect(self, username, password): + self.server = POP3(self.hostname, self.port) + self.server.user(username) + self.server.pass_(password) + + def get_message(self): + message_count = len(self.server.list()[1]) + for i in range(message_count): + try: + msg_contents = "\r\n".join(self.server.retr(i + 1)[1]) + message = email.message_from_string(msg_contents) + yield message + except email.Errors.MessageParseError: + continue + self.server.dele(i + 1) + self.server.quit() + return + +class ImapMailEnumerator(object): + def __init__(self, hostname, port): + self.hostname = hostname + self.port = port + + def connect(self, username, password): + self.server = IMAP4(self.hostname, self.port) + typ, msg = self.server.login(username, password) + self.server.select() + + def get_message(self): + typ, inbox = self.server.search(None, 'ALL') + for key in inbox[0].split(): + try: + typ, msg_contents = self.server.fetch(key, '(RFC822)') + message = email.message_from_string(msg_contents[0][1]) + yield message + except email.Errors.MessageParseError: + continue + self.server.store(key, "+FLAGS", "\\Deleted"); + self.server.expunge() + return + diff --git a/readme.rst b/readme.rst new file mode 100644 index 0000000..3c3a37d --- /dev/null +++ b/readme.rst @@ -0,0 +1,56 @@ +Introduction +~~~~~~~~~~~~ + +How many times have you had to consume some sort of POP3 or IMAP mailbox for incoming content? One too many times for me. + +This small Django application will allow you to specify IMAP or POP3 mailboxes that you would like consumed for incoming content; the e-mail will be stored, and you can process it at will (or, if you're in a hurry, by subscribing to a signal). + +WARNING! This app will delete any messages it can find in the inbox you specify-- please make sure you don't have anything important in there. + +URI Examples +============ + +Mailbox URIs are in the normal URI format:: + + protocol://username:password@domain + +IMAP Example: ``imap://username:password@server`` + +POP3 Example: ``pop3://username:password@server`` + +Subscribing to the incoming mail signal +======================================= + +To subscribe to the incoming mail signal, following this lead:: + + from django_mailbox.signals import message_received + from django.dispatch import receiver + + @receiver(message_received) + def dance_jig(sender, message, **args): + print "I just recieved a message titled %s from a mailbox named %s" % (message.subject, message.mailbox.name, ) + +Getting incoming mail +======================= + +In your code +------------ + +Mailbox instances have a method named ``get_new_mail``; this method will gather new messages from the server. + +Using the Django Admin +---------------------- + +Check the box next to each of the mailboxes you'd like to fetch e-mail from, and select the 'Get new mail' option. + +Using a cron job +---------------- + +You can easily consume incoming mail by running the management command named ``getmail`` (optionally with an argument of the name of the mailbox you'd like to get the mail for).:: + + python manage.py getmail + +Settings +======== + +You can disable mailbox information from being listed in the Django admin by adding a setting named ``DJANGO_MAILBOX_ADMIN_ENABLED`` indicating your preference toward whether or not the models appear in the admin (defaulting to ``True``). diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6e401a3 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup, find_packages + +setup( + name='django_mailbox', + version='0.1', + url='http://bitbucket.org/latestrevision/django-mailbox/', + description='Automatically import mail from POP3 or IMAP into Django', + author='Adam Coddington', + author_email='me@adamcoddington.net', + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Utilities', + ], + packages=find_packages(), +)