1
0
Fork 1
mirror of https://github.com/coddingtonbear/django-mailbox.git synced 2026-07-09 22:38:19 +02:00

adds testable repo with added features

This commit is contained in:
Joe 2021-02-03 23:05:48 -05:00
parent 62805f324e
commit c7997ebd9b
58 changed files with 2421 additions and 1541 deletions

View file

@ -1,7 +0,0 @@
[bumpversion]
current_version = 4.8.2
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:django_mailbox/__init__.py]

View file

@ -1,2 +1,4 @@
[flake8] [flake8]
exclude = django_mailbox/migrations, django_mailbox/south_migrations max-line-length = 200
exclude = docs, django_mailbox/migrations, django_mailbox/south_migrations
ignore = E231,W503

1
.gitignore vendored
View file

@ -16,3 +16,4 @@ messages
manage.py manage.py
old/ old/
poetry.lock poetry.lock
websrv

View file

@ -1,46 +0,0 @@
version: ~> 1.0
os: linux
language: python
jobs:
- env: TOX_ENV=docs
python: 3.8
# sort by django version, next to by python version
# Django 1.11 support all supported Python interpreter
- env: TOX_ENV=django111-py35
python: 3.5
- env: TOX_ENV=django111-py36
python: 3.6
# Django 2.0 support 3.5, 3.6
- env: TOX_ENV=django20-py35
python: 3.5
- env: TOX_ENV=django20-py36
python: 3.6
# Django 2.1 support Python 3.5, 3.6, 3.7
- env: TOX_ENV=django21-py35
python: 3.5
- env: TOX_ENV=django21-py36
python: 3.6
- env: TOX_ENV=django21-py37
python: 3.7
# Django 2.2 support Python 3.5, 3.6, 3.7
- env: TOX_ENV=django22-py35
python: 3.5
- env: TOX_ENV=django22-py36
python: 3.6
- env: TOX_ENV=django22-py37
python: 3.7
# Django 3.0 (beta) support Python 3.6, 3.7, 3.8
- env: TOX_ENV=django30-py36
python: 3.6
- env: TOX_ENV=django30-py37
python: 3.7
- env: TOX_ENV=django30-py38 INTEGRATION=1
python: 3.8
install:
- pip install tox
script:
- tox -e $TOX_ENV

View file

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [0.1.1] - 2021-02-03 ## [0.1.1] - 2021-02-03
### Added ### Added
- adds modified documenation - adds modified documenation
- adds testable repo with added features
## [0.1.0] - 2021-02-03 ## [0.1.0] - 2021-02-03
### Added ### Added

View file

@ -1,9 +0,0 @@
include *.rst
include .travis.yml
include MANIFEST
recursive-include django_mailbox *.eml
recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs Makefile
recursive-include django_mailbox/locale *.po
recursive-include django_mailbox/locale *.mo

View file

@ -1,3 +1,3 @@
__version__ = '4.8.2' __version__ = "4.8.2"
default_app_config = 'django_mailbox.apps.MailBoxConfig' default_app_config = "django_mailbox.apps.MailBoxConfig"

View file

@ -23,35 +23,38 @@ def get_new_mail(mailbox_admin, request, queryset):
queryset.get_new_mail() queryset.get_new_mail()
get_new_mail.short_description = _('Get new mail') get_new_mail.short_description = _("Get new mail")
def resend_message_received_signal(message_admin, request, queryset): def resend_message_received_signal(message_admin, request, queryset):
for message in queryset.all(): for message in queryset.all():
logger.debug('Resending \'message_received\' signal for %s' % message) logger.debug("Resending 'message_received' signal for %s" % message)
message_received.send(sender=message_admin, message=message) message_received.send(sender=message_admin, message=message)
resend_message_received_signal.short_description = ( resend_message_received_signal.short_description = _("Re-send message received signal")
_('Re-send message received signal')
)
class MailboxAdmin(admin.ModelAdmin): class MailboxAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'name', "name",
'uri', "uri",
'from_email', "from_email",
'active', "active",
'last_polling', "last_polling",
) )
readonly_fields = ['last_polling', ] readonly_fields = [
"last_polling",
]
actions = [get_new_mail] actions = [get_new_mail]
class MessageAttachmentAdmin(admin.ModelAdmin): class MessageAttachmentAdmin(admin.ModelAdmin):
raw_id_fields = ('message', ) raw_id_fields = ("message",)
list_display = ('message', 'document',) list_display = (
"message",
"document",
)
class MessageAttachmentInline(admin.TabularInline): class MessageAttachmentInline(admin.TabularInline):
@ -63,50 +66,44 @@ class MessageAdmin(admin.ModelAdmin):
def attachment_count(self, msg): def attachment_count(self, msg):
return msg.attachments.count() return msg.attachments.count()
attachment_count.short_description = _('Attachment count') attachment_count.short_description = _("Attachment count")
def subject(self, msg): def subject(self, msg):
return convert_header_to_unicode(msg.subject) return convert_header_to_unicode(msg.subject)
def envelope_headers(self, msg): def envelope_headers(self, msg):
email = msg.get_email_object() email = msg.get_email_object()
return '\n'.join( return "\n".join([("{}: {}".format(h, v)) for h, v in email.items()])
[('{}: {}'.format(h, v)) for h, v in email.items()]
)
inlines = [ inlines = [
MessageAttachmentInline, MessageAttachmentInline,
] ]
list_display = ( list_display = (
'subject', "subject",
'processed', "processed",
'read', "read",
'mailbox', "mailbox",
'outgoing', "outgoing",
'attachment_count', "attachment_count",
) )
ordering = ['-processed'] ordering = ["-processed"]
list_filter = ( list_filter = (
'mailbox', "mailbox",
'outgoing', "outgoing",
'processed', "processed",
'read', "read",
)
exclude = (
'body',
)
raw_id_fields = (
'in_reply_to',
) )
exclude = ("body",)
raw_id_fields = ("in_reply_to",)
readonly_fields = ( readonly_fields = (
'envelope_headers', "envelope_headers",
'text', "text",
'html', "html",
) )
actions = [resend_message_received_signal] actions = [resend_message_received_signal]
if getattr(settings, 'DJANGO_MAILBOX_ADMIN_ENABLED', True): if getattr(settings, "DJANGO_MAILBOX_ADMIN_ENABLED", True):
admin.site.register(Message, MessageAdmin) admin.site.register(Message, MessageAdmin)
admin.site.register(MessageAttachment, MessageAttachmentAdmin) admin.site.register(MessageAttachment, MessageAttachmentAdmin)
admin.site.register(Mailbox, MailboxAdmin) admin.site.register(Mailbox, MailboxAdmin)

View file

@ -3,5 +3,5 @@ from django.utils.translation import gettext_lazy as _
class MailBoxConfig(AppConfig): class MailBoxConfig(AppConfig):
name = 'django_mailbox' name = "django_mailbox"
verbose_name = _("Mail Box") verbose_name = _("Mail Box")

View file

@ -28,7 +28,7 @@ def get_google_access_token(email):
# TODO: This should be cacheable # TODO: This should be cacheable
try: try:
me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2") me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2")
return me.extra_data['access_token'] return me.extra_data["access_token"]
except (UserSocialAuth.DoesNotExist, KeyError): except (UserSocialAuth.DoesNotExist, KeyError):
raise AccessTokenNotFound raise AccessTokenNotFound
@ -45,7 +45,7 @@ def update_google_extra_data(email, extra_data):
def get_google_refresh_token(email): def get_google_refresh_token(email):
try: try:
me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2") me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2")
return me.extra_data['refresh_token'] return me.extra_data["refresh_token"]
except (UserSocialAuth.DoesNotExist, KeyError): except (UserSocialAuth.DoesNotExist, KeyError):
raise RefreshTokenNotFound raise RefreshTokenNotFound
@ -72,9 +72,11 @@ def google_api_post(email, url, post_data, authorized=True):
# TODO: Make this a lot less ugly. especially the 401 handling # TODO: Make this a lot less ugly. especially the 401 handling
headers = dict() headers = dict()
if authorized is True: if authorized is True:
headers.update(dict( headers.update(
Authorization="Bearer %s" % get_google_access_token(email), dict(
)) Authorization="Bearer %s" % get_google_access_token(email),
)
)
r = requests.post(url, headers=headers, data=post_data) r = requests.post(url, headers=headers, data=post_data)
if r.status_code == 401: if r.status_code == 401:
refresh_authorization(email) refresh_authorization(email)
@ -92,20 +94,20 @@ def refresh_authorization(email):
refresh_token=refresh_token, refresh_token=refresh_token,
client_id=get_google_consumer_key(), client_id=get_google_consumer_key(),
client_secret=get_google_consumer_secret(), client_secret=get_google_consumer_secret(),
grant_type='refresh_token', grant_type="refresh_token",
) )
results = google_api_post( results = google_api_post(
email, email,
"https://accounts.google.com/o/oauth2/token?access_type=offline", "https://accounts.google.com/o/oauth2/token?access_type=offline",
post_data, post_data,
authorized=False) authorized=False,
results.update({'refresh_token': refresh_token}) )
results.update({"refresh_token": refresh_token})
update_google_extra_data(email, results) update_google_extra_data(email, results)
def fetch_user_info(email): def fetch_user_info(email):
result = google_api_get( result = google_api_get(
email, email, "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
"https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
) )
return result return result

View file

@ -13,18 +13,11 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
mailboxes = Mailbox.active_mailboxes.all() mailboxes = Mailbox.active_mailboxes.all()
if args: if args:
mailboxes = mailboxes.filter( mailboxes = mailboxes.filter(name=" ".join(args))
name=' '.join(args)
)
for mailbox in mailboxes: for mailbox in mailboxes:
logger.info( logger.info("Gathering messages for %s", mailbox.name)
'Gathering messages for %s',
mailbox.name
)
messages = mailbox.get_new_mail() messages = mailbox.get_new_mail()
for message in messages: for message in messages:
logger.info( logger.info(
'Received %s (from %s)', "Received %s (from %s)", message.subject, message.from_address
message.subject,
message.from_address
) )

View file

@ -1,6 +1,7 @@
import email import email
import logging import logging
import sys import sys
try: try:
from email import utils from email import utils
except ImportError: except ImportError:
@ -21,9 +22,9 @@ class Command(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
'mailbox_name', "mailbox_name",
nargs='?', nargs="?",
help="The name of the mailbox that will receive the message" help="The name of the mailbox that will receive the message",
) )
def handle(self, mailbox_name=None, *args, **options): def handle(self, mailbox_name=None, *args, **options):
@ -34,10 +35,7 @@ class Command(BaseCommand):
else: else:
mailbox = self.get_mailbox_for_message(message) mailbox = self.get_mailbox_for_message(message)
mailbox.process_incoming_message(message) mailbox.process_incoming_message(message)
logger.info( logger.info("Message received from %s", message["from"])
"Message received from %s",
message['from']
)
else: else:
logger.warning("Message not processable.") logger.warning("Message not processable.")
@ -48,5 +46,5 @@ class Command(BaseCommand):
return mailbox return mailbox
def get_mailbox_for_message(self, message): def get_mailbox_for_message(self, message):
email_address = utils.parseaddr(message['to'])[1][0:255] email_address = utils.parseaddr(message["to"])[1][0:255]
return self.get_mailbox_by_name(email_address) return self.get_mailbox_by_name(email_address)

View file

@ -11,7 +11,7 @@ logging.basicConfig(level=logging.INFO)
class Command(BaseCommand): class Command(BaseCommand):
""" Briefly, a bug existed in a migration that may have caused message """Briefly, a bug existed in a migration that may have caused message
attachments to become disassociated with their messages. This management attachments to become disassociated with their messages. This management
command will read through existing message attachments and attempt to command will read through existing message attachments and attempt to
re-associate them with their original message. re-associate them with their original message.
@ -22,14 +22,13 @@ class Command(BaseCommand):
have been quite unlucky to have ran the bad migration). have been quite unlucky to have ran the bad migration).
""" """
def handle(self, *args, **options): def handle(self, *args, **options):
attachment_hash_map = {} attachment_hash_map = {}
attachments_without_messages = MessageAttachment.objects.filter( attachments_without_messages = MessageAttachment.objects.filter(
message=None message=None
).order_by( ).order_by("id")
'id'
)
if attachments_without_messages.count() < 1: if attachments_without_messages.count() < 1:
return return
@ -40,13 +39,13 @@ class Command(BaseCommand):
md5.update(chunk) md5.update(chunk)
attachment_hash_map[md5.hexdigest()] = attachment.pk attachment_hash_map[md5.hexdigest()] = attachment.pk
for message_record in Message.objects.all().order_by('id'): for message_record in Message.objects.all().order_by("id"):
message = email.message_from_string(message_record.body) message = email.message_from_string(message_record.body)
if message.is_multipart(): if message.is_multipart():
for part in message.walk(): for part in message.walk():
if part.get_content_maintype() == 'multipart': if part.get_content_maintype() == "multipart":
continue continue
if part.get('Content-Disposition') is None: if part.get("Content-Disposition") is None:
continue continue
md5 = hashlib.md5() md5 = hashlib.md5()
md5.update(part.get_payload(decode=True)) md5.update(part.get_payload(decode=True))
@ -61,11 +60,11 @@ class Command(BaseCommand):
"Associated message %s with attachment %s (%s)", "Associated message %s with attachment %s (%s)",
message_record.pk, message_record.pk,
attachment.pk, attachment.pk,
digest digest,
) )
else: else:
logger.info( logger.info(
"%s(%s) not found in currently-stored attachments", "%s(%s) not found in currently-stored attachments",
part.get_filename(), part.get_filename(),
digest digest,
) )

View file

@ -3,54 +3,163 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Mailbox', name="Mailbox",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('name', models.CharField(max_length=255, verbose_name='Name')), "id",
('uri', models.CharField(default=None, max_length=255, blank=True, help_text="Example: imap+ssl://myusername:mypassword@someserver <br /><br />Internet transports include 'imap' and 'pop3'; common local file transports include 'maildir', 'mbox', and less commonly 'babyl', 'mh', and 'mmdf'. <br /><br />Be sure to urlencode your username and password should they contain illegal characters (like @, :, etc).", null=True, verbose_name='URI')), models.AutoField(
('from_email', models.CharField(default=None, max_length=255, blank=True, help_text="Example: MailBot &lt;mailbot@yourdomain.com&gt;<br />'From' header to set for outgoing email.<br /><br />If you do not use this e-mail inbox for outgoing mail, this setting is unnecessary.<br />If you send e-mail without setting this, your 'From' header will'be set to match the setting `DEFAULT_FROM_EMAIL`.", null=True, verbose_name='From email')), verbose_name="ID",
('active', models.BooleanField(default=True, help_text='Check this e-mail inbox for new e-mail messages during polling cycles. This checkbox does not have an effect upon whether mail is collected here when this mailbox receives mail from a pipe, and does not affect whether e-mail messages can be dispatched from this mailbox. ', verbose_name='Active')), serialize=False,
auto_created=True,
primary_key=True,
),
),
("name", models.CharField(max_length=255, verbose_name="Name")),
(
"uri",
models.CharField(
default=None,
max_length=255,
blank=True,
help_text="Example: imap+ssl://myusername:mypassword@someserver <br /><br />Internet transports include 'imap' and 'pop3'; common local file transports include 'maildir', 'mbox', and less commonly 'babyl', 'mh', and 'mmdf'. <br /><br />Be sure to urlencode your username and password should they contain illegal characters (like @, :, etc).",
null=True,
verbose_name="URI",
),
),
(
"from_email",
models.CharField(
default=None,
max_length=255,
blank=True,
help_text="Example: MailBot &lt;mailbot@yourdomain.com&gt;<br />'From' header to set for outgoing email.<br /><br />If you do not use this e-mail inbox for outgoing mail, this setting is unnecessary.<br />If you send e-mail without setting this, your 'From' header will'be set to match the setting `DEFAULT_FROM_EMAIL`.",
null=True,
verbose_name="From email",
),
),
(
"active",
models.BooleanField(
default=True,
help_text="Check this e-mail inbox for new e-mail messages during polling cycles. This checkbox does not have an effect upon whether mail is collected here when this mailbox receives mail from a pipe, and does not affect whether e-mail messages can be dispatched from this mailbox. ",
verbose_name="Active",
),
),
], ],
options={ options={
'verbose_name_plural': 'Mailboxes', "verbose_name_plural": "Mailboxes",
}, },
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Message', name="Message",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('subject', models.CharField(max_length=255, verbose_name='Subject')), "id",
('message_id', models.CharField(max_length=255, verbose_name='Message ID')), models.AutoField(
('from_header', models.CharField(max_length=255, verbose_name='From header')), verbose_name="ID",
('to_header', models.TextField(verbose_name='To header')), serialize=False,
('outgoing', models.BooleanField(default=False, verbose_name='Outgoing')), auto_created=True,
('body', models.TextField(verbose_name='Body')), primary_key=True,
('encoded', models.BooleanField(default=False, help_text='True if the e-mail body is Base64 encoded', verbose_name='Encoded')), ),
('processed', models.DateTimeField(auto_now_add=True, verbose_name='Processed')), ),
('read', models.DateTimeField(default=None, null=True, verbose_name='Read', blank=True)), ("subject", models.CharField(max_length=255, verbose_name="Subject")),
('in_reply_to', models.ForeignKey(related_name='replies', verbose_name='In reply to', blank=True, to='django_mailbox.Message', null=True, on_delete=models.CASCADE)), (
('mailbox', models.ForeignKey(related_name='messages', verbose_name='Mailbox', to='django_mailbox.Mailbox', on_delete=models.CASCADE)), "message_id",
models.CharField(max_length=255, verbose_name="Message ID"),
),
(
"from_header",
models.CharField(max_length=255, verbose_name="From header"),
),
("to_header", models.TextField(verbose_name="To header")),
(
"outgoing",
models.BooleanField(default=False, verbose_name="Outgoing"),
),
("body", models.TextField(verbose_name="Body")),
(
"encoded",
models.BooleanField(
default=False,
help_text="True if the e-mail body is Base64 encoded",
verbose_name="Encoded",
),
),
(
"processed",
models.DateTimeField(auto_now_add=True, verbose_name="Processed"),
),
(
"read",
models.DateTimeField(
default=None, null=True, verbose_name="Read", blank=True
),
),
(
"in_reply_to",
models.ForeignKey(
related_name="replies",
verbose_name="In reply to",
blank=True,
to="django_mailbox.Message",
null=True,
on_delete=models.CASCADE,
),
),
(
"mailbox",
models.ForeignKey(
related_name="messages",
verbose_name="Mailbox",
to="django_mailbox.Mailbox",
on_delete=models.CASCADE,
),
),
], ],
options={ options={},
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='MessageAttachment', name="MessageAttachment",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('headers', models.TextField(null=True, verbose_name='Headers', blank=True)), "id",
('document', models.FileField(upload_to=b'mailbox_attachments/%Y/%m/%d/', verbose_name='Document')), models.AutoField(
('message', models.ForeignKey(related_name='attachments', verbose_name='Message', blank=True, to='django_mailbox.Message', null=True, on_delete=models.CASCADE)), verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"headers",
models.TextField(null=True, verbose_name="Headers", blank=True),
),
(
"document",
models.FileField(
upload_to=b"mailbox_attachments/%Y/%m/%d/",
verbose_name="Document",
),
),
(
"message",
models.ForeignKey(
related_name="attachments",
verbose_name="Message",
blank=True,
to="django_mailbox.Message",
null=True,
on_delete=models.CASCADE,
),
),
], ],
options={ options={},
},
bases=(models.Model,), bases=(models.Model,),
), ),
] ]

View file

@ -4,14 +4,19 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0001_initial'), ("django_mailbox", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='message', model_name="message",
name='eml', name="eml",
field=models.FileField(help_text='Original full content of message', upload_to=b'messages', null=True, verbose_name='Message as a file'), field=models.FileField(
help_text="Original full content of message",
upload_to=b"messages",
null=True,
verbose_name="Message as a file",
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -4,13 +4,18 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0002_add_eml_to_message'), ("django_mailbox", "0002_add_eml_to_message"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='message', model_name="message",
name='eml', name="eml",
field=models.FileField(help_text='Original full content of message', upload_to=b'messages', null=True, verbose_name='Raw message contents'), field=models.FileField(
help_text="Original full content of message",
upload_to=b"messages",
null=True,
verbose_name="Raw message contents",
),
), ),
] ]

View file

@ -4,18 +4,25 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0003_auto_20150409_0316'), ("django_mailbox", "0003_auto_20150409_0316"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='message', model_name="message",
name='eml', name="eml",
field=models.FileField(verbose_name='Raw message contents', upload_to='messages', null=True, help_text='Original full content of message'), field=models.FileField(
verbose_name="Raw message contents",
upload_to="messages",
null=True,
help_text="Original full content of message",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='messageattachment', model_name="messageattachment",
name='document', name="document",
field=models.FileField(verbose_name='Document', upload_to='mailbox_attachments/%Y/%m/%d/'), field=models.FileField(
verbose_name="Document", upload_to="mailbox_attachments/%Y/%m/%d/"
),
), ),
] ]

View file

@ -5,13 +5,16 @@ import django_mailbox.utils
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0004_bytestring_to_unicode'), ("django_mailbox", "0004_bytestring_to_unicode"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='messageattachment', model_name="messageattachment",
name='document', name="document",
field=models.FileField(upload_to=django_mailbox.utils.get_attachment_save_path, verbose_name='Document'), field=models.FileField(
upload_to=django_mailbox.utils.get_attachment_save_path,
verbose_name="Document",
),
), ),
] ]

View file

@ -6,13 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0005_auto_20160523_2240'), ("django_mailbox", "0005_auto_20160523_2240"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='mailbox', model_name="mailbox",
name='last_polling', name="last_polling",
field=models.DateTimeField(blank=True, help_text='The time of last successful polling for messages.It is blank for new mailboxes and is not set for mailboxes that only receive messages via a pipe.', null=True, verbose_name='Last polling'), field=models.DateTimeField(
blank=True,
help_text="The time of last successful polling for messages.It is blank for new mailboxes and is not set for mailboxes that only receive messages via a pipe.",
null=True,
verbose_name="Last polling",
),
), ),
] ]

View file

@ -6,20 +6,26 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0006_mailbox_last_polling'), ("django_mailbox", "0006_mailbox_last_polling"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='mailbox', name="mailbox",
options={'verbose_name': 'Mailbox', 'verbose_name_plural': 'Mailboxes'}, options={"verbose_name": "Mailbox", "verbose_name_plural": "Mailboxes"},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='message', name="message",
options={'verbose_name': 'E-mail message', 'verbose_name_plural': 'E-mail messages'}, options={
"verbose_name": "E-mail message",
"verbose_name_plural": "E-mail messages",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='messageattachment', name="messageattachment",
options={'verbose_name': 'Message attachment', 'verbose_name_plural': 'Message attachments'}, options={
"verbose_name": "Message attachment",
"verbose_name_plural": "Message attachments",
},
), ),
] ]

View file

@ -6,18 +6,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_mailbox', '0007_auto_20180421_0026'), ("django_mailbox", "0007_auto_20180421_0026"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='mailbox', model_name="mailbox",
name='active', name="active",
field=models.BooleanField(blank=True, default=True, help_text='Check this e-mail inbox for new e-mail messages during polling cycles. This checkbox does not have an effect upon whether mail is collected here when this mailbox receives mail from a pipe, and does not affect whether e-mail messages can be dispatched from this mailbox. ', verbose_name='Active'), field=models.BooleanField(
blank=True,
default=True,
help_text="Check this e-mail inbox for new e-mail messages during polling cycles. This checkbox does not have an effect upon whether mail is collected here when this mailbox receives mail from a pipe, and does not affect whether e-mail messages can be dispatched from this mailbox. ",
verbose_name="Active",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='message', model_name="message",
name='outgoing', name="outgoing",
field=models.BooleanField(blank=True, default=False, verbose_name='Outgoing'), field=models.BooleanField(
blank=True, default=False, verbose_name="Outgoing"
),
), ),
] ]

View file

@ -15,7 +15,8 @@ import email
import logging import logging
import mimetypes import mimetypes
import os.path import os.path
import sys
# import sys
import uuid import uuid
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
@ -29,9 +30,16 @@ from django.utils.timezone import now
from django_mailbox import utils from django_mailbox import utils
from django_mailbox.signals import message_received from django_mailbox.signals import message_received
from django_mailbox.transports import Pop3Transport, ImapTransport, \ from django_mailbox.transports import (
MaildirTransport, MboxTransport, BabylTransport, MHTransport, \ Pop3Transport,
MMDFTransport, GmailImapTransport ImapTransport,
MaildirTransport,
MboxTransport,
BabylTransport,
MHTransport,
MMDFTransport,
GmailImapTransport,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,9 +48,9 @@ class MailboxQuerySet(models.QuerySet):
def get_new_mail(self): def get_new_mail(self):
count = 0 count = 0
for mailbox in self.all(): for mailbox in self.all():
logger.debug('Receiving mail for %s' % mailbox) logger.debug("Receiving mail for %s" % mailbox)
count += sum(1 for i in mailbox.get_new_mail()) count += sum(1 for i in mailbox.get_new_mail())
logger.debug('Received %d %s.', count, 'mails' if count != 1 else 'mail') logger.debug("Received %d %s.", count, "mails" if count != 1 else "mail")
class MailboxManager(models.Manager): class MailboxManager(models.Manager):
@ -52,72 +60,86 @@ class MailboxManager(models.Manager):
class ActiveMailboxManager(MailboxManager): class ActiveMailboxManager(MailboxManager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter( return (
active=True, super()
.get_queryset()
.filter(
active=True,
)
) )
class Mailbox(models.Model): class Mailbox(models.Model):
name = models.CharField( name = models.CharField(
_('Name'), _("Name"),
max_length=255, max_length=255,
) )
uri = models.CharField( uri = models.CharField(
_('URI'), _("URI"),
max_length=255, max_length=255,
help_text=(_( help_text=(
"Example: imap+ssl://myusername:mypassword@someserver <br />" _(
"<br />" "Example: imap+ssl://myusername:mypassword@someserver <br />"
"Internet transports include 'imap' and 'pop3'; " "<br />"
"common local file transports include 'maildir', 'mbox', " "Internet transports include 'imap' and 'pop3'; "
"and less commonly 'babyl', 'mh', and 'mmdf'. <br />" "common local file transports include 'maildir', 'mbox', "
"<br />" "and less commonly 'babyl', 'mh', and 'mmdf'. <br />"
"Be sure to urlencode your username and password should they " "<br />"
"contain illegal characters (like @, :, etc)." "Be sure to urlencode your username and password should they "
)), "contain illegal characters (like @, :, etc)."
)
),
blank=True, blank=True,
null=True, null=True,
default=None, default=None,
) )
from_email = models.CharField( from_email = models.CharField(
_('From email'), _("From email"),
max_length=255, max_length=255,
help_text=(_( help_text=(
"Example: MailBot &lt;mailbot@yourdomain.com&gt;<br />" _(
"'From' header to set for outgoing email.<br />" "Example: MailBot &lt;mailbot@yourdomain.com&gt;<br />"
"<br />" "'From' header to set for outgoing email.<br />"
"If you do not use this e-mail inbox for outgoing mail, this " "<br />"
"setting is unnecessary.<br />" "If you do not use this e-mail inbox for outgoing mail, this "
"If you send e-mail without setting this, your 'From' header will'" "setting is unnecessary.<br />"
"be set to match the setting `DEFAULT_FROM_EMAIL`." "If you send e-mail without setting this, your 'From' header will'"
)), "be set to match the setting `DEFAULT_FROM_EMAIL`."
)
),
blank=True, blank=True,
null=True, null=True,
default=None, default=None,
) )
active = models.BooleanField( active = models.BooleanField(
_('Active'), _("Active"),
help_text=(_( help_text=(
"Check this e-mail inbox for new e-mail messages during polling " _(
"cycles. This checkbox does not have an effect upon whether " "Check this e-mail inbox for new e-mail messages during polling "
"mail is collected here when this mailbox receives mail from a " "cycles. This checkbox does not have an effect upon whether "
"pipe, and does not affect whether e-mail messages can be " "mail is collected here when this mailbox receives mail from a "
"dispatched from this mailbox. " "pipe, and does not affect whether e-mail messages can be "
)), "dispatched from this mailbox. "
)
),
blank=True, blank=True,
default=True, default=True,
) )
last_polling = models.DateTimeField( last_polling = models.DateTimeField(
_("Last polling"), _("Last polling"),
help_text=(_("The time of last successful polling for messages." help_text=(
"It is blank for new mailboxes and is not set for " _(
"mailboxes that only receive messages via a pipe.")), "The time of last successful polling for messages."
"It is blank for new mailboxes and is not set for "
"mailboxes that only receive messages via a pipe."
)
),
blank=True, blank=True,
null=True null=True,
) )
objects = MailboxManager() objects = MailboxManager()
@ -153,30 +175,30 @@ class Mailbox(models.Model):
@property @property
def location(self): def location(self):
"""Returns the location (domain and path) of messages.""" """Returns the location (domain and path) of messages."""
return self._domain if self._domain else '' + self._protocol_info.path return self._domain if self._domain else "" + self._protocol_info.path
@property @property
def type(self): def type(self):
"""Returns the 'transport' name for this mailbox.""" """Returns the 'transport' name for this mailbox."""
scheme = self._protocol_info.scheme.lower() scheme = self._protocol_info.scheme.lower()
if '+' in scheme: if "+" in scheme:
return scheme.split('+')[0] return scheme.split("+")[0]
return scheme return scheme
@property @property
def use_ssl(self): def use_ssl(self):
"""Returns whether or not this mailbox's connection uses SSL.""" """Returns whether or not this mailbox's connection uses SSL."""
return '+ssl' in self._protocol_info.scheme.lower() return "+ssl" in self._protocol_info.scheme.lower()
@property @property
def use_tls(self): def use_tls(self):
"""Returns whether or not this mailbox's connection uses STARTTLS.""" """Returns whether or not this mailbox's connection uses STARTTLS."""
return '+tls' in self._protocol_info.scheme.lower() return "+tls" in self._protocol_info.scheme.lower()
@property @property
def archive(self): def archive(self):
"""Returns (if specified) the folder to archive messages to.""" """Returns (if specified) the folder to archive messages to."""
archive_folder = self._query_string.get('archive', None) archive_folder = self._query_string.get("archive", None)
if not archive_folder: if not archive_folder:
return None return None
return archive_folder[0] return archive_folder[0]
@ -184,7 +206,7 @@ class Mailbox(models.Model):
@property @property
def folder(self): def folder(self):
"""Returns (if specified) the folder to fetch mail from.""" """Returns (if specified) the folder to fetch mail from."""
folder = self._query_string.get('folder', None) folder = self._query_string.get("folder", None)
if not folder: if not folder:
return None return None
return folder[0] return folder[0]
@ -198,40 +220,38 @@ class Mailbox(models.Model):
""" """
if not self.uri: if not self.uri:
return None return None
elif self.type == 'imap': elif self.type == "imap":
conn = ImapTransport( conn = ImapTransport(
self.location, self.location,
port=self.port if self.port else None, port=self.port if self.port else None,
ssl=self.use_ssl, ssl=self.use_ssl,
tls=self.use_tls, tls=self.use_tls,
archive=self.archive, archive=self.archive,
folder=self.folder folder=self.folder,
) )
conn.connect(self.username, self.password) conn.connect(self.username, self.password)
elif self.type == 'gmail': elif self.type == "gmail":
conn = GmailImapTransport( conn = GmailImapTransport(
self.location, self.location,
port=self.port if self.port else None, port=self.port if self.port else None,
ssl=True, ssl=True,
archive=self.archive archive=self.archive,
) )
conn.connect(self.username, self.password) conn.connect(self.username, self.password)
elif self.type == 'pop3': elif self.type == "pop3":
conn = Pop3Transport( conn = Pop3Transport(
self.location, self.location, port=self.port if self.port else None, ssl=self.use_ssl
port=self.port if self.port else None,
ssl=self.use_ssl
) )
conn.connect(self.username, self.password) conn.connect(self.username, self.password)
elif self.type == 'maildir': elif self.type == "maildir":
conn = MaildirTransport(self.location) conn = MaildirTransport(self.location)
elif self.type == 'mbox': elif self.type == "mbox":
conn = MboxTransport(self.location) conn = MboxTransport(self.location)
elif self.type == 'babyl': elif self.type == "babyl":
conn = BabylTransport(self.location) conn = BabylTransport(self.location)
elif self.type == 'mh': elif self.type == "mh":
conn = MHTransport(self.location) conn = MHTransport(self.location)
elif self.type == 'mmdf': elif self.type == "mmdf":
conn = MMDFTransport(self.location) conn = MMDFTransport(self.location)
return conn return conn
@ -264,29 +284,22 @@ class Mailbox(models.Model):
for header, value in msg.items(): for header, value in msg.items():
new[header] = value new[header] = value
for part in msg.get_payload(): for part in msg.get_payload():
new.attach( new.attach(self._get_dehydrated_message(part, record))
self._get_dehydrated_message(part, record)
)
elif ( elif (
settings['strip_unallowed_mimetypes'] settings["strip_unallowed_mimetypes"]
and not msg.get_content_type() in settings['allowed_mimetypes'] and not msg.get_content_type() in settings["allowed_mimetypes"]
): ):
for header, value in msg.items(): for header, value in msg.items():
new[header] = value new[header] = value
# Delete header, otherwise when attempting to deserialize the # Delete header, otherwise when attempting to deserialize the
# payload, it will be expecting a body for this. # payload, it will be expecting a body for this.
del new['Content-Transfer-Encoding'] del new["Content-Transfer-Encoding"]
new[settings['altered_message_header']] = ( new[
'Stripped; Content type %s not allowed' % ( settings["altered_message_header"]
msg.get_content_type() ] = "Stripped; Content type %s not allowed" % (msg.get_content_type())
) new.set_payload("")
) elif (msg.get_content_type() not in settings["text_stored_mimetypes"]) or (
new.set_payload('') "attachment" in msg.get("Content-Disposition", "")
elif (
(
msg.get_content_type() not in settings['text_stored_mimetypes']
) or
('attachment' in msg.get('Content-Disposition', ''))
): ):
filename = None filename = None
raw_filename = msg.get_filename() raw_filename = msg.get_filename()
@ -297,17 +310,13 @@ class Mailbox(models.Model):
else: else:
_, extension = os.path.splitext(filename) _, extension = os.path.splitext(filename)
if not extension: if not extension:
extension = '.bin' extension = ".bin"
attachment = MessageAttachment() attachment = MessageAttachment()
attachment.document.save( attachment.document.save(
uuid.uuid4().hex + extension, uuid.uuid4().hex + extension,
ContentFile( ContentFile(BytesIO(msg.get_payload(decode=True)).getvalue()),
BytesIO(
msg.get_payload(decode=True)
).getvalue()
)
) )
attachment.message = record attachment.message = record
for key, value in msg.items(): for key, value in msg.items():
@ -315,14 +324,14 @@ class Mailbox(models.Model):
attachment.save() attachment.save()
placeholder = EmailMessage() placeholder = EmailMessage()
placeholder[ placeholder[settings["attachment_interpolation_header"]] = str(
settings['attachment_interpolation_header'] attachment.pk
] = str(attachment.pk) )
new = placeholder new = placeholder
else: else:
content_charset = msg.get_content_charset() content_charset = msg.get_content_charset()
if not content_charset: if not content_charset:
content_charset = 'ascii' content_charset = "ascii"
try: try:
# Make sure that the payload can be properly decoded in the # Make sure that the payload can be properly decoded in the
# defined charset, if it can't, let's mash some things # defined charset, if it can't, let's mash some things
@ -330,26 +339,15 @@ class Mailbox(models.Model):
msg.get_payload(decode=True).decode(content_charset) msg.get_payload(decode=True).decode(content_charset)
except LookupError: except LookupError:
logger.warning( logger.warning(
"Unknown encoding %s; interpreting as ASCII!", "Unknown encoding %s; interpreting as ASCII!", content_charset
content_charset
)
msg.set_payload(
msg.get_payload(decode=True).decode(
'ascii',
'ignore'
)
) )
msg.set_payload(msg.get_payload(decode=True).decode("ascii", "ignore"))
except ValueError: except ValueError:
logger.warning( logger.warning(
"Decoding error encountered; interpreting %s as ASCII!", "Decoding error encountered; interpreting %s as ASCII!",
content_charset content_charset,
)
msg.set_payload(
msg.get_payload(decode=True).decode(
'ascii',
'ignore'
)
) )
msg.set_payload(msg.get_payload(decode=True).decode("ascii", "ignore"))
new = msg new = msg
return new return new
@ -358,23 +356,19 @@ class Mailbox(models.Model):
msg._email_object = message msg._email_object = message
settings = utils.get_settings() settings = utils.get_settings()
if settings['store_original_message']: if settings["store_original_message"]:
self._process_save_original_message(message, msg) self._process_save_original_message(message, msg)
msg.mailbox = self msg.mailbox = self
if 'subject' in message: if "subject" in message:
msg.subject = ( msg.subject = utils.convert_header_to_unicode(message["subject"])[0:255]
utils.convert_header_to_unicode(message['subject'])[0:255] if "message-id" in message:
) msg.message_id = message["message-id"][0:255].strip()
if 'message-id' in message: if "from" in message:
msg.message_id = message['message-id'][0:255].strip() msg.from_header = utils.convert_header_to_unicode(message["from"])
if 'from' in message: if "to" in message:
msg.from_header = utils.convert_header_to_unicode(message['from']) msg.to_header = utils.convert_header_to_unicode(message["to"])
if 'to' in message: elif "Delivered-To" in message:
msg.to_header = utils.convert_header_to_unicode(message['to']) msg.to_header = utils.convert_header_to_unicode(message["Delivered-To"])
elif 'Delivered-To' in message:
msg.to_header = utils.convert_header_to_unicode(
message['Delivered-To']
)
msg.save() msg.save()
message = self._get_dehydrated_message(message, msg) message = self._get_dehydrated_message(message, msg)
try: try:
@ -382,13 +376,16 @@ class Mailbox(models.Model):
except KeyError as exc: except KeyError as exc:
# email.message.replace_header may raise 'KeyError' if the header # email.message.replace_header may raise 'KeyError' if the header
# 'content-transfer-encoding' is missing # 'content-transfer-encoding' is missing
logger.warning("Failed to parse message: %s", exc,) logger.warning(
"Failed to parse message: %s",
exc,
)
return None return None
msg.set_body(body) msg.set_body(body)
if message['in-reply-to']: if message["in-reply-to"]:
try: try:
msg.in_reply_to = Message.objects.filter( msg.in_reply_to = Message.objects.filter(
message_id=message['in-reply-to'].strip() message_id=message["in-reply-to"].strip()
)[0] )[0]
except IndexError: except IndexError:
pass pass
@ -397,36 +394,31 @@ class Mailbox(models.Model):
def _process_save_original_message(self, message, msg): def _process_save_original_message(self, message, msg):
settings = utils.get_settings() settings = utils.get_settings()
if settings['compress_original_message']: if settings["compress_original_message"]:
with NamedTemporaryFile(suffix=".eml.gz") as fp_tmp: with NamedTemporaryFile(suffix=".eml.gz") as fp_tmp:
with gzip.GzipFile(fileobj=fp_tmp, mode="w") as fp: with gzip.GzipFile(fileobj=fp_tmp, mode="w") as fp:
fp.write(message.as_string().encode('utf-8')) fp.write(message.as_string().encode("utf-8"))
msg.eml.save( msg.eml.save("{}.eml.gz".format(uuid.uuid4()), File(fp_tmp), save=False)
"{}.eml.gz".format(uuid.uuid4()),
File(fp_tmp),
save=False
)
else: else:
msg.eml.save( msg.eml.save(
'%s.eml' % uuid.uuid4(), "%s.eml" % uuid.uuid4(), ContentFile(message.as_string()), save=False
ContentFile(message.as_string()),
save=False
) )
def get_new_mail(self, condition=None): def get_new_mail(self, condition=None):
"""Connect to this transport and fetch new messages.""" """Connect to this transport and fetch new messages."""
new_mail = [] # Delete this
# new_mail = []
connection = self.get_connection() connection = self.get_connection()
if not connection: if not connection:
return return
for message in connection.get_message(condition): for message in connection.get_message(condition):
msg = self.process_incoming_message(message) msg = self.process_incoming_message(message)
if not msg is None: if msg is not None:
yield msg yield msg
self.last_polling = now() self.last_polling = now()
if django.VERSION >= (1, 5): # Django 1.5 introduces update_fields if django.VERSION >= (1, 5): # Django 1.5 introduces update_fields
self.save(update_fields=['last_polling']) self.save(update_fields=["last_polling"])
else: else:
self.save() self.save()
@ -434,100 +426,97 @@ class Mailbox(models.Model):
return self.name return self.name
class Meta: class Meta:
verbose_name = _('Mailbox') verbose_name = _("Mailbox")
verbose_name_plural = _('Mailboxes') verbose_name_plural = _("Mailboxes")
class IncomingMessageManager(models.Manager): class IncomingMessageManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter( return (
outgoing=False, super()
.get_queryset()
.filter(
outgoing=False,
)
) )
class OutgoingMessageManager(models.Manager): class OutgoingMessageManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter( return (
outgoing=True, super()
.get_queryset()
.filter(
outgoing=True,
)
) )
class UnreadMessageManager(models.Manager): class UnreadMessageManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter( return super().get_queryset().filter(read=None)
read=None
)
class Message(models.Model): class Message(models.Model):
mailbox = models.ForeignKey( mailbox = models.ForeignKey(
Mailbox, Mailbox,
related_name='messages', related_name="messages",
verbose_name=_('Mailbox'), verbose_name=_("Mailbox"),
on_delete=models.CASCADE on_delete=models.CASCADE,
) )
subject = models.CharField( subject = models.CharField(_("Subject"), max_length=255)
_('Subject'),
max_length=255
)
message_id = models.CharField( message_id = models.CharField(_("Message ID"), max_length=255)
_('Message ID'),
max_length=255
)
in_reply_to = models.ForeignKey( in_reply_to = models.ForeignKey(
'django_mailbox.Message', "django_mailbox.Message",
related_name='replies', related_name="replies",
blank=True, blank=True,
null=True, null=True,
verbose_name=_('In reply to'), verbose_name=_("In reply to"),
on_delete=models.CASCADE on_delete=models.CASCADE,
) )
from_header = models.CharField( from_header = models.CharField(
_('From header'), _("From header"),
max_length=255, max_length=255,
) )
to_header = models.TextField( to_header = models.TextField(
_('To header'), _("To header"),
) )
outgoing = models.BooleanField( outgoing = models.BooleanField(
_('Outgoing'), _("Outgoing"),
default=False, default=False,
blank=True, blank=True,
) )
body = models.TextField( body = models.TextField(
_('Body'), _("Body"),
) )
encoded = models.BooleanField( encoded = models.BooleanField(
_('Encoded'), _("Encoded"),
default=False, default=False,
help_text=_('True if the e-mail body is Base64 encoded'), help_text=_("True if the e-mail body is Base64 encoded"),
) )
processed = models.DateTimeField( processed = models.DateTimeField(_("Processed"), auto_now_add=True)
_('Processed'),
auto_now_add=True
)
read = models.DateTimeField( read = models.DateTimeField(
_('Read'), _("Read"),
default=None, default=None,
blank=True, blank=True,
null=True, null=True,
) )
eml = models.FileField( eml = models.FileField(
_('Raw message contents'), _("Raw message contents"),
null=True, null=True,
upload_to="messages", upload_to="messages",
help_text=_('Original full content of message') help_text=_("Original full content of message"),
) )
objects = models.Manager() objects = models.Manager()
unread_messages = UnreadMessageManager() unread_messages = UnreadMessageManager()
@ -570,13 +559,9 @@ class Message(models.Model):
def to_addresses(self): def to_addresses(self):
"""Returns a list of addresses to which this message was sent.""" """Returns a list of addresses to which this message was sent."""
addresses = [] addresses = []
for address in self.to_header.split(','): for address in self.to_header.split(","):
if address: if address:
addresses.append( addresses.append(parseaddr(address)[1].lower())
parseaddr(
address
)[1].lower()
)
return addresses return addresses
def reply(self, message): def reply(self, message):
@ -593,14 +578,12 @@ class Message(models.Model):
message.from_email = self.mailbox.from_email message.from_email = self.mailbox.from_email
else: else:
message.from_email = django_settings.DEFAULT_FROM_EMAIL message.from_email = django_settings.DEFAULT_FROM_EMAIL
message.extra_headers['Message-ID'] = make_msgid() message.extra_headers["Message-ID"] = make_msgid()
message.extra_headers['Date'] = formatdate() message.extra_headers["Date"] = formatdate()
message.extra_headers['In-Reply-To'] = self.message_id.strip() message.extra_headers["In-Reply-To"] = self.message_id.strip()
message.send() message.send()
return self.mailbox.record_outgoing_message( return self.mailbox.record_outgoing_message(
email.message_from_string( email.message_from_string(message.message().as_string())
message.message().as_string()
)
) )
@property @property
@ -608,18 +591,22 @@ class Message(models.Model):
""" """
Returns the message body matching content type 'text/plain'. Returns the message body matching content type 'text/plain'.
""" """
return utils.get_body_from_message( return (
self.get_email_object(), 'text', 'plain' utils.get_body_from_message(self.get_email_object(), "text", "plain")
).replace('=\n', '').strip() .replace("=\n", "")
.strip()
)
@property @property
def html(self): def html(self):
""" """
Returns the message body matching content type 'text/html'. Returns the message body matching content type 'text/html'.
""" """
return utils.get_body_from_message( return (
self.get_email_object(), 'text', 'html' utils.get_body_from_message(self.get_email_object(), "text", "html")
).replace('\n', '').strip() .replace("\n", "")
.strip()
)
def _rehydrate(self, msg): def _rehydrate(self, msg):
new = EmailMessage() new = EmailMessage()
@ -629,53 +616,43 @@ class Message(models.Model):
for header, value in msg.items(): for header, value in msg.items():
new[header] = value new[header] = value
for part in msg.get_payload(): for part in msg.get_payload():
new.attach( new.attach(self._rehydrate(part))
self._rehydrate(part) elif settings["attachment_interpolation_header"] in msg.keys():
)
elif settings['attachment_interpolation_header'] in msg.keys():
try: try:
attachment = MessageAttachment.objects.get( attachment = MessageAttachment.objects.get(
pk=msg[settings['attachment_interpolation_header']] pk=msg[settings["attachment_interpolation_header"]]
) )
for header, value in attachment.items(): for header, value in attachment.items():
new[header] = value new[header] = value
encoding = new['Content-Transfer-Encoding'] encoding = new["Content-Transfer-Encoding"]
if encoding and encoding.lower() == 'quoted-printable': if encoding and encoding.lower() == "quoted-printable":
# Cannot use `email.encoders.encode_quopri due to # Cannot use `email.encoders.encode_quopri due to
# bug 14360: http://bugs.python.org/issue14360 # bug 14360: http://bugs.python.org/issue14360
output = BytesIO() output = BytesIO()
encode_quopri( encode_quopri(
BytesIO( BytesIO(attachment.document.read()),
attachment.document.read()
),
output, output,
quotetabs=True, quotetabs=True,
header=False, header=False,
) )
new.set_payload( new.set_payload(output.getvalue().decode().replace(" ", "=20"))
output.getvalue().decode().replace(' ', '=20') del new["Content-Transfer-Encoding"]
) new["Content-Transfer-Encoding"] = "quoted-printable"
del new['Content-Transfer-Encoding']
new['Content-Transfer-Encoding'] = 'quoted-printable'
else: else:
new.set_payload( new.set_payload(attachment.document.read())
attachment.document.read() del new["Content-Transfer-Encoding"]
)
del new['Content-Transfer-Encoding']
encode_base64(new) encode_base64(new)
except MessageAttachment.DoesNotExist: except MessageAttachment.DoesNotExist:
new[settings['altered_message_header']] = ( new[
'Missing; Attachment %s not found' % ( settings["altered_message_header"]
msg[settings['attachment_interpolation_header']] ] = "Missing; Attachment %s not found" % (
) msg[settings["attachment_interpolation_header"]]
) )
new.set_payload('') new.set_payload("")
else: else:
for header, value in msg.items(): for header, value in msg.items():
new[header] = value new[header] = value
new.set_payload( new.set_payload(msg.get_payload())
msg.get_payload()
)
return new return new
def get_body(self): def get_body(self):
@ -686,8 +663,8 @@ class Message(models.Model):
""" """
if self.encoded: if self.encoded:
return base64.b64decode(self.body.encode('ascii')) return base64.b64decode(self.body.encode("ascii"))
return self.body.encode('utf-8') return self.body.encode("utf-8")
def set_body(self, body): def set_body(self, body):
"""Set the `body` field of this record. """Set the `body` field of this record.
@ -698,7 +675,7 @@ class Message(models.Model):
""" """
self.encoded = True self.encoded = True
self.body = base64.b64encode(body.encode('utf-8')).decode('ascii') self.body = base64.b64encode(body.encode("utf-8")).decode("ascii")
def get_email_object(self): def get_email_object(self):
"""Returns an `email.message.EmailMessage` instance representing the """Returns an `email.message.EmailMessage` instance representing the
@ -719,9 +696,9 @@ class Message(models.Model):
(https://docs.python.org/3/library/email.message.html) (https://docs.python.org/3/library/email.message.html)
""" """
if not hasattr(self, '_email_object'): # Cache fill if not hasattr(self, "_email_object"): # Cache fill
if self.eml: if self.eml:
if self.eml.name.endswith('.gz'): if self.eml.name.endswith(".gz"):
body = gzip.GzipFile(fileobj=self.eml).read() body = gzip.GzipFile(fileobj=self.eml).read()
else: else:
self.eml.open() self.eml.open()
@ -744,28 +721,28 @@ class Message(models.Model):
return self.subject return self.subject
class Meta: class Meta:
verbose_name = _('E-mail message') verbose_name = _("E-mail message")
verbose_name_plural = _('E-mail messages') verbose_name_plural = _("E-mail messages")
class MessageAttachment(models.Model): class MessageAttachment(models.Model):
message = models.ForeignKey( message = models.ForeignKey(
Message, Message,
related_name='attachments', related_name="attachments",
null=True, null=True,
blank=True, blank=True,
verbose_name=_('Message'), verbose_name=_("Message"),
on_delete=models.CASCADE on_delete=models.CASCADE,
) )
headers = models.TextField( headers = models.TextField(
_('Headers'), _("Headers"),
null=True, null=True,
blank=True, blank=True,
) )
document = models.FileField( document = models.FileField(
_('Document'), _("Document"),
upload_to=utils.get_attachment_save_path, upload_to=utils.get_attachment_save_path,
) )
@ -810,12 +787,12 @@ class MessageAttachment(models.Model):
def __getitem__(self, name): def __getitem__(self, name):
value = self._get_rehydrated_headers()[name] value = self._get_rehydrated_headers()[name]
if value is None: if value is None:
raise KeyError('Header %s does not exist' % name) raise KeyError("Header %s does not exist" % name)
return value return value
def __str__(self): def __str__(self):
return self.document.url return self.document.url
class Meta: class Meta:
verbose_name = _('Message attachment') verbose_name = _("Message attachment")
verbose_name_plural = _('Message attachments') verbose_name_plural = _("Message attachments")

View file

@ -1,3 +1,3 @@
from django.dispatch.dispatcher import Signal from django.dispatch.dispatcher import Signal
message_received = Signal(providing_args=['message']) message_received = Signal(providing_args=["message"])

View file

@ -5,54 +5,92 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding model 'Mailbox' # Adding model 'Mailbox'
db.create_table('django_mailbox_mailbox', ( db.create_table(
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), "django_mailbox_mailbox",
('name', self.gf('django.db.models.fields.CharField')(max_length=255)), (
('uri', self.gf('django.db.models.fields.CharField')(max_length=255)), ("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
)) ("name", self.gf("django.db.models.fields.CharField")(max_length=255)),
db.send_create_signal('django_mailbox', ['Mailbox']) ("uri", self.gf("django.db.models.fields.CharField")(max_length=255)),
),
)
db.send_create_signal("django_mailbox", ["Mailbox"])
# Adding model 'Message' # Adding model 'Message'
db.create_table('django_mailbox_message', ( db.create_table(
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), "django_mailbox_message",
('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)), ("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
('message_id', self.gf('django.db.models.fields.CharField')(max_length=255)), (
('from_address', self.gf('django.db.models.fields.CharField')(max_length=255)), "mailbox",
('body', self.gf('django.db.models.fields.TextField')()), self.gf("django.db.models.fields.related.ForeignKey")(
('received', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), to=orm["django_mailbox.Mailbox"]
)) ),
db.send_create_signal('django_mailbox', ['Message']) ),
(
"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): def backwards(self, orm):
# Deleting model 'Mailbox' # Deleting model 'Mailbox'
db.delete_table('django_mailbox_mailbox') db.delete_table("django_mailbox_mailbox")
# Deleting model 'Message' # Deleting model 'Message'
db.delete_table('django_mailbox_message') db.delete_table("django_mailbox_message")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('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"}),
}, },
'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'] complete_apps = ["django_mailbox"]

View file

@ -5,34 +5,64 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Changing field 'Mailbox.uri' # Changing field 'Mailbox.uri'
db.alter_column('django_mailbox_mailbox', 'uri', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) db.alter_column(
"django_mailbox_mailbox",
"uri",
self.gf("django.db.models.fields.CharField")(max_length=255, null=True),
)
def backwards(self, orm): def backwards(self, orm):
# User chose to not deal with backwards NULL issues for 'Mailbox.uri' # User chose to not deal with backwards NULL issues for 'Mailbox.uri'
raise RuntimeError("Cannot reverse this migration. 'Mailbox.uri' and its values cannot be restored.") raise RuntimeError(
"Cannot reverse this migration. 'Mailbox.uri' and its values cannot be restored."
)
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"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",
[],
{"related_name": "'messages'", "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"}),
}, },
'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', [], {'related_name': "'messages'", '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'] complete_apps = ["django_mailbox"]

View file

@ -5,37 +5,62 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Mailbox.active' # Adding field 'Mailbox.active'
db.add_column('django_mailbox_mailbox', 'active', db.add_column(
self.gf('django.db.models.fields.BooleanField')(default=True), "django_mailbox_mailbox",
keep_default=False) "active",
self.gf("django.db.models.fields.BooleanField")(default=True),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Mailbox.active' # Deleting field 'Mailbox.active'
db.delete_column('django_mailbox_mailbox', 'active') db.delete_column("django_mailbox_mailbox", "active")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"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",
[],
{"related_name": "'messages'", "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"}),
}, },
'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', [], {'related_name': "'messages'", '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'] complete_apps = ["django_mailbox"]

View file

@ -5,38 +5,67 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Message.outgoing' # Adding field 'Message.outgoing'
db.add_column('django_mailbox_message', 'outgoing', db.add_column(
self.gf('django.db.models.fields.BooleanField')(default=False), "django_mailbox_message",
keep_default=False) "outgoing",
self.gf("django.db.models.fields.BooleanField")(default=False),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Message.outgoing' # Deleting field 'Message.outgoing'
db.delete_column('django_mailbox_message', 'outgoing') db.delete_column("django_mailbox_message", "outgoing")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"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",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"received": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"subject": ("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', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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'] complete_apps = ["django_mailbox"]

View file

@ -5,34 +5,58 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
db.rename_column('django_mailbox_message', 'from_address', 'address') db.rename_column("django_mailbox_message", "from_address", "address")
db.rename_column('django_mailbox_message', 'received', 'processed') db.rename_column("django_mailbox_message", "received", "processed")
def backwards(self, orm): def backwards(self, orm):
db.rename_column('django_mailbox_message', 'address', 'from_address') db.rename_column("django_mailbox_message", "address", "from_address")
db.rename_column('django_mailbox_message', 'processed', 'received') db.rename_column("django_mailbox_message", "processed", "received")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"address": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"body": ("django.db.models.fields.TextField", [], {}),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'body': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,51 +5,116 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Message.in_reply_to' # Adding field 'Message.in_reply_to'
db.add_column('django_mailbox_message', 'in_reply_to', db.add_column(
self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='replies', null=True, to=orm['django_mailbox.Message']), "django_mailbox_message",
keep_default=False) "in_reply_to",
self.gf("django.db.models.fields.related.ForeignKey")(
blank=True,
related_name="replies",
null=True,
to=orm["django_mailbox.Message"],
),
keep_default=False,
)
# Adding M2M table for field references on 'Message' # Adding M2M table for field references on 'Message'
db.create_table('django_mailbox_message_references', ( db.create_table(
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), "django_mailbox_message_references",
('from_message', models.ForeignKey(orm['django_mailbox.message'], null=False)), (
('to_message', models.ForeignKey(orm['django_mailbox.message'], null=False)) (
)) "id",
db.create_unique('django_mailbox_message_references', ['from_message_id', 'to_message_id']) models.AutoField(
verbose_name="ID", primary_key=True, auto_created=True
),
),
(
"from_message",
models.ForeignKey(orm["django_mailbox.message"], null=False),
),
(
"to_message",
models.ForeignKey(orm["django_mailbox.message"], null=False),
),
),
)
db.create_unique(
"django_mailbox_message_references", ["from_message_id", "to_message_id"]
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Message.in_reply_to' # Deleting field 'Message.in_reply_to'
db.delete_column('django_mailbox_message', 'in_reply_to_id') db.delete_column("django_mailbox_message", "in_reply_to_id")
# Removing M2M table for field references on 'Message' # Removing M2M table for field references on 'Message'
db.delete_table('django_mailbox_message_references') db.delete_table("django_mailbox_message_references")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"address": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"body": ("django.db.models.fields.TextField", [], {}),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"references": (
"django.db.models.fields.related.ManyToManyField",
[],
{
"blank": "True",
"related_name": "'referenced_by'",
"null": "True",
"symmetrical": "False",
"to": "orm['django_mailbox.Message']",
},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'body': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'references': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'referenced_by'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['django_mailbox.Message']"}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,55 +5,108 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Deleting field 'Message.address' # Deleting field 'Message.address'
db.delete_column('django_mailbox_message', 'address') db.delete_column("django_mailbox_message", "address")
# Adding field 'Message.from_header' # Adding field 'Message.from_header'
db.add_column('django_mailbox_message', 'from_header', db.add_column(
self.gf('django.db.models.fields.CharField')(default='', max_length=255), "django_mailbox_message",
keep_default=False) "from_header",
self.gf("django.db.models.fields.CharField")(default="", max_length=255),
keep_default=False,
)
# Adding field 'Message.to_header' # Adding field 'Message.to_header'
db.add_column('django_mailbox_message', 'to_header', db.add_column(
self.gf('django.db.models.fields.TextField')(default=''), "django_mailbox_message",
keep_default=False) "to_header",
self.gf("django.db.models.fields.TextField")(default=""),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# User chose to not deal with backwards NULL issues for 'Message.address' # User chose to not deal with backwards NULL issues for 'Message.address'
raise RuntimeError("Cannot reverse this migration. 'Message.address' and its values cannot be restored.") raise RuntimeError(
"Cannot reverse this migration. 'Message.address' and its values cannot be restored."
)
# Deleting field 'Message.from_header' # Deleting field 'Message.from_header'
db.delete_column('django_mailbox_message', 'from_header') db.delete_column("django_mailbox_message", "from_header")
# Deleting field 'Message.to_header' # Deleting field 'Message.to_header'
db.delete_column('django_mailbox_message', 'to_header') db.delete_column("django_mailbox_message", "to_header")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"body": ("django.db.models.fields.TextField", [], {}),
"from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"references": (
"django.db.models.fields.related.ManyToManyField",
[],
{
"blank": "True",
"related_name": "'referenced_by'",
"null": "True",
"symmetrical": "False",
"to": "orm['django_mailbox.Message']",
},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'body': ('django.db.models.fields.TextField', [], {}),
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'references': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'referenced_by'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['django_mailbox.Message']"}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'to_header': ('django.db.models.fields.TextField', [], {})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -4,43 +4,89 @@ from south.db import db
from south.v2 import DataMigration from south.v2 import DataMigration
from django.db import models from django.db import models
class Migration(DataMigration):
class Migration(DataMigration):
def forwards(self, orm): def forwards(self, orm):
for message in orm['django_mailbox.message'].objects.all(): for message in orm["django_mailbox.message"].objects.all():
msg_object = email.message_from_string( msg_object = email.message_from_string(message.body)
message.body message.from_header = msg_object["from"]
) message.to_header = msg_object["to"]
message.from_header = msg_object['from']
message.to_header = msg_object['to']
message.save() message.save()
def backwards(self, orm): def backwards(self, orm):
raise RuntimeError('Cannot reverse this migration.') raise RuntimeError("Cannot reverse this migration.")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"body": ("django.db.models.fields.TextField", [], {}),
"from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"references": (
"django.db.models.fields.related.ManyToManyField",
[],
{
"blank": "True",
"related_name": "'referenced_by'",
"null": "True",
"symmetrical": "False",
"to": "orm['django_mailbox.Message']",
},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'body': ('django.db.models.fields.TextField', [], {}),
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'references': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'referenced_by'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['django_mailbox.Message']"}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'to_header': ('django.db.models.fields.TextField', [], {})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]
symmetrical = True symmetrical = True

View file

@ -5,43 +5,94 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Removing M2M table for field references on 'Message' # Removing M2M table for field references on 'Message'
db.delete_table('django_mailbox_message_references') db.delete_table("django_mailbox_message_references")
def backwards(self, orm): def backwards(self, orm):
# Adding M2M table for field references on 'Message' # Adding M2M table for field references on 'Message'
db.create_table('django_mailbox_message_references', ( db.create_table(
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), "django_mailbox_message_references",
('from_message', models.ForeignKey(orm['django_mailbox.message'], null=False)), (
('to_message', models.ForeignKey(orm['django_mailbox.message'], null=False)) (
)) "id",
db.create_unique('django_mailbox_message_references', ['from_message_id', 'to_message_id']) models.AutoField(
verbose_name="ID", primary_key=True, auto_created=True
),
),
(
"from_message",
models.ForeignKey(orm["django_mailbox.message"], null=False),
),
(
"to_message",
models.ForeignKey(orm["django_mailbox.message"], null=False),
),
),
)
db.create_unique(
"django_mailbox_message_references", ["from_message_id", "to_message_id"]
)
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) "uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"body": ("django.db.models.fields.TextField", [], {}),
"from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'body': ('django.db.models.fields.TextField', [], {}),
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'to_header': ('django.db.models.fields.TextField', [], {})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,41 +5,90 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Mailbox.from_email' # Adding field 'Mailbox.from_email'
db.add_column('django_mailbox_mailbox', 'from_email', db.add_column(
self.gf('django.db.models.fields.CharField')(default=None, max_length=255, null=True, blank=True), "django_mailbox_mailbox",
keep_default=False) "from_email",
self.gf("django.db.models.fields.CharField")(
default=None, max_length=255, null=True, blank=True
),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Mailbox.from_email' # Deleting field 'Mailbox.from_email'
db.delete_column('django_mailbox_mailbox', 'from_email') db.delete_column("django_mailbox_mailbox", "from_email")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"body": ("django.db.models.fields.TextField", [], {}),
"from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'body': ('django.db.models.fields.TextField', [], {}),
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'to_header': ('django.db.models.fields.TextField', [], {})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,42 +5,95 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Message.read' # Adding field 'Message.read'
db.add_column('django_mailbox_message', 'read', db.add_column(
self.gf('django.db.models.fields.DateTimeField')(default=None, null=True, blank=True), "django_mailbox_message",
keep_default=False) "read",
self.gf("django.db.models.fields.DateTimeField")(
default=None, null=True, blank=True
),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Message.read' # Deleting field 'Message.read'
db.delete_column('django_mailbox_message', 'read') db.delete_column("django_mailbox_message", "read")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
},
"django_mailbox.message": {
"Meta": {"object_name": "Message"},
"body": ("django.db.models.fields.TextField", [], {}),
"from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
}, },
'django_mailbox.message': {
'Meta': {'object_name': 'Message'},
'body': ('django.db.models.fields.TextField', [], {}),
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'to_header': ('django.db.models.fields.TextField', [], {})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -3,64 +3,148 @@ from south.db import db
from south.v2 import SchemaMigration from south.v2 import SchemaMigration
from django.db import models from django.db import models
class Migration(SchemaMigration):
class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding model 'MessageAttachment' # Adding model 'MessageAttachment'
db.create_table('django_mailbox_messageattachment', ( db.create_table(
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), "django_mailbox_messageattachment",
('document', self.gf('django.db.models.fields.files.FileField')(max_length=100)), (
)) ("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
db.send_create_signal('django_mailbox', ['MessageAttachment']) (
"document",
self.gf("django.db.models.fields.files.FileField")(max_length=100),
),
),
)
db.send_create_signal("django_mailbox", ["MessageAttachment"])
# Adding M2M table for field attachments on 'Message' # Adding M2M table for field attachments on 'Message'
db.create_table('django_mailbox_message_attachments', ( db.create_table(
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), "django_mailbox_message_attachments",
('message', models.ForeignKey(orm['django_mailbox.message'], null=False)), (
('messageattachment', models.ForeignKey(orm['django_mailbox.messageattachment'], null=False)) (
)) "id",
db.create_unique('django_mailbox_message_attachments', ['message_id', 'messageattachment_id']) models.AutoField(
verbose_name="ID", primary_key=True, auto_created=True
),
),
(
"message",
models.ForeignKey(orm["django_mailbox.message"], null=False),
),
(
"messageattachment",
models.ForeignKey(
orm["django_mailbox.messageattachment"], null=False
),
),
),
)
db.create_unique(
"django_mailbox_message_attachments", ["message_id", "messageattachment_id"]
)
def backwards(self, orm): def backwards(self, orm):
# Deleting model 'MessageAttachment' # Deleting model 'MessageAttachment'
db.delete_table('django_mailbox_messageattachment') db.delete_table("django_mailbox_messageattachment")
# Removing M2M table for field attachments on 'Message' # Removing M2M table for field attachments on 'Message'
db.delete_table('django_mailbox_message_attachments') db.delete_table("django_mailbox_message_attachments")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
}, },
'django_mailbox.message': { "django_mailbox.message": {
'Meta': {'object_name': 'Message'}, "Meta": {"object_name": "Message"},
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['django_mailbox.MessageAttachment']", 'symmetrical': 'False'}), "attachments": (
'body': ('django.db.models.fields.TextField', [], {}), "django.db.models.fields.related.ManyToManyField",
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), {
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}), "to": "orm['django_mailbox.MessageAttachment']",
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}), "symmetrical": "False",
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), },
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), ),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), "body": ("django.db.models.fields.TextField", [], {}),
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), "from_header": (
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "django.db.models.fields.CharField",
'to_header': ('django.db.models.fields.TextField', [], {}) [],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
},
"django_mailbox.messageattachment": {
"Meta": {"object_name": "MessageAttachment"},
"document": (
"django.db.models.fields.files.FileField",
[],
{"max_length": "100"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
}, },
'django_mailbox.messageattachment': {
'Meta': {'object_name': 'MessageAttachment'},
'document': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,49 +5,127 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'MessageAttachment.message' # Adding field 'MessageAttachment.message'
db.add_column('django_mailbox_messageattachment', 'message', db.add_column(
self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='attachments', null=True, to=orm['django_mailbox.Message']), "django_mailbox_messageattachment",
keep_default=False) "message",
self.gf("django.db.models.fields.related.ForeignKey")(
blank=True,
related_name="attachments",
null=True,
to=orm["django_mailbox.Message"],
),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'MessageAttachment.message' # Deleting field 'MessageAttachment.message'
db.delete_column('django_mailbox_messageattachment', 'message_id') db.delete_column("django_mailbox_messageattachment", "message_id")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
}, },
'django_mailbox.message': { "django_mailbox.message": {
'Meta': {'object_name': 'Message'}, "Meta": {"object_name": "Message"},
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'messages_old'", 'blank': 'True', 'to': "orm['django_mailbox.MessageAttachment']"}), "attachments": (
'body': ('django.db.models.fields.TextField', [], {}), "django.db.models.fields.related.ManyToManyField",
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), {
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}), "symmetrical": "False",
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}), "related_name": "'messages_old'",
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "blank": "True",
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), "to": "orm['django_mailbox.MessageAttachment']",
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), },
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), ),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "body": ("django.db.models.fields.TextField", [], {}),
'to_header': ('django.db.models.fields.TextField', [], {}) "from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
},
"django_mailbox.messageattachment": {
"Meta": {"object_name": "MessageAttachment"},
"document": (
"django.db.models.fields.files.FileField",
[],
{"max_length": "100"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"message": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'attachments_new'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
}, },
'django_mailbox.messageattachment': {
'Meta': {'object_name': 'MessageAttachment'},
'document': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attachments_new'", 'null': 'True', 'to': "orm['django_mailbox.Message']"})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -6,49 +6,121 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
no_dry_run = True no_dry_run = True
def forwards(self, orm): def forwards(self, orm):
for message in orm['django_mailbox.Message'].objects.all(): for message in orm["django_mailbox.Message"].objects.all():
for attachment in message.attachments.all(): for attachment in message.attachments.all():
attachment.message = message attachment.message = message
attachment.save() attachment.save()
def backwards(self, orm): def backwards(self, orm):
for attachment in orm['django_mailbox.MessageAttachment'].objects.all(): for attachment in orm["django_mailbox.MessageAttachment"].objects.all():
if attachment.message: if attachment.message:
attachment.message.attachments.add( attachment.message.attachments.add(attachment)
attachment
)
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
}, },
'django_mailbox.message': { "django_mailbox.message": {
'Meta': {'object_name': 'Message'}, "Meta": {"object_name": "Message"},
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'messages_old'", 'blank': 'True', 'to': "orm['django_mailbox.MessageAttachment']"}), "attachments": (
'body': ('django.db.models.fields.TextField', [], {}), "django.db.models.fields.related.ManyToManyField",
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), {
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}), "symmetrical": "False",
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}), "related_name": "'messages_old'",
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "blank": "True",
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), "to": "orm['django_mailbox.MessageAttachment']",
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), },
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), ),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "body": ("django.db.models.fields.TextField", [], {}),
'to_header': ('django.db.models.fields.TextField', [], {}) "from_header": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
},
"django_mailbox.messageattachment": {
"Meta": {"object_name": "MessageAttachment"},
"document": (
"django.db.models.fields.files.FileField",
[],
{"max_length": "100"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"message": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'attachments_new'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
}, },
'django_mailbox.messageattachment': {
'Meta': {'object_name': 'MessageAttachment'},
'document': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attachments_new'", 'null': 'True', 'to': "orm['django_mailbox.Message']"})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,60 +5,146 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Removing M2M table for field attachments on 'Message' # Removing M2M table for field attachments on 'Message'
db.delete_table('django_mailbox_message_attachments') db.delete_table("django_mailbox_message_attachments")
# Adding field 'MessageAttachment.headers' # Adding field 'MessageAttachment.headers'
db.add_column('django_mailbox_messageattachment', 'headers', db.add_column(
self.gf('django.db.models.fields.TextField')(null=True, blank=True), "django_mailbox_messageattachment",
keep_default=False) "headers",
self.gf("django.db.models.fields.TextField")(null=True, blank=True),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Adding M2M table for field attachments on 'Message' # Adding M2M table for field attachments on 'Message'
db.create_table('django_mailbox_message_attachments', ( db.create_table(
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), "django_mailbox_message_attachments",
('message', models.ForeignKey(orm['django_mailbox.message'], null=False)), (
('messageattachment', models.ForeignKey(orm['django_mailbox.messageattachment'], null=False)) (
)) "id",
db.create_unique('django_mailbox_message_attachments', ['message_id', 'messageattachment_id']) models.AutoField(
verbose_name="ID", primary_key=True, auto_created=True
),
),
(
"message",
models.ForeignKey(orm["django_mailbox.message"], null=False),
),
(
"messageattachment",
models.ForeignKey(
orm["django_mailbox.messageattachment"], null=False
),
),
),
)
db.create_unique(
"django_mailbox_message_attachments", ["message_id", "messageattachment_id"]
)
# Deleting field 'MessageAttachment.headers' # Deleting field 'MessageAttachment.headers'
db.delete_column('django_mailbox_messageattachment', 'headers') db.delete_column("django_mailbox_messageattachment", "headers")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
}, },
'django_mailbox.message': { "django_mailbox.message": {
'Meta': {'object_name': 'Message'}, "Meta": {"object_name": "Message"},
'body': ('django.db.models.fields.TextField', [], {}), "body": ("django.db.models.fields.TextField", [], {}),
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "from_header": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}), [],
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}), {"max_length": "255"},
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), ),
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), "in_reply_to": (
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), "django.db.models.fields.related.ForeignKey",
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'to_header': ('django.db.models.fields.TextField', [], {}) {
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
},
"django_mailbox.messageattachment": {
"Meta": {"object_name": "MessageAttachment"},
"document": (
"django.db.models.fields.files.FileField",
[],
{"max_length": "100"},
),
"headers": (
"django.db.models.fields.TextField",
[],
{"null": "True", "blank": "True"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"message": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'attachments'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
}, },
'django_mailbox.messageattachment': {
'Meta': {'object_name': 'MessageAttachment'},
'document': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'headers': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attachments'", 'null': 'True', 'to': "orm['django_mailbox.Message']"})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,50 +5,122 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Message.encoded' # Adding field 'Message.encoded'
db.add_column('django_mailbox_message', 'encoded', db.add_column(
self.gf('django.db.models.fields.BooleanField')(default=False), "django_mailbox_message",
keep_default=False) "encoded",
self.gf("django.db.models.fields.BooleanField")(default=False),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Message.encoded' # Deleting field 'Message.encoded'
db.delete_column('django_mailbox_message', 'encoded') db.delete_column("django_mailbox_message", "encoded")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
}, },
'django_mailbox.message': { "django_mailbox.message": {
'Meta': {'object_name': 'Message'}, "Meta": {"object_name": "Message"},
'body': ('django.db.models.fields.TextField', [], {}), "body": ("django.db.models.fields.TextField", [], {}),
'encoded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), "encoded": (
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "django.db.models.fields.BooleanField",
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), [],
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}), {"default": "False"},
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}), ),
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "from_header": (
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), "django.db.models.fields.CharField",
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), [],
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), {"max_length": "255"},
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}), ),
'to_header': ('django.db.models.fields.TextField', [], {}) "id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
},
"django_mailbox.messageattachment": {
"Meta": {"object_name": "MessageAttachment"},
"document": (
"django.db.models.fields.files.FileField",
[],
{"max_length": "100"},
),
"headers": (
"django.db.models.fields.TextField",
[],
{"null": "True", "blank": "True"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"message": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'attachments'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
}, },
'django_mailbox.messageattachment': {
'Meta': {'object_name': 'MessageAttachment'},
'document': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'headers': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attachments'", 'null': 'True', 'to': "orm['django_mailbox.Message']"})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -5,51 +5,129 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding field 'Message.eml' # Adding field 'Message.eml'
db.add_column('django_mailbox_message', 'eml', db.add_column(
self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True), "django_mailbox_message",
keep_default=False) "eml",
self.gf("django.db.models.fields.files.FileField")(
max_length=100, null=True
),
keep_default=False,
)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'Message.eml' # Deleting field 'Message.eml'
db.delete_column('django_mailbox_message', 'eml') db.delete_column("django_mailbox_message", "eml")
models = { models = {
'django_mailbox.mailbox': { "django_mailbox.mailbox": {
'Meta': {'object_name': 'Mailbox'}, "Meta": {"object_name": "Mailbox"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), "active": ("django.db.models.fields.BooleanField", [], {"default": "True"}),
'from_email': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), "from_email": (
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), "django.db.models.fields.CharField",
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'uri': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}) {
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"name": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"uri": (
"django.db.models.fields.CharField",
[],
{
"default": "None",
"max_length": "255",
"null": "True",
"blank": "True",
},
),
}, },
'django_mailbox.message': { "django_mailbox.message": {
'Meta': {'object_name': 'Message'}, "Meta": {"object_name": "Message"},
'body': ('django.db.models.fields.TextField', [], {}), "body": ("django.db.models.fields.TextField", [], {}),
'eml': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), "eml": (
'encoded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), "django.db.models.fields.files.FileField",
'from_header': ('django.db.models.fields.CharField', [], {'max_length': '255'}), [],
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), {"max_length": "100", "null": "True"},
'in_reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'replies'", 'null': 'True', 'to': "orm['django_mailbox.Message']"}), ),
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['django_mailbox.Mailbox']"}), "encoded": (
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "django.db.models.fields.BooleanField",
'outgoing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), [],
'processed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), {"default": "False"},
'read': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), ),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}), "from_header": (
'to_header': ('django.db.models.fields.TextField', [], {}) "django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"in_reply_to": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'replies'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
"mailbox": (
"django.db.models.fields.related.ForeignKey",
[],
{"related_name": "'messages'", "to": "orm['django_mailbox.Mailbox']"},
),
"message_id": (
"django.db.models.fields.CharField",
[],
{"max_length": "255"},
),
"outgoing": (
"django.db.models.fields.BooleanField",
[],
{"default": "False"},
),
"processed": (
"django.db.models.fields.DateTimeField",
[],
{"auto_now_add": "True", "blank": "True"},
),
"read": (
"django.db.models.fields.DateTimeField",
[],
{"default": "None", "null": "True", "blank": "True"},
),
"subject": ("django.db.models.fields.CharField", [], {"max_length": "255"}),
"to_header": ("django.db.models.fields.TextField", [], {}),
},
"django_mailbox.messageattachment": {
"Meta": {"object_name": "MessageAttachment"},
"document": (
"django.db.models.fields.files.FileField",
[],
{"max_length": "100"},
),
"headers": (
"django.db.models.fields.TextField",
[],
{"null": "True", "blank": "True"},
),
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
"message": (
"django.db.models.fields.related.ForeignKey",
[],
{
"blank": "True",
"related_name": "'attachments'",
"null": "True",
"to": "orm['django_mailbox.Message']",
},
),
}, },
'django_mailbox.messageattachment': {
'Meta': {'object_name': 'MessageAttachment'},
'document': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'headers': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attachments'", 'null': 'True', 'to': "orm['django_mailbox.Message']"})
}
} }
complete_apps = ['django_mailbox'] complete_apps = ["django_mailbox"]

View file

@ -17,39 +17,37 @@ def get_email_as_text(name):
with open( with open(
os.path.join( os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'messages', "messages",
name, name,
), ),
'rb' "rb",
) as f: ) as f:
return f.read() return f.read()
class EmailMessageTestCase(TestCase): class EmailMessageTestCase(TestCase):
ALLOWED_EXTRA_HEADERS = [ ALLOWED_EXTRA_HEADERS = [
'MIME-Version', "MIME-Version",
'Content-Transfer-Encoding', "Content-Transfer-Encoding",
] ]
def setUp(self): def setUp(self):
dm_settings = utils.get_settings() dm_settings = utils.get_settings()
self._ALLOWED_MIMETYPES = dm_settings['allowed_mimetypes'] self._ALLOWED_MIMETYPES = dm_settings["allowed_mimetypes"]
self._STRIP_UNALLOWED_MIMETYPES = ( self._STRIP_UNALLOWED_MIMETYPES = dm_settings["strip_unallowed_mimetypes"]
dm_settings['strip_unallowed_mimetypes'] self._TEXT_STORED_MIMETYPES = dm_settings["text_stored_mimetypes"]
)
self._TEXT_STORED_MIMETYPES = dm_settings['text_stored_mimetypes']
self.mailbox = Mailbox.objects.create(from_email='from@example.com') self.mailbox = Mailbox.objects.create(from_email="from@example.com")
self.test_account = os.environ.get('EMAIL_ACCOUNT') self.test_account = os.environ.get("EMAIL_ACCOUNT")
self.test_password = os.environ.get('EMAIL_PASSWORD') self.test_password = os.environ.get("EMAIL_PASSWORD")
self.test_smtp_server = os.environ.get('EMAIL_SMTP_SERVER') self.test_smtp_server = os.environ.get("EMAIL_SMTP_SERVER")
self.test_from_email = 'nobody@nowhere.com' self.test_from_email = "nobody@nowhere.com"
self.maximum_wait_seconds = 60 * 5 self.maximum_wait_seconds = 60 * 5
settings.EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' settings.EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
settings.EMAIL_HOST = self.test_smtp_server settings.EMAIL_HOST = self.test_smtp_server
settings.EMAIL_PORT = 587 settings.EMAIL_PORT = 587
settings.EMAIL_HOST_USER = self.test_account settings.EMAIL_HOST_USER = self.test_account
@ -80,10 +78,10 @@ class EmailMessageTestCase(TestCase):
with open( with open(
os.path.join( os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'messages', "messages",
name, name,
), ),
'rb' "rb",
) as f: ) as f:
return f.read() return f.read()
@ -92,26 +90,26 @@ class EmailMessageTestCase(TestCase):
return email.message_from_bytes(copy) return email.message_from_bytes(copy)
def _headers_identical(self, left, right, header=None): def _headers_identical(self, left, right, header=None):
""" Check if headers are (close enough to) identical. """Check if headers are (close enough to) identical.
* This is particularly tricky because Python 2.6, Python 2.7 and * This is particularly tricky because Python 2.6, Python 2.7 and
Python 3 each handle header strings slightly differently. This Python 3 each handle header strings slightly differently. This
should mash away all of the differences, though. should mash away all of the differences, though.
* This also has a small loophole in that when re-writing e-mail * This also has a small loophole in that when re-writing e-mail
payload encodings, we re-build the Content-Type header, so if the payload encodings, we re-build the Content-Type header, so if the
header was originally unquoted, it will be quoted when rehydrating header was originally unquoted, it will be quoted when rehydrating
the e-mail message. the e-mail message.
""" """
if header.lower() == 'content-type': if header.lower() == "content-type":
# Special case; given that we re-write the header, we'll be quoting # Special case; given that we re-write the header, we'll be quoting
# the new content type; we need to make sure that doesn't cause # the new content type; we need to make sure that doesn't cause
# this comparison to fail. Also, the case of the encoding could # this comparison to fail. Also, the case of the encoding could
# be changed, etc. etc. etc. # be changed, etc. etc. etc.
left = left.replace('"', '').upper() left = left.replace('"', "").upper()
right = right.replace('"', '').upper() right = right.replace('"', "").upper()
left = left.replace('\n\t', ' ').replace('\n ', ' ') left = left.replace("\n\t", " ").replace("\n ", " ")
right = right.replace('\n\t', ' ').replace('\n ', ' ') right = right.replace("\n\t", " ").replace("\n ", " ")
if right != left: if right != left:
return False return False
return True return True
@ -152,10 +150,7 @@ class EmailMessageTestCase(TestCase):
if len(left_payloads) != len(right_payloads): if len(left_payloads) != len(right_payloads):
self._raise_mismatched(left, right) self._raise_mismatched(left, right)
for n in range(len(left_payloads)): for n in range(len(left_payloads)):
self.compare_email_objects( self.compare_email_objects(left_payloads[n], right_payloads[n])
left_payloads[n],
right_payloads[n]
)
else: else:
if left.get_payload() is None or right.get_payload() is None: if left.get_payload() is None or right.get_payload() is None:
if left.get_payload() is None: if left.get_payload() is None:
@ -170,8 +165,7 @@ class EmailMessageTestCase(TestCase):
def _raise_mismatched(self, left, right): def _raise_mismatched(self, left, right):
raise AssertionError( raise AssertionError(
"Message payloads do not match:\n{}\n{}".format( "Message payloads do not match:\n{}\n{}".format(
left.as_string(), left.as_string(), right.as_string()
right.as_string()
) )
) )

View file

@ -1,12 +1,12 @@
DATABASES = { DATABASES = {
'default': { "default": {
'NAME': 'db.sqlite3', "NAME": "db.sqlite3",
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
}, },
} }
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django_mailbox', "django_mailbox",
] ]
SECRET_KEY = 'beepboop' SECRET_KEY = "beepboop"

View file

@ -8,16 +8,14 @@ from django_mailbox.models import Mailbox
from django_mailbox.tests.base import EmailMessageTestCase from django_mailbox.tests.base import EmailMessageTestCase
__all__ = ['TestImap'] __all__ = ["TestImap"]
class TestImap(EmailMessageTestCase): class TestImap(EmailMessageTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.test_imap_server = ( self.test_imap_server = os.environ.get("EMAIL_IMAP_SERVER")
os.environ.get('EMAIL_IMAP_SERVER')
)
required_settings = [ required_settings = [
self.test_imap_server, self.test_imap_server,
@ -35,8 +33,7 @@ class TestImap(EmailMessageTestCase):
) )
self.mailbox = Mailbox.objects.create( self.mailbox = Mailbox.objects.create(
name='Integration Test Imap', name="Integration Test Imap", uri=self.get_connection_string()
uri=self.get_connection_string()
) )
self.arbitrary_identifier = str(uuid.uuid4()) self.arbitrary_identifier = str(uuid.uuid4())
@ -48,20 +45,19 @@ class TestImap(EmailMessageTestCase):
) )
def test_get_imap_message(self): def test_get_imap_message(self):
text_content = 'This is some content' text_content = "This is some content"
msg = EmailMultiAlternatives( msg = EmailMultiAlternatives(
self.arbitrary_identifier, self.arbitrary_identifier,
text_content, text_content,
self.test_from_email, self.test_from_email,
[ [
self.test_account, self.test_account,
] ],
) )
msg.send() msg.send()
messages = self._get_new_messages( messages = self._get_new_messages(
self.mailbox, self.mailbox, condition=lambda m: m["subject"] == self.arbitrary_identifier
condition=lambda m: m['subject'] == self.arbitrary_identifier
) )
message = next(messages) message = next(messages)

View file

@ -5,15 +5,15 @@ from django.test import TestCase
from django_mailbox.models import Mailbox from django_mailbox.models import Mailbox
__all__ = ['TestMailbox'] __all__ = ["TestMailbox"]
class TestMailbox(TestCase): class TestMailbox(TestCase):
def test_protocol_info(self): def test_protocol_info(self):
mailbox = Mailbox() mailbox = Mailbox()
mailbox.uri = 'alpha://test.com' mailbox.uri = "alpha://test.com"
expected_protocol = 'alpha' expected_protocol = "alpha"
actual_protocol = mailbox._protocol_info.scheme actual_protocol = mailbox._protocol_info.scheme
self.assertEqual( self.assertEqual(
@ -23,24 +23,30 @@ class TestMailbox(TestCase):
def test_last_polling_field_exists(self): def test_last_polling_field_exists(self):
mailbox = Mailbox() mailbox = Mailbox()
self.assertTrue(hasattr(mailbox, 'last_polling')) self.assertTrue(hasattr(mailbox, "last_polling"))
def test_get_new_mail_update_last_polling(self): def test_get_new_mail_update_last_polling(self):
mailbox = Mailbox.objects.create(uri="mbox://" + os.path.join( mailbox = Mailbox.objects.create(
uri="mbox://"
+ os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'messages', "messages",
'generic_message.eml', "generic_message.eml",
)) )
)
self.assertEqual(mailbox.last_polling, None) self.assertEqual(mailbox.last_polling, None)
list(mailbox.get_new_mail()) list(mailbox.get_new_mail())
self.assertNotEqual(mailbox.last_polling, None) self.assertNotEqual(mailbox.last_polling, None)
def test_queryset_get_new_mail(self): def test_queryset_get_new_mail(self):
mailbox = Mailbox.objects.create(uri="mbox://" + os.path.join( mailbox = Mailbox.objects.create(
uri="mbox://"
+ os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'messages', "messages",
'generic_message.eml', "generic_message.eml",
)) )
)
Mailbox.objects.filter(pk=mailbox.pk).get_new_mail() Mailbox.objects.filter(pk=mailbox.pk).get_new_mail()
mailbox.refresh_from_db() mailbox.refresh_from_db()
self.assertNotEqual(mailbox.last_polling, None) self.assertNotEqual(mailbox.last_polling, None)

View file

@ -7,20 +7,20 @@ from django_mailbox.models import Message
from django_mailbox.tests.base import EmailMessageTestCase from django_mailbox.tests.base import EmailMessageTestCase
__all__ = ['TestMessageFlattening'] __all__ = ["TestMessageFlattening"]
class TestMessageFlattening(EmailMessageTestCase): class TestMessageFlattening(EmailMessageTestCase):
def test_quopri_message_is_properly_rehydrated(self): def test_quopri_message_is_properly_rehydrated(self):
incoming_email_object = self._get_email_object( incoming_email_object = self._get_email_object(
'message_with_many_multiparts.eml', "message_with_many_multiparts.eml",
) )
# Note: this is identical to the above, but it appears that # Note: this is identical to the above, but it appears that
# while reading-in an e-mail message, we do alter it slightly # while reading-in an e-mail message, we do alter it slightly
expected_email_object = self._get_email_object( expected_email_object = self._get_email_object(
'message_with_many_multiparts.eml', "message_with_many_multiparts.eml",
) )
models.TEXT_STORED_MIMETYPES = ['text/plain'] models.TEXT_STORED_MIMETYPES = ["text/plain"]
msg = self.mailbox.process_incoming_message(incoming_email_object) msg = self.mailbox.process_incoming_message(incoming_email_object)
@ -33,12 +33,12 @@ class TestMessageFlattening(EmailMessageTestCase):
def test_base64_message_is_properly_rehydrated(self): def test_base64_message_is_properly_rehydrated(self):
incoming_email_object = self._get_email_object( incoming_email_object = self._get_email_object(
'message_with_attachment.eml', "message_with_attachment.eml",
) )
# Note: this is identical to the above, but it appears that # Note: this is identical to the above, but it appears that
# while reading-in an e-mail message, we do alter it slightly # while reading-in an e-mail message, we do alter it slightly
expected_email_object = self._get_email_object( expected_email_object = self._get_email_object(
'message_with_attachment.eml', "message_with_attachment.eml",
) )
msg = self.mailbox.process_incoming_message(incoming_email_object) msg = self.mailbox.process_incoming_message(incoming_email_object)
@ -52,10 +52,10 @@ class TestMessageFlattening(EmailMessageTestCase):
def test_message_handles_rehydration_problems(self): def test_message_handles_rehydration_problems(self):
incoming_email_object = self._get_email_object( incoming_email_object = self._get_email_object(
'message_with_defective_attachment_association.eml', "message_with_defective_attachment_association.eml",
) )
expected_email_object = self._get_email_object( expected_email_object = self._get_email_object(
'message_with_defective_attachment_association_result.eml', "message_with_defective_attachment_association_result.eml",
) )
# Note: this is identical to the above, but it appears that # Note: this is identical to the above, but it appears that
# while reading-in an e-mail message, we do alter it slightly # while reading-in an e-mail message, we do alter it slightly
@ -74,17 +74,17 @@ class TestMessageFlattening(EmailMessageTestCase):
def test_message_content_type_stripping(self): def test_message_content_type_stripping(self):
incoming_email_object = self._get_email_object( incoming_email_object = self._get_email_object(
'message_with_many_multiparts.eml', "message_with_many_multiparts.eml",
) )
expected_email_object = self._get_email_object( expected_email_object = self._get_email_object(
'message_with_many_multiparts_stripped_html.eml', "message_with_many_multiparts_stripped_html.eml",
) )
default_settings = utils.get_settings() default_settings = utils.get_settings()
with mock.patch('django_mailbox.utils.get_settings') as get_settings: with mock.patch("django_mailbox.utils.get_settings") as get_settings:
altered = copy.deepcopy(default_settings) altered = copy.deepcopy(default_settings)
altered['strip_unallowed_mimetypes'] = True altered["strip_unallowed_mimetypes"] = True
altered['allowed_mimetypes'] = ['text/plain'] altered["allowed_mimetypes"] = ["text/plain"]
get_settings.return_value = altered get_settings.return_value = altered
@ -100,7 +100,7 @@ class TestMessageFlattening(EmailMessageTestCase):
def test_message_processing_unknown_encoding(self): def test_message_processing_unknown_encoding(self):
incoming_email_object = self._get_email_object( incoming_email_object = self._get_email_object(
'message_with_invalid_encoding.eml', "message_with_invalid_encoding.eml",
) )
msg = self.mailbox.process_incoming_message(incoming_email_object) msg = self.mailbox.process_incoming_message(incoming_email_object)

View file

@ -1,6 +1,7 @@
import gzip import gzip
import os.path import os.path
import sys
# import sys
import copy import copy
from unittest import mock from unittest import mock
@ -12,64 +13,56 @@ from django_mailbox.tests.base import EmailMessageTestCase
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
__all__ = ['TestProcessEmail'] __all__ = ["TestProcessEmail"]
class TestProcessEmail(EmailMessageTestCase): class TestProcessEmail(EmailMessageTestCase):
def test_message_without_attachments(self): def test_message_without_attachments(self):
message = self._get_email_object('generic_message.eml') message = self._get_email_object("generic_message.eml")
mailbox = Mailbox.objects.create() mailbox = Mailbox.objects.create()
msg = mailbox.process_incoming_message(message) msg = mailbox.process_incoming_message(message)
self.assertEqual( self.assertEqual(msg.mailbox, mailbox)
msg.mailbox, self.assertEqual(msg.subject, "Message Without Attachment")
mailbox
)
self.assertEqual(msg.subject, 'Message Without Attachment')
self.assertEqual( self.assertEqual(
msg.message_id, msg.message_id,
( ("<CAMdmm+hGH8Dgn-_0xnXJCd=PhyNAiouOYm5zFP0z" "-foqTO60zA@mail.gmail.com>"),
'<CAMdmm+hGH8Dgn-_0xnXJCd=PhyNAiouOYm5zFP0z'
'-foqTO60zA@mail.gmail.com>'
)
) )
self.assertEqual( self.assertEqual(
msg.from_header, msg.from_header,
'Adam Coddington <test@adamcoddington.net>', "Adam Coddington <test@adamcoddington.net>",
) )
self.assertEqual( self.assertEqual(
msg.to_header, msg.to_header,
'Adam Coddington <test@adamcoddington.net>', "Adam Coddington <test@adamcoddington.net>",
) )
def test_message_with_encoded_attachment_filenames(self): def test_message_with_encoded_attachment_filenames(self):
message = self._get_email_object( message = self._get_email_object("message_with_koi8r_filename_attachments.eml")
'message_with_koi8r_filename_attachments.eml'
)
mailbox = Mailbox.objects.create() mailbox = Mailbox.objects.create()
msg = mailbox.process_incoming_message(message) msg = mailbox.process_incoming_message(message)
attachments = msg.attachments.order_by('pk').all() attachments = msg.attachments.order_by("pk").all()
self.assertEqual( self.assertEqual(
'\u041f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u043b' "\u041f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u043b"
'\u043e\u0436\u0435\u043d\u0438\u0439 HSE Career Fair 8 \u0430' "\u043e\u0436\u0435\u043d\u0438\u0439 HSE Career Fair 8 \u0430"
'\u043f\u0440\u0435\u043b\u044f 2016.pdf', "\u043f\u0440\u0435\u043b\u044f 2016.pdf",
attachments[0].get_filename() attachments[0].get_filename(),
) )
self.assertEqual( self.assertEqual(
'\u0412\u0435\u0434\u043e\u043c\u043e\u0441\u0442\u0438.pdf', "\u0412\u0435\u0434\u043e\u043c\u043e\u0441\u0442\u0438.pdf",
attachments[1].get_filename() attachments[1].get_filename(),
) )
self.assertEqual( self.assertEqual(
'\u041f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u043b' "\u041f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u043b"
'\u043e\u0436\u0435\u043d\u0438\u0439 2016.pptx', "\u043e\u0436\u0435\u043d\u0438\u0439 2016.pptx",
attachments[2].get_filename() attachments[2].get_filename(),
) )
def test_message_with_attachments(self): def test_message_with_attachments(self):
message = self._get_email_object('message_with_attachment.eml') message = self._get_email_object("message_with_attachment.eml")
mailbox = Mailbox.objects.create() mailbox = Mailbox.objects.create()
msg = mailbox.process_incoming_message(message) msg = mailbox.process_incoming_message(message)
@ -85,16 +78,16 @@ class TestProcessEmail(EmailMessageTestCase):
attachment = msg.attachments.all()[0] attachment = msg.attachments.all()[0]
self.assertEqual( self.assertEqual(
attachment.get_filename(), attachment.get_filename(),
'heart.png', "heart.png",
) )
def test_message_with_utf8_attachment_header(self): def test_message_with_utf8_attachment_header(self):
""" Ensure that we properly handle UTF-8 encoded attachment """Ensure that we properly handle UTF-8 encoded attachment
Safe for regress of #104 too Safe for regress of #104 too
""" """
email_object = self._get_email_object( email_object = self._get_email_object(
'message_with_utf8_attachment.eml', "message_with_utf8_attachment.eml",
) )
mailbox = Mailbox.objects.create() mailbox = Mailbox.objects.create()
msg = mailbox.process_incoming_message(email_object) msg = mailbox.process_incoming_message(email_object)
@ -108,24 +101,18 @@ class TestProcessEmail(EmailMessageTestCase):
) )
attachment = msg.attachments.all()[0] attachment = msg.attachments.all()[0]
self.assertEqual( self.assertEqual(attachment.get_filename(), "pi\u0142kochwyty.jpg")
attachment.get_filename(),
'pi\u0142kochwyty.jpg'
)
attachment = msg.attachments.all()[1] attachment = msg.attachments.all()[1]
self.assertEqual( self.assertEqual(attachment.get_filename(), "odpowied\u017a Burmistrza.jpg")
attachment.get_filename(),
'odpowied\u017a Burmistrza.jpg'
)
def test_message_get_text_body(self): def test_message_get_text_body(self):
message = self._get_email_object('multipart_text.eml') message = self._get_email_object("multipart_text.eml")
mailbox = Mailbox.objects.create() mailbox = Mailbox.objects.create()
msg = mailbox.process_incoming_message(message) msg = mailbox.process_incoming_message(message)
expected_results = 'Hello there!' expected_results = "Hello there!"
actual_results = msg.text.strip() actual_results = msg.text.strip()
self.assertEqual( self.assertEqual(
@ -135,29 +122,21 @@ class TestProcessEmail(EmailMessageTestCase):
def test_get_text_body_properly_recomposes_line_continuations(self): def test_get_text_body_properly_recomposes_line_continuations(self):
message = Message() message = Message()
email_object = self._get_email_object( email_object = self._get_email_object("message_with_long_text_lines.eml")
'message_with_long_text_lines.eml'
)
message.get_email_object = lambda: email_object message.get_email_object = lambda: email_object
actual_text = message.text actual_text = message.text
expected_text = ( expected_text = (
'The one of us with a bike pump is far ahead, ' "The one of us with a bike pump is far ahead, "
'but a man stopped to help us and gave us his pump.' "but a man stopped to help us and gave us his pump."
) )
self.assertEqual( self.assertEqual(actual_text, expected_text)
actual_text,
expected_text
)
def test_get_body_properly_handles_unicode_body(self): def test_get_body_properly_handles_unicode_body(self):
with open( with open(
os.path.join( os.path.join(os.path.dirname(__file__), "messages/generic_message.eml")
os.path.dirname(__file__),
'messages/generic_message.eml'
)
) as f: ) as f:
unicode_body = f.read() unicode_body = f.read()
@ -167,23 +146,18 @@ class TestProcessEmail(EmailMessageTestCase):
expected_body = unicode_body expected_body = unicode_body
actual_body = message.get_email_object().as_string() actual_body = message.get_email_object().as_string()
self.assertEqual( self.assertEqual(expected_body, actual_body)
expected_body,
actual_body
)
def test_message_issue_82(self): def test_message_issue_82(self):
""" Ensure that we properly handle incorrectly encoded messages """Ensure that we properly handle incorrectly encoded messages"""
email_object = self._get_email_object("email_issue_82.eml")
""" it = "works"
email_object = self._get_email_object('email_issue_82.eml')
it = 'works'
try: try:
# it's ok to call as_string() before passing email_object # it's ok to call as_string() before passing email_object
# to _get_dehydrated_message() # to _get_dehydrated_message()
email_object.as_string() email_object.as_string()
except: except Exception:
it = 'do not works' it = "do not works"
success = True success = True
try: try:
@ -191,15 +165,15 @@ class TestProcessEmail(EmailMessageTestCase):
except ValueError: except ValueError:
success = False success = False
self.assertEqual(it, 'works') self.assertEqual(it, "works")
self.assertEqual(True, success) self.assertEqual(True, success)
def test_message_issue_82_bis(self): def test_message_issue_82_bis(self):
""" Ensure that the email object is good before and after """Ensure that the email object is good before and after
calling _get_dehydrated_message() calling _get_dehydrated_message()
""" """
message = self._get_email_object('email_issue_82.eml') message = self._get_email_object("email_issue_82.eml")
success = True success = True
@ -209,16 +183,16 @@ class TestProcessEmail(EmailMessageTestCase):
# msg.eml.save('%s.eml' % uuid.uuid4(), ContentFile(message), # msg.eml.save('%s.eml' % uuid.uuid4(), ContentFile(message),
# save=False) # save=False)
msg.mailbox = self.mailbox msg.mailbox = self.mailbox
if 'subject' in message: if "subject" in message:
msg.subject = convert_header_to_unicode(message['subject'])[0:255] msg.subject = convert_header_to_unicode(message["subject"])[0:255]
if 'message-id' in message: if "message-id" in message:
msg.message_id = message['message-id'][0:255] msg.message_id = message["message-id"][0:255]
if 'from' in message: if "from" in message:
msg.from_header = convert_header_to_unicode(message['from']) msg.from_header = convert_header_to_unicode(message["from"])
if 'to' in message: if "to" in message:
msg.to_header = convert_header_to_unicode(message['to']) msg.to_header = convert_header_to_unicode(message["to"])
elif 'Delivered-To' in message: elif "Delivered-To" in message:
msg.to_header = convert_header_to_unicode(message['Delivered-To']) msg.to_header = convert_header_to_unicode(message["Delivered-To"])
msg.save() msg.save()
# here the message is ok # here the message is ok
@ -227,14 +201,14 @@ class TestProcessEmail(EmailMessageTestCase):
try: try:
# here as_string raises UnicodeEncodeError # here as_string raises UnicodeEncodeError
str_msg = message.as_string() str_msg = message.as_string()
except: except Exception:
success = False success = False
msg.set_body(str_msg) msg.set_body(str_msg)
if message['in-reply-to']: if message["in-reply-to"]:
try: try:
msg.in_reply_to = Message.objects.filter( msg.in_reply_to = Message.objects.filter(
message_id=message['in-reply-to'] message_id=message["in-reply-to"]
)[0] )[0]
except IndexError: except IndexError:
pass pass
@ -243,18 +217,20 @@ class TestProcessEmail(EmailMessageTestCase):
self.assertEqual(True, success) self.assertEqual(True, success)
def test_message_with_misplaced_utf8_content(self): def test_message_with_misplaced_utf8_content(self):
""" Ensure that we properly handle incorrectly encoded messages """Ensure that we properly handle incorrectly encoded messages
``message_with_utf8_char.eml``'s primary text payload is marked ``message_with_utf8_char.eml``'s primary text payload is marked
as being iso-8859-1 data, but actually contains UTF-8 bytes. as being iso-8859-1 data, but actually contains UTF-8 bytes.
""" """
email_object = self._get_email_object('message_with_utf8_char.eml') email_object = self._get_email_object("message_with_utf8_char.eml")
msg = self.mailbox.process_incoming_message(email_object) msg = self.mailbox.process_incoming_message(email_object)
expected_text = 'This message contains funny UTF16 characters ' + \ expected_text = (
'like this one: "\xc2\xa0" and this one "\xe2\x9c\xbf".' "This message contains funny UTF16 characters "
+ 'like this one: "\xc2\xa0" and this one "\xe2\x9c\xbf".'
)
actual_text = msg.text actual_text = msg.text
self.assertEqual( self.assertEqual(
@ -263,7 +239,7 @@ class TestProcessEmail(EmailMessageTestCase):
) )
def test_message_with_invalid_content_for_declared_encoding(self): def test_message_with_invalid_content_for_declared_encoding(self):
""" Ensure that we gracefully handle mis-encoded bodies. """Ensure that we gracefully handle mis-encoded bodies.
Should a payload body be misencoded, we should: Should a payload body be misencoded, we should:
@ -275,7 +251,7 @@ class TestProcessEmail(EmailMessageTestCase):
""" """
email_object = self._get_email_object( email_object = self._get_email_object(
'message_with_invalid_content_for_declared_encoding.eml', "message_with_invalid_content_for_declared_encoding.eml",
) )
msg = self.mailbox.process_incoming_message(email_object) msg = self.mailbox.process_incoming_message(email_object)
@ -284,18 +260,20 @@ class TestProcessEmail(EmailMessageTestCase):
def test_message_with_valid_content_in_single_byte_encoding(self): def test_message_with_valid_content_in_single_byte_encoding(self):
email_object = self._get_email_object( email_object = self._get_email_object(
'message_with_single_byte_encoding.eml', "message_with_single_byte_encoding.eml",
) )
msg = self.mailbox.process_incoming_message(email_object) msg = self.mailbox.process_incoming_message(email_object)
actual_text = msg.text actual_text = msg.text
expected_body = '\u042d\u0442\u043e ' + \ expected_body = (
'\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 ' + \ "\u042d\u0442\u043e "
'\u0438\u043c\u0435\u0435\u0442 ' + \ + "\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 "
'\u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d' + \ + "\u0438\u043c\u0435\u0435\u0442 "
'\u0443\u044e ' + \ + "\u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d"
'\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430.' + "\u0443\u044e "
+ "\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430."
)
self.assertEqual( self.assertEqual(
actual_text, actual_text,
@ -304,42 +282,44 @@ class TestProcessEmail(EmailMessageTestCase):
def test_message_with_single_byte_subject_encoding(self): def test_message_with_single_byte_subject_encoding(self):
email_object = self._get_email_object( email_object = self._get_email_object(
'message_with_single_byte_extended_subject_encoding.eml', "message_with_single_byte_extended_subject_encoding.eml",
) )
msg = self.mailbox.process_incoming_message(email_object) msg = self.mailbox.process_incoming_message(email_object)
expected_subject = '\u00D3\u00E7\u00ED\u00E0\u00E9 \u00EA\u00E0\u00EA ' + \ expected_subject = (
'\u00E7\u00E0\u00F0\u00E0\u00E1\u00E0\u00F2\u00FB\u00E2' + \ "\u00D3\u00E7\u00ED\u00E0\u00E9 \u00EA\u00E0\u00EA "
'\u00E0\u00F2\u00FC \u00EE\u00F2 1000$ \u00E2 ' + \ + "\u00E7\u00E0\u00F0\u00E0\u00E1\u00E0\u00F2\u00FB\u00E2"
'\u00ED\u00E5\u00E4\u00E5\u00EB\u00FE!' + "\u00E0\u00F2\u00FC \u00EE\u00F2 1000$ \u00E2 "
+ "\u00ED\u00E5\u00E4\u00E5\u00EB\u00FE!"
)
actual_subject = msg.subject actual_subject = msg.subject
self.assertEqual(actual_subject, expected_subject) self.assertEqual(actual_subject, expected_subject)
expected_from = 'test test <mr.test32@mail.ru>' expected_from = "test test <mr.test32@mail.ru>"
actual_from = msg.from_header actual_from = msg.from_header
self.assertEqual(expected_from, actual_from) self.assertEqual(expected_from, actual_from)
def test_message_reply(self): def test_message_reply(self):
email_object = EmailMessage( email_object = EmailMessage(
'Test subject', # subject "Test subject", # subject
'Test body', # body "Test body", # body
'username@example.com', # from "username@example.com", # from
['mr.test32@mail.ru'], # to ["mr.test32@mail.ru"], # to
) )
msg = self.mailbox.record_outgoing_message(email_object.message()) msg = self.mailbox.record_outgoing_message(email_object.message())
self.assertTrue(msg.outgoing) self.assertTrue(msg.outgoing)
actual_from = 'username@example.com' actual_from = "username@example.com"
reply_email_object = EmailMessage( reply_email_object = EmailMessage(
'Test subject', # subject "Test subject", # subject
'Test body', # body "Test body", # body
actual_from, # from actual_from, # from
['mr.test32@mail.ru'], # to ["mr.test32@mail.ru"], # to
) )
with mock.patch.object(reply_email_object, 'send'): with mock.patch.object(reply_email_object, "send"):
reply_msg = msg.reply(reply_email_object) reply_msg = msg.reply(reply_email_object)
self.assertEqual(reply_msg.in_reply_to, msg) self.assertEqual(reply_msg.in_reply_to, msg)
@ -348,62 +328,58 @@ class TestProcessEmail(EmailMessageTestCase):
reply_email_object.from_email = None reply_email_object.from_email = None
with mock.patch.object(reply_email_object, 'send'): with mock.patch.object(reply_email_object, "send"):
second_reply_msg = msg.reply(reply_email_object) second_reply_msg = msg.reply(reply_email_object)
self.assertEqual(self.mailbox.from_email, second_reply_msg.from_header) self.assertEqual(self.mailbox.from_email, second_reply_msg.from_header)
def test_message_with_text_attachment(self): def test_message_with_text_attachment(self):
email_object = self._get_email_object( email_object = self._get_email_object(
'message_with_text_attachment.eml', "message_with_text_attachment.eml",
) )
msg = self.mailbox.process_incoming_message(email_object) msg = self.mailbox.process_incoming_message(email_object)
self.assertEqual(msg.attachments.all().count(), 1) self.assertEqual(msg.attachments.all().count(), 1)
self.assertEqual('Has an attached text document, too!', msg.text) self.assertEqual("Has an attached text document, too!", msg.text)
def test_message_with_long_content(self): def test_message_with_long_content(self):
email_object = self._get_email_object( email_object = self._get_email_object(
'message_with_long_content.eml', "message_with_long_content.eml",
) )
size = len(force_text(email_object.as_string())) size = len(force_text(email_object.as_string()))
msg = self.mailbox.process_incoming_message(email_object) msg = self.mailbox.process_incoming_message(email_object)
self.assertEqual(size, self.assertEqual(size, len(force_text(msg.get_email_object().as_string())))
len(force_text(msg.get_email_object().as_string())))
def test_message_saved(self): def test_message_saved(self):
message = self._get_email_object('generic_message.eml') message = self._get_email_object("generic_message.eml")
default_settings = utils.get_settings() default_settings = utils.get_settings()
with mock.patch('django_mailbox.utils.get_settings') as get_settings: with mock.patch("django_mailbox.utils.get_settings") as get_settings:
altered = copy.deepcopy(default_settings) altered = copy.deepcopy(default_settings)
altered['store_original_message'] = True altered["store_original_message"] = True
get_settings.return_value = altered get_settings.return_value = altered
msg = self.mailbox.process_incoming_message(message) msg = self.mailbox.process_incoming_message(message)
self.assertNotEquals(msg.eml, None) self.assertNotEquals(msg.eml, None)
self.assertTrue(msg.eml.name.endswith('.eml')) self.assertTrue(msg.eml.name.endswith(".eml"))
with open(msg.eml.name, 'rb') as f: with open(msg.eml.name, "rb") as f:
self.assertEqual( self.assertEqual(f.read(), self._get_email_as_text("generic_message.eml"))
f.read(),
self._get_email_as_text('generic_message.eml')
)
def test_message_saving_ignored(self): def test_message_saving_ignored(self):
message = self._get_email_object('generic_message.eml') message = self._get_email_object("generic_message.eml")
default_settings = utils.get_settings() default_settings = utils.get_settings()
with mock.patch('django_mailbox.utils.get_settings') as get_settings: with mock.patch("django_mailbox.utils.get_settings") as get_settings:
altered = copy.deepcopy(default_settings) altered = copy.deepcopy(default_settings)
altered['store_original_message'] = False altered["store_original_message"] = False
get_settings.return_value = altered get_settings.return_value = altered
msg = self.mailbox.process_incoming_message(message) msg = self.mailbox.process_incoming_message(message)
@ -411,22 +387,21 @@ class TestProcessEmail(EmailMessageTestCase):
self.assertEquals(msg.eml, None) self.assertEquals(msg.eml, None)
def test_message_compressed(self): def test_message_compressed(self):
message = self._get_email_object('generic_message.eml') message = self._get_email_object("generic_message.eml")
default_settings = utils.get_settings() default_settings = utils.get_settings()
with mock.patch('django_mailbox.utils.get_settings') as get_settings: with mock.patch("django_mailbox.utils.get_settings") as get_settings:
altered = copy.deepcopy(default_settings) altered = copy.deepcopy(default_settings)
altered['compress_original_message'] = True altered["compress_original_message"] = True
altered['store_original_message'] = True altered["store_original_message"] = True
get_settings.return_value = altered get_settings.return_value = altered
msg = self.mailbox.process_incoming_message(message) msg = self.mailbox.process_incoming_message(message)
actual_email_object = msg.get_email_object() actual_email_object = msg.get_email_object()
print(actual_email_object)
self.assertTrue(msg.eml.name.endswith(".eml.gz"))
self.assertTrue(msg.eml.name.endswith('.eml.gz')) with gzip.open(msg.eml.name, "rb") as f:
self.assertEqual(f.read(), self._get_email_as_text("generic_message.eml"))
with gzip.open(msg.eml.name, 'rb') as f:
self.assertEqual(f.read(),
self._get_email_as_text('generic_message.eml'))

View file

@ -5,6 +5,7 @@ from django.core.management import call_command, CommandError
from django.test import TestCase from django.test import TestCase
import django import django
class CommandsTestCase(TestCase): class CommandsTestCase(TestCase):
def test_processincomingmessage_no_args(self): def test_processincomingmessage_no_args(self):
"""Check that processincomingmessage works with no args""" """Check that processincomingmessage works with no args"""
@ -12,16 +13,18 @@ class CommandsTestCase(TestCase):
mailbox_name = None mailbox_name = None
# Mock handle so that the test doesn't hang waiting for input. Note that we are only testing # Mock handle so that the test doesn't hang waiting for input. Note that we are only testing
# the argument parsing here -- functionality should be tested elsewhere # the argument parsing here -- functionality should be tested elsewhere
with mock.patch('django_mailbox.management.commands.processincomingmessage.Command.handle') as handle: with mock.patch(
"django_mailbox.management.commands.processincomingmessage.Command.handle"
) as handle:
# Don't care about the return value # Don't care about the return value
handle.return_value = None handle.return_value = None
call_command('processincomingmessage') call_command("processincomingmessage")
args, kwargs = handle.call_args args, kwargs = handle.call_args
# Make sure that we called with the right arguments # Make sure that we called with the right arguments
try: try:
self.assertEqual(kwargs['mailbox_name'], mailbox_name) self.assertEqual(kwargs["mailbox_name"], mailbox_name)
except KeyError: except KeyError:
# Handle Django 1.7 # Handle Django 1.7
# It uses optparse instead of argparse, so instead of being # It uses optparse instead of argparse, so instead of being
@ -29,19 +32,20 @@ class CommandsTestCase(TestCase):
# Thus we expect an empty tuple here # Thus we expect an empty tuple here
self.assertEqual(args, tuple()) self.assertEqual(args, tuple())
def test_processincomingmessage_with_arg(self): def test_processincomingmessage_with_arg(self):
"""Check that processincomingmessage works with mailbox_name given""" """Check that processincomingmessage works with mailbox_name given"""
mailbox_name = 'foo_mailbox' mailbox_name = "foo_mailbox"
with mock.patch('django_mailbox.management.commands.processincomingmessage.Command.handle') as handle: with mock.patch(
"django_mailbox.management.commands.processincomingmessage.Command.handle"
) as handle:
handle.return_value = None handle.return_value = None
call_command('processincomingmessage', mailbox_name) call_command("processincomingmessage", mailbox_name)
args, kwargs = handle.call_args args, kwargs = handle.call_args
try: try:
self.assertEqual(kwargs['mailbox_name'], mailbox_name) self.assertEqual(kwargs["mailbox_name"], mailbox_name)
except (AssertionError, KeyError): except (AssertionError, KeyError):
# Handle Django 1.7 # Handle Django 1.7
# It uses optparse instead of argparse, so instead of being # It uses optparse instead of argparse, so instead of being
@ -55,11 +59,12 @@ class CommandsTestCase(TestCase):
# error, which means this test is worthless anyway # error, which means this test is worthless anyway
# For the "compatibility" versions, unexpected arguments aren't handled # For the "compatibility" versions, unexpected arguments aren't handled
# very well, and result in a TypeError # very well, and result in a TypeError
if (LooseVersion(django.get_version()) >= LooseVersion('1.8') and if LooseVersion(django.get_version()) >= LooseVersion("1.8") and LooseVersion(
LooseVersion(django.get_version()) < LooseVersion('1.10')): django.get_version()
) < LooseVersion("1.10"):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg') call_command("processincomingmessage", "foo_mailbox", "invalid_arg")
# In 1.10 and later a proper CommandError should be raised # In 1.10 and later a proper CommandError should be raised
elif LooseVersion(django.get_version()) >= LooseVersion('1.10'): elif LooseVersion(django.get_version()) >= LooseVersion("1.10"):
with self.assertRaises(CommandError): with self.assertRaises(CommandError):
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg') call_command("processincomingmessage", "foo_mailbox", "invalid_arg")

View file

@ -1,44 +1,32 @@
from unittest import mock from unittest import mock
from os.path import exists
from os import remove
from django.test.utils import override_settings from django.test.utils import override_settings
from django_mailbox.tests.base import EmailMessageTestCase, get_email_as_text from django_mailbox.tests.base import EmailMessageTestCase, get_email_as_text
from django_mailbox.transports import ImapTransport, Pop3Transport from django_mailbox.transports import ImapTransport, Pop3Transport
FAKE_UID_SEARCH_ANSWER = ( FAKE_UID_SEARCH_ANSWER = (
'OK', "OK",
[ [
b'18 19 20 21 22 23 24 25 26 27 28 29 ' + b"18 19 20 21 22 23 24 25 26 27 28 29 "
b'30 31 32 33 34 35 36 37 38 39 40 41 42 43 44' + b"30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
] ],
) )
FAKE_UID_FETCH_SIZES = ( FAKE_UID_FETCH_SIZES = (
'OK', "OK",
[ [b"1 (UID 18 RFC822.SIZE 58070000000)", b"2 (UID 19 RFC822.SIZE 2593)"],
b'1 (UID 18 RFC822.SIZE 58070000000)',
b'2 (UID 19 RFC822.SIZE 2593)'
]
) )
FAKE_UID_FETCH_MSG = ( FAKE_UID_FETCH_MSG = (
'OK', "OK",
[ [
( (b"1 (UID 18 RFC822 {5807}", get_email_as_text("generic_message.eml")),
b'1 (UID 18 RFC822 {5807}', ],
get_email_as_text('generic_message.eml')
),
]
)
FAKE_UID_COPY_MSG = (
'OK',
[
b'[COPYUID 1 2 2] (Success)'
]
) )
FAKE_UID_COPY_MSG = ("OK", [b"[COPYUID 1 2 2] (Success)"])
FAKE_LIST_ARCHIVE_FOLDERS_ANSWERS = ( FAKE_LIST_ARCHIVE_FOLDERS_ANSWERS = (
'OK', "OK",
[ [b'(\\HasNoChildren \\All) "/" "[Gmail]/All Mail"'],
b'(\\HasNoChildren \\All) "/" "[Gmail]/All Mail"'
]
) )
@ -47,14 +35,14 @@ class IMAPTestCase(EmailMessageTestCase):
def imap_server_uid_method(*args): def imap_server_uid_method(*args):
cmd = args[0] cmd = args[0]
arg2 = args[2] arg2 = args[2]
if cmd == 'search': if cmd == "search":
return FAKE_UID_SEARCH_ANSWER return FAKE_UID_SEARCH_ANSWER
if cmd == 'copy': if cmd == "copy":
return FAKE_UID_COPY_MSG return FAKE_UID_COPY_MSG
if cmd == 'fetch': if cmd == "fetch":
if arg2 == '(RFC822.SIZE)': if arg2 == "(RFC822.SIZE)":
return FAKE_UID_FETCH_SIZES return FAKE_UID_FETCH_SIZES
if arg2 == '(RFC822)': if arg2 == "(RFC822)":
return FAKE_UID_FETCH_MSG return FAKE_UID_FETCH_MSG
def imap_server_list_method(pattern=None): def imap_server_list_method(pattern=None):
@ -69,39 +57,35 @@ class IMAPTestCase(EmailMessageTestCase):
class TestImapTransport(IMAPTestCase): class TestImapTransport(IMAPTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.arbitrary_hostname = 'one.two.three' self.arbitrary_hostname = "one.two.three"
self.arbitrary_port = 100 self.arbitrary_port = 100
self.ssl = False self.ssl = False
self.transport = ImapTransport( self.transport = ImapTransport(
self.arbitrary_hostname, self.arbitrary_hostname, self.arbitrary_port, self.ssl
self.arbitrary_port,
self.ssl
) )
self.transport.server = self.imap_server self.transport.server = self.imap_server
def test_get_email_message(self): def test_get_email_message(self):
if exists("store.json"):
remove("store.json")
actual_messages = list(self.transport.get_message()) actual_messages = list(self.transport.get_message())
self.assertEqual(len(actual_messages), 27) self.assertEqual(len(actual_messages), 27)
actual_message = actual_messages[0] actual_message = actual_messages[0]
expected_message = self._get_email_object('generic_message.eml') expected_message = self._get_email_object("generic_message.eml")
self.assertEqual(expected_message, actual_message) self.assertEqual(expected_message, actual_message)
class TestImapArchivedTransport(TestImapTransport): class TestImapArchivedTransport(TestImapTransport):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.archive = 'Archive' self.archive = "Archive"
self.transport = ImapTransport( self.transport = ImapTransport(
self.arbitrary_hostname, self.arbitrary_hostname, self.arbitrary_port, self.ssl, self.archive
self.arbitrary_port,
self.ssl,
self.archive
) )
self.transport.server = self.imap_server self.transport.server = self.imap_server
class TestMaxSizeImapTransport(TestImapTransport): class TestMaxSizeImapTransport(TestImapTransport):
@override_settings(DJANGO_MAILBOX_MAX_MESSAGE_SIZE=5807) @override_settings(DJANGO_MAILBOX_MAX_MESSAGE_SIZE=5807)
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -121,38 +105,40 @@ class TestMaxSizeImapTransport(TestImapTransport):
self.assertEqual(len(small_message_ids), 1) self.assertEqual(len(small_message_ids), 1)
def test_get_email_message(self): def test_get_email_message(self):
if exists("store.json"):
remove("store.json")
actual_messages = list(self.transport.get_message()) actual_messages = list(self.transport.get_message())
self.assertEqual(len(actual_messages), 1) self.assertEqual(len(actual_messages), 1)
actual_message = actual_messages[0] actual_message = actual_messages[0]
expected_message = self._get_email_object('generic_message.eml') expected_message = self._get_email_object("generic_message.eml")
self.assertEqual(expected_message, actual_message) self.assertEqual(expected_message, actual_message)
class TestPop3Transport(EmailMessageTestCase): class TestPop3Transport(EmailMessageTestCase):
def setUp(self): def setUp(self):
self.arbitrary_hostname = 'one.two.three' self.arbitrary_hostname = "one.two.three"
self.arbitrary_port = 100 self.arbitrary_port = 100
self.ssl = False self.ssl = False
self.transport = Pop3Transport( self.transport = Pop3Transport(
self.arbitrary_hostname, self.arbitrary_hostname, self.arbitrary_port, self.ssl
self.arbitrary_port,
self.ssl
) )
self.transport.server = None self.transport.server = None
super().setUp() super().setUp()
def test_get_email_message(self): def test_get_email_message(self):
with mock.patch.object(self.transport, 'server') as server: if exists("store.json"):
remove("store.json")
with mock.patch.object(self.transport, "server") as server:
# Consider this value arbitrary, the second parameter # Consider this value arbitrary, the second parameter
# should have one entry per message in the inbox # should have one entry per message in the inbox
server.list.return_value = [None, ['some_msg']] server.list.return_value = [None, ["some_msg"]]
server.retr.return_value = [ server.retr.return_value = [
'+OK message follows', "+OK message follows",
[ [
line.encode('ascii') line.encode("ascii")
for line in self._get_email_as_text( for line in self._get_email_as_text("generic_message.eml")
'generic_message.eml' .decode("ascii")
).decode('ascii').split('\n') .split("\n")
], ],
10018, # Some arbitrary size, ideally matching the above 10018, # Some arbitrary size, ideally matching the above
] ]
@ -162,6 +148,6 @@ class TestPop3Transport(EmailMessageTestCase):
self.assertEqual(len(actual_messages), 1) self.assertEqual(len(actual_messages), 1)
actual_message = actual_messages[0] actual_message = actual_messages[0]
expected_message = self._get_email_object('generic_message.eml') expected_message = self._get_email_object("generic_message.eml")
self.assertEqual(expected_message, actual_message) self.assertEqual(expected_message, actual_message)

View file

@ -1,4 +1,4 @@
import sys # import sys
from .base import EmailTransport from .base import EmailTransport

View file

@ -7,7 +7,6 @@ logger = logging.getLogger(__name__)
class GmailImapTransport(ImapTransport): class GmailImapTransport(ImapTransport):
def connect(self, username, password): def connect(self, username, password):
# Try to use oauth2 first. It's much safer # Try to use oauth2 first. It's much safer
try: try:
@ -28,30 +27,25 @@ class GmailImapTransport(ImapTransport):
AccessTokenNotFound, AccessTokenNotFound,
) )
except ImportError: except ImportError:
raise ValueError( raise ValueError("Install python-social-auth to use oauth2 auth for gmail")
"Install python-social-auth to use oauth2 auth for gmail"
)
access_token = None access_token = None
while access_token is None: while access_token is None:
try: try:
access_token = get_google_access_token(username) access_token = get_google_access_token(username)
google_email_address = fetch_user_info(username)['email'] google_email_address = fetch_user_info(username)["email"]
except TypeError: except TypeError:
# This means that the google process took too long # This means that the google process took too long
# Trying again is the right thing to do # Trying again is the right thing to do
pass pass
except AccessTokenNotFound: except AccessTokenNotFound:
raise ValueError( raise ValueError(
"No Token available in python-social-auth for %s" % ( "No Token available in python-social-auth for %s" % (username)
username
)
) )
auth_string = 'user={}\1auth=Bearer {}\1\1'.format( auth_string = "user={}\1auth=Bearer {}\1\1".format(
google_email_address, google_email_address, access_token
access_token
) )
self.server = self.transport(self.hostname, self.port) self.server = self.transport(self.hostname, self.port)
self.server.authenticate('XOAUTH2', lambda x: auth_string) self.server.authenticate("XOAUTH2", lambda x: auth_string)
self.server.select() self.server.select()

View file

@ -1,6 +1,7 @@
import imaplib import imaplib
import logging import logging
from os.path import exists
import json
from django.conf import settings from django.conf import settings
from .base import EmailTransport, MessageParseError from .base import EmailTransport, MessageParseError
@ -18,28 +19,25 @@ logger = logging.getLogger(__name__)
class ImapTransport(EmailTransport): class ImapTransport(EmailTransport):
def __init__( def __init__(
self, hostname, port=None, ssl=False, tls=False, self,
archive='', folder=None, hostname,
port=None,
ssl=False,
tls=False,
archive="",
folder=None,
): ):
self.max_message_size = getattr( self.max_message_size = getattr(
settings, settings, "DJANGO_MAILBOX_MAX_MESSAGE_SIZE", False
'DJANGO_MAILBOX_MAX_MESSAGE_SIZE',
False
) )
self.integration_testing_subject = getattr( self.integration_testing_subject = getattr(
settings, settings, "DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT", None
'DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT',
None
) )
self.delete_message_from_server = getattr( self.delete_message_from_server = getattr(
settings, settings, "DJANGO_MAILBOX_DELETE_MESSAGE_FROM_SERVER", True
'DJANGO_MAILBOX_DELETE_MESSAGE_FROM_SERVER',
True
) )
self.delete_message_store = getattr( self.delete_message_store = getattr(
settings, settings, "DJANGO_MAILBOX_DELETE_MESSAGE_ID_STORE", "/tmp/store.json"
'DJANGO_MAILBOX_DELETE_MESSAGE_ID_STORE',
"/tmp/store.json"
) )
self.hostname = hostname self.hostname = hostname
self.port = port self.port = port
@ -68,13 +66,13 @@ class ImapTransport(EmailTransport):
def _get_all_message_ids(self): def _get_all_message_ids(self):
# Fetch all the message uids # Fetch all the message uids
response, message_ids = self.server.uid('search', None, 'ALL') response, message_ids = self.server.uid("search", None, "ALL")
message_id_string = message_ids[0].strip() message_id_string = message_ids[0].strip()
# Usually `message_id_string` will be a list of space-separated # Usually `message_id_string` will be a list of space-separated
# ids; we must make sure that it isn't an empty string before # ids; we must make sure that it isn't an empty string before
# splitting into individual UIDs. # splitting into individual UIDs.
if message_id_string: if message_id_string:
return message_id_string.decode().split(' ') return message_id_string.decode().split(" ")
return [] return []
def _get_small_message_ids(self, message_ids): def _get_small_message_ids(self, message_ids):
@ -83,23 +81,17 @@ class ImapTransport(EmailTransport):
# limit # limit
safe_message_ids = [] safe_message_ids = []
status, data = self.server.uid( status, data = self.server.uid("fetch", ",".join(message_ids), "(RFC822.SIZE)")
'fetch',
','.join(message_ids),
'(RFC822.SIZE)'
)
for each_msg in data: for each_msg in data:
each_msg = each_msg.decode() each_msg = each_msg.decode()
try: try:
uid = each_msg.split(' ')[2] uid = each_msg.split(" ")[2]
size = each_msg.split(' ')[4].rstrip(')') size = each_msg.split(" ")[4].rstrip(")")
if int(size) <= int(self.max_message_size): if int(size) <= int(self.max_message_size):
safe_message_ids.append(uid) safe_message_ids.append(uid)
except ValueError as e: except ValueError as e:
logger.warning( logger.warning("ValueError: {} working on {}".format(e, each_msg[0]))
"ValueError: {} working on {}".format(e, each_msg[0])
)
pass pass
return safe_message_ids return safe_message_ids
@ -120,30 +112,29 @@ class ImapTransport(EmailTransport):
self.server.create(self.archive) self.server.create(self.archive)
# PoC of alternative method of storing email # PoC of alternative method of storing email
if not self.delete_from_server: if not self.delete_message_from_server:
j = {"uids":[]} j = {"uids": []}
if os.path.exists("store.json"): if exists("store.json"):
with open("store.json","r") as f: with open("store.json", "r") as f:
j = json.load(f) j = json.load(f)
else: else:
with open("store.json","w") as f: with open("store.json", "w") as f:
json.dump(j,f) json.dump(j, f)
# PoC of alternative method of storing email # PoC of alternative method of storing email
for uid in message_ids: for uid in message_ids:
# PoC of alternative method of storing email # PoC of alternative method of storing email
if not self.delete_from_server: if not self.delete_message_from_server:
if uid in j["uids"]: if uid in j["uids"]:
continue continue
else: else:
j["uids"].append(uid) j["uids"].append(uid)
with open("store.json","w") as f: with open("store.json", "w") as f:
json.dump(j,f) json.dump(j, f)
# PoC of alternative method of storing email # PoC of alternative method of storing email
try: try:
typ, msg_contents = self.server.uid('fetch', uid, '(RFC822)') typ, msg_contents = self.server.uid("fetch", uid, "(RFC822)")
if not msg_contents: if not msg_contents:
continue continue
try: try:
@ -162,8 +153,9 @@ class ImapTransport(EmailTransport):
continue continue
if self.archive: if self.archive:
self.server.uid('copy', uid, self.archive) self.server.uid("copy", uid, self.archive)
self.delete_message_from_server:
self.server.uid('store', uid, "+FLAGS", "(\\Deleted)") if self.delete_message_from_server:
self.server.uid("store", uid, "+FLAGS", "(\\Deleted)")
self.server.expunge() self.server.expunge()
return return

View file

@ -22,15 +22,13 @@ class Pop3Transport(EmailTransport):
self.server.pass_(password) self.server.pass_(password)
def get_message_body(self, message_lines): def get_message_body(self, message_lines):
return bytes('\r\n', 'ascii').join(message_lines) return bytes("\r\n", "ascii").join(message_lines)
def get_message(self, condition=None): def get_message(self, condition=None):
message_count = len(self.server.list()[1]) message_count = len(self.server.list()[1])
for i in range(message_count): for i in range(message_count):
try: try:
msg_contents = self.get_message_body( msg_contents = self.get_message_body(self.server.retr(i + 1)[1])
self.server.retr(i + 1)[1]
)
message = self.get_email_from_bytes(msg_contents) message = self.get_email_from_bytes(msg_contents)
if condition and not condition(message): if condition and not condition(message):

View file

@ -11,127 +11,112 @@ logger = logging.getLogger(__name__)
def get_settings(): def get_settings():
return { return {
'strip_unallowed_mimetypes': getattr( "strip_unallowed_mimetypes": getattr(
settings, settings, "DJANGO_MAILBOX_STRIP_UNALLOWED_MIMETYPES", False
'DJANGO_MAILBOX_STRIP_UNALLOWED_MIMETYPES',
False
), ),
'allowed_mimetypes': getattr( "allowed_mimetypes": getattr(
settings, settings, "DJANGO_MAILBOX_ALLOWED_MIMETYPES", ["text/plain", "text/html"]
'DJANGO_MAILBOX_ALLOWED_MIMETYPES',
[
'text/plain',
'text/html'
]
), ),
'text_stored_mimetypes': getattr( "text_stored_mimetypes": getattr(
settings, settings,
'DJANGO_MAILBOX_TEXT_STORED_MIMETYPES', "DJANGO_MAILBOX_TEXT_STORED_MIMETYPES",
[ ["text/plain", "text/html"],
'text/plain',
'text/html'
]
), ),
'altered_message_header': getattr( "altered_message_header": getattr(
settings, settings,
'DJANGO_MAILBOX_ALTERED_MESSAGE_HEADER', "DJANGO_MAILBOX_ALTERED_MESSAGE_HEADER",
'X-Django-Mailbox-Altered-Message' "X-Django-Mailbox-Altered-Message",
), ),
'attachment_interpolation_header': getattr( "attachment_interpolation_header": getattr(
settings, settings,
'DJANGO_MAILBOX_ATTACHMENT_INTERPOLATION_HEADER', "DJANGO_MAILBOX_ATTACHMENT_INTERPOLATION_HEADER",
'X-Django-Mailbox-Interpolate-Attachment' "X-Django-Mailbox-Interpolate-Attachment",
), ),
'attachment_upload_to': getattr( "attachment_upload_to": getattr(
settings, settings,
'DJANGO_MAILBOX_ATTACHMENT_UPLOAD_TO', "DJANGO_MAILBOX_ATTACHMENT_UPLOAD_TO",
'mailbox_attachments/%Y/%m/%d/' "mailbox_attachments/%Y/%m/%d/",
), ),
'store_original_message': getattr( "store_original_message": getattr(
settings, settings, "DJANGO_MAILBOX_STORE_ORIGINAL_MESSAGE", False
'DJANGO_MAILBOX_STORE_ORIGINAL_MESSAGE',
False
), ),
'compress_original_message': getattr( "compress_original_message": getattr(
settings, settings, "DJANGO_MAILBOX_COMPRESS_ORIGINAL_MESSAGE", False
'DJANGO_MAILBOX_COMPRESS_ORIGINAL_MESSAGE',
False
), ),
'original_message_compression': getattr( "original_message_compression": getattr(
settings, settings, "DJANGO_MAILBOX_ORIGINAL_MESSAGE_COMPRESSION", 6
'DJANGO_MAILBOX_ORIGINAL_MESSAGE_COMPRESSION',
6
), ),
'default_charset': getattr( "default_charset": getattr(
settings, settings,
'DJANGO_MAILBOX_default_charset', "DJANGO_MAILBOX_default_charset",
'iso8859-1', "iso8859-1",
) ),
} }
def convert_header_to_unicode(header): def convert_header_to_unicode(header):
default_charset = get_settings()['default_charset'] default_charset = get_settings()["default_charset"]
def _decode(value, encoding): def _decode(value, encoding):
if isinstance(value, str): if isinstance(value, str):
return value return value
if not encoding or encoding == 'unknown-8bit': if not encoding or encoding == "unknown-8bit":
encoding = default_charset encoding = default_charset
return value.decode(encoding, 'replace') return value.decode(encoding, "replace")
try: try:
return ''.join( return "".join(
[ [
( (_decode(bytestr, encoding))
_decode(bytestr, encoding) for bytestr, encoding in email.header.decode_header(header)
) for bytestr, encoding in email.header.decode_header(header)
] ]
) )
except UnicodeDecodeError: except UnicodeDecodeError:
logger.exception( logger.exception(
'Errors encountered decoding header %s into encoding %s.', "Errors encountered decoding header %s into encoding %s.",
header, header,
default_charset, default_charset,
) )
return header.decode(default_charset, 'replace') return header.decode(default_charset, "replace")
def get_body_from_message(message, maintype, subtype): def get_body_from_message(message, maintype, subtype):
""" """
Fetchs the body message matching main/sub content type. Fetchs the body message matching main/sub content type.
""" """
body = '' body = ""
for part in message.walk(): for part in message.walk():
if part.get('content-disposition', '').startswith('attachment;'): if part.get("content-disposition", "").startswith("attachment;"):
continue continue
if part.get_content_maintype() == maintype and \ if (
part.get_content_subtype() == subtype: part.get_content_maintype() == maintype
and part.get_content_subtype() == subtype
):
charset = part.get_content_charset() charset = part.get_content_charset()
this_part = part.get_payload(decode=True) this_part = part.get_payload(decode=True)
if charset: if charset:
try: try:
this_part = this_part.decode(charset, 'replace') this_part = this_part.decode(charset, "replace")
except LookupError: except LookupError:
this_part = this_part.decode('ascii', 'replace') this_part = this_part.decode("ascii", "replace")
logger.warning( logger.warning(
'Unknown encoding %s encountered while decoding ' "Unknown encoding %s encountered while decoding "
'text payload. Interpreting as ASCII with ' "text payload. Interpreting as ASCII with "
'replacement, but some data may not be ' "replacement, but some data may not be "
'represented as the sender intended.', "represented as the sender intended.",
charset charset,
) )
except ValueError: except ValueError:
this_part = this_part.decode('ascii', 'replace') this_part = this_part.decode("ascii", "replace")
logger.warning( logger.warning(
'Error encountered while decoding text ' "Error encountered while decoding text "
'payload from an incorrectly-constructed ' "payload from an incorrectly-constructed "
'e-mail; payload was converted to ASCII with ' "e-mail; payload was converted to ASCII with "
'replacement, but some data may not be ' "replacement, but some data may not be "
'represented as the sender intended.' "represented as the sender intended."
) )
else: else:
this_part = this_part.decode('ascii', 'replace') this_part = this_part.decode("ascii", "replace")
body += this_part body += this_part
@ -141,8 +126,8 @@ def get_body_from_message(message, maintype, subtype):
def get_attachment_save_path(instance, filename): def get_attachment_save_path(instance, filename):
settings = get_settings() settings = get_settings()
path = settings['attachment_upload_to'] path = settings["attachment_upload_to"]
if '%' in path: if "%" in path:
path = datetime.datetime.utcnow().strftime(path) path = datetime.datetime.utcnow().strftime(path)
return os.path.join( return os.path.join(

View file

@ -15,106 +15,106 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0' # needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
#source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = 'django-mailbox' project = "django-mailbox"
copyright = '2020, Adam Coddington' copyright = "2020, Adam Coddington"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '3.3' version = "3.3"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '3.3' release = "3.3"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
#language = None # language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
#today = '' # today = ''
# Else, today_fmt is used as the format for a strftime call. # Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y' # today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ['_build'] exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents. # The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None # default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text. # If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True # add_function_parentheses = True
# If true, the current module name will be prepended to all description # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
#add_module_names = True # add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
#modindex_common_prefix = [] # modindex_common_prefix = []
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'default' html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} # html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = [] # html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
#html_title = None # html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None # html_short_title = None
# The name of an image file (relative to this directory) to place at the top # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # of the sidebar.
#html_logo = None # html_logo = None
# The name of an image file (within the static path) to use as favicon of the # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
#html_favicon = None # html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
@ -123,88 +123,91 @@ html_theme = 'default'
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y' # html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True # html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} # html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
#html_additional_pages = {} # html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True # html_domain_indices = True
# If false, no index is generated. # If false, no index is generated.
#html_use_index = True # html_use_index = True
# If true, the index is split into individual pages for each letter. # If true, the index is split into individual pages for each letter.
#html_split_index = False # html_split_index = False
# If true, links to the reST sources are added to the pages. # If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True # html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True # html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True # html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will # If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # base URL from which the finished HTML is served.
#html_use_opensearch = '' # html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml"). # This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None # html_file_suffix = None
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'django-mailboxdoc' htmlhelp_basename = "django-mailboxdoc"
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt',
#'pointsize': '10pt', # Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
} }
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'django-mailbox.tex', 'django-mailbox Documentation', (
'Adam Coddington', 'manual'), "index",
"django-mailbox.tex",
"django-mailbox Documentation",
"Adam Coddington",
"manual",
),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
#latex_logo = None # latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
#latex_use_parts = False # latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
#latex_show_pagerefs = False # latex_show_pagerefs = False
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#latex_show_urls = False # latex_show_urls = False
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#latex_appendices = [] # latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#latex_domain_indices = True # latex_domain_indices = True
# -- Options for manual page output -------------------------------------------- # -- Options for manual page output --------------------------------------------
@ -212,12 +215,11 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'django-mailbox', 'django-mailbox Documentation', ("index", "django-mailbox", "django-mailbox Documentation", ["Adam Coddington"], 1)
['Adam Coddington'], 1)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#man_show_urls = False # man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------ # -- Options for Texinfo output ------------------------------------------------
@ -226,29 +228,41 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'django-mailbox', 'django-mailbox Documentation', (
'Adam Coddington', 'django-mailbox', 'One line description of project.', "index",
'Miscellaneous'), "django-mailbox",
"django-mailbox Documentation",
"Adam Coddington",
"django-mailbox",
"One line description of project.",
"Miscellaneous",
),
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#texinfo_appendices = [] # texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#texinfo_domain_indices = True # texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote' # texinfo_show_urls = 'footnote'
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath(".."))
import inspect import inspect
import django import django
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.conf import settings from django.conf import settings
settings.configure(INSTALLED_APPS=['django_mailbox', ])
settings.configure(
INSTALLED_APPS=[
"django_mailbox",
]
)
django.setup() django.setup()
def process_docstring(app, what, name, obj, options, lines): def process_docstring(app, what, name, obj, options, lines):
# Source: https://gist.github.com/abulka/48b54ea4cbc7eb014308 # Source: https://gist.github.com/abulka/48b54ea4cbc7eb014308
# This causes import errors if left outside the function # This causes import errors if left outside the function
@ -261,7 +275,7 @@ def process_docstring(app, what, name, obj, options, lines):
for field in fields: for field in fields:
# Skip ManyToOneRel and ManyToManyRel fields which have no 'verbose_name' or 'help_text' # Skip ManyToOneRel and ManyToManyRel fields which have no 'verbose_name' or 'help_text'
if not hasattr(field, 'verbose_name'): if not hasattr(field, "verbose_name"):
continue continue
# Decode and strip any html out of the field's help text # Decode and strip any html out of the field's help text
@ -274,22 +288,27 @@ def process_docstring(app, what, name, obj, options, lines):
if help_text: if help_text:
# Add the model field to the end of the docstring as a param # Add the model field to the end of the docstring as a param
# using the help text as the description # using the help text as the description
lines.append(':param {}: {}'.format(field.attname, help_text)) lines.append(":param {}: {}".format(field.attname, help_text))
else: else:
# Add the model field to the end of the docstring as a param # Add the model field to the end of the docstring as a param
# using the verbose name as the description # using the verbose name as the description
lines.append(':param {}: {}'.format(field.attname, verbose_name)) lines.append(":param {}: {}".format(field.attname, verbose_name))
# Add the field's type to the docstring # Add the field's type to the docstring
if isinstance(field, models.ForeignKey): if isinstance(field, models.ForeignKey):
to = field.related_model to = field.related_model
lines.append(':type {}: {} to :class:`~{}.{}`'.format(field.attname, type(field).__name__, to.__module__, to.__name__)) lines.append(
":type {}: {} to :class:`~{}.{}`".format(
field.attname, type(field).__name__, to.__module__, to.__name__
)
)
else: else:
lines.append(':type {}: {}'.format(field.attname, type(field).__name__)) lines.append(":type {}: {}".format(field.attname, type(field).__name__))
# Return the extended docstring # Return the extended docstring
return lines return lines
def setup(app): def setup(app):
# Register the docstring processor with sphinx # Register the docstring processor with sphinx
app.connect('autodoc-process-docstring', process_docstring) app.connect("autodoc-process-docstring", process_docstring)

View file

@ -11,6 +11,8 @@ Django = "^3.1.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^6.2.2" pytest = "^6.2.2"
pytest-django = "^4.1.0" pytest-django = "^4.1.0"
flake8 = "^3.8.4"
black = "^20.8b1"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View file

@ -1,3 +1,7 @@
Django Mailbox2
===============
This is a modified version of the original.
Easily ingest messages from POP3, IMAP, or local mailboxes into your Django application. Easily ingest messages from POP3, IMAP, or local mailboxes into your Django application.

View file

@ -1,4 +0,0 @@
[pytest]
norecursedirs=env docs lib .eggs
DJANGO_SETTINGS_MODULE=django_mailbox.tests.settings
addopts = --tb=short -rxs

View file

@ -1,60 +0,0 @@
from setuptools import find_packages, setup
from django_mailbox import __version__ as version_string
tests_require = [
'django',
'mock',
'unittest2',
]
gmail_oauth2_require = [
'python-social-auth',
]
setup(
name='django-mailbox',
version=version_string,
url='http://github.com/coddingtonbear/django-mailbox/',
description=(
'Import mail from POP3, IMAP, local mailboxes or directly from '
'Postfix or Exim4 into your Django application automatically.'
),
license='MIT',
author='Adam Coddington',
author_email='me@adamcoddington.net',
extras_require={
'gmail-oauth2': gmail_oauth2_require
},
python_requires=">=3",
classifiers=[
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0',
'Framework :: Django :: 2.1',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Communications :: Email',
'Topic :: Communications :: Email :: Post-Office',
'Topic :: Communications :: Email :: Post-Office :: IMAP',
'Topic :: Communications :: Email :: Post-Office :: POP3',
'Topic :: Communications :: Email :: Email Clients (MUA)',
],
packages=find_packages(),
include_package_data=True,
install_requires=[
'six>=1.6.1'
]
)