mirror of
https://github.com/coddingtonbear/django-mailbox.git
synced 2026-07-09 22:38:19 +02:00
wrong folder stage
This commit is contained in:
parent
a52f20dabe
commit
50ee86d1d9
69 changed files with 4191 additions and 85 deletions
3
.idea/.gitignore
generated
vendored
3
.idea/.gitignore
generated
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
14
.idea/django-mailbox.iml
generated
14
.idea/django-mailbox.iml
generated
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="format" value="PLAIN" />
|
|
||||||
<option name="myDocStringFormat" value="Plain" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
41
.idea/inspectionProfiles/Project_Default.xml
generated
41
.idea/inspectionProfiles/Project_Default.xml
generated
|
|
@ -1,41 +0,0 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
||||||
<option name="ignoredPackages">
|
|
||||||
<value>
|
|
||||||
<list size="28">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="pyfcm" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="qrcode" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="python-dateutil" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="django-stdimage" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="psycopg2-binary" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="xhtml2pdf" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="requests" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="django-mailbox" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="redis" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="fbmq" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="jdcal" />
|
|
||||||
<item index="11" class="java.lang.String" itemvalue="celery" />
|
|
||||||
<item index="12" class="java.lang.String" itemvalue="bleach" />
|
|
||||||
<item index="13" class="java.lang.String" itemvalue="ics" />
|
|
||||||
<item index="14" class="java.lang.String" itemvalue="djangorestframework" />
|
|
||||||
<item index="15" class="java.lang.String" itemvalue="django-cors-headers" />
|
|
||||||
<item index="16" class="java.lang.String" itemvalue="djangorestframework-jwt" />
|
|
||||||
<item index="17" class="java.lang.String" itemvalue="reportlab" />
|
|
||||||
<item index="18" class="java.lang.String" itemvalue="et-xmlfile" />
|
|
||||||
<item index="19" class="java.lang.String" itemvalue="gunicorn" />
|
|
||||||
<item index="20" class="java.lang.String" itemvalue="python-telegram-bot" />
|
|
||||||
<item index="21" class="java.lang.String" itemvalue="python-memcached" />
|
|
||||||
<item index="22" class="java.lang.String" itemvalue="openpyxl" />
|
|
||||||
<item index="23" class="java.lang.String" itemvalue="Pillow" />
|
|
||||||
<item index="24" class="java.lang.String" itemvalue="google-cloud-logging" />
|
|
||||||
<item index="25" class="java.lang.String" itemvalue="Django" />
|
|
||||||
<item index="26" class="java.lang.String" itemvalue="six" />
|
|
||||||
<item index="27" class="java.lang.String" itemvalue="urllib3" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
|
|
@ -1,6 +0,0 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
||||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (django-mailbox)" project-jdk-type="Python SDK" />
|
|
||||||
<component name="PyCharmProfessionalAdvertiser">
|
|
||||||
<option name="shown" value="true" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/django-mailbox.iml" filepath="$PROJECT_DIR$/.idea/django-mailbox.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
3
build/lib/django_mailbox/__init__.py
Normal file
3
build/lib/django_mailbox/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
__version__ = '4.8.2'
|
||||||
|
|
||||||
|
default_app_config = 'django_mailbox.apps.MailBoxConfig'
|
||||||
112
build/lib/django_mailbox/admin.py
Normal file
112
build/lib/django_mailbox/admin.py
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Model configuration in application ``django_mailbox`` for administration
|
||||||
|
console.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from django_mailbox.models import MessageAttachment, Message, Mailbox
|
||||||
|
from django_mailbox.signals import message_received
|
||||||
|
from django_mailbox.utils import convert_header_to_unicode
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_mail(mailbox_admin, request, queryset):
|
||||||
|
queryset.get_new_mail()
|
||||||
|
|
||||||
|
|
||||||
|
get_new_mail.short_description = _('Get new mail')
|
||||||
|
|
||||||
|
|
||||||
|
def resend_message_received_signal(message_admin, request, queryset):
|
||||||
|
for message in queryset.all():
|
||||||
|
logger.debug('Resending \'message_received\' signal for %s' % message)
|
||||||
|
message_received.send(sender=message_admin, message=message)
|
||||||
|
|
||||||
|
|
||||||
|
resend_message_received_signal.short_description = (
|
||||||
|
_('Re-send message received signal')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MailboxAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'name',
|
||||||
|
'uri',
|
||||||
|
'from_email',
|
||||||
|
'active',
|
||||||
|
'last_polling',
|
||||||
|
)
|
||||||
|
readonly_fields = ['last_polling', ]
|
||||||
|
actions = [get_new_mail]
|
||||||
|
|
||||||
|
|
||||||
|
class MessageAttachmentAdmin(admin.ModelAdmin):
|
||||||
|
raw_id_fields = ('message', )
|
||||||
|
list_display = ('message', 'document',)
|
||||||
|
|
||||||
|
|
||||||
|
class MessageAttachmentInline(admin.TabularInline):
|
||||||
|
model = MessageAttachment
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class MessageAdmin(admin.ModelAdmin):
|
||||||
|
def attachment_count(self, msg):
|
||||||
|
return msg.attachments.count()
|
||||||
|
|
||||||
|
attachment_count.short_description = _('Attachment count')
|
||||||
|
|
||||||
|
def subject(self, msg):
|
||||||
|
return convert_header_to_unicode(msg.subject)
|
||||||
|
|
||||||
|
def envelope_headers(self, msg):
|
||||||
|
email = msg.get_email_object()
|
||||||
|
return '\n'.join(
|
||||||
|
[('{}: {}'.format(h, v)) for h, v in email.items()]
|
||||||
|
)
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
MessageAttachmentInline,
|
||||||
|
]
|
||||||
|
list_display = (
|
||||||
|
'subject',
|
||||||
|
'processed',
|
||||||
|
'read',
|
||||||
|
'mailbox',
|
||||||
|
'outgoing',
|
||||||
|
'attachment_count',
|
||||||
|
)
|
||||||
|
ordering = ['-processed']
|
||||||
|
list_filter = (
|
||||||
|
'mailbox',
|
||||||
|
'outgoing',
|
||||||
|
'processed',
|
||||||
|
'read',
|
||||||
|
)
|
||||||
|
exclude = (
|
||||||
|
'body',
|
||||||
|
)
|
||||||
|
raw_id_fields = (
|
||||||
|
'in_reply_to',
|
||||||
|
)
|
||||||
|
readonly_fields = (
|
||||||
|
'envelope_headers',
|
||||||
|
'text',
|
||||||
|
'html',
|
||||||
|
)
|
||||||
|
actions = [resend_message_received_signal]
|
||||||
|
|
||||||
|
|
||||||
|
if getattr(settings, 'DJANGO_MAILBOX_ADMIN_ENABLED', True):
|
||||||
|
admin.site.register(Message, MessageAdmin)
|
||||||
|
admin.site.register(MessageAttachment, MessageAttachmentAdmin)
|
||||||
|
admin.site.register(Mailbox, MailboxAdmin)
|
||||||
7
build/lib/django_mailbox/apps.py
Normal file
7
build/lib/django_mailbox/apps.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MailBoxConfig(AppConfig):
|
||||||
|
name = 'django_mailbox'
|
||||||
|
verbose_name = _("Mail Box")
|
||||||
111
build/lib/django_mailbox/google_utils.py
Normal file
111
build/lib/django_mailbox/google_utils.py
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import requests
|
||||||
|
from social.apps.django_app.default.models import UserSocialAuth
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessTokenNotFound(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshTokenNotFound(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_google_consumer_key():
|
||||||
|
return settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY
|
||||||
|
|
||||||
|
|
||||||
|
def get_google_consumer_secret():
|
||||||
|
return settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET
|
||||||
|
|
||||||
|
|
||||||
|
def get_google_access_token(email):
|
||||||
|
# TODO: This should be cacheable
|
||||||
|
try:
|
||||||
|
me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2")
|
||||||
|
return me.extra_data['access_token']
|
||||||
|
except (UserSocialAuth.DoesNotExist, KeyError):
|
||||||
|
raise AccessTokenNotFound
|
||||||
|
|
||||||
|
|
||||||
|
def update_google_extra_data(email, extra_data):
|
||||||
|
try:
|
||||||
|
me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2")
|
||||||
|
me.extra_data = extra_data
|
||||||
|
me.save()
|
||||||
|
except (UserSocialAuth.DoesNotExist, KeyError):
|
||||||
|
raise AccessTokenNotFound
|
||||||
|
|
||||||
|
|
||||||
|
def get_google_refresh_token(email):
|
||||||
|
try:
|
||||||
|
me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2")
|
||||||
|
return me.extra_data['refresh_token']
|
||||||
|
except (UserSocialAuth.DoesNotExist, KeyError):
|
||||||
|
raise RefreshTokenNotFound
|
||||||
|
|
||||||
|
|
||||||
|
def google_api_get(email, url):
|
||||||
|
headers = dict(
|
||||||
|
Authorization="Bearer %s" % get_google_access_token(email),
|
||||||
|
)
|
||||||
|
r = requests.get(url, headers=headers)
|
||||||
|
logger.info("I got a %s", r.status_code)
|
||||||
|
if r.status_code == 401:
|
||||||
|
# Go use the refresh token
|
||||||
|
refresh_authorization(email)
|
||||||
|
r = requests.get(url, headers=headers)
|
||||||
|
logger.info("I got a %s", r.status_code)
|
||||||
|
if r.status_code == 200:
|
||||||
|
try:
|
||||||
|
return r.json()
|
||||||
|
except ValueError:
|
||||||
|
return r.text
|
||||||
|
|
||||||
|
|
||||||
|
def google_api_post(email, url, post_data, authorized=True):
|
||||||
|
# TODO: Make this a lot less ugly. especially the 401 handling
|
||||||
|
headers = dict()
|
||||||
|
if authorized is True:
|
||||||
|
headers.update(dict(
|
||||||
|
Authorization="Bearer %s" % get_google_access_token(email),
|
||||||
|
))
|
||||||
|
r = requests.post(url, headers=headers, data=post_data)
|
||||||
|
if r.status_code == 401:
|
||||||
|
refresh_authorization(email)
|
||||||
|
r = requests.post(url, headers=headers, data=post_data)
|
||||||
|
if r.status_code == 200:
|
||||||
|
try:
|
||||||
|
return r.json()
|
||||||
|
except ValueError:
|
||||||
|
return r.text
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_authorization(email):
|
||||||
|
refresh_token = get_google_refresh_token(email)
|
||||||
|
post_data = dict(
|
||||||
|
refresh_token=refresh_token,
|
||||||
|
client_id=get_google_consumer_key(),
|
||||||
|
client_secret=get_google_consumer_secret(),
|
||||||
|
grant_type='refresh_token',
|
||||||
|
)
|
||||||
|
results = google_api_post(
|
||||||
|
email,
|
||||||
|
"https://accounts.google.com/o/oauth2/token?access_type=offline",
|
||||||
|
post_data,
|
||||||
|
authorized=False)
|
||||||
|
results.update({'refresh_token': refresh_token})
|
||||||
|
update_google_extra_data(email, results)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_user_info(email):
|
||||||
|
result = google_api_get(
|
||||||
|
email,
|
||||||
|
"https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
|
||||||
|
)
|
||||||
|
return result
|
||||||
BIN
build/lib/django_mailbox/locale/ru_RU/LC_MESSAGES/django.mo
Normal file
BIN
build/lib/django_mailbox/locale/ru_RU/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
193
build/lib/django_mailbox/locale/ru_RU/LC_MESSAGES/django.po
Normal file
193
build/lib/django_mailbox/locale/ru_RU/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2017-07-09 05:36-0500\n"
|
||||||
|
"PO-Revision-Date: 2017-07-09 13:37+0300\n"
|
||||||
|
"Last-Translator: Ivlev Denis <di-erz@yandex.ru>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: ru_RU\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
|
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
|
||||||
|
"%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||||
|
"X-Generator: Poedit 1.8.7.1\n"
|
||||||
|
|
||||||
|
#: django_mailbox/admin.py:29
|
||||||
|
msgid "Get new mail"
|
||||||
|
msgstr "Получить новые сообщения"
|
||||||
|
|
||||||
|
#: django_mailbox/admin.py:39
|
||||||
|
msgid "Re-send message received signal"
|
||||||
|
msgstr "Повторно отправить сигнал о получении"
|
||||||
|
|
||||||
|
#: django_mailbox/admin.py:69
|
||||||
|
msgid "Attachment count"
|
||||||
|
msgstr "Кол-во вложений"
|
||||||
|
|
||||||
|
#: django_mailbox/apps.py:7
|
||||||
|
msgid "Mail Box"
|
||||||
|
msgstr "Почтовый ящик"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:49
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Название"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:54
|
||||||
|
msgid "URI"
|
||||||
|
msgstr "URI"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:57
|
||||||
|
msgid ""
|
||||||
|
"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)."
|
||||||
|
msgstr ""
|
||||||
|
"Пример: imap+ssl://myusername:mypassword@someserver <br /><br />Интернет-"
|
||||||
|
"транспорт может быть 'imap' или 'pop3'; поддерживаются локальные файловые "
|
||||||
|
"транспорты 'maildir', 'mbox', а также 'babyl', 'mh', and 'mmdf'. <br /><br /"
|
||||||
|
">Используйте urlencode, если имя пользователя или пароль содержат "
|
||||||
|
"недопустипые символы (@, :, и т.д.)."
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:72
|
||||||
|
msgid "From email"
|
||||||
|
msgstr "От"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:75
|
||||||
|
msgid ""
|
||||||
|
"Example: MailBot <mailbot@yourdomain.com><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`."
|
||||||
|
msgstr ""
|
||||||
|
"Пример: MailBot <mailbot@yourdomain.com><br />Исходящая электронная "
|
||||||
|
"почта.<br /><br />Если вы не используете этот почтовый ящик для исходящей "
|
||||||
|
"почты, этот параметр не нужен.<br />Если вы не указали данный параметр, "
|
||||||
|
"будет использоваться указанный в настройках `DEFAULT_FROM_EMAIL`."
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:89
|
||||||
|
msgid "Active"
|
||||||
|
msgstr "Активный"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:91
|
||||||
|
msgid ""
|
||||||
|
"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. "
|
||||||
|
msgstr ""
|
||||||
|
"Параметр указывает на необходимость проверки почтового ящика на наличие "
|
||||||
|
"новых сообщений в цикле опроса. Этот флажок не влияет на сбор почты, когда "
|
||||||
|
"этот почтовый ящик получает почту из канала и не влияет на отправку "
|
||||||
|
"сообщений электронной почты из этого почтового ящика."
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:102
|
||||||
|
msgid "Last polling"
|
||||||
|
msgstr "Последний опрос"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:103
|
||||||
|
msgid ""
|
||||||
|
"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."
|
||||||
|
msgstr ""
|
||||||
|
"Время последнего успешного опроса сообщений. Для нового почтового ящика не "
|
||||||
|
"установлено. Также не устанавливается для почтовых ящиков "
|
||||||
|
"обновляющих сообщения через pipe."
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:409 django_mailbox/models.py:438
|
||||||
|
msgid "Mailbox"
|
||||||
|
msgstr "Почтовый ящик"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:410
|
||||||
|
msgid "Mailboxes"
|
||||||
|
msgstr "Почтовые ящики"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:442
|
||||||
|
msgid "Subject"
|
||||||
|
msgstr "Тема"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:447
|
||||||
|
msgid "Message ID"
|
||||||
|
msgstr "Идентификатор сообщения"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:456
|
||||||
|
msgid "In reply to"
|
||||||
|
msgstr "В ответ на"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:460
|
||||||
|
msgid "From header"
|
||||||
|
msgstr "От(From)"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:465
|
||||||
|
msgid "To header"
|
||||||
|
msgstr "Кому(To)"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:469
|
||||||
|
msgid "Outgoing"
|
||||||
|
msgstr "Исходящее"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:475
|
||||||
|
msgid "Body"
|
||||||
|
msgstr "Тело"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:479
|
||||||
|
msgid "Encoded"
|
||||||
|
msgstr "Закодировано"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:481
|
||||||
|
msgid "True if the e-mail body is Base64 encoded"
|
||||||
|
msgstr "True если тело сообщения закодировано в Base64"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:485
|
||||||
|
msgid "Processed"
|
||||||
|
msgstr "Обработано"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:490
|
||||||
|
msgid "Read"
|
||||||
|
msgstr "Прочитано"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:497
|
||||||
|
msgid "Raw message contents"
|
||||||
|
msgstr "Исходное содержимое сообщения"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:500
|
||||||
|
msgid "Original full content of message"
|
||||||
|
msgstr "Полное содержимое сообщения"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:716
|
||||||
|
msgid "E-mail message"
|
||||||
|
msgstr "Сообщение"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:717
|
||||||
|
msgid "E-mail messages"
|
||||||
|
msgstr "Сообщения"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:726
|
||||||
|
msgid "Message"
|
||||||
|
msgstr "Сообщение"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:730
|
||||||
|
msgid "Headers"
|
||||||
|
msgstr "Заголовки"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:736
|
||||||
|
msgid "Document"
|
||||||
|
msgstr "Документ"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:793
|
||||||
|
msgid "Message attachment"
|
||||||
|
msgstr "Вложение"
|
||||||
|
|
||||||
|
#: django_mailbox/models.py:794
|
||||||
|
msgid "Message attachments"
|
||||||
|
msgstr "Вложения"
|
||||||
0
build/lib/django_mailbox/management/__init__.py
Normal file
0
build/lib/django_mailbox/management/__init__.py
Normal file
0
build/lib/django_mailbox/management/commands/__init__.py
Normal file
0
build/lib/django_mailbox/management/commands/__init__.py
Normal file
30
build/lib/django_mailbox/management/commands/getmail.py
Normal file
30
build/lib/django_mailbox/management/commands/getmail.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from django_mailbox.models import Mailbox
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
mailboxes = Mailbox.active_mailboxes.all()
|
||||||
|
if args:
|
||||||
|
mailboxes = mailboxes.filter(
|
||||||
|
name=' '.join(args)
|
||||||
|
)
|
||||||
|
for mailbox in mailboxes:
|
||||||
|
logger.info(
|
||||||
|
'Gathering messages for %s',
|
||||||
|
mailbox.name
|
||||||
|
)
|
||||||
|
messages = mailbox.get_new_mail()
|
||||||
|
for message in messages:
|
||||||
|
logger.info(
|
||||||
|
'Received %s (from %s)',
|
||||||
|
message.subject,
|
||||||
|
message.from_address
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import email
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
from email import utils
|
||||||
|
except ImportError:
|
||||||
|
import rfc822 as utils
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from django_mailbox.models import Mailbox
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
args = "<[Mailbox Name (optional)]>"
|
||||||
|
help = "Receive incoming mail via stdin"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'mailbox_name',
|
||||||
|
nargs='?',
|
||||||
|
help="The name of the mailbox that will receive the message"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, mailbox_name=None, *args, **options):
|
||||||
|
message = email.message_from_string(sys.stdin.read())
|
||||||
|
if message:
|
||||||
|
if mailbox_name:
|
||||||
|
mailbox = self.get_mailbox_by_name(mailbox_name)
|
||||||
|
else:
|
||||||
|
mailbox = self.get_mailbox_for_message(message)
|
||||||
|
mailbox.process_incoming_message(message)
|
||||||
|
logger.info(
|
||||||
|
"Message received from %s",
|
||||||
|
message['from']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning("Message not processable.")
|
||||||
|
|
||||||
|
def get_mailbox_by_name(self, name):
|
||||||
|
mailbox, created = Mailbox.objects.get_or_create(
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
return mailbox
|
||||||
|
|
||||||
|
def get_mailbox_for_message(self, message):
|
||||||
|
email_address = utils.parseaddr(message['to'])[1][0:255]
|
||||||
|
return self.get_mailbox_by_name(email_address)
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import email
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from django_mailbox.models import MessageAttachment, Message
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
""" Briefly, a bug existed in a migration that may have caused message
|
||||||
|
attachments to become disassociated with their messages. This management
|
||||||
|
command will read through existing message attachments and attempt to
|
||||||
|
re-associate them with their original message.
|
||||||
|
|
||||||
|
This isn't foolproof, I'm afraid. If an attachment exists twice, it will
|
||||||
|
be associated only with the most recent e-mail message. That said,
|
||||||
|
I'm quite sure that the bug in the migration is gone (and you'd have to
|
||||||
|
have been quite unlucky to have ran the bad migration).
|
||||||
|
|
||||||
|
"""
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
attachment_hash_map = {}
|
||||||
|
|
||||||
|
attachments_without_messages = MessageAttachment.objects.filter(
|
||||||
|
message=None
|
||||||
|
).order_by(
|
||||||
|
'id'
|
||||||
|
)
|
||||||
|
|
||||||
|
if attachments_without_messages.count() < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
for attachment in attachments_without_messages:
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
for chunk in attachment.document.file.chunks():
|
||||||
|
md5.update(chunk)
|
||||||
|
attachment_hash_map[md5.hexdigest()] = attachment.pk
|
||||||
|
|
||||||
|
for message_record in Message.objects.all().order_by('id'):
|
||||||
|
message = email.message_from_string(message_record.body)
|
||||||
|
if message.is_multipart():
|
||||||
|
for part in message.walk():
|
||||||
|
if part.get_content_maintype() == 'multipart':
|
||||||
|
continue
|
||||||
|
if part.get('Content-Disposition') is None:
|
||||||
|
continue
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
md5.update(part.get_payload(decode=True))
|
||||||
|
digest = md5.hexdigest()
|
||||||
|
if digest in attachment_hash_map:
|
||||||
|
attachment = MessageAttachment.objects.get(
|
||||||
|
pk=attachment_hash_map[digest]
|
||||||
|
)
|
||||||
|
attachment.message = message_record
|
||||||
|
attachment.save()
|
||||||
|
logger.info(
|
||||||
|
"Associated message %s with attachment %s (%s)",
|
||||||
|
message_record.pk,
|
||||||
|
attachment.pk,
|
||||||
|
digest
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
"%s(%s) not found in currently-stored attachments",
|
||||||
|
part.get_filename(),
|
||||||
|
digest
|
||||||
|
)
|
||||||
56
build/lib/django_mailbox/migrations/0001_initial.py
Normal file
56
build/lib/django_mailbox/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Mailbox',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', 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 <mailbot@yourdomain.com><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={
|
||||||
|
'verbose_name_plural': 'Mailboxes',
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Message',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('subject', models.CharField(max_length=255, verbose_name='Subject')),
|
||||||
|
('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={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MessageAttachment',
|
||||||
|
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)),
|
||||||
|
('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={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='eml',
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0002_add_eml_to_message'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='message',
|
||||||
|
name='eml',
|
||||||
|
field=models.FileField(help_text='Original full content of message', upload_to=b'messages', null=True, verbose_name='Raw message contents'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0003_auto_20150409_0316'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='message',
|
||||||
|
name='eml',
|
||||||
|
field=models.FileField(verbose_name='Raw message contents', upload_to='messages', null=True, help_text='Original full content of message'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='messageattachment',
|
||||||
|
name='document',
|
||||||
|
field=models.FileField(verbose_name='Document', upload_to='mailbox_attachments/%Y/%m/%d/'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django_mailbox.utils
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0004_bytestring_to_unicode'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='messageattachment',
|
||||||
|
name='document',
|
||||||
|
field=models.FileField(upload_to=django_mailbox.utils.get_attachment_save_path, verbose_name='Document'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 1.9.8 on 2016-08-15 22:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0005_auto_20160523_2240'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mailbox',
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 1.10.7 on 2018-04-21 00:26
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0006_mailbox_last_polling'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='mailbox',
|
||||||
|
options={'verbose_name': 'Mailbox', 'verbose_name_plural': 'Mailboxes'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='message',
|
||||||
|
options={'verbose_name': 'E-mail message', 'verbose_name_plural': 'E-mail messages'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='messageattachment',
|
||||||
|
options={'verbose_name': 'Message attachment', 'verbose_name_plural': 'Message attachments'},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-02-19 14:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_mailbox', '0007_auto_20180421_0026'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mailbox',
|
||||||
|
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(
|
||||||
|
model_name='message',
|
||||||
|
name='outgoing',
|
||||||
|
field=models.BooleanField(blank=True, default=False, verbose_name='Outgoing'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
build/lib/django_mailbox/migrations/__init__.py
Normal file
0
build/lib/django_mailbox/migrations/__init__.py
Normal file
855
build/lib/django_mailbox/models.py
Normal file
855
build/lib/django_mailbox/models.py
Normal file
|
|
@ -0,0 +1,855 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Models declaration for application ``django_mailbox``.
|
||||||
|
"""
|
||||||
|
import gzip
|
||||||
|
from email.encoders import encode_base64
|
||||||
|
from email.message import Message as EmailMessage
|
||||||
|
from email.utils import formatdate, parseaddr
|
||||||
|
from urllib.parse import parse_qs, unquote, urlparse
|
||||||
|
from quopri import encode as encode_quopri
|
||||||
|
from io import BytesIO
|
||||||
|
import base64
|
||||||
|
import email
|
||||||
|
import logging
|
||||||
|
import mimetypes
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.conf import settings as django_settings
|
||||||
|
from django.core.files.base import ContentFile, File
|
||||||
|
from django.core.mail.message import make_msgid
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from django_mailbox import utils
|
||||||
|
from django_mailbox.signals import message_received
|
||||||
|
from django_mailbox.transports import Pop3Transport, ImapTransport, \
|
||||||
|
MaildirTransport, MboxTransport, BabylTransport, MHTransport, \
|
||||||
|
MMDFTransport, GmailImapTransport, Office365Transport
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MailboxQuerySet(models.QuerySet):
|
||||||
|
def get_new_mail(self):
|
||||||
|
count = 0
|
||||||
|
for mailbox in self.all():
|
||||||
|
logger.debug('Receiving mail for %s' % mailbox)
|
||||||
|
count += sum(1 for i in mailbox.get_new_mail())
|
||||||
|
logger.debug('Received %d %s.', count, 'mails' if count != 1 else 'mail')
|
||||||
|
|
||||||
|
|
||||||
|
class MailboxManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return MailboxQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveMailboxManager(MailboxManager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Mailbox(models.Model):
|
||||||
|
name = models.CharField(
|
||||||
|
_('Name'),
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
|
|
||||||
|
uri = models.CharField(
|
||||||
|
_('URI'),
|
||||||
|
max_length=255,
|
||||||
|
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)."
|
||||||
|
)),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
from_email = models.CharField(
|
||||||
|
_('From email'),
|
||||||
|
max_length=255,
|
||||||
|
help_text=(_(
|
||||||
|
"Example: MailBot <mailbot@yourdomain.com><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`."
|
||||||
|
)),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
active = models.BooleanField(
|
||||||
|
_('Active'),
|
||||||
|
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. "
|
||||||
|
)),
|
||||||
|
blank=True,
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
last_polling = models.DateTimeField(
|
||||||
|
_("Last polling"),
|
||||||
|
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.")),
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = MailboxManager()
|
||||||
|
active_mailboxes = ActiveMailboxManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _protocol_info(self):
|
||||||
|
return urlparse(self.uri)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _query_string(self):
|
||||||
|
return parse_qs(self._protocol_info.query)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _domain(self):
|
||||||
|
return self._protocol_info.hostname
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port(self):
|
||||||
|
"""Returns the port to use for fetching messages."""
|
||||||
|
return self._protocol_info.port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self):
|
||||||
|
"""Returns the username to use for fetching messages."""
|
||||||
|
return unquote(self._protocol_info.username)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password(self):
|
||||||
|
"""Returns the password to use for fetching messages."""
|
||||||
|
return unquote(self._protocol_info.password)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def location(self):
|
||||||
|
"""Returns the location (domain and path) of messages."""
|
||||||
|
return self._domain if self._domain else '' + self._protocol_info.path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
"""Returns the 'transport' name for this mailbox."""
|
||||||
|
scheme = self._protocol_info.scheme.lower()
|
||||||
|
if '+' in scheme:
|
||||||
|
return scheme.split('+')[0]
|
||||||
|
return scheme
|
||||||
|
|
||||||
|
@property
|
||||||
|
def use_ssl(self):
|
||||||
|
"""Returns whether or not this mailbox's connection uses SSL."""
|
||||||
|
return '+ssl' in self._protocol_info.scheme.lower()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def use_tls(self):
|
||||||
|
"""Returns whether or not this mailbox's connection uses STARTTLS."""
|
||||||
|
return '+tls' in self._protocol_info.scheme.lower()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def archive(self):
|
||||||
|
"""Returns (if specified) the folder to archive messages to."""
|
||||||
|
archive_folder = self._query_string.get('archive', None)
|
||||||
|
if not archive_folder:
|
||||||
|
return None
|
||||||
|
return archive_folder[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def folder(self):
|
||||||
|
"""Returns (if specified) the folder to fetch mail from."""
|
||||||
|
folder = self._query_string.get('folder', None)
|
||||||
|
if not folder:
|
||||||
|
return None
|
||||||
|
return folder[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_id(self):
|
||||||
|
"""Returns (if specified) the client id for Office365."""
|
||||||
|
client_id = self._query_string.get('client_id', None)
|
||||||
|
if not client_id:
|
||||||
|
return None
|
||||||
|
return client_id[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_secret(self):
|
||||||
|
"""Returns (if specified) the client secret for Office365."""
|
||||||
|
client_secret = self._query_string.get('client_secret', None)
|
||||||
|
if not client_secret:
|
||||||
|
return None
|
||||||
|
return client_secret[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tenant_id(self):
|
||||||
|
"""Returns (if specified) the tenant id for Office365."""
|
||||||
|
tenant_id = self._query_string.get('tentant_id', None)
|
||||||
|
if not tenant_id:
|
||||||
|
return None
|
||||||
|
return tenant_id[0]
|
||||||
|
|
||||||
|
def get_connection(self):
|
||||||
|
"""Returns the transport instance for this mailbox.
|
||||||
|
|
||||||
|
These will always be instances of
|
||||||
|
`django_mailbox.transports.base.EmailTransport`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.uri:
|
||||||
|
return None
|
||||||
|
elif self.type == 'imap':
|
||||||
|
conn = ImapTransport(
|
||||||
|
self.location,
|
||||||
|
port=self.port if self.port else None,
|
||||||
|
ssl=self.use_ssl,
|
||||||
|
tls=self.use_tls,
|
||||||
|
archive=self.archive,
|
||||||
|
folder=self.folder
|
||||||
|
)
|
||||||
|
conn.connect(self.username, self.password)
|
||||||
|
elif self.type == 'gmail':
|
||||||
|
conn = GmailImapTransport(
|
||||||
|
self.location,
|
||||||
|
port=self.port if self.port else None,
|
||||||
|
ssl=True,
|
||||||
|
archive=self.archive
|
||||||
|
)
|
||||||
|
conn.connect(self.username, self.password)
|
||||||
|
elif self.type == 'pop3':
|
||||||
|
conn = Pop3Transport(
|
||||||
|
self.location,
|
||||||
|
port=self.port if self.port else None,
|
||||||
|
ssl=self.use_ssl
|
||||||
|
)
|
||||||
|
conn.connect(self.username, self.password)
|
||||||
|
elif self.type == 'office365':
|
||||||
|
conn = Office365Transport(
|
||||||
|
port=self.port if self.port else None,
|
||||||
|
ssl=True,
|
||||||
|
archive=self.archive,
|
||||||
|
client_id=self.client_id,
|
||||||
|
client_secret=self.client_secret,
|
||||||
|
tenant_id=self.tenant_id
|
||||||
|
)
|
||||||
|
conn.connect(self.username, self.password)
|
||||||
|
elif self.type == 'maildir':
|
||||||
|
conn = MaildirTransport(self.location)
|
||||||
|
elif self.type == 'mbox':
|
||||||
|
conn = MboxTransport(self.location)
|
||||||
|
elif self.type == 'babyl':
|
||||||
|
conn = BabylTransport(self.location)
|
||||||
|
elif self.type == 'mh':
|
||||||
|
conn = MHTransport(self.location)
|
||||||
|
elif self.type == 'mmdf':
|
||||||
|
conn = MMDFTransport(self.location)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def process_incoming_message(self, message):
|
||||||
|
"""Process a message incoming to this mailbox."""
|
||||||
|
msg = self._process_message(message)
|
||||||
|
if msg is None:
|
||||||
|
return None
|
||||||
|
msg.outgoing = False
|
||||||
|
msg.save()
|
||||||
|
|
||||||
|
message_received.send(sender=self, message=msg)
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def record_outgoing_message(self, message):
|
||||||
|
"""Record an outgoing message associated with this mailbox."""
|
||||||
|
msg = self._process_message(message)
|
||||||
|
if msg is None:
|
||||||
|
return None
|
||||||
|
msg.outgoing = True
|
||||||
|
msg.save()
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def _get_dehydrated_message(self, msg, record):
|
||||||
|
settings = utils.get_settings()
|
||||||
|
|
||||||
|
new = EmailMessage()
|
||||||
|
if msg.is_multipart():
|
||||||
|
for header, value in msg.items():
|
||||||
|
new[header] = value
|
||||||
|
for part in msg.get_payload():
|
||||||
|
new.attach(
|
||||||
|
self._get_dehydrated_message(part, record)
|
||||||
|
)
|
||||||
|
elif (
|
||||||
|
settings['strip_unallowed_mimetypes']
|
||||||
|
and not msg.get_content_type() in settings['allowed_mimetypes']
|
||||||
|
):
|
||||||
|
for header, value in msg.items():
|
||||||
|
new[header] = value
|
||||||
|
# Delete header, otherwise when attempting to deserialize the
|
||||||
|
# payload, it will be expecting a body for this.
|
||||||
|
del new['Content-Transfer-Encoding']
|
||||||
|
new[settings['altered_message_header']] = (
|
||||||
|
'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
|
||||||
|
('attachment' in msg.get('Content-Disposition', ''))
|
||||||
|
):
|
||||||
|
filename = None
|
||||||
|
raw_filename = msg.get_filename()
|
||||||
|
if raw_filename:
|
||||||
|
filename = utils.convert_header_to_unicode(raw_filename)
|
||||||
|
if not filename:
|
||||||
|
extension = mimetypes.guess_extension(msg.get_content_type())
|
||||||
|
else:
|
||||||
|
_, extension = os.path.splitext(filename)
|
||||||
|
if not extension:
|
||||||
|
extension = '.bin'
|
||||||
|
|
||||||
|
attachment = MessageAttachment()
|
||||||
|
|
||||||
|
attachment.document.save(
|
||||||
|
uuid.uuid4().hex + extension,
|
||||||
|
ContentFile(
|
||||||
|
BytesIO(
|
||||||
|
msg.get_payload(decode=True)
|
||||||
|
).getvalue()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
attachment.message = record
|
||||||
|
for key, value in msg.items():
|
||||||
|
attachment[key] = value
|
||||||
|
attachment.save()
|
||||||
|
|
||||||
|
placeholder = EmailMessage()
|
||||||
|
placeholder[
|
||||||
|
settings['attachment_interpolation_header']
|
||||||
|
] = str(attachment.pk)
|
||||||
|
new = placeholder
|
||||||
|
else:
|
||||||
|
content_charset = msg.get_content_charset()
|
||||||
|
if not content_charset:
|
||||||
|
content_charset = 'ascii'
|
||||||
|
try:
|
||||||
|
# Make sure that the payload can be properly decoded in the
|
||||||
|
# defined charset, if it can't, let's mash some things
|
||||||
|
# inside the payload :-\
|
||||||
|
msg.get_payload(decode=True).decode(content_charset)
|
||||||
|
except LookupError:
|
||||||
|
logger.warning(
|
||||||
|
"Unknown encoding %s; interpreting as ASCII!",
|
||||||
|
content_charset
|
||||||
|
)
|
||||||
|
msg.set_payload(
|
||||||
|
msg.get_payload(decode=True).decode(
|
||||||
|
'ascii',
|
||||||
|
'ignore'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(
|
||||||
|
"Decoding error encountered; interpreting %s as ASCII!",
|
||||||
|
content_charset
|
||||||
|
)
|
||||||
|
msg.set_payload(
|
||||||
|
msg.get_payload(decode=True).decode(
|
||||||
|
'ascii',
|
||||||
|
'ignore'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
new = msg
|
||||||
|
return new
|
||||||
|
|
||||||
|
def _process_message(self, message):
|
||||||
|
msg = Message()
|
||||||
|
msg._email_object = message
|
||||||
|
settings = utils.get_settings()
|
||||||
|
|
||||||
|
if settings['store_original_message']:
|
||||||
|
self._process_save_original_message(message, msg)
|
||||||
|
msg.mailbox = self
|
||||||
|
if 'subject' in message:
|
||||||
|
msg.subject = (
|
||||||
|
utils.convert_header_to_unicode(message['subject'])[0:255]
|
||||||
|
)
|
||||||
|
if 'message-id' in message:
|
||||||
|
msg.message_id = message['message-id'][0:255].strip()
|
||||||
|
if 'from' in message:
|
||||||
|
msg.from_header = utils.convert_header_to_unicode(message['from'])
|
||||||
|
if 'to' in message:
|
||||||
|
msg.to_header = utils.convert_header_to_unicode(message['to'])
|
||||||
|
elif 'Delivered-To' in message:
|
||||||
|
msg.to_header = utils.convert_header_to_unicode(
|
||||||
|
message['Delivered-To']
|
||||||
|
)
|
||||||
|
msg.save()
|
||||||
|
message = self._get_dehydrated_message(message, msg)
|
||||||
|
try:
|
||||||
|
body = message.as_string()
|
||||||
|
except KeyError as exc:
|
||||||
|
# email.message.replace_header may raise 'KeyError' if the header
|
||||||
|
# 'content-transfer-encoding' is missing
|
||||||
|
logger.warning("Failed to parse message: %s", exc,)
|
||||||
|
return None
|
||||||
|
msg.set_body(body)
|
||||||
|
if message['in-reply-to']:
|
||||||
|
try:
|
||||||
|
msg.in_reply_to = Message.objects.filter(
|
||||||
|
message_id=message['in-reply-to'].strip()
|
||||||
|
)[0]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
msg.save()
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def _process_save_original_message(self, message, msg):
|
||||||
|
settings = utils.get_settings()
|
||||||
|
if settings['compress_original_message']:
|
||||||
|
with NamedTemporaryFile(suffix=".eml.gz") as fp_tmp:
|
||||||
|
with gzip.GzipFile(fileobj=fp_tmp, mode="w") as fp:
|
||||||
|
fp.write(message.as_string().encode('utf-8'))
|
||||||
|
msg.eml.save(
|
||||||
|
"{}.eml.gz".format(uuid.uuid4()),
|
||||||
|
File(fp_tmp),
|
||||||
|
save=False
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg.eml.save(
|
||||||
|
'%s.eml' % uuid.uuid4(),
|
||||||
|
ContentFile(message.as_string()),
|
||||||
|
save=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_new_mail(self, condition=None):
|
||||||
|
"""Connect to this transport and fetch new messages."""
|
||||||
|
new_mail = []
|
||||||
|
connection = self.get_connection()
|
||||||
|
if not connection:
|
||||||
|
return
|
||||||
|
for message in connection.get_message(condition):
|
||||||
|
msg = self.process_incoming_message(message)
|
||||||
|
if not msg is None:
|
||||||
|
yield msg
|
||||||
|
self.last_polling = now()
|
||||||
|
if django.VERSION >= (1, 5): # Django 1.5 introduces update_fields
|
||||||
|
self.save(update_fields=['last_polling'])
|
||||||
|
else:
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Mailbox')
|
||||||
|
verbose_name_plural = _('Mailboxes')
|
||||||
|
|
||||||
|
|
||||||
|
class IncomingMessageManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(
|
||||||
|
outgoing=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OutgoingMessageManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(
|
||||||
|
outgoing=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UnreadMessageManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(
|
||||||
|
read=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Message(models.Model):
|
||||||
|
mailbox = models.ForeignKey(
|
||||||
|
Mailbox,
|
||||||
|
related_name='messages',
|
||||||
|
verbose_name=_('Mailbox'),
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
subject = models.CharField(
|
||||||
|
_('Subject'),
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
|
message_id = models.CharField(
|
||||||
|
_('Message ID'),
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
|
in_reply_to = models.ForeignKey(
|
||||||
|
'django_mailbox.Message',
|
||||||
|
related_name='replies',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('In reply to'),
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
from_header = models.CharField(
|
||||||
|
_('From header'),
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
|
|
||||||
|
to_header = models.TextField(
|
||||||
|
_('To header'),
|
||||||
|
)
|
||||||
|
|
||||||
|
outgoing = models.BooleanField(
|
||||||
|
_('Outgoing'),
|
||||||
|
default=False,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
body = models.TextField(
|
||||||
|
_('Body'),
|
||||||
|
)
|
||||||
|
|
||||||
|
encoded = models.BooleanField(
|
||||||
|
_('Encoded'),
|
||||||
|
default=False,
|
||||||
|
help_text=_('True if the e-mail body is Base64 encoded'),
|
||||||
|
)
|
||||||
|
|
||||||
|
processed = models.DateTimeField(
|
||||||
|
_('Processed'),
|
||||||
|
auto_now_add=True
|
||||||
|
)
|
||||||
|
|
||||||
|
read = models.DateTimeField(
|
||||||
|
_('Read'),
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
eml = models.FileField(
|
||||||
|
_('Raw message contents'),
|
||||||
|
null=True,
|
||||||
|
upload_to="messages",
|
||||||
|
help_text=_('Original full content of message')
|
||||||
|
)
|
||||||
|
objects = models.Manager()
|
||||||
|
unread_messages = UnreadMessageManager()
|
||||||
|
incoming_messages = IncomingMessageManager()
|
||||||
|
outgoing_messages = OutgoingMessageManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address(self):
|
||||||
|
"""Property allowing one to get the relevant address(es).
|
||||||
|
|
||||||
|
In earlier versions of this library, the model had an `address` field
|
||||||
|
storing the e-mail address from which a message was received. During
|
||||||
|
later refactorings, it became clear that perhaps storing sent messages
|
||||||
|
would also be useful, so the address field was replaced with two
|
||||||
|
separate fields.
|
||||||
|
|
||||||
|
"""
|
||||||
|
addresses = []
|
||||||
|
addresses = self.to_addresses + self.from_address
|
||||||
|
return addresses
|
||||||
|
|
||||||
|
@property
|
||||||
|
def from_address(self):
|
||||||
|
"""Returns the address (as a list) from which this message was received
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This was once (and probably should be) a string rather than a list,
|
||||||
|
but in a pull request received long, long ago it was changed;
|
||||||
|
presumably to make the interface identical to that of
|
||||||
|
`to_addresses`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.from_header:
|
||||||
|
return [parseaddr(self.from_header)[1].lower()]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def to_addresses(self):
|
||||||
|
"""Returns a list of addresses to which this message was sent."""
|
||||||
|
addresses = []
|
||||||
|
for address in self.to_header.split(','):
|
||||||
|
if address:
|
||||||
|
addresses.append(
|
||||||
|
parseaddr(
|
||||||
|
address
|
||||||
|
)[1].lower()
|
||||||
|
)
|
||||||
|
return addresses
|
||||||
|
|
||||||
|
def reply(self, message):
|
||||||
|
"""Sends a message as a reply to this message instance.
|
||||||
|
|
||||||
|
Although Django's e-mail processing will set both Message-ID
|
||||||
|
and Date upon generating the e-mail message, we will not be able
|
||||||
|
to retrieve that information through normal channels, so we must
|
||||||
|
pre-set it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not message.from_email:
|
||||||
|
if self.mailbox.from_email:
|
||||||
|
message.from_email = self.mailbox.from_email
|
||||||
|
else:
|
||||||
|
message.from_email = django_settings.DEFAULT_FROM_EMAIL
|
||||||
|
message.extra_headers['Message-ID'] = make_msgid()
|
||||||
|
message.extra_headers['Date'] = formatdate()
|
||||||
|
message.extra_headers['In-Reply-To'] = self.message_id.strip()
|
||||||
|
message.send()
|
||||||
|
return self.mailbox.record_outgoing_message(
|
||||||
|
email.message_from_string(
|
||||||
|
message.message().as_string()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
"""
|
||||||
|
Returns the message body matching content type 'text/plain'.
|
||||||
|
"""
|
||||||
|
return utils.get_body_from_message(
|
||||||
|
self.get_email_object(), 'text', 'plain'
|
||||||
|
).replace('=\n', '').strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def html(self):
|
||||||
|
"""
|
||||||
|
Returns the message body matching content type 'text/html'.
|
||||||
|
"""
|
||||||
|
return utils.get_body_from_message(
|
||||||
|
self.get_email_object(), 'text', 'html'
|
||||||
|
).replace('\n', '').strip()
|
||||||
|
|
||||||
|
def _rehydrate(self, msg):
|
||||||
|
new = EmailMessage()
|
||||||
|
settings = utils.get_settings()
|
||||||
|
|
||||||
|
if msg.is_multipart():
|
||||||
|
for header, value in msg.items():
|
||||||
|
new[header] = value
|
||||||
|
for part in msg.get_payload():
|
||||||
|
new.attach(
|
||||||
|
self._rehydrate(part)
|
||||||
|
)
|
||||||
|
elif settings['attachment_interpolation_header'] in msg.keys():
|
||||||
|
try:
|
||||||
|
attachment = MessageAttachment.objects.get(
|
||||||
|
pk=msg[settings['attachment_interpolation_header']]
|
||||||
|
)
|
||||||
|
for header, value in attachment.items():
|
||||||
|
new[header] = value
|
||||||
|
encoding = new['Content-Transfer-Encoding']
|
||||||
|
if encoding and encoding.lower() == 'quoted-printable':
|
||||||
|
# Cannot use `email.encoders.encode_quopri due to
|
||||||
|
# bug 14360: http://bugs.python.org/issue14360
|
||||||
|
output = BytesIO()
|
||||||
|
encode_quopri(
|
||||||
|
BytesIO(
|
||||||
|
attachment.document.read()
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
quotetabs=True,
|
||||||
|
header=False,
|
||||||
|
)
|
||||||
|
new.set_payload(
|
||||||
|
output.getvalue().decode().replace(' ', '=20')
|
||||||
|
)
|
||||||
|
del new['Content-Transfer-Encoding']
|
||||||
|
new['Content-Transfer-Encoding'] = 'quoted-printable'
|
||||||
|
else:
|
||||||
|
new.set_payload(
|
||||||
|
attachment.document.read()
|
||||||
|
)
|
||||||
|
del new['Content-Transfer-Encoding']
|
||||||
|
encode_base64(new)
|
||||||
|
except MessageAttachment.DoesNotExist:
|
||||||
|
new[settings['altered_message_header']] = (
|
||||||
|
'Missing; Attachment %s not found' % (
|
||||||
|
msg[settings['attachment_interpolation_header']]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
new.set_payload('')
|
||||||
|
else:
|
||||||
|
for header, value in msg.items():
|
||||||
|
new[header] = value
|
||||||
|
new.set_payload(
|
||||||
|
msg.get_payload()
|
||||||
|
)
|
||||||
|
return new
|
||||||
|
|
||||||
|
def get_body(self):
|
||||||
|
"""Returns the `body` field of this record.
|
||||||
|
|
||||||
|
This will automatically base64-decode the message contents
|
||||||
|
if they are encoded as such.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.encoded:
|
||||||
|
return base64.b64decode(self.body.encode('ascii'))
|
||||||
|
return self.body.encode('utf-8')
|
||||||
|
|
||||||
|
def set_body(self, body):
|
||||||
|
"""Set the `body` field of this record.
|
||||||
|
|
||||||
|
This will automatically base64-encode the message contents to
|
||||||
|
circumvent a limitation in earlier versions of Django in which
|
||||||
|
no fields existed for storing arbitrary bytes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.encoded = True
|
||||||
|
self.body = base64.b64encode(body.encode('utf-8')).decode('ascii')
|
||||||
|
|
||||||
|
def get_email_object(self):
|
||||||
|
"""Returns an `email.message.EmailMessage` instance representing the
|
||||||
|
contents of this message and all attachments.
|
||||||
|
|
||||||
|
See [email.message.EmailMessage]_ for more information as to what methods
|
||||||
|
and properties are available on `email.message.EmailMessage` instances.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Depending upon the storage methods in use (specifically --
|
||||||
|
whether ``DJANGO_MAILBOX_STORE_ORIGINAL_MESSAGE`` is set
|
||||||
|
to ``True``, this may either create a "rehydrated" message
|
||||||
|
using stored attachments, or read the message contents stored
|
||||||
|
on-disk.
|
||||||
|
|
||||||
|
.. [email.message.EmailMessage] Python's `email.message.EmailMessage` docs
|
||||||
|
(https://docs.python.org/3/library/email.message.html)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not hasattr(self, '_email_object'): # Cache fill
|
||||||
|
if self.eml:
|
||||||
|
if self.eml.name.endswith('.gz'):
|
||||||
|
body = gzip.GzipFile(fileobj=self.eml).read()
|
||||||
|
else:
|
||||||
|
self.eml.open()
|
||||||
|
body = self.eml.file.read()
|
||||||
|
self.eml.close()
|
||||||
|
else:
|
||||||
|
body = self.get_body()
|
||||||
|
flat = email.message_from_bytes(body)
|
||||||
|
self._email_object = self._rehydrate(flat)
|
||||||
|
return self._email_object
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""Delete this message and all stored attachments."""
|
||||||
|
for attachment in self.attachments.all():
|
||||||
|
# This attachment is attached only to this message.
|
||||||
|
attachment.delete()
|
||||||
|
return super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.subject
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('E-mail message')
|
||||||
|
verbose_name_plural = _('E-mail messages')
|
||||||
|
|
||||||
|
|
||||||
|
class MessageAttachment(models.Model):
|
||||||
|
message = models.ForeignKey(
|
||||||
|
Message,
|
||||||
|
related_name='attachments',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Message'),
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
headers = models.TextField(
|
||||||
|
_('Headers'),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
document = models.FileField(
|
||||||
|
_('Document'),
|
||||||
|
upload_to=utils.get_attachment_save_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""Deletes the attachment."""
|
||||||
|
self.document.delete()
|
||||||
|
return super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
def _get_rehydrated_headers(self):
|
||||||
|
headers = self.headers
|
||||||
|
if headers is None:
|
||||||
|
return EmailMessage()
|
||||||
|
return email.message_from_string(headers)
|
||||||
|
|
||||||
|
def _set_dehydrated_headers(self, email_object):
|
||||||
|
self.headers = email_object.as_string()
|
||||||
|
|
||||||
|
def __delitem__(self, name):
|
||||||
|
rehydrated = self._get_rehydrated_headers()
|
||||||
|
del rehydrated[name]
|
||||||
|
self._set_dehydrated_headers(rehydrated)
|
||||||
|
|
||||||
|
def __setitem__(self, name, value):
|
||||||
|
rehydrated = self._get_rehydrated_headers()
|
||||||
|
rehydrated[name] = value
|
||||||
|
self._set_dehydrated_headers(rehydrated)
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
"""Returns the original filename of this attachment."""
|
||||||
|
file_name = self._get_rehydrated_headers().get_filename()
|
||||||
|
if isinstance(file_name, str):
|
||||||
|
result = utils.convert_header_to_unicode(file_name)
|
||||||
|
if result is None:
|
||||||
|
return file_name
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self._get_rehydrated_headers().items()
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
value = self._get_rehydrated_headers()[name]
|
||||||
|
if value is None:
|
||||||
|
raise KeyError('Header %s does not exist' % name)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.document.url
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Message attachment')
|
||||||
|
verbose_name_plural = _('Message attachments')
|
||||||
3
build/lib/django_mailbox/signals.py
Normal file
3
build/lib/django_mailbox/signals.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.dispatch.dispatcher import Signal
|
||||||
|
|
||||||
|
message_received = Signal(providing_args=['message'])
|
||||||
58
build/lib/django_mailbox/south_migrations/0001_initial.py
Normal file
58
build/lib/django_mailbox/south_migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding model 'Mailbox'
|
||||||
|
db.create_table('django_mailbox_mailbox', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||||
|
('uri', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||||
|
))
|
||||||
|
db.send_create_signal('django_mailbox', ['Mailbox'])
|
||||||
|
|
||||||
|
# Adding model 'Message'
|
||||||
|
db.create_table('django_mailbox_message', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('mailbox', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_mailbox.Mailbox'])),
|
||||||
|
('subject', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||||
|
('message_id', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||||
|
('from_address', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||||
|
('body', self.gf('django.db.models.fields.TextField')()),
|
||||||
|
('received', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||||
|
))
|
||||||
|
db.send_create_signal('django_mailbox', ['Message'])
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting model 'Mailbox'
|
||||||
|
db.delete_table('django_mailbox_mailbox')
|
||||||
|
|
||||||
|
# Deleting model 'Message'
|
||||||
|
db.delete_table('django_mailbox_message')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||||
|
},
|
||||||
|
'django_mailbox.message': {
|
||||||
|
'Meta': {'object_name': 'Message'},
|
||||||
|
'body': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'from_address': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_mailbox.Mailbox']"}),
|
||||||
|
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'received': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['django_mailbox']
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
|
||||||
|
# Changing field 'Mailbox.uri'
|
||||||
|
db.alter_column('django_mailbox_mailbox', 'uri', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
|
||||||
|
# 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.")
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'uri': ('django.db.models.fields.CharField', [], {'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'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['django_mailbox']
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Mailbox.active'
|
||||||
|
db.add_column('django_mailbox_mailbox', 'active',
|
||||||
|
self.gf('django.db.models.fields.BooleanField')(default=True),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Mailbox.active'
|
||||||
|
db.delete_column('django_mailbox_mailbox', 'active')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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_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']
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Message.outgoing'
|
||||||
|
db.add_column('django_mailbox_message', 'outgoing',
|
||||||
|
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Message.outgoing'
|
||||||
|
db.delete_column('django_mailbox_message', 'outgoing')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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_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']
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
db.rename_column('django_mailbox_message', 'from_address', 'address')
|
||||||
|
db.rename_column('django_mailbox_message', 'received', 'processed')
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
db.rename_column('django_mailbox_message', 'address', 'from_address')
|
||||||
|
db.rename_column('django_mailbox_message', 'processed', 'received')
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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'},
|
||||||
|
'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']
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Message.in_reply_to'
|
||||||
|
db.add_column('django_mailbox_message', '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'
|
||||||
|
db.create_table('django_mailbox_message_references', (
|
||||||
|
('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):
|
||||||
|
# Deleting field 'Message.in_reply_to'
|
||||||
|
db.delete_column('django_mailbox_message', 'in_reply_to_id')
|
||||||
|
|
||||||
|
# Removing M2M table for field references on 'Message'
|
||||||
|
db.delete_table('django_mailbox_message_references')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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'},
|
||||||
|
'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']
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Deleting field 'Message.address'
|
||||||
|
db.delete_column('django_mailbox_message', 'address')
|
||||||
|
|
||||||
|
# Adding field 'Message.from_header'
|
||||||
|
db.add_column('django_mailbox_message', 'from_header',
|
||||||
|
self.gf('django.db.models.fields.CharField')(default='', max_length=255),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
# Adding field 'Message.to_header'
|
||||||
|
db.add_column('django_mailbox_message', 'to_header',
|
||||||
|
self.gf('django.db.models.fields.TextField')(default=''),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
|
||||||
|
# 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.")
|
||||||
|
# Deleting field 'Message.from_header'
|
||||||
|
db.delete_column('django_mailbox_message', 'from_header')
|
||||||
|
|
||||||
|
# Deleting field 'Message.to_header'
|
||||||
|
db.delete_column('django_mailbox_message', 'to_header')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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'}),
|
||||||
|
'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']
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import datetime
|
||||||
|
import email
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import DataMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Migration(DataMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
for message in orm['django_mailbox.message'].objects.all():
|
||||||
|
msg_object = email.message_from_string(
|
||||||
|
message.body
|
||||||
|
)
|
||||||
|
message.from_header = msg_object['from']
|
||||||
|
message.to_header = msg_object['to']
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
raise RuntimeError('Cannot reverse this migration.')
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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'}),
|
||||||
|
'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']
|
||||||
|
symmetrical = True
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Removing M2M table for field references on 'Message'
|
||||||
|
db.delete_table('django_mailbox_message_references')
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Adding M2M table for field references on 'Message'
|
||||||
|
db.create_table('django_mailbox_message_references', (
|
||||||
|
('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 = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': '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', [], {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['django_mailbox']
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Mailbox.from_email'
|
||||||
|
db.add_column('django_mailbox_mailbox', '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):
|
||||||
|
# Deleting field 'Mailbox.from_email'
|
||||||
|
db.delete_column('django_mailbox_mailbox', 'from_email')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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', [], {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['django_mailbox']
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Message.read'
|
||||||
|
db.add_column('django_mailbox_message', 'read',
|
||||||
|
self.gf('django.db.models.fields.DateTimeField')(default=None, null=True, blank=True),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Message.read'
|
||||||
|
db.delete_column('django_mailbox_message', 'read')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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', [], {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['django_mailbox']
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
|
||||||
|
# Adding model 'MessageAttachment'
|
||||||
|
db.create_table('django_mailbox_messageattachment', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('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'
|
||||||
|
db.create_table('django_mailbox_message_attachments', (
|
||||||
|
('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):
|
||||||
|
|
||||||
|
# Deleting model 'MessageAttachment'
|
||||||
|
db.delete_table('django_mailbox_messageattachment')
|
||||||
|
|
||||||
|
# Removing M2M table for field attachments on 'Message'
|
||||||
|
db.delete_table('django_mailbox_message_attachments')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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'},
|
||||||
|
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['django_mailbox.MessageAttachment']", 'symmetrical': 'False'}),
|
||||||
|
'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.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']
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'MessageAttachment.message'
|
||||||
|
db.add_column('django_mailbox_messageattachment', '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):
|
||||||
|
# Deleting field 'MessageAttachment.message'
|
||||||
|
db.delete_column('django_mailbox_messageattachment', 'message_id')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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'},
|
||||||
|
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'messages_old'", 'blank': 'True', 'to': "orm['django_mailbox.MessageAttachment']"}),
|
||||||
|
'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.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']
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
no_dry_run = True
|
||||||
|
def forwards(self, orm):
|
||||||
|
for message in orm['django_mailbox.Message'].objects.all():
|
||||||
|
for attachment in message.attachments.all():
|
||||||
|
attachment.message = message
|
||||||
|
attachment.save()
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
for attachment in orm['django_mailbox.MessageAttachment'].objects.all():
|
||||||
|
if attachment.message:
|
||||||
|
attachment.message.attachments.add(
|
||||||
|
attachment
|
||||||
|
)
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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'},
|
||||||
|
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'messages_old'", 'blank': 'True', 'to': "orm['django_mailbox.MessageAttachment']"}),
|
||||||
|
'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.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']
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Removing M2M table for field attachments on 'Message'
|
||||||
|
db.delete_table('django_mailbox_message_attachments')
|
||||||
|
|
||||||
|
# Adding field 'MessageAttachment.headers'
|
||||||
|
db.add_column('django_mailbox_messageattachment', 'headers',
|
||||||
|
self.gf('django.db.models.fields.TextField')(null=True, blank=True),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Adding M2M table for field attachments on 'Message'
|
||||||
|
db.create_table('django_mailbox_message_attachments', (
|
||||||
|
('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'
|
||||||
|
db.delete_column('django_mailbox_messageattachment', 'headers')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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.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']
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Message.encoded'
|
||||||
|
db.add_column('django_mailbox_message', 'encoded',
|
||||||
|
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Message.encoded'
|
||||||
|
db.delete_column('django_mailbox_message', 'encoded')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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', [], {}),
|
||||||
|
'encoded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'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'}),
|
||||||
|
'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']
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
from south.utils import datetime_utils as datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Message.eml'
|
||||||
|
db.add_column('django_mailbox_message', 'eml',
|
||||||
|
self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Message.eml'
|
||||||
|
db.delete_column('django_mailbox_message', 'eml')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'django_mailbox.mailbox': {
|
||||||
|
'Meta': {'object_name': 'Mailbox'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'from_email': ('django.db.models.fields.CharField', [], {'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', [], {}),
|
||||||
|
'eml': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
|
||||||
|
'encoded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'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'}),
|
||||||
|
'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']
|
||||||
0
build/lib/django_mailbox/south_migrations/__init__.py
Normal file
0
build/lib/django_mailbox/south_migrations/__init__.py
Normal file
0
build/lib/django_mailbox/tests/__init__.py
Normal file
0
build/lib/django_mailbox/tests/__init__.py
Normal file
191
build/lib/django_mailbox/tests/base.py
Normal file
191
build/lib/django_mailbox/tests/base.py
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
import email
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from django_mailbox import models, utils
|
||||||
|
from django_mailbox.models import Mailbox, Message
|
||||||
|
|
||||||
|
|
||||||
|
class EmailIntegrationTimeout(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_email_as_text(name):
|
||||||
|
with open(
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'messages',
|
||||||
|
name,
|
||||||
|
),
|
||||||
|
'rb'
|
||||||
|
) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
class EmailMessageTestCase(TestCase):
|
||||||
|
ALLOWED_EXTRA_HEADERS = [
|
||||||
|
'MIME-Version',
|
||||||
|
'Content-Transfer-Encoding',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
dm_settings = utils.get_settings()
|
||||||
|
|
||||||
|
self._ALLOWED_MIMETYPES = dm_settings['allowed_mimetypes']
|
||||||
|
self._STRIP_UNALLOWED_MIMETYPES = (
|
||||||
|
dm_settings['strip_unallowed_mimetypes']
|
||||||
|
)
|
||||||
|
self._TEXT_STORED_MIMETYPES = dm_settings['text_stored_mimetypes']
|
||||||
|
|
||||||
|
self.mailbox = Mailbox.objects.create(from_email='from@example.com')
|
||||||
|
|
||||||
|
self.test_account = os.environ.get('EMAIL_ACCOUNT')
|
||||||
|
self.test_password = os.environ.get('EMAIL_PASSWORD')
|
||||||
|
self.test_smtp_server = os.environ.get('EMAIL_SMTP_SERVER')
|
||||||
|
self.test_from_email = 'nobody@nowhere.com'
|
||||||
|
|
||||||
|
self.maximum_wait_seconds = 60 * 5
|
||||||
|
|
||||||
|
settings.EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
settings.EMAIL_HOST = self.test_smtp_server
|
||||||
|
settings.EMAIL_PORT = 587
|
||||||
|
settings.EMAIL_HOST_USER = self.test_account
|
||||||
|
settings.EMAIL_HOST_PASSWORD = self.test_password
|
||||||
|
settings.EMAIL_USE_TLS = True
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def _get_new_messages(self, mailbox, condition=None):
|
||||||
|
start_time = time.time()
|
||||||
|
# wait until there is at least one message
|
||||||
|
while time.time() - start_time < self.maximum_wait_seconds:
|
||||||
|
|
||||||
|
messages = self.mailbox.get_new_mail(condition)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# check if generator contains at least one element
|
||||||
|
message = next(messages)
|
||||||
|
yield message
|
||||||
|
yield from messages
|
||||||
|
return
|
||||||
|
|
||||||
|
except StopIteration:
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
raise EmailIntegrationTimeout()
|
||||||
|
|
||||||
|
def _get_email_as_text(self, name):
|
||||||
|
with open(
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'messages',
|
||||||
|
name,
|
||||||
|
),
|
||||||
|
'rb'
|
||||||
|
) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def _get_email_object(self, name):
|
||||||
|
copy = self._get_email_as_text(name)
|
||||||
|
return email.message_from_bytes(copy)
|
||||||
|
|
||||||
|
def _headers_identical(self, left, right, header=None):
|
||||||
|
""" Check if headers are (close enough to) identical.
|
||||||
|
|
||||||
|
* This is particularly tricky because Python 2.6, Python 2.7 and
|
||||||
|
Python 3 each handle header strings slightly differently. This
|
||||||
|
should mash away all of the differences, though.
|
||||||
|
* 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
|
||||||
|
header was originally unquoted, it will be quoted when rehydrating
|
||||||
|
the e-mail message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if header.lower() == 'content-type':
|
||||||
|
# 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
|
||||||
|
# this comparison to fail. Also, the case of the encoding could
|
||||||
|
# be changed, etc. etc. etc.
|
||||||
|
left = left.replace('"', '').upper()
|
||||||
|
right = right.replace('"', '').upper()
|
||||||
|
left = left.replace('\n\t', ' ').replace('\n ', ' ')
|
||||||
|
right = right.replace('\n\t', ' ').replace('\n ', ' ')
|
||||||
|
if right != left:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def compare_email_objects(self, left, right):
|
||||||
|
# Compare headers
|
||||||
|
for key, value in left.items():
|
||||||
|
if not right[key] and key in self.ALLOWED_EXTRA_HEADERS:
|
||||||
|
continue
|
||||||
|
if not right[key]:
|
||||||
|
raise AssertionError("Extra header '%s'" % key)
|
||||||
|
if not self._headers_identical(right[key], value, header=key):
|
||||||
|
raise AssertionError(
|
||||||
|
"Header '{}' unequal:\n{}\n{}".format(
|
||||||
|
key,
|
||||||
|
repr(value),
|
||||||
|
repr(right[key]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for key, value in right.items():
|
||||||
|
if not left[key] and key in self.ALLOWED_EXTRA_HEADERS:
|
||||||
|
continue
|
||||||
|
if not left[key]:
|
||||||
|
raise AssertionError("Extra header '%s'" % key)
|
||||||
|
if not self._headers_identical(left[key], value, header=key):
|
||||||
|
raise AssertionError(
|
||||||
|
"Header '{}' unequal:\n{}\n{}".format(
|
||||||
|
key,
|
||||||
|
repr(value),
|
||||||
|
repr(right[key]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if left.is_multipart() != right.is_multipart():
|
||||||
|
self._raise_mismatched(left, right)
|
||||||
|
if left.is_multipart():
|
||||||
|
left_payloads = left.get_payload()
|
||||||
|
right_payloads = right.get_payload()
|
||||||
|
if len(left_payloads) != len(right_payloads):
|
||||||
|
self._raise_mismatched(left, right)
|
||||||
|
for n in range(len(left_payloads)):
|
||||||
|
self.compare_email_objects(
|
||||||
|
left_payloads[n],
|
||||||
|
right_payloads[n]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if left.get_payload() is None or right.get_payload() is None:
|
||||||
|
if left.get_payload() is None:
|
||||||
|
if right.get_payload is not None:
|
||||||
|
self._raise_mismatched(left, right)
|
||||||
|
if right.get_payload() is None:
|
||||||
|
if left.get_payload is not None:
|
||||||
|
self._raise_mismatched(left, right)
|
||||||
|
elif left.get_payload().strip() != right.get_payload().strip():
|
||||||
|
self._raise_mismatched(left, right)
|
||||||
|
|
||||||
|
def _raise_mismatched(self, left, right):
|
||||||
|
raise AssertionError(
|
||||||
|
"Message payloads do not match:\n{}\n{}".format(
|
||||||
|
left.as_string(),
|
||||||
|
right.as_string()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertEqual(self, left, right): # noqa: N802
|
||||||
|
if not isinstance(left, email.message.Message):
|
||||||
|
return super().assertEqual(left, right)
|
||||||
|
return self.compare_email_objects(left, right)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for message in Message.objects.all():
|
||||||
|
message.delete()
|
||||||
|
models.ALLOWED_MIMETYPES = self._ALLOWED_MIMETYPES
|
||||||
|
models.STRIP_UNALLOWED_MIMETYPES = self._STRIP_UNALLOWED_MIMETYPES
|
||||||
|
models.TEXT_STORED_MIMETYPES = self._TEXT_STORED_MIMETYPES
|
||||||
|
|
||||||
|
self.mailbox.delete()
|
||||||
|
super().tearDown()
|
||||||
12
build/lib/django_mailbox/tests/settings.py
Normal file
12
build/lib/django_mailbox/tests/settings.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'NAME': 'db.sqlite3',
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django_mailbox',
|
||||||
|
]
|
||||||
|
SECRET_KEY = 'beepboop'
|
||||||
70
build/lib/django_mailbox/tests/test_integration_imap.py
Normal file
70
build/lib/django_mailbox/tests/test_integration_imap.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from urllib import parse
|
||||||
|
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
|
||||||
|
from django_mailbox.models import Mailbox
|
||||||
|
from django_mailbox.tests.base import EmailMessageTestCase
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['TestImap']
|
||||||
|
|
||||||
|
|
||||||
|
class TestImap(EmailMessageTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.test_imap_server = (
|
||||||
|
os.environ.get('EMAIL_IMAP_SERVER')
|
||||||
|
)
|
||||||
|
|
||||||
|
required_settings = [
|
||||||
|
self.test_imap_server,
|
||||||
|
self.test_account,
|
||||||
|
self.test_password,
|
||||||
|
self.test_smtp_server,
|
||||||
|
self.test_from_email,
|
||||||
|
]
|
||||||
|
if not all(required_settings):
|
||||||
|
self.skipTest(
|
||||||
|
"Integration tests are not available without having "
|
||||||
|
"the the following environment variables set: "
|
||||||
|
"EMAIL_ACCOUNT, EMAIL_PASSWORD, EMAIL_SMTP_SERVER, "
|
||||||
|
"EMAIL_IMAP_SERVER."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mailbox = Mailbox.objects.create(
|
||||||
|
name='Integration Test Imap',
|
||||||
|
uri=self.get_connection_string()
|
||||||
|
)
|
||||||
|
self.arbitrary_identifier = str(uuid.uuid4())
|
||||||
|
|
||||||
|
def get_connection_string(self):
|
||||||
|
return "imap+ssl://{account}:{password}@{server}".format(
|
||||||
|
account=parse.quote(self.test_account),
|
||||||
|
password=parse.quote(self.test_password),
|
||||||
|
server=self.test_imap_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_imap_message(self):
|
||||||
|
text_content = 'This is some content'
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
self.arbitrary_identifier,
|
||||||
|
text_content,
|
||||||
|
self.test_from_email,
|
||||||
|
[
|
||||||
|
self.test_account,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
msg.send()
|
||||||
|
|
||||||
|
messages = self._get_new_messages(
|
||||||
|
self.mailbox,
|
||||||
|
condition=lambda m: m['subject'] == self.arbitrary_identifier
|
||||||
|
)
|
||||||
|
message = next(messages)
|
||||||
|
|
||||||
|
self.assertEqual(message.subject, self.arbitrary_identifier)
|
||||||
|
self.assertEqual(message.text, text_content)
|
||||||
|
self.assertEqual(0, len(list(messages)))
|
||||||
46
build/lib/django_mailbox/tests/test_mailbox.py
Normal file
46
build/lib/django_mailbox/tests/test_mailbox.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from django_mailbox.models import Mailbox
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['TestMailbox']
|
||||||
|
|
||||||
|
|
||||||
|
class TestMailbox(TestCase):
|
||||||
|
def test_protocol_info(self):
|
||||||
|
mailbox = Mailbox()
|
||||||
|
mailbox.uri = 'alpha://test.com'
|
||||||
|
|
||||||
|
expected_protocol = 'alpha'
|
||||||
|
actual_protocol = mailbox._protocol_info.scheme
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_protocol,
|
||||||
|
actual_protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_last_polling_field_exists(self):
|
||||||
|
mailbox = Mailbox()
|
||||||
|
self.assertTrue(hasattr(mailbox, 'last_polling'))
|
||||||
|
|
||||||
|
def test_get_new_mail_update_last_polling(self):
|
||||||
|
mailbox = Mailbox.objects.create(uri="mbox://" + os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'messages',
|
||||||
|
'generic_message.eml',
|
||||||
|
))
|
||||||
|
self.assertEqual(mailbox.last_polling, None)
|
||||||
|
list(mailbox.get_new_mail())
|
||||||
|
self.assertNotEqual(mailbox.last_polling, None)
|
||||||
|
|
||||||
|
def test_queryset_get_new_mail(self):
|
||||||
|
mailbox = Mailbox.objects.create(uri="mbox://" + os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'messages',
|
||||||
|
'generic_message.eml',
|
||||||
|
))
|
||||||
|
Mailbox.objects.filter(pk=mailbox.pk).get_new_mail()
|
||||||
|
mailbox.refresh_from_db()
|
||||||
|
self.assertNotEqual(mailbox.last_polling, None)
|
||||||
116
build/lib/django_mailbox/tests/test_message_flattening.py
Normal file
116
build/lib/django_mailbox/tests/test_message_flattening.py
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django_mailbox import models, utils
|
||||||
|
from django_mailbox.models import Message
|
||||||
|
from django_mailbox.tests.base import EmailMessageTestCase
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['TestMessageFlattening']
|
||||||
|
|
||||||
|
|
||||||
|
class TestMessageFlattening(EmailMessageTestCase):
|
||||||
|
def test_quopri_message_is_properly_rehydrated(self):
|
||||||
|
incoming_email_object = self._get_email_object(
|
||||||
|
'message_with_many_multiparts.eml',
|
||||||
|
)
|
||||||
|
# Note: this is identical to the above, but it appears that
|
||||||
|
# while reading-in an e-mail message, we do alter it slightly
|
||||||
|
expected_email_object = self._get_email_object(
|
||||||
|
'message_with_many_multiparts.eml',
|
||||||
|
)
|
||||||
|
models.TEXT_STORED_MIMETYPES = ['text/plain']
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(incoming_email_object)
|
||||||
|
|
||||||
|
actual_email_object = msg.get_email_object()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
actual_email_object,
|
||||||
|
expected_email_object,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_base64_message_is_properly_rehydrated(self):
|
||||||
|
incoming_email_object = self._get_email_object(
|
||||||
|
'message_with_attachment.eml',
|
||||||
|
)
|
||||||
|
# Note: this is identical to the above, but it appears that
|
||||||
|
# while reading-in an e-mail message, we do alter it slightly
|
||||||
|
expected_email_object = self._get_email_object(
|
||||||
|
'message_with_attachment.eml',
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(incoming_email_object)
|
||||||
|
|
||||||
|
actual_email_object = msg.get_email_object()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
actual_email_object,
|
||||||
|
expected_email_object,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_handles_rehydration_problems(self):
|
||||||
|
incoming_email_object = self._get_email_object(
|
||||||
|
'message_with_defective_attachment_association.eml',
|
||||||
|
)
|
||||||
|
expected_email_object = self._get_email_object(
|
||||||
|
'message_with_defective_attachment_association_result.eml',
|
||||||
|
)
|
||||||
|
# Note: this is identical to the above, but it appears that
|
||||||
|
# while reading-in an e-mail message, we do alter it slightly
|
||||||
|
message = Message()
|
||||||
|
message.body = incoming_email_object.as_string()
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(incoming_email_object)
|
||||||
|
|
||||||
|
del msg._email_object # Cache flush
|
||||||
|
actual_email_object = msg.get_email_object()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
actual_email_object,
|
||||||
|
expected_email_object,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_content_type_stripping(self):
|
||||||
|
incoming_email_object = self._get_email_object(
|
||||||
|
'message_with_many_multiparts.eml',
|
||||||
|
)
|
||||||
|
expected_email_object = self._get_email_object(
|
||||||
|
'message_with_many_multiparts_stripped_html.eml',
|
||||||
|
)
|
||||||
|
default_settings = utils.get_settings()
|
||||||
|
|
||||||
|
with mock.patch('django_mailbox.utils.get_settings') as get_settings:
|
||||||
|
altered = copy.deepcopy(default_settings)
|
||||||
|
altered['strip_unallowed_mimetypes'] = True
|
||||||
|
altered['allowed_mimetypes'] = ['text/plain']
|
||||||
|
|
||||||
|
get_settings.return_value = altered
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(incoming_email_object)
|
||||||
|
|
||||||
|
del msg._email_object # Cache flush
|
||||||
|
actual_email_object = msg.get_email_object()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
actual_email_object,
|
||||||
|
expected_email_object,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_processing_unknown_encoding(self):
|
||||||
|
incoming_email_object = self._get_email_object(
|
||||||
|
'message_with_invalid_encoding.eml',
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(incoming_email_object)
|
||||||
|
|
||||||
|
expected_text = (
|
||||||
|
"We offer loans to private individuals and corporate "
|
||||||
|
"organizations at 2% interest rate. Interested serious "
|
||||||
|
"applicants should apply via email with details of their "
|
||||||
|
"requirements.\n\nWarm Regards,\nLoan Team"
|
||||||
|
)
|
||||||
|
actual_text = msg.text
|
||||||
|
|
||||||
|
self.assertEqual(actual_text, expected_text)
|
||||||
432
build/lib/django_mailbox/tests/test_process_email.py
Normal file
432
build/lib/django_mailbox/tests/test_process_email.py
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
import gzip
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import copy
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django_mailbox.models import Mailbox, Message
|
||||||
|
from django_mailbox.utils import convert_header_to_unicode
|
||||||
|
from django_mailbox import utils
|
||||||
|
from django_mailbox.tests.base import EmailMessageTestCase
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
|
__all__ = ['TestProcessEmail']
|
||||||
|
|
||||||
|
|
||||||
|
class TestProcessEmail(EmailMessageTestCase):
|
||||||
|
def test_message_without_attachments(self):
|
||||||
|
message = self._get_email_object('generic_message.eml')
|
||||||
|
|
||||||
|
mailbox = Mailbox.objects.create()
|
||||||
|
msg = mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
msg.mailbox,
|
||||||
|
mailbox
|
||||||
|
)
|
||||||
|
self.assertEqual(msg.subject, 'Message Without Attachment')
|
||||||
|
self.assertEqual(
|
||||||
|
msg.message_id,
|
||||||
|
(
|
||||||
|
'<CAMdmm+hGH8Dgn-_0xnXJCd=PhyNAiouOYm5zFP0z'
|
||||||
|
'-foqTO60zA@mail.gmail.com>'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
msg.from_header,
|
||||||
|
'Adam Coddington <test@adamcoddington.net>',
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
msg.to_header,
|
||||||
|
'Adam Coddington <test@adamcoddington.net>',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_with_encoded_attachment_filenames(self):
|
||||||
|
message = self._get_email_object(
|
||||||
|
'message_with_koi8r_filename_attachments.eml'
|
||||||
|
)
|
||||||
|
|
||||||
|
mailbox = Mailbox.objects.create()
|
||||||
|
msg = mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
attachments = msg.attachments.order_by('pk').all()
|
||||||
|
self.assertEqual(
|
||||||
|
'\u041f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u043b'
|
||||||
|
'\u043e\u0436\u0435\u043d\u0438\u0439 HSE Career Fair 8 \u0430'
|
||||||
|
'\u043f\u0440\u0435\u043b\u044f 2016.pdf',
|
||||||
|
attachments[0].get_filename()
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
'\u0412\u0435\u0434\u043e\u043c\u043e\u0441\u0442\u0438.pdf',
|
||||||
|
attachments[1].get_filename()
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
'\u041f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u043b'
|
||||||
|
'\u043e\u0436\u0435\u043d\u0438\u0439 2016.pptx',
|
||||||
|
attachments[2].get_filename()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_with_attachments(self):
|
||||||
|
message = self._get_email_object('message_with_attachment.eml')
|
||||||
|
|
||||||
|
mailbox = Mailbox.objects.create()
|
||||||
|
msg = mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
expected_count = 1
|
||||||
|
actual_count = msg.attachments.count()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_count,
|
||||||
|
actual_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
attachment = msg.attachments.all()[0]
|
||||||
|
self.assertEqual(
|
||||||
|
attachment.get_filename(),
|
||||||
|
'heart.png',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_with_utf8_attachment_header(self):
|
||||||
|
""" Ensure that we properly handle UTF-8 encoded attachment
|
||||||
|
|
||||||
|
Safe for regress of #104 too
|
||||||
|
"""
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_utf8_attachment.eml',
|
||||||
|
)
|
||||||
|
mailbox = Mailbox.objects.create()
|
||||||
|
msg = mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
expected_count = 2
|
||||||
|
actual_count = msg.attachments.count()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_count,
|
||||||
|
actual_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
attachment = msg.attachments.all()[0]
|
||||||
|
self.assertEqual(
|
||||||
|
attachment.get_filename(),
|
||||||
|
'pi\u0142kochwyty.jpg'
|
||||||
|
)
|
||||||
|
|
||||||
|
attachment = msg.attachments.all()[1]
|
||||||
|
self.assertEqual(
|
||||||
|
attachment.get_filename(),
|
||||||
|
'odpowied\u017a Burmistrza.jpg'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_get_text_body(self):
|
||||||
|
message = self._get_email_object('multipart_text.eml')
|
||||||
|
|
||||||
|
mailbox = Mailbox.objects.create()
|
||||||
|
msg = mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
expected_results = 'Hello there!'
|
||||||
|
actual_results = msg.text.strip()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_results,
|
||||||
|
actual_results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_text_body_properly_recomposes_line_continuations(self):
|
||||||
|
message = Message()
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_long_text_lines.eml'
|
||||||
|
)
|
||||||
|
|
||||||
|
message.get_email_object = lambda: email_object
|
||||||
|
|
||||||
|
actual_text = message.text
|
||||||
|
expected_text = (
|
||||||
|
'The one of us with a bike pump is far ahead, '
|
||||||
|
'but a man stopped to help us and gave us his pump.'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
actual_text,
|
||||||
|
expected_text
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_body_properly_handles_unicode_body(self):
|
||||||
|
with open(
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'messages/generic_message.eml'
|
||||||
|
)
|
||||||
|
) as f:
|
||||||
|
unicode_body = f.read()
|
||||||
|
|
||||||
|
message = Message()
|
||||||
|
message.body = unicode_body
|
||||||
|
|
||||||
|
expected_body = unicode_body
|
||||||
|
actual_body = message.get_email_object().as_string()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_body,
|
||||||
|
actual_body
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_issue_82(self):
|
||||||
|
""" Ensure that we properly handle incorrectly encoded messages
|
||||||
|
|
||||||
|
"""
|
||||||
|
email_object = self._get_email_object('email_issue_82.eml')
|
||||||
|
it = 'works'
|
||||||
|
try:
|
||||||
|
# it's ok to call as_string() before passing email_object
|
||||||
|
# to _get_dehydrated_message()
|
||||||
|
email_object.as_string()
|
||||||
|
except:
|
||||||
|
it = 'do not works'
|
||||||
|
|
||||||
|
success = True
|
||||||
|
try:
|
||||||
|
self.mailbox.process_incoming_message(email_object)
|
||||||
|
except ValueError:
|
||||||
|
success = False
|
||||||
|
|
||||||
|
self.assertEqual(it, 'works')
|
||||||
|
self.assertEqual(True, success)
|
||||||
|
|
||||||
|
def test_message_issue_82_bis(self):
|
||||||
|
""" Ensure that the email object is good before and after
|
||||||
|
calling _get_dehydrated_message()
|
||||||
|
|
||||||
|
"""
|
||||||
|
message = self._get_email_object('email_issue_82.eml')
|
||||||
|
|
||||||
|
success = True
|
||||||
|
|
||||||
|
# this is the code of _process_message()
|
||||||
|
msg = Message()
|
||||||
|
# if STORE_ORIGINAL_MESSAGE:
|
||||||
|
# msg.eml.save('%s.eml' % uuid.uuid4(), ContentFile(message),
|
||||||
|
# save=False)
|
||||||
|
msg.mailbox = self.mailbox
|
||||||
|
if 'subject' in message:
|
||||||
|
msg.subject = convert_header_to_unicode(message['subject'])[0:255]
|
||||||
|
if 'message-id' in message:
|
||||||
|
msg.message_id = message['message-id'][0:255]
|
||||||
|
if 'from' in message:
|
||||||
|
msg.from_header = convert_header_to_unicode(message['from'])
|
||||||
|
if 'to' in message:
|
||||||
|
msg.to_header = convert_header_to_unicode(message['to'])
|
||||||
|
elif 'Delivered-To' in message:
|
||||||
|
msg.to_header = convert_header_to_unicode(message['Delivered-To'])
|
||||||
|
msg.save()
|
||||||
|
|
||||||
|
# here the message is ok
|
||||||
|
str_msg = message.as_string()
|
||||||
|
message = self.mailbox._get_dehydrated_message(message, msg)
|
||||||
|
try:
|
||||||
|
# here as_string raises UnicodeEncodeError
|
||||||
|
str_msg = message.as_string()
|
||||||
|
except:
|
||||||
|
success = False
|
||||||
|
|
||||||
|
msg.set_body(str_msg)
|
||||||
|
if message['in-reply-to']:
|
||||||
|
try:
|
||||||
|
msg.in_reply_to = Message.objects.filter(
|
||||||
|
message_id=message['in-reply-to']
|
||||||
|
)[0]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
msg.save()
|
||||||
|
|
||||||
|
self.assertEqual(True, success)
|
||||||
|
|
||||||
|
def test_message_with_misplaced_utf8_content(self):
|
||||||
|
""" Ensure that we properly handle incorrectly encoded messages
|
||||||
|
|
||||||
|
``message_with_utf8_char.eml``'s primary text payload is marked
|
||||||
|
as being iso-8859-1 data, but actually contains UTF-8 bytes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
email_object = self._get_email_object('message_with_utf8_char.eml')
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
expected_text = 'This message contains funny UTF16 characters ' + \
|
||||||
|
'like this one: "\xc2\xa0" and this one "\xe2\x9c\xbf".'
|
||||||
|
actual_text = msg.text
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_text,
|
||||||
|
actual_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_with_invalid_content_for_declared_encoding(self):
|
||||||
|
""" Ensure that we gracefully handle mis-encoded bodies.
|
||||||
|
|
||||||
|
Should a payload body be misencoded, we should:
|
||||||
|
|
||||||
|
- Not explode
|
||||||
|
|
||||||
|
Note: there is (intentionally) no assertion below; the only guarantee
|
||||||
|
we make via this library is that processing this e-mail message will
|
||||||
|
not cause an exception to be raised.
|
||||||
|
|
||||||
|
"""
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_invalid_content_for_declared_encoding.eml',
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
msg.text
|
||||||
|
|
||||||
|
def test_message_with_valid_content_in_single_byte_encoding(self):
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_single_byte_encoding.eml',
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
actual_text = msg.text
|
||||||
|
expected_body = '\u042d\u0442\u043e ' + \
|
||||||
|
'\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 ' + \
|
||||||
|
'\u0438\u043c\u0435\u0435\u0442 ' + \
|
||||||
|
'\u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d' + \
|
||||||
|
'\u0443\u044e ' + \
|
||||||
|
'\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430.'
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
actual_text,
|
||||||
|
expected_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_with_single_byte_subject_encoding(self):
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_single_byte_extended_subject_encoding.eml',
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
expected_subject = '\u00D3\u00E7\u00ED\u00E0\u00E9 \u00EA\u00E0\u00EA ' + \
|
||||||
|
'\u00E7\u00E0\u00F0\u00E0\u00E1\u00E0\u00F2\u00FB\u00E2' + \
|
||||||
|
'\u00E0\u00F2\u00FC \u00EE\u00F2 1000$ \u00E2 ' + \
|
||||||
|
'\u00ED\u00E5\u00E4\u00E5\u00EB\u00FE!'
|
||||||
|
actual_subject = msg.subject
|
||||||
|
self.assertEqual(actual_subject, expected_subject)
|
||||||
|
|
||||||
|
expected_from = 'test test <mr.test32@mail.ru>'
|
||||||
|
actual_from = msg.from_header
|
||||||
|
self.assertEqual(expected_from, actual_from)
|
||||||
|
|
||||||
|
def test_message_reply(self):
|
||||||
|
email_object = EmailMessage(
|
||||||
|
'Test subject', # subject
|
||||||
|
'Test body', # body
|
||||||
|
'username@example.com', # from
|
||||||
|
['mr.test32@mail.ru'], # to
|
||||||
|
)
|
||||||
|
msg = self.mailbox.record_outgoing_message(email_object.message())
|
||||||
|
|
||||||
|
self.assertTrue(msg.outgoing)
|
||||||
|
|
||||||
|
actual_from = 'username@example.com'
|
||||||
|
reply_email_object = EmailMessage(
|
||||||
|
'Test subject', # subject
|
||||||
|
'Test body', # body
|
||||||
|
actual_from, # from
|
||||||
|
['mr.test32@mail.ru'], # to
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock.patch.object(reply_email_object, 'send'):
|
||||||
|
reply_msg = msg.reply(reply_email_object)
|
||||||
|
|
||||||
|
self.assertEqual(reply_msg.in_reply_to, msg)
|
||||||
|
|
||||||
|
self.assertEqual(actual_from, msg.from_header)
|
||||||
|
|
||||||
|
reply_email_object.from_email = None
|
||||||
|
|
||||||
|
with mock.patch.object(reply_email_object, 'send'):
|
||||||
|
second_reply_msg = msg.reply(reply_email_object)
|
||||||
|
|
||||||
|
self.assertEqual(self.mailbox.from_email, second_reply_msg.from_header)
|
||||||
|
|
||||||
|
def test_message_with_text_attachment(self):
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_text_attachment.eml',
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments.all().count(), 1)
|
||||||
|
self.assertEqual('Has an attached text document, too!', msg.text)
|
||||||
|
|
||||||
|
def test_message_with_long_content(self):
|
||||||
|
email_object = self._get_email_object(
|
||||||
|
'message_with_long_content.eml',
|
||||||
|
)
|
||||||
|
size = len(force_text(email_object.as_string()))
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(email_object)
|
||||||
|
|
||||||
|
self.assertEqual(size,
|
||||||
|
len(force_text(msg.get_email_object().as_string())))
|
||||||
|
|
||||||
|
def test_message_saved(self):
|
||||||
|
message = self._get_email_object('generic_message.eml')
|
||||||
|
|
||||||
|
default_settings = utils.get_settings()
|
||||||
|
|
||||||
|
with mock.patch('django_mailbox.utils.get_settings') as get_settings:
|
||||||
|
altered = copy.deepcopy(default_settings)
|
||||||
|
altered['store_original_message'] = True
|
||||||
|
get_settings.return_value = altered
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
self.assertNotEquals(msg.eml, None)
|
||||||
|
|
||||||
|
self.assertTrue(msg.eml.name.endswith('.eml'))
|
||||||
|
|
||||||
|
with open(msg.eml.name, 'rb') as f:
|
||||||
|
self.assertEqual(
|
||||||
|
f.read(),
|
||||||
|
self._get_email_as_text('generic_message.eml')
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_message_saving_ignored(self):
|
||||||
|
message = self._get_email_object('generic_message.eml')
|
||||||
|
|
||||||
|
default_settings = utils.get_settings()
|
||||||
|
|
||||||
|
with mock.patch('django_mailbox.utils.get_settings') as get_settings:
|
||||||
|
altered = copy.deepcopy(default_settings)
|
||||||
|
altered['store_original_message'] = False
|
||||||
|
get_settings.return_value = altered
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
self.assertEquals(msg.eml, None)
|
||||||
|
|
||||||
|
def test_message_compressed(self):
|
||||||
|
message = self._get_email_object('generic_message.eml')
|
||||||
|
|
||||||
|
default_settings = utils.get_settings()
|
||||||
|
|
||||||
|
with mock.patch('django_mailbox.utils.get_settings') as get_settings:
|
||||||
|
altered = copy.deepcopy(default_settings)
|
||||||
|
altered['compress_original_message'] = True
|
||||||
|
altered['store_original_message'] = True
|
||||||
|
get_settings.return_value = altered
|
||||||
|
|
||||||
|
msg = self.mailbox.process_incoming_message(message)
|
||||||
|
|
||||||
|
actual_email_object = msg.get_email_object()
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.core.management import call_command, CommandError
|
||||||
|
from django.test import TestCase
|
||||||
|
import django
|
||||||
|
|
||||||
|
class CommandsTestCase(TestCase):
|
||||||
|
def test_processincomingmessage_no_args(self):
|
||||||
|
"""Check that processincomingmessage works with no args"""
|
||||||
|
|
||||||
|
mailbox_name = None
|
||||||
|
# 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
|
||||||
|
with mock.patch('django_mailbox.management.commands.processincomingmessage.Command.handle') as handle:
|
||||||
|
# Don't care about the return value
|
||||||
|
handle.return_value = None
|
||||||
|
|
||||||
|
call_command('processincomingmessage')
|
||||||
|
args, kwargs = handle.call_args
|
||||||
|
|
||||||
|
# Make sure that we called with the right arguments
|
||||||
|
try:
|
||||||
|
self.assertEqual(kwargs['mailbox_name'], mailbox_name)
|
||||||
|
except KeyError:
|
||||||
|
# Handle Django 1.7
|
||||||
|
# It uses optparse instead of argparse, so instead of being
|
||||||
|
# set to None, mailbox_name is simply left out altogether
|
||||||
|
# Thus we expect an empty tuple here
|
||||||
|
self.assertEqual(args, tuple())
|
||||||
|
|
||||||
|
|
||||||
|
def test_processincomingmessage_with_arg(self):
|
||||||
|
"""Check that processincomingmessage works with mailbox_name given"""
|
||||||
|
|
||||||
|
mailbox_name = 'foo_mailbox'
|
||||||
|
|
||||||
|
with mock.patch('django_mailbox.management.commands.processincomingmessage.Command.handle') as handle:
|
||||||
|
handle.return_value = None
|
||||||
|
|
||||||
|
call_command('processincomingmessage', mailbox_name)
|
||||||
|
args, kwargs = handle.call_args
|
||||||
|
try:
|
||||||
|
self.assertEqual(kwargs['mailbox_name'], mailbox_name)
|
||||||
|
except (AssertionError, KeyError):
|
||||||
|
# Handle Django 1.7
|
||||||
|
# It uses optparse instead of argparse, so instead of being
|
||||||
|
# in kwargs, mailbox_name is in args
|
||||||
|
self.assertEqual(args[0], mailbox_name)
|
||||||
|
|
||||||
|
def test_processincomingmessage_too_many_args(self):
|
||||||
|
"""Check that processincomingmessage raises an error if too many args"""
|
||||||
|
# Only perform this test for Django versions greater than 1.7.*. This
|
||||||
|
# is because, with optparse, too many arguments doesn't result in an
|
||||||
|
# error, which means this test is worthless anyway
|
||||||
|
# For the "compatibility" versions, unexpected arguments aren't handled
|
||||||
|
# very well, and result in a TypeError
|
||||||
|
if (LooseVersion(django.get_version()) >= LooseVersion('1.8') and
|
||||||
|
LooseVersion(django.get_version()) < LooseVersion('1.10')):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg')
|
||||||
|
# In 1.10 and later a proper CommandError should be raised
|
||||||
|
elif LooseVersion(django.get_version()) >= LooseVersion('1.10'):
|
||||||
|
with self.assertRaises(CommandError):
|
||||||
|
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg')
|
||||||
167
build/lib/django_mailbox/tests/test_transports.py
Normal file
167
build/lib/django_mailbox/tests/test_transports.py
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
from django_mailbox.tests.base import EmailMessageTestCase, get_email_as_text
|
||||||
|
from django_mailbox.transports import ImapTransport, Pop3Transport
|
||||||
|
|
||||||
|
FAKE_UID_SEARCH_ANSWER = (
|
||||||
|
'OK',
|
||||||
|
[
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
FAKE_UID_FETCH_SIZES = (
|
||||||
|
'OK',
|
||||||
|
[
|
||||||
|
b'1 (UID 18 RFC822.SIZE 58070000000)',
|
||||||
|
b'2 (UID 19 RFC822.SIZE 2593)'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
FAKE_UID_FETCH_MSG = (
|
||||||
|
'OK',
|
||||||
|
[
|
||||||
|
(
|
||||||
|
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_LIST_ARCHIVE_FOLDERS_ANSWERS = (
|
||||||
|
'OK',
|
||||||
|
[
|
||||||
|
b'(\\HasNoChildren \\All) "/" "[Gmail]/All Mail"'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IMAPTestCase(EmailMessageTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
def imap_server_uid_method(*args):
|
||||||
|
cmd = args[0]
|
||||||
|
arg2 = args[2]
|
||||||
|
if cmd == 'search':
|
||||||
|
return FAKE_UID_SEARCH_ANSWER
|
||||||
|
if cmd == 'copy':
|
||||||
|
return FAKE_UID_COPY_MSG
|
||||||
|
if cmd == 'fetch':
|
||||||
|
if arg2 == '(RFC822.SIZE)':
|
||||||
|
return FAKE_UID_FETCH_SIZES
|
||||||
|
if arg2 == '(RFC822)':
|
||||||
|
return FAKE_UID_FETCH_MSG
|
||||||
|
|
||||||
|
def imap_server_list_method(pattern=None):
|
||||||
|
return FAKE_LIST_ARCHIVE_FOLDERS_ANSWERS
|
||||||
|
|
||||||
|
self.imap_server = mock.Mock()
|
||||||
|
self.imap_server.uid = imap_server_uid_method
|
||||||
|
self.imap_server.list = imap_server_list_method
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
|
||||||
|
class TestImapTransport(IMAPTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.arbitrary_hostname = 'one.two.three'
|
||||||
|
self.arbitrary_port = 100
|
||||||
|
self.ssl = False
|
||||||
|
self.transport = ImapTransport(
|
||||||
|
self.arbitrary_hostname,
|
||||||
|
self.arbitrary_port,
|
||||||
|
self.ssl
|
||||||
|
)
|
||||||
|
self.transport.server = self.imap_server
|
||||||
|
|
||||||
|
def test_get_email_message(self):
|
||||||
|
actual_messages = list(self.transport.get_message())
|
||||||
|
self.assertEqual(len(actual_messages), 27)
|
||||||
|
actual_message = actual_messages[0]
|
||||||
|
expected_message = self._get_email_object('generic_message.eml')
|
||||||
|
self.assertEqual(expected_message, actual_message)
|
||||||
|
|
||||||
|
|
||||||
|
class TestImapArchivedTransport(TestImapTransport):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.archive = 'Archive'
|
||||||
|
self.transport = ImapTransport(
|
||||||
|
self.arbitrary_hostname,
|
||||||
|
self.arbitrary_port,
|
||||||
|
self.ssl,
|
||||||
|
self.archive
|
||||||
|
)
|
||||||
|
self.transport.server = self.imap_server
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaxSizeImapTransport(TestImapTransport):
|
||||||
|
|
||||||
|
@override_settings(DJANGO_MAILBOX_MAX_MESSAGE_SIZE=5807)
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.transport = ImapTransport(
|
||||||
|
self.arbitrary_hostname,
|
||||||
|
self.arbitrary_port,
|
||||||
|
self.ssl,
|
||||||
|
)
|
||||||
|
self.transport.server = self.imap_server
|
||||||
|
|
||||||
|
def test_size_limit(self):
|
||||||
|
all_message_ids = self.transport._get_all_message_ids()
|
||||||
|
small_message_ids = self.transport._get_small_message_ids(
|
||||||
|
all_message_ids,
|
||||||
|
)
|
||||||
|
self.assertEqual(len(small_message_ids), 1)
|
||||||
|
|
||||||
|
def test_get_email_message(self):
|
||||||
|
actual_messages = list(self.transport.get_message())
|
||||||
|
self.assertEqual(len(actual_messages), 1)
|
||||||
|
actual_message = actual_messages[0]
|
||||||
|
expected_message = self._get_email_object('generic_message.eml')
|
||||||
|
self.assertEqual(expected_message, actual_message)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPop3Transport(EmailMessageTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.arbitrary_hostname = 'one.two.three'
|
||||||
|
self.arbitrary_port = 100
|
||||||
|
self.ssl = False
|
||||||
|
self.transport = Pop3Transport(
|
||||||
|
self.arbitrary_hostname,
|
||||||
|
self.arbitrary_port,
|
||||||
|
self.ssl
|
||||||
|
)
|
||||||
|
self.transport.server = None
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def test_get_email_message(self):
|
||||||
|
with mock.patch.object(self.transport, 'server') as server:
|
||||||
|
# Consider this value arbitrary, the second parameter
|
||||||
|
# should have one entry per message in the inbox
|
||||||
|
server.list.return_value = [None, ['some_msg']]
|
||||||
|
server.retr.return_value = [
|
||||||
|
'+OK message follows',
|
||||||
|
[
|
||||||
|
line.encode('ascii')
|
||||||
|
for line in self._get_email_as_text(
|
||||||
|
'generic_message.eml'
|
||||||
|
).decode('ascii').split('\n')
|
||||||
|
],
|
||||||
|
10018, # Some arbitrary size, ideally matching the above
|
||||||
|
]
|
||||||
|
|
||||||
|
actual_messages = list(self.transport.get_message())
|
||||||
|
|
||||||
|
self.assertEqual(len(actual_messages), 1)
|
||||||
|
|
||||||
|
actual_message = actual_messages[0]
|
||||||
|
expected_message = self._get_email_object('generic_message.eml')
|
||||||
|
|
||||||
|
self.assertEqual(expected_message, actual_message)
|
||||||
11
build/lib/django_mailbox/transports/__init__.py
Normal file
11
build/lib/django_mailbox/transports/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# all imports below are only used by external modules
|
||||||
|
# flake8: noqa
|
||||||
|
from django_mailbox.transports.imap import ImapTransport
|
||||||
|
from django_mailbox.transports.pop3 import Pop3Transport
|
||||||
|
from django_mailbox.transports.maildir import MaildirTransport
|
||||||
|
from django_mailbox.transports.mbox import MboxTransport
|
||||||
|
from django_mailbox.transports.babyl import BabylTransport
|
||||||
|
from django_mailbox.transports.mh import MHTransport
|
||||||
|
from django_mailbox.transports.mmdf import MMDFTransport
|
||||||
|
from django_mailbox.transports.gmail import GmailImapTransport
|
||||||
|
from django_mailbox.transports.office365 import Office365Transport
|
||||||
6
build/lib/django_mailbox/transports/babyl.py
Normal file
6
build/lib/django_mailbox/transports/babyl.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from mailbox import Babyl
|
||||||
|
from django_mailbox.transports.generic import GenericFileMailbox
|
||||||
|
|
||||||
|
|
||||||
|
class BabylTransport(GenericFileMailbox):
|
||||||
|
_variant = Babyl
|
||||||
11
build/lib/django_mailbox/transports/base.py
Normal file
11
build/lib/django_mailbox/transports/base.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import email
|
||||||
|
|
||||||
|
# Do *not* remove this, we need to use this in subclasses of EmailTransport
|
||||||
|
from email.errors import MessageParseError # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
class EmailTransport:
|
||||||
|
def get_email_from_bytes(self, contents):
|
||||||
|
message = email.message_from_bytes(contents)
|
||||||
|
|
||||||
|
return message
|
||||||
26
build/lib/django_mailbox/transports/generic.py
Normal file
26
build/lib/django_mailbox/transports/generic.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .base import EmailTransport
|
||||||
|
|
||||||
|
|
||||||
|
class GenericFileMailbox(EmailTransport):
|
||||||
|
_variant = None
|
||||||
|
_path = None
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
super().__init__()
|
||||||
|
self._path = path
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
return self._variant(self._path)
|
||||||
|
|
||||||
|
def get_message(self, condition=None):
|
||||||
|
repository = self.get_instance()
|
||||||
|
repository.lock()
|
||||||
|
for key, message in repository.items():
|
||||||
|
if condition and not condition(message):
|
||||||
|
continue
|
||||||
|
repository.remove(key)
|
||||||
|
yield message
|
||||||
|
repository.flush()
|
||||||
|
repository.unlock()
|
||||||
57
build/lib/django_mailbox/transports/gmail.py
Normal file
57
build/lib/django_mailbox/transports/gmail.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django_mailbox.transports.imap import ImapTransport
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GmailImapTransport(ImapTransport):
|
||||||
|
|
||||||
|
def connect(self, username, password):
|
||||||
|
# Try to use oauth2 first. It's much safer
|
||||||
|
try:
|
||||||
|
self._connect_oauth(username)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
logger.warning("Couldn't do oauth2 because %s" % e)
|
||||||
|
self.server = self.transport(self.hostname, self.port)
|
||||||
|
typ, msg = self.server.login(username, password)
|
||||||
|
self.server.select()
|
||||||
|
|
||||||
|
def _connect_oauth(self, username):
|
||||||
|
# username should be an email address that has already been authorized
|
||||||
|
# for gmail access
|
||||||
|
try:
|
||||||
|
from django_mailbox.google_utils import (
|
||||||
|
get_google_access_token,
|
||||||
|
fetch_user_info,
|
||||||
|
AccessTokenNotFound,
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
raise ValueError(
|
||||||
|
"Install python-social-auth to use oauth2 auth for gmail"
|
||||||
|
)
|
||||||
|
|
||||||
|
access_token = None
|
||||||
|
while access_token is None:
|
||||||
|
try:
|
||||||
|
access_token = get_google_access_token(username)
|
||||||
|
google_email_address = fetch_user_info(username)['email']
|
||||||
|
except TypeError:
|
||||||
|
# This means that the google process took too long
|
||||||
|
# Trying again is the right thing to do
|
||||||
|
pass
|
||||||
|
except AccessTokenNotFound:
|
||||||
|
raise ValueError(
|
||||||
|
"No Token available in python-social-auth for %s" % (
|
||||||
|
username
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
auth_string = 'user={}\1auth=Bearer {}\1\1'.format(
|
||||||
|
google_email_address,
|
||||||
|
access_token
|
||||||
|
)
|
||||||
|
self.server = self.transport(self.hostname, self.port)
|
||||||
|
self.server.authenticate('XOAUTH2', lambda x: auth_string)
|
||||||
|
self.server.select()
|
||||||
137
build/lib/django_mailbox/transports/imap.py
Normal file
137
build/lib/django_mailbox/transports/imap.py
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import imaplib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .base import EmailTransport, MessageParseError
|
||||||
|
|
||||||
|
|
||||||
|
# By default, imaplib will raise an exception if it encounters more
|
||||||
|
# than 10k bytes; sometimes users attempt to consume mailboxes that
|
||||||
|
# have a more, and modern computers are skookum-enough to handle just
|
||||||
|
# a *few* more messages without causing any sort of problem.
|
||||||
|
imaplib._MAXLINE = 1000000
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImapTransport(EmailTransport):
|
||||||
|
def __init__(
|
||||||
|
self, hostname, port=None, ssl=False, tls=False,
|
||||||
|
archive='', folder=None,
|
||||||
|
):
|
||||||
|
self.max_message_size = getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_MAX_MESSAGE_SIZE',
|
||||||
|
False
|
||||||
|
)
|
||||||
|
self.integration_testing_subject = getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT',
|
||||||
|
None
|
||||||
|
)
|
||||||
|
self.hostname = hostname
|
||||||
|
self.port = port
|
||||||
|
self.archive = archive
|
||||||
|
self.folder = folder
|
||||||
|
self.tls = tls
|
||||||
|
if ssl:
|
||||||
|
self.transport = imaplib.IMAP4_SSL
|
||||||
|
if not self.port:
|
||||||
|
self.port = 993
|
||||||
|
else:
|
||||||
|
self.transport = imaplib.IMAP4
|
||||||
|
if not self.port:
|
||||||
|
self.port = 143
|
||||||
|
|
||||||
|
def connect(self, username, password):
|
||||||
|
self.server = self.transport(self.hostname, self.port)
|
||||||
|
if self.tls:
|
||||||
|
self.server.starttls()
|
||||||
|
typ, msg = self.server.login(username, password)
|
||||||
|
|
||||||
|
if self.folder:
|
||||||
|
self.server.select(self.folder)
|
||||||
|
else:
|
||||||
|
self.server.select()
|
||||||
|
|
||||||
|
def _get_all_message_ids(self):
|
||||||
|
# Fetch all the message uids
|
||||||
|
response, message_ids = self.server.uid('search', None, 'ALL')
|
||||||
|
message_id_string = message_ids[0].strip()
|
||||||
|
# Usually `message_id_string` will be a list of space-separated
|
||||||
|
# ids; we must make sure that it isn't an empty string before
|
||||||
|
# splitting into individual UIDs.
|
||||||
|
if message_id_string:
|
||||||
|
return message_id_string.decode().split(' ')
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _get_small_message_ids(self, message_ids):
|
||||||
|
# Using existing message uids, get the sizes and
|
||||||
|
# return only those that are under the size
|
||||||
|
# limit
|
||||||
|
safe_message_ids = []
|
||||||
|
|
||||||
|
status, data = self.server.uid(
|
||||||
|
'fetch',
|
||||||
|
','.join(message_ids),
|
||||||
|
'(RFC822.SIZE)'
|
||||||
|
)
|
||||||
|
|
||||||
|
for each_msg in data:
|
||||||
|
each_msg = each_msg.decode()
|
||||||
|
try:
|
||||||
|
uid = each_msg.split(' ')[2]
|
||||||
|
size = each_msg.split(' ')[4].rstrip(')')
|
||||||
|
if int(size) <= int(self.max_message_size):
|
||||||
|
safe_message_ids.append(uid)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(
|
||||||
|
"ValueError: {} working on {}".format(e, each_msg[0])
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
return safe_message_ids
|
||||||
|
|
||||||
|
def get_message(self, condition=None):
|
||||||
|
message_ids = self._get_all_message_ids()
|
||||||
|
|
||||||
|
if not message_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Limit the uids to the small ones if we care about that
|
||||||
|
if self.max_message_size:
|
||||||
|
message_ids = self._get_small_message_ids(message_ids)
|
||||||
|
|
||||||
|
if self.archive:
|
||||||
|
typ, folders = self.server.list(pattern=self.archive)
|
||||||
|
if folders[0] is None:
|
||||||
|
# If the archive folder does not exist, create it
|
||||||
|
self.server.create(self.archive)
|
||||||
|
|
||||||
|
for uid in message_ids:
|
||||||
|
try:
|
||||||
|
typ, msg_contents = self.server.uid('fetch', uid, '(RFC822)')
|
||||||
|
if not msg_contents:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
message = self.get_email_from_bytes(msg_contents[0][1])
|
||||||
|
except TypeError:
|
||||||
|
# This happens if another thread/process deletes the
|
||||||
|
# message between our generating the ID list and our
|
||||||
|
# processing it here.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if condition and not condition(message):
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield message
|
||||||
|
except MessageParseError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.archive:
|
||||||
|
self.server.uid('copy', uid, self.archive)
|
||||||
|
|
||||||
|
self.server.uid('store', uid, "+FLAGS", "(\\Deleted)")
|
||||||
|
self.server.expunge()
|
||||||
|
return
|
||||||
9
build/lib/django_mailbox/transports/maildir.py
Normal file
9
build/lib/django_mailbox/transports/maildir.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from mailbox import Maildir
|
||||||
|
from django_mailbox.transports.generic import GenericFileMailbox
|
||||||
|
|
||||||
|
|
||||||
|
class MaildirTransport(GenericFileMailbox):
|
||||||
|
_variant = Maildir
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
return self._variant(self._path, None)
|
||||||
6
build/lib/django_mailbox/transports/mbox.py
Normal file
6
build/lib/django_mailbox/transports/mbox.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from mailbox import mbox
|
||||||
|
from django_mailbox.transports.generic import GenericFileMailbox
|
||||||
|
|
||||||
|
|
||||||
|
class MboxTransport(GenericFileMailbox):
|
||||||
|
_variant = mbox
|
||||||
6
build/lib/django_mailbox/transports/mh.py
Normal file
6
build/lib/django_mailbox/transports/mh.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from mailbox import MH
|
||||||
|
from django_mailbox.transports.generic import GenericFileMailbox
|
||||||
|
|
||||||
|
|
||||||
|
class MHTransport(GenericFileMailbox):
|
||||||
|
_variant = MH
|
||||||
6
build/lib/django_mailbox/transports/mmdf.py
Normal file
6
build/lib/django_mailbox/transports/mmdf.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from mailbox import MMDF
|
||||||
|
from django_mailbox.transports.generic import GenericFileMailbox
|
||||||
|
|
||||||
|
|
||||||
|
class MMDFTransport(GenericFileMailbox):
|
||||||
|
_variant = MMDF
|
||||||
131
build/lib/django_mailbox/transports/office365.py
Normal file
131
build/lib/django_mailbox/transports/office365.py
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
import O365
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .base import EmailTransport, MessageParseError
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Office365Transport(EmailTransport):
|
||||||
|
def __init__(
|
||||||
|
self, hostname, port=None, ssl=False, tls=False,
|
||||||
|
archive='', folder=None, client_id=None, client_secret=None, tenant_id=None
|
||||||
|
):
|
||||||
|
self.max_message_size = getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_MAX_MESSAGE_SIZE',
|
||||||
|
False
|
||||||
|
)
|
||||||
|
self.integration_testing_subject = getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT',
|
||||||
|
None
|
||||||
|
)
|
||||||
|
self.hostname = hostname
|
||||||
|
self.port = port
|
||||||
|
self.archive = archive
|
||||||
|
self.folder = folder
|
||||||
|
self.tls = tls
|
||||||
|
if ssl:
|
||||||
|
#self.transport = imaplib.IMAP4_SSL
|
||||||
|
if not self.port:
|
||||||
|
self.port = 993
|
||||||
|
else:
|
||||||
|
#self.transport = imaplib.IMAP4
|
||||||
|
if not self.port:
|
||||||
|
self.port = 143
|
||||||
|
self.client_id = client_id
|
||||||
|
self.client_secret = client_secret
|
||||||
|
self.tenant_id = tenant_id
|
||||||
|
|
||||||
|
def connect(self, username, password):
|
||||||
|
credentials = (self.client_id, self.client_secret)
|
||||||
|
|
||||||
|
# the default protocol will be Microsoft Graph
|
||||||
|
# the default authentication method will be "on behalf of a user"
|
||||||
|
|
||||||
|
self.server = O365.Account(credentials, auth_flow_type='credentials', tenant_id=self.tenant_id)
|
||||||
|
if self.server.authenticate(scopes=['mailbox']):
|
||||||
|
print('Authenticated!')
|
||||||
|
|
||||||
|
def _get_all_message_ids(self):
|
||||||
|
# # Fetch all the message uids
|
||||||
|
# response, message_ids = self.server.uid('search', None, 'ALL')
|
||||||
|
# message_id_string = message_ids[0].strip()
|
||||||
|
# # Usually `message_id_string` will be a list of space-separated
|
||||||
|
# # ids; we must make sure that it isn't an empty string before
|
||||||
|
# # splitting into individual UIDs.
|
||||||
|
# if message_id_string:
|
||||||
|
# return message_id_string.decode().split(' ')
|
||||||
|
# return []
|
||||||
|
|
||||||
|
def _get_small_message_ids(self, message_ids):
|
||||||
|
# Using existing message uids, get the sizes and
|
||||||
|
# return only those that are under the size
|
||||||
|
# limit
|
||||||
|
safe_message_ids = []
|
||||||
|
|
||||||
|
status, data = self.server.uid(
|
||||||
|
'fetch',
|
||||||
|
','.join(message_ids),
|
||||||
|
'(RFC822.SIZE)'
|
||||||
|
)
|
||||||
|
|
||||||
|
for each_msg in data:
|
||||||
|
each_msg = each_msg.decode()
|
||||||
|
try:
|
||||||
|
uid = each_msg.split(' ')[2]
|
||||||
|
size = each_msg.split(' ')[4].rstrip(')')
|
||||||
|
if int(size) <= int(self.max_message_size):
|
||||||
|
safe_message_ids.append(uid)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(
|
||||||
|
"ValueError: {} working on {}".format(e, each_msg[0])
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
return safe_message_ids
|
||||||
|
|
||||||
|
def get_message(self, condition=None):
|
||||||
|
message_ids = self._get_all_message_ids()
|
||||||
|
|
||||||
|
if not message_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Limit the uids to the small ones if we care about that
|
||||||
|
if self.max_message_size:
|
||||||
|
message_ids = self._get_small_message_ids(message_ids)
|
||||||
|
|
||||||
|
if self.archive:
|
||||||
|
typ, folders = self.server.list(pattern=self.archive)
|
||||||
|
if folders[0] is None:
|
||||||
|
# If the archive folder does not exist, create it
|
||||||
|
self.server.create(self.archive)
|
||||||
|
|
||||||
|
for uid in message_ids:
|
||||||
|
try:
|
||||||
|
typ, msg_contents = self.server.uid('fetch', uid, '(RFC822)')
|
||||||
|
if not msg_contents:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
message = self.get_email_from_bytes(msg_contents[0][1])
|
||||||
|
except TypeError:
|
||||||
|
# This happens if another thread/process deletes the
|
||||||
|
# message between our generating the ID list and our
|
||||||
|
# processing it here.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if condition and not condition(message):
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield message
|
||||||
|
except MessageParseError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.archive:
|
||||||
|
self.server.uid('copy', uid, self.archive)
|
||||||
|
|
||||||
|
self.server.uid('store', uid, "+FLAGS", "(\\Deleted)")
|
||||||
|
self.server.expunge()
|
||||||
|
return
|
||||||
44
build/lib/django_mailbox/transports/pop3.py
Normal file
44
build/lib/django_mailbox/transports/pop3.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
from poplib import POP3, POP3_SSL
|
||||||
|
|
||||||
|
from .base import EmailTransport, MessageParseError
|
||||||
|
|
||||||
|
|
||||||
|
class Pop3Transport(EmailTransport):
|
||||||
|
def __init__(self, hostname, port=None, ssl=False):
|
||||||
|
self.hostname = hostname
|
||||||
|
self.port = port
|
||||||
|
if ssl:
|
||||||
|
self.transport = POP3_SSL
|
||||||
|
if not self.port:
|
||||||
|
self.port = 995
|
||||||
|
else:
|
||||||
|
self.transport = POP3
|
||||||
|
if not self.port:
|
||||||
|
self.port = 110
|
||||||
|
|
||||||
|
def connect(self, username, password):
|
||||||
|
self.server = self.transport(self.hostname, self.port)
|
||||||
|
self.server.user(username)
|
||||||
|
self.server.pass_(password)
|
||||||
|
|
||||||
|
def get_message_body(self, message_lines):
|
||||||
|
return bytes('\r\n', 'ascii').join(message_lines)
|
||||||
|
|
||||||
|
def get_message(self, condition=None):
|
||||||
|
message_count = len(self.server.list()[1])
|
||||||
|
for i in range(message_count):
|
||||||
|
try:
|
||||||
|
msg_contents = self.get_message_body(
|
||||||
|
self.server.retr(i + 1)[1]
|
||||||
|
)
|
||||||
|
message = self.get_email_from_bytes(msg_contents)
|
||||||
|
|
||||||
|
if condition and not condition(message):
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield message
|
||||||
|
except MessageParseError:
|
||||||
|
continue
|
||||||
|
self.server.dele(i + 1)
|
||||||
|
self.server.quit()
|
||||||
|
return
|
||||||
151
build/lib/django_mailbox/utils.py
Normal file
151
build/lib/django_mailbox/utils.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import datetime
|
||||||
|
import email.header
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings():
|
||||||
|
return {
|
||||||
|
'strip_unallowed_mimetypes': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_STRIP_UNALLOWED_MIMETYPES',
|
||||||
|
False
|
||||||
|
),
|
||||||
|
'allowed_mimetypes': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_ALLOWED_MIMETYPES',
|
||||||
|
[
|
||||||
|
'text/plain',
|
||||||
|
'text/html'
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'text_stored_mimetypes': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_TEXT_STORED_MIMETYPES',
|
||||||
|
[
|
||||||
|
'text/plain',
|
||||||
|
'text/html'
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'altered_message_header': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_ALTERED_MESSAGE_HEADER',
|
||||||
|
'X-Django-Mailbox-Altered-Message'
|
||||||
|
),
|
||||||
|
'attachment_interpolation_header': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_ATTACHMENT_INTERPOLATION_HEADER',
|
||||||
|
'X-Django-Mailbox-Interpolate-Attachment'
|
||||||
|
),
|
||||||
|
'attachment_upload_to': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_ATTACHMENT_UPLOAD_TO',
|
||||||
|
'mailbox_attachments/%Y/%m/%d/'
|
||||||
|
),
|
||||||
|
'store_original_message': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_STORE_ORIGINAL_MESSAGE',
|
||||||
|
False
|
||||||
|
),
|
||||||
|
'compress_original_message': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_COMPRESS_ORIGINAL_MESSAGE',
|
||||||
|
False
|
||||||
|
),
|
||||||
|
'original_message_compression': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_ORIGINAL_MESSAGE_COMPRESSION',
|
||||||
|
6
|
||||||
|
),
|
||||||
|
'default_charset': getattr(
|
||||||
|
settings,
|
||||||
|
'DJANGO_MAILBOX_default_charset',
|
||||||
|
'iso8859-1',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def convert_header_to_unicode(header):
|
||||||
|
default_charset = get_settings()['default_charset']
|
||||||
|
|
||||||
|
def _decode(value, encoding):
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value
|
||||||
|
if not encoding or encoding == 'unknown-8bit':
|
||||||
|
encoding = default_charset
|
||||||
|
return value.decode(encoding, 'replace')
|
||||||
|
|
||||||
|
try:
|
||||||
|
return ''.join(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
_decode(bytestr, encoding)
|
||||||
|
) for bytestr, encoding in email.header.decode_header(header)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
logger.exception(
|
||||||
|
'Errors encountered decoding header %s into encoding %s.',
|
||||||
|
header,
|
||||||
|
default_charset,
|
||||||
|
)
|
||||||
|
return header.decode(default_charset, 'replace')
|
||||||
|
|
||||||
|
|
||||||
|
def get_body_from_message(message, maintype, subtype):
|
||||||
|
"""
|
||||||
|
Fetchs the body message matching main/sub content type.
|
||||||
|
"""
|
||||||
|
body = ''
|
||||||
|
for part in message.walk():
|
||||||
|
if part.get('content-disposition', '').startswith('attachment;'):
|
||||||
|
continue
|
||||||
|
if part.get_content_maintype() == maintype and \
|
||||||
|
part.get_content_subtype() == subtype:
|
||||||
|
charset = part.get_content_charset()
|
||||||
|
this_part = part.get_payload(decode=True)
|
||||||
|
if charset:
|
||||||
|
try:
|
||||||
|
this_part = this_part.decode(charset, 'replace')
|
||||||
|
except LookupError:
|
||||||
|
this_part = this_part.decode('ascii', 'replace')
|
||||||
|
logger.warning(
|
||||||
|
'Unknown encoding %s encountered while decoding '
|
||||||
|
'text payload. Interpreting as ASCII with '
|
||||||
|
'replacement, but some data may not be '
|
||||||
|
'represented as the sender intended.',
|
||||||
|
charset
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
this_part = this_part.decode('ascii', 'replace')
|
||||||
|
logger.warning(
|
||||||
|
'Error encountered while decoding text '
|
||||||
|
'payload from an incorrectly-constructed '
|
||||||
|
'e-mail; payload was converted to ASCII with '
|
||||||
|
'replacement, but some data may not be '
|
||||||
|
'represented as the sender intended.'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
this_part = this_part.decode('ascii', 'replace')
|
||||||
|
|
||||||
|
body += this_part
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def get_attachment_save_path(instance, filename):
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
path = settings['attachment_upload_to']
|
||||||
|
if '%' in path:
|
||||||
|
path = datetime.datetime.utcnow().strftime(path)
|
||||||
|
|
||||||
|
return os.path.join(
|
||||||
|
path,
|
||||||
|
filename,
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue