From a0913eeea77dae05fd156938cf1fa421954213cd Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Sun, 28 Nov 2021 22:56:19 +0200 Subject: [PATCH 01/21] Upgrades --- .travis.yml | 10 ++++------ db_email_backend/models.py | 4 ---- test_app/settings.py | 6 +++++- tox.ini | 20 +++++--------------- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdc91c6..3cfaeee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,11 @@ language: python python: - - 2.7 - 3.6 + - 3.8 env: - - DJANGO=1.8 - - DJANGO=1.9 - - DJANGO=1.10 - - DJANGO=1.11 - - DJANGO=2.0 + - DJANGO=2.2 + - DJANGO=3.2 + matrixs: exclude: - python: 3.6 diff --git a/db_email_backend/models.py b/db_email_backend/models.py index dfa5e85..01ea97e 100644 --- a/db_email_backend/models.py +++ b/db_email_backend/models.py @@ -2,10 +2,8 @@ from __future__ import unicode_literals from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Email(models.Model): create_date = models.DateTimeField(auto_now_add=True) subject = models.TextField(blank=True) @@ -21,7 +19,6 @@ def __str__(self): return '{} - {}, {}'.format(self.subject, self.to, self.create_date) -@python_2_unicode_compatible class EmailAlternative(models.Model): email = models.ForeignKey(Email, on_delete=models.CASCADE, related_name='alternatives') content = models.TextField(blank=True) @@ -31,7 +28,6 @@ def __str__(self): return '{}: alternative {}'.format(self.email, self.mimetype) -@python_2_unicode_compatible class EmailAttachment(models.Model): email = models.ForeignKey(Email, on_delete=models.CASCADE, related_name='attachments') filename = models.CharField(max_length=1000, blank=True) diff --git a/test_app/settings.py b/test_app/settings.py index 754cbd8..c6681af 100644 --- a/test_app/settings.py +++ b/test_app/settings.py @@ -1,5 +1,6 @@ import os import django + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DEBUG = True @@ -53,7 +54,8 @@ 'OPTIONS': { 'context_processors': [ 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages' + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request' ] }, }, @@ -70,3 +72,5 @@ pass else: INSTALLED_APPS += ('south',) + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/tox.ini b/tox.ini index ca7535f..d50aaf8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,12 @@ [tox] envlist = - py{27,36}-django{18,19,110,111} - py{36}-django{20,21,22} + py{36}-django{32,22} + py{38}-django{32,22} [travis:env] DJANGO = - 1.8: django18 - 1.9: django19 - 1.10: django110 - 1.11: django111 - 2.0: django20 - 2.1: django21 2.2: django22 + 3.2: django32 [testenv] commands = django-admin.py test @@ -19,13 +14,8 @@ setenv = DJANGO_SETTINGS_MODULE=test_app.settings PYTHONPATH={toxinidir} deps = - django18: django>=1.8, <1.9 - django19: django>=1.9, <1.10 - django110: django>=1.10, <1.11 - django111: django>=1.11, <2.0 - django20: django>=2.0, <2.1 - django21: django>=2.1, <2.2 django22: django>=2.2, <2.3 + django32: django>=3.2, <4 [testenv:coverage] basepython=python3.6 @@ -34,4 +24,4 @@ commands = coveralls deps = coveralls - django>=2.0, <2.1 + django From 306320d2341f8a3ab7cd6de9a00184b9a6d31da1 Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Sun, 28 Nov 2021 23:53:54 +0200 Subject: [PATCH 02/21] Send mail --- db_email_backend/admin.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index befe601..e96bb48 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals from django.contrib import admin +from django.core.mail import EmailMessage +from django.test import override_settings from .models import Email, EmailAlternative, EmailAttachment @@ -28,24 +30,43 @@ class EmailAdmin(admin.ModelAdmin): ('to', 'cc', 'bcc'), 'subject', 'body', 'headers') readonly_fields = ( - 'create_date','from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers') + 'create_date', 'from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers') list_display = ('subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count') list_filter = ('content_subtype',) date_hierarchy = 'create_date' search_fields = ('to', 'from_email', 'cc', 'bcc', 'subject', 'body') inlines = (EmailAlternativeInline, EmailAttachmentInline) + actions = ['send_mail'] + + @override_settings(EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend') + def send_mail(self, request, queryset): + for row in queryset: + msg = EmailMessage() + msg.subject = row.subject + msg.body = row.body + msg.content_subtype = row.content_subtype + msg.from_email = row.from_email + msg.to = [x for x in row.to.split('; ') if x] + msg.cc = [x for x in row.cc.split('; ') if x] + msg.bcc = [x for x in row.bcc.split('; ') if x] + + msg.send(fail_silently=False) + + send_mail.short_description = "Send Actual Email via SMTP" + def attachment_count(self, instance): return instance.attachments.count() + attachment_count.short_description = 'Attachments' def alternative_count(self, instance): return instance.alternatives.count() + alternative_count.short_description = 'Alternatives' def has_add_permission(self, request, obj=None): return False -admin.site.register(Email, EmailAdmin) - +admin.site.register(Email, EmailAdmin) From 482bc56d1f16814839ed790dc0df75c3df9cb1be Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Sun, 28 Nov 2021 23:56:43 +0200 Subject: [PATCH 03/21] Message user --- db_email_backend/admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index e96bb48..d161bab 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -53,6 +53,8 @@ def send_mail(self, request, queryset): msg.send(fail_silently=False) + self.message_user(request, f'{queryset.count()}Emails sent', 25) + send_mail.short_description = "Send Actual Email via SMTP" def attachment_count(self, instance): From 38c2a5ed3fccb4ca55f19dcbc12b9965e23a3dba Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Mon, 29 Nov 2021 00:06:15 +0200 Subject: [PATCH 04/21] add space --- db_email_backend/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index d161bab..9ff27e2 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -53,7 +53,7 @@ def send_mail(self, request, queryset): msg.send(fail_silently=False) - self.message_user(request, f'{queryset.count()}Emails sent', 25) + self.message_user(request, f'{queryset.count()} Emails sent', 25) send_mail.short_description = "Send Actual Email via SMTP" From 35fa9906b564a781e5baab47663fe9d27c75aa62 Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 20 Apr 2022 12:36:40 +0200 Subject: [PATCH 05/21] * Adds `SMTPDBEmailBackend` , a mixture between SMTP and DB mail backend. * Remove south migrations * Adjust setup to release to Pypi --- .gitignore | 1 + CHANGELOG.rst | 18 +++ db_email_backend/__init__.py | 2 +- db_email_backend/backend.py | 116 +++++++++++++----- db_email_backend/models.py | 3 + .../south_migrations/0001_initial.py | 88 ------------- db_email_backend/south_migrations/__init__.py | 0 setup.py | 12 +- 8 files changed, 112 insertions(+), 128 deletions(-) create mode 100644 CHANGELOG.rst delete mode 100644 db_email_backend/south_migrations/0001_initial.py delete mode 100644 db_email_backend/south_migrations/__init__.py diff --git a/.gitignore b/.gitignore index 0b1ce3e..e9c7f15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp *.pyc __pycache__ +.tox/* diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..73f1a05 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,18 @@ +Change Log +========== + + +Under development +~~~~~~~~~~~~~~~~~~ +* + +2022.04.20 +~~~~~~~~~~ +* Adds `SMTPDBEmailBackend` , a mixture between SMTP and DB mail backend. +* Remove south migrations + + +2021.11.29 +~~~~~~~~~~ +* Upgrade compatibility from the main fork to work with Django 3.2 + +* Add option on admin to (re)send the mail using the smtp backend \ No newline at end of file diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index 65e89da..c042314 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,2 +1,2 @@ -VERSION = (0, 4, 1) +VERSION = (0, 5, 0) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/backend.py b/db_email_backend/backend.py index 008956f..76d70ce 100644 --- a/db_email_backend/backend.py +++ b/db_email_backend/backend.py @@ -1,43 +1,95 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals - +import logging from django.core.files.base import ContentFile from django.core.mail.backends.base import BaseEmailBackend from .models import Email, EmailAlternative, EmailAttachment +from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend + +logger = logging.getLogger('db_mail_backend') + + +def record_email_message(msg, fail_silently): + try: + email = Email.objects.create( + subject=msg.subject, + body=msg.body, + content_subtype=msg.content_subtype, + from_email=msg.from_email, + to='; '.join(msg.to), + cc='; '.join(msg.cc), + bcc='; '.join(msg.bcc), + headers='\n'.join('{}: {}'.format(k, v) + for k, v in msg.extra_headers.items()), + ) + # output.append(email) + + alternatives = getattr(msg, 'alternatives', []) + for content, mimetype in alternatives: + EmailAlternative.objects.create( + email=email, + content=content, + mimetype=mimetype or '', + ) + + for filename, content, mimetype in msg.attachments: + attachment = EmailAttachment.objects.create( + email=email, + filename=filename, + mimetype=mimetype or '', + ) + attachment.file.save(filename, ContentFile(content)) + except: + email = None + if not fail_silently: + raise + + return email + class DBEmailBackend(BaseEmailBackend): def send_messages(self, email_messages): for msg in email_messages: - try: - email = Email.objects.create( - subject=msg.subject, - body=msg.body, - content_subtype=msg.content_subtype, - from_email=msg.from_email, - to='; '.join(msg.to), - cc='; '.join(msg.cc), - bcc='; '.join(msg.bcc), - headers='\n'.join('{}: {}'.format(k, v) - for k, v in msg.extra_headers.items()), - ) - alternatives = getattr(msg, 'alternatives', []) - for content, mimetype in alternatives: - EmailAlternative.objects.create( - email=email, - content=content, - mimetype=mimetype or '', - ) - - for filename, content, mimetype in msg.attachments: - attachment = EmailAttachment.objects.create( - email=email, - filename=filename, - mimetype=mimetype or '', - ) - attachment.file.save(filename, ContentFile(content)) - except: - if not self.fail_silently: - raise - + return record_email_message(msg, fail_silently=self.fail_silently) return len(email_messages) + + +class SMTPDBEmailBackend(SMTPEmailBackend): + """ + This backend is mixture between SMTP and DB mail backend + It writes to the database then send the email over smtp, + if any errors happen while sending it is reflected in the email model + """ + + def send_messages(self, email_messages): + """ + Send one or more EmailMessage objects and return the number of email + messages sent. + """ + if not email_messages: + return 0 + with self._lock: + new_conn_created = self.open() + if not self.connection or new_conn_created is None: + # We failed silently on open(). + # Trying to send would be pointless. + return 0 + num_sent = 0 + for message in email_messages: + try: + email_inst = record_email_message(message, fail_silently=self.fail_silently) + except Exception as e: + logger.error(e) + + try: + sent = self._send(message) + if sent: + num_sent += 1 + except Exception as e: + if email_inst: + Email.objects.filter(pk=email_inst.pk).update(has_errors=True, error=str(e)) + raise e + if new_conn_created: + self.close() + return num_sent diff --git a/db_email_backend/models.py b/db_email_backend/models.py index 01ea97e..3c830b8 100644 --- a/db_email_backend/models.py +++ b/db_email_backend/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.db import models +from django.utils.translation import gettext_lazy as _ class Email(models.Model): @@ -14,6 +15,8 @@ class Email(models.Model): cc = models.TextField(blank=True) bcc = models.TextField(blank=True) headers = models.TextField(blank=True) + has_errors = models.BooleanField(default=False, verbose_name=_('Has Errors')) + error = models.TextField(blank=True, verbose_name=_('Errors')) def __str__(self): return '{} - {}, {}'.format(self.subject, self.to, self.create_date) diff --git a/db_email_backend/south_migrations/0001_initial.py b/db_email_backend/south_migrations/0001_initial.py deleted file mode 100644 index 568a65f..0000000 --- a/db_email_backend/south_migrations/0001_initial.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -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 model 'Email' - db.create_table(u'db_email_backend_email', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('create_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('subject', self.gf('django.db.models.fields.TextField')(blank=True)), - ('body', self.gf('django.db.models.fields.TextField')(blank=True)), - ('content_subtype', self.gf('django.db.models.fields.CharField')(max_length=254)), - ('from_email', self.gf('django.db.models.fields.CharField')(max_length=254, blank=True)), - ('to', self.gf('django.db.models.fields.TextField')(blank=True)), - ('cc', self.gf('django.db.models.fields.TextField')(blank=True)), - ('bcc', self.gf('django.db.models.fields.TextField')(blank=True)), - ('headers', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal(u'db_email_backend', ['Email']) - - # Adding model 'EmailAlternative' - db.create_table(u'db_email_backend_emailalternative', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('email', self.gf('django.db.models.fields.related.ForeignKey')(related_name=u'alternatives', to=orm['db_email_backend.Email'])), - ('content', self.gf('django.db.models.fields.TextField')(blank=True)), - ('mimetype', self.gf('django.db.models.fields.CharField')(max_length=254, blank=True)), - )) - db.send_create_signal(u'db_email_backend', ['EmailAlternative']) - - # Adding model 'EmailAttachment' - db.create_table(u'db_email_backend_emailattachment', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('email', self.gf('django.db.models.fields.related.ForeignKey')(related_name=u'attachments', to=orm['db_email_backend.Email'])), - ('filename', self.gf('django.db.models.fields.CharField')(max_length=1000, blank=True)), - ('mimetype', self.gf('django.db.models.fields.CharField')(max_length=254, blank=True)), - ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - )) - db.send_create_signal(u'db_email_backend', ['EmailAttachment']) - - - def backwards(self, orm): - # Deleting model 'Email' - db.delete_table(u'db_email_backend_email') - - # Deleting model 'EmailAlternative' - db.delete_table(u'db_email_backend_emailalternative') - - # Deleting model 'EmailAttachment' - db.delete_table(u'db_email_backend_emailattachment') - - - models = { - u'db_email_backend.email': { - 'Meta': {'object_name': 'Email'}, - 'bcc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'cc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content_subtype': ('django.db.models.fields.CharField', [], {'max_length': '254'}), - 'create_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'from_email': ('django.db.models.fields.CharField', [], {'max_length': '254', 'blank': 'True'}), - 'headers': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'subject': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'to': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - u'db_email_backend.emailalternative': { - 'Meta': {'object_name': 'EmailAlternative'}, - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'email': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'alternatives'", 'to': u"orm['db_email_backend.Email']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '254', 'blank': 'True'}) - }, - u'db_email_backend.emailattachment': { - 'Meta': {'object_name': 'EmailAttachment'}, - 'email': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'attachments'", 'to': u"orm['db_email_backend.Email']"}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '254', 'blank': 'True'}) - } - } - - complete_apps = ['db_email_backend'] \ No newline at end of file diff --git a/db_email_backend/south_migrations/__init__.py b/db_email_backend/south_migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/setup.py b/setup.py index 42b2dcd..c8b9059 100755 --- a/setup.py +++ b/setup.py @@ -14,13 +14,13 @@ long_description = open('README.md').read() setup_args = dict( - name='django-db-email-backend', + name='kn-django-db-email-backend', version=db_email_backend.__version__, description='Django email backend for storing messages to a database.', long_description=long_description, - author='Jeremy Satterfield', - author_email='jsatt@jsatt.com', - url='https://github.com/jsatt/django-db-email-backend', + author='Ramez Ashraf', + author_email='ramez@kuwaitnet.com', + url='https://github.com/KUWAITNET/django-db-email-backend', license="MIT License", classifiers=[ 'Development Status :: 4 - Beta', @@ -30,17 +30,15 @@ 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Software Development', ], packages=[ 'db_email_backend', 'db_email_backend.migrations', - 'db_email_backend.south_migrations' ], install_requires=[ - "django>=1.8", + "django>=2.2", "pytz", ], ) From 3ce74d1b04d5d929d6f70218cf7f9af30967997e Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 20 Apr 2022 13:29:23 +0200 Subject: [PATCH 06/21] Adjusting setup.py --- README.md | 16 ++++++---------- setup.py | 11 ++++++----- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index bde8387..8b3542e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ Django DB Email Backend ======================= -[![Build Status](https://travis-ci.org/jsatt/django-db-email-backend.svg?branch=master)](https://travis-ci.org/jsatt/django-db-email-backend) - -Django email backend for storing messages to a database. This is intended to be used in developement in cases where you want to test sending emails, but don't want to send real emails and don't have access to the console output (such as on a remote server). - -This is NOT intended for production use in any capacity. +Django email backend for storing messages to a database. This is intended to be used in developement in cases where you +want to test sending emails, but don't want to send real emails and don't have access to the console output (such as on +a remote server). To install: @@ -16,10 +14,8 @@ pip install django-db-email-backend In settings.py: ```python -INSTALLED_APPS = [ - ... - 'db_email_backend', -] - +INSTALLED_APPS += ['db_email_backend'] EMAIL_BACKEND = 'db_email_backend.backend.DBEmailBackend' +# or +# EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' ``` diff --git a/setup.py b/setup.py index c8b9059..e271bf1 100755 --- a/setup.py +++ b/setup.py @@ -11,13 +11,17 @@ os.system("python setup.py sdist upload") sys.exit() -long_description = open('README.md').read() +# long_description = open('README.md').read_text() +from pathlib import Path +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text() -setup_args = dict( +setup( name='kn-django-db-email-backend', version=db_email_backend.__version__, description='Django email backend for storing messages to a database.', long_description=long_description, + long_description_content_type='text/markdown', author='Ramez Ashraf', author_email='ramez@kuwaitnet.com', url='https://github.com/KUWAITNET/django-db-email-backend', @@ -42,6 +46,3 @@ "pytz", ], ) - -if __name__ == '__main__': - setup(**setup_args) From 37495c980fba9a39626c789099342cd332e1edfa Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 20 Apr 2022 14:03:45 +0200 Subject: [PATCH 07/21] Allow only superuser to access email body. --- CHANGELOG.rst | 1 + README.md | 21 --------------------- README.rst | 26 ++++++++++++++++++++++++++ db_email_backend/__init__.py | 2 +- db_email_backend/admin.py | 17 +++++++++++++---- setup.py | 4 ++-- 6 files changed, 43 insertions(+), 28 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 73f1a05..d861354 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Under development ~~~~~~~~~~ * Adds `SMTPDBEmailBackend` , a mixture between SMTP and DB mail backend. * Remove south migrations +* Allow only superusers to access email body. 2021.11.29 diff --git a/README.md b/README.md deleted file mode 100644 index 8b3542e..0000000 --- a/README.md +++ /dev/null @@ -1,21 +0,0 @@ -Django DB Email Backend -======================= - -Django email backend for storing messages to a database. This is intended to be used in developement in cases where you -want to test sending emails, but don't want to send real emails and don't have access to the console output (such as on -a remote server). - -To install: - -```sh -pip install django-db-email-backend -``` - -In settings.py: - -```python -INSTALLED_APPS += ['db_email_backend'] -EMAIL_BACKEND = 'db_email_backend.backend.DBEmailBackend' -# or -# EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' -``` diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..70e1037 --- /dev/null +++ b/README.rst @@ -0,0 +1,26 @@ +Django DB Email Backend +======================= + +Initially, Django email backend for storing messages to a database. This is intended to be used in developement in cases where you +want to test sending emails, but don't want to send real emails and don't have access to the console output (such as on +a remote server). + +As this KUWAITNET fork, package has been updated and introduced `SMTPDBEmailBackend` which is a mixture between SMTP and DB mail backend, +it writes to the database then send the email over smtp, if any errors happen while sending it is reflected in the email model. +Also Package is now production ready. + +Access to Email message content is allowed only for superusers for security concerns. + +To install:: + + pip install django-db-email-backend + + +In settings.py:: + + INSTALLED_APPS += ['db_email_backend'] + EMAIL_BACKEND = 'db_email_backend.backend.DBEmailBackend' + # or for live + # EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' + + diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index c042314..e2ce6d7 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,2 +1,2 @@ -VERSION = (0, 5, 0) +VERSION = (0, 5, 1) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index 9ff27e2..c89d4d3 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -28,17 +28,26 @@ class EmailAdmin(admin.ModelAdmin): fields = ( ('from_email', 'create_date', 'content_subtype'), ('to', 'cc', 'bcc'), - 'subject', 'body', 'headers') + 'subject', 'body', 'headers', 'has_errors', 'error') readonly_fields = ( - 'create_date', 'from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers') - list_display = ('subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count') - list_filter = ('content_subtype',) + 'create_date', 'from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers', 'has_errors', + 'error') + list_display = ( + 'subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count', 'has_errors', 'error') + list_filter = ('has_errors', 'content_subtype',) date_hierarchy = 'create_date' search_fields = ('to', 'from_email', 'cc', 'bcc', 'subject', 'body') inlines = (EmailAlternativeInline, EmailAttachmentInline) actions = ['send_mail'] + def get_fields(self, request, obj=None): + fields = super().get_fields(request, obj) + if not request.user.is_superuser: + fields = list(fields) + fields.remove('body') + return fields + @override_settings(EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend') def send_mail(self, request, queryset): for row in queryset: diff --git a/setup.py b/setup.py index e271bf1..cb5bebd 100755 --- a/setup.py +++ b/setup.py @@ -14,14 +14,14 @@ # long_description = open('README.md').read_text() from pathlib import Path this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() +long_description = (this_directory / "README.rst").read_text() setup( name='kn-django-db-email-backend', version=db_email_backend.__version__, description='Django email backend for storing messages to a database.', long_description=long_description, - long_description_content_type='text/markdown', + long_description_content_type='text/x-rst', author='Ramez Ashraf', author_email='ramez@kuwaitnet.com', url='https://github.com/KUWAITNET/django-db-email-backend', From 3718b3ef52385fc868cc93e354f8397443b20941 Mon Sep 17 00:00:00 2001 From: Ramez Date: Thu, 21 Apr 2022 11:12:55 +0200 Subject: [PATCH 08/21] Adds `SMTP_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from smtp sending --- CHANGELOG.rst | 9 +++++++-- README.rst | 7 +++++++ db_email_backend/__init__.py | 2 +- db_email_backend/app_settings.py | 10 ++++++++++ db_email_backend/backend.py | 8 ++++++-- db_email_backend/utils.py | 6 ++++++ test_app/settings.py | 7 ------- 7 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 db_email_backend/app_settings.py create mode 100644 db_email_backend/utils.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d861354..49cc7cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,13 @@ Under development ~~~~~~~~~~~~~~~~~~ * -2022.04.20 -~~~~~~~~~~ +2022.04.21 v05.2 +~~~~~~~~~~~~~~~~ +* Adds `SMTP_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from smtp sending. + + +2022.04.20 v05.1 +~~~~~~~~~~~~~~~~ * Adds `SMTPDBEmailBackend` , a mixture between SMTP and DB mail backend. * Remove south migrations * Allow only superusers to access email body. diff --git a/README.rst b/README.rst index 70e1037..da9f20d 100644 --- a/README.rst +++ b/README.rst @@ -24,3 +24,10 @@ In settings.py:: # EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' + +Configuration +============= + +SMTP_EMAIL_FILTER_FUNCTION_PATH default to `db_email_backend.utils.smtp_filter_email_function`. a dotted path to the smtp email filter function. +A filter function for the smtp email, takes the email_message as a parameter, and return Boolean. Case it returns False, then the backend won't send this message via smtp. +You can use it to disallow SMTP sending for certain email based to their properties. diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index e2ce6d7..ace50eb 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,2 +1,2 @@ -VERSION = (0, 5, 1) +VERSION = (0, 5, 2) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/app_settings.py b/db_email_backend/app_settings.py new file mode 100644 index 0000000..6747ae7 --- /dev/null +++ b/db_email_backend/app_settings.py @@ -0,0 +1,10 @@ +from django.conf import settings +from django.utils.module_loading import import_string + +SMTP_EMAIL_FILTER_FUNCTION_PATH = getattr(settings, 'SMTP_EMAIL_FILTER_FUNCTION_PATH', + 'db_email_backend.utils.smtp_filter_email_function') + +try: + email_filter = import_string(SMTP_EMAIL_FILTER_FUNCTION_PATH) +except Exception as e: + raise e diff --git a/db_email_backend/backend.py b/db_email_backend/backend.py index 76d70ce..af4d326 100644 --- a/db_email_backend/backend.py +++ b/db_email_backend/backend.py @@ -1,11 +1,14 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals + import logging + from django.core.files.base import ContentFile from django.core.mail.backends.base import BaseEmailBackend +from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend from .models import Email, EmailAlternative, EmailAttachment -from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend +from .app_settings import email_filter logger = logging.getLogger('db_mail_backend') @@ -81,8 +84,9 @@ def send_messages(self, email_messages): email_inst = record_email_message(message, fail_silently=self.fail_silently) except Exception as e: logger.error(e) - try: + if not email_filter(message): + continue sent = self._send(message) if sent: num_sent += 1 diff --git a/db_email_backend/utils.py b/db_email_backend/utils.py new file mode 100644 index 0000000..c98e24d --- /dev/null +++ b/db_email_backend/utils.py @@ -0,0 +1,6 @@ +def smtp_filter_email_function(message): + """ + A filter function for the smtp email... + if return False the backend won't send this message via smtp. + """ + return True diff --git a/test_app/settings.py b/test_app/settings.py index c6681af..18adc41 100644 --- a/test_app/settings.py +++ b/test_app/settings.py @@ -66,11 +66,4 @@ MEDIA_ROOT = BASE_DIR + '/media/' MEDIA_URL = '/media/' -try: - import south -except: - pass -else: - INSTALLED_APPS += ('south',) - DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' From 80e22a8b611be154fd6f5e6e84119fe23c9de611 Mon Sep 17 00:00:00 2001 From: Ramez Date: Thu, 21 Apr 2022 11:47:34 +0200 Subject: [PATCH 09/21] Enhance apps --- CHANGELOG.rst | 1 + db_email_backend/__init__.py | 4 +++- db_email_backend/apps.py | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 db_email_backend/apps.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 49cc7cd..b11fad7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Under development 2022.04.21 v05.2 ~~~~~~~~~~~~~~~~ * Adds `SMTP_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from smtp sending. +* Enhance apps 2022.04.20 v05.1 diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index ace50eb..b6818e2 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,2 +1,4 @@ -VERSION = (0, 5, 2) +default_app_config = 'db_email_backend.apps.DBEmailBackendConfig' + +VERSION = (0, 5, 3) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/apps.py b/db_email_backend/apps.py new file mode 100644 index 0000000..ca9224f --- /dev/null +++ b/db_email_backend/apps.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class DBEmailBackendConfig(AppConfig): + name = 'db_email_backend' + verbose_name = _('Email Backend') From 6244253b25c1c269e4c2be5af7b116731f5f6318 Mon Sep 17 00:00:00 2001 From: Ramez Date: Thu, 21 Apr 2022 12:01:57 +0200 Subject: [PATCH 10/21] Rename has_error to succeeded for clearer more relaxed visual --- CHANGELOG.rst | 4 +++- db_email_backend/__init__.py | 2 +- db_email_backend/admin.py | 8 +++---- .../migrations/0002_auto_20220420_1246.py | 23 +++++++++++++++++++ .../migrations/0003_auto_20220421_1249.py | 22 ++++++++++++++++++ db_email_backend/models.py | 2 +- 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 db_email_backend/migrations/0002_auto_20220420_1246.py create mode 100644 db_email_backend/migrations/0003_auto_20220421_1249.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b11fad7..ae33926 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,10 +6,11 @@ Under development ~~~~~~~~~~~~~~~~~~ * -2022.04.21 v05.2 +2022.04.21 v05.4 ~~~~~~~~~~~~~~~~ * Adds `SMTP_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from smtp sending. * Enhance apps +* Rename has_error to succeeded for clearer more relaxed visual 2022.04.20 v05.1 @@ -17,6 +18,7 @@ Under development * Adds `SMTPDBEmailBackend` , a mixture between SMTP and DB mail backend. * Remove south migrations * Allow only superusers to access email body. +* Adds has_error, and error ro Email model 2021.11.29 diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index b6818e2..e5513ff 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,4 +1,4 @@ default_app_config = 'db_email_backend.apps.DBEmailBackendConfig' -VERSION = (0, 5, 3) +VERSION = (0, 5, 4) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index c89d4d3..0f42820 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -28,13 +28,13 @@ class EmailAdmin(admin.ModelAdmin): fields = ( ('from_email', 'create_date', 'content_subtype'), ('to', 'cc', 'bcc'), - 'subject', 'body', 'headers', 'has_errors', 'error') + 'subject', 'body', 'headers', 'succeeded', 'error') readonly_fields = ( - 'create_date', 'from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers', 'has_errors', + 'create_date', 'from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers', 'succeeded', 'error') list_display = ( - 'subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count', 'has_errors', 'error') - list_filter = ('has_errors', 'content_subtype',) + 'subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count', 'succeeded', 'error') + list_filter = ('succeeded', 'content_subtype',) date_hierarchy = 'create_date' search_fields = ('to', 'from_email', 'cc', 'bcc', 'subject', 'body') inlines = (EmailAlternativeInline, EmailAttachmentInline) diff --git a/db_email_backend/migrations/0002_auto_20220420_1246.py b/db_email_backend/migrations/0002_auto_20220420_1246.py new file mode 100644 index 0000000..bfe093a --- /dev/null +++ b/db_email_backend/migrations/0002_auto_20220420_1246.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.24 on 2022-04-20 09:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db_email_backend', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='email', + name='error', + field=models.TextField(blank=True, verbose_name='Errors'), + ), + migrations.AddField( + model_name='email', + name='has_errors', + field=models.BooleanField(default=False, verbose_name='Has Errors'), + ), + ] diff --git a/db_email_backend/migrations/0003_auto_20220421_1249.py b/db_email_backend/migrations/0003_auto_20220421_1249.py new file mode 100644 index 0000000..5244dbf --- /dev/null +++ b/db_email_backend/migrations/0003_auto_20220421_1249.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.24 on 2022-04-21 09:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db_email_backend', '0002_auto_20220420_1246'), + ] + + operations = [ + migrations.RemoveField( + model_name='email', + name='has_errors', + ), + migrations.AddField( + model_name='email', + name='succeeded', + field=models.BooleanField(default=True, verbose_name='Succeeded?'), + ), + ] diff --git a/db_email_backend/models.py b/db_email_backend/models.py index 3c830b8..28cb44b 100644 --- a/db_email_backend/models.py +++ b/db_email_backend/models.py @@ -15,7 +15,7 @@ class Email(models.Model): cc = models.TextField(blank=True) bcc = models.TextField(blank=True) headers = models.TextField(blank=True) - has_errors = models.BooleanField(default=False, verbose_name=_('Has Errors')) + succeeded = models.BooleanField(default=True, verbose_name=_('Succeeded?')) error = models.TextField(blank=True, verbose_name=_('Errors')) def __str__(self): From 38a86cb8b1ec123518961bd2ab9a77fe970cdf86 Mon Sep 17 00:00:00 2001 From: Ramez Date: Mon, 18 Jul 2022 15:00:28 +0200 Subject: [PATCH 11/21] Fix in error --- db_email_backend/backend.py | 2 +- db_email_backend/models.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/db_email_backend/backend.py b/db_email_backend/backend.py index af4d326..42ad415 100644 --- a/db_email_backend/backend.py +++ b/db_email_backend/backend.py @@ -92,7 +92,7 @@ def send_messages(self, email_messages): num_sent += 1 except Exception as e: if email_inst: - Email.objects.filter(pk=email_inst.pk).update(has_errors=True, error=str(e)) + Email.objects.filter(pk=email_inst.pk).update(error=str(e)) raise e if new_conn_created: self.close() diff --git a/db_email_backend/models.py b/db_email_backend/models.py index 28cb44b..91b93e3 100644 --- a/db_email_backend/models.py +++ b/db_email_backend/models.py @@ -7,14 +7,14 @@ class Email(models.Model): create_date = models.DateTimeField(auto_now_add=True) - subject = models.TextField(blank=True) - body = models.TextField(blank=True) + subject = models.TextField(blank=True, verbose_name=_("Subject")) + body = models.TextField(blank=True, verbose_name=_('Body')) content_subtype = models.CharField(max_length=254) - from_email = models.CharField(max_length=254, blank=True) - to = models.TextField(blank=True) - cc = models.TextField(blank=True) - bcc = models.TextField(blank=True) - headers = models.TextField(blank=True) + from_email = models.CharField(max_length=254, blank=True, verbose_name=_('From')) + to = models.TextField(blank=True, verbose_name=_('To')) + cc = models.TextField(blank=True, verbose_name=_('CC')) + bcc = models.TextField(blank=True, verbose_name=_('BCC')) + headers = models.TextField(blank=True, verbose_name=_('Headers')) succeeded = models.BooleanField(default=True, verbose_name=_('Succeeded?')) error = models.TextField(blank=True, verbose_name=_('Errors')) From 496c4cc966fe9302c0bbee1468b82f40f54cd269 Mon Sep 17 00:00:00 2001 From: Ramez Date: Mon, 18 Jul 2022 15:06:57 +0200 Subject: [PATCH 12/21] Add succeeded = False on error --- db_email_backend/__init__.py | 2 +- db_email_backend/backend.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index e5513ff..60e8e13 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,4 +1,4 @@ default_app_config = 'db_email_backend.apps.DBEmailBackendConfig' -VERSION = (0, 5, 4) +VERSION = (0, 5, 6) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/backend.py b/db_email_backend/backend.py index 42ad415..18ba6b1 100644 --- a/db_email_backend/backend.py +++ b/db_email_backend/backend.py @@ -92,7 +92,7 @@ def send_messages(self, email_messages): num_sent += 1 except Exception as e: if email_inst: - Email.objects.filter(pk=email_inst.pk).update(error=str(e)) + Email.objects.filter(pk=email_inst.pk).update(error=str(e), succeeded=False) raise e if new_conn_created: self.close() From 71e838a64bdba17ece71abfcd4df3414a00867f4 Mon Sep 17 00:00:00 2001 From: Ramez Date: Tue, 19 Jul 2022 22:21:37 +0200 Subject: [PATCH 13/21] Adds DB_EMAIL_FILTER_FUNCTION_PATH Read me --- CHANGELOG.rst | 8 ++++++ README.rst | 42 ++++++++++++++++++++++---------- db_email_backend/admin.py | 5 ++-- db_email_backend/app_settings.py | 6 +++++ db_email_backend/backend.py | 17 +++++++------ db_email_backend/models.py | 4 +-- db_email_backend/utils.py | 9 +++++++ 7 files changed, 66 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ae33926..2c3d705 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,14 @@ Under development ~~~~~~~~~~~~~~~~~~ * +2022.04.21 v05.6 +~~~~~~~~~~~~~~~~ +* Adds `DB_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from DB recording. + +2022.04.21 v05.5 +~~~~~~~~~~~~~~~~ +* Fixes issues in case of error sending the mail. + 2022.04.21 v05.4 ~~~~~~~~~~~~~~~~ * Adds `SMTP_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from smtp sending. diff --git a/README.rst b/README.rst index da9f20d..c996bd7 100644 --- a/README.rst +++ b/README.rst @@ -1,33 +1,49 @@ Django DB Email Backend ======================= -Initially, Django email backend for storing messages to a database. This is intended to be used in developement in cases where you -want to test sending emails, but don't want to send real emails and don't have access to the console output (such as on -a remote server). +Record Email Messages Sent to database , with the ability to also send them via SMTP. -As this KUWAITNET fork, package has been updated and introduced `SMTPDBEmailBackend` which is a mixture between SMTP and DB mail backend, -it writes to the database then send the email over smtp, if any errors happen while sending it is reflected in the email model. -Also Package is now production ready. -Access to Email message content is allowed only for superusers for security concerns. +Usage +----- -To install:: +Install :: pip install django-db-email-backend - In settings.py:: INSTALLED_APPS += ['db_email_backend'] + EMAIL_BACKEND = 'db_email_backend.backend.DBEmailBackend' - # or for live - # EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' + # recrd the email message to database + # or + # EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' + # Record to database and send via SMTP. + # IF errors happened you can see it in the Admin and resend it again. Configuration ============= SMTP_EMAIL_FILTER_FUNCTION_PATH default to `db_email_backend.utils.smtp_filter_email_function`. a dotted path to the smtp email filter function. -A filter function for the smtp email, takes the email_message as a parameter, and return Boolean. Case it returns False, then the backend won't send this message via smtp. -You can use it to disallow SMTP sending for certain email based to their properties. +A filter function for the smtp email, takes the email_message (django.core.mail.message.EmailMessage) as a parameter, and return False if this message should be filter out out and not sent via the backend. + +Example:: + + def only_allow_emails_to_mahad(message): + return True if 'mahad@kuwaitnet.com' in message.to else False + + +DB_EMAIL_FILTER_FUNCTION_PATH default to `db_email_backend.utils.db_filter_email_function`. a dotted path to the db email filter function. same as the SMTP one but for the Database. + +Example:: + + def dont_record_error_emails(message): + return False if settings.EMAIL_SUBJECT_PREFIX in message.subject else True + +Admin +----- + +Package have and admin integration ready where you can send the email SMTP even if the EMAIL_HOST = "db_email_backend.backend.DBEmailBackend" diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index 0f42820..9d6df5d 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -4,6 +4,7 @@ from django.contrib import admin from django.core.mail import EmailMessage from django.test import override_settings +from django.utils.translation import gettext_lazy as _ from .models import Email, EmailAlternative, EmailAttachment @@ -33,7 +34,7 @@ class EmailAdmin(admin.ModelAdmin): 'create_date', 'from_email', 'to', 'cc', 'bcc', 'subject', 'body', 'content_subtype', 'headers', 'succeeded', 'error') list_display = ( - 'subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count', 'succeeded', 'error') + 'subject', 'to', 'from_email', 'create_date', 'attachment_count', 'alternative_count', 'succeeded', 'error') list_filter = ('succeeded', 'content_subtype',) date_hierarchy = 'create_date' search_fields = ('to', 'from_email', 'cc', 'bcc', 'subject', 'body') @@ -64,7 +65,7 @@ def send_mail(self, request, queryset): self.message_user(request, f'{queryset.count()} Emails sent', 25) - send_mail.short_description = "Send Actual Email via SMTP" + send_mail.short_description = _("Send Email") def attachment_count(self, instance): return instance.attachments.count() diff --git a/db_email_backend/app_settings.py b/db_email_backend/app_settings.py index 6747ae7..502c46f 100644 --- a/db_email_backend/app_settings.py +++ b/db_email_backend/app_settings.py @@ -3,8 +3,14 @@ SMTP_EMAIL_FILTER_FUNCTION_PATH = getattr(settings, 'SMTP_EMAIL_FILTER_FUNCTION_PATH', 'db_email_backend.utils.smtp_filter_email_function') +DB_EMAIL_FILTER_FUNCTION_PATH = getattr(settings, 'SMTP_EMAIL_FILTER_FUNCTION_PATH', + 'db_email_backend.utils.db_filter_email_function') try: email_filter = import_string(SMTP_EMAIL_FILTER_FUNCTION_PATH) except Exception as e: raise e +try: + db_email_filter = import_string(DB_EMAIL_FILTER_FUNCTION_PATH) +except Exception as e: + raise e diff --git a/db_email_backend/backend.py b/db_email_backend/backend.py index 18ba6b1..2c57b67 100644 --- a/db_email_backend/backend.py +++ b/db_email_backend/backend.py @@ -8,7 +8,7 @@ from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend from .models import Email, EmailAlternative, EmailAttachment -from .app_settings import email_filter +from .app_settings import email_filter, db_email_filter logger = logging.getLogger('db_mail_backend') @@ -54,7 +54,8 @@ def record_email_message(msg, fail_silently): class DBEmailBackend(BaseEmailBackend): def send_messages(self, email_messages): for msg in email_messages: - return record_email_message(msg, fail_silently=self.fail_silently) + if db_email_filter(msg): + return record_email_message(msg, fail_silently=self.fail_silently) return len(email_messages) @@ -81,15 +82,15 @@ def send_messages(self, email_messages): num_sent = 0 for message in email_messages: try: - email_inst = record_email_message(message, fail_silently=self.fail_silently) + if db_email_filter(message): + email_inst = record_email_message(message, fail_silently=self.fail_silently) except Exception as e: logger.error(e) try: - if not email_filter(message): - continue - sent = self._send(message) - if sent: - num_sent += 1 + if email_filter(message): + sent = self._send(message) + if sent: + num_sent += 1 except Exception as e: if email_inst: Email.objects.filter(pk=email_inst.pk).update(error=str(e), succeeded=False) diff --git a/db_email_backend/models.py b/db_email_backend/models.py index 91b93e3..da02b79 100644 --- a/db_email_backend/models.py +++ b/db_email_backend/models.py @@ -6,10 +6,10 @@ class Email(models.Model): - create_date = models.DateTimeField(auto_now_add=True) + create_date = models.DateTimeField(auto_now_add=True, verbose_name=_("Creation date")) subject = models.TextField(blank=True, verbose_name=_("Subject")) body = models.TextField(blank=True, verbose_name=_('Body')) - content_subtype = models.CharField(max_length=254) + content_subtype = models.CharField(max_length=254, verbose_name=_("content subtype")) from_email = models.CharField(max_length=254, blank=True, verbose_name=_('From')) to = models.TextField(blank=True, verbose_name=_('To')) cc = models.TextField(blank=True, verbose_name=_('CC')) diff --git a/db_email_backend/utils.py b/db_email_backend/utils.py index c98e24d..8b3b37a 100644 --- a/db_email_backend/utils.py +++ b/db_email_backend/utils.py @@ -4,3 +4,12 @@ def smtp_filter_email_function(message): if return False the backend won't send this message via smtp. """ return True + + +def db_filter_email_function(message): + """ + A filter function for the smtp email... + if return False the backend won't record this message to database. + """ + return True + From 047749132788666eb4f059fe7325a2044194ab97 Mon Sep 17 00:00:00 2001 From: Ramez Date: Tue, 19 Jul 2022 22:23:30 +0200 Subject: [PATCH 14/21] Version bump --- CHANGELOG.rst | 3 ++- db_email_backend/__init__.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2c3d705..b7fbccf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,9 +6,10 @@ Under development ~~~~~~~~~~~~~~~~~~ * -2022.04.21 v05.6 +2022.04.21 v05.7 ~~~~~~~~~~~~~~~~ * Adds `DB_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from DB recording. +* Enhances in read me and strings translations. 2022.04.21 v05.5 ~~~~~~~~~~~~~~~~ diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index 60e8e13..dc83d15 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,4 +1,4 @@ default_app_config = 'db_email_backend.apps.DBEmailBackendConfig' -VERSION = (0, 5, 6) +VERSION = (0, 5, 7) __version__ = '.'.join(map(str, VERSION)) From d530d3d7cf05b3cf27cd669665b706e7ab448da6 Mon Sep 17 00:00:00 2001 From: Ramez Date: Tue, 19 Jul 2022 22:26:27 +0200 Subject: [PATCH 15/21] Fix in package name --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c996bd7..47a4726 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,7 @@ Usage Install :: - pip install django-db-email-backend + pip install kn-django-db-email-backend In settings.py:: From 5b9c568660fdeae7e81ab7554289d58b654824dd Mon Sep 17 00:00:00 2001 From: Ramez Date: Tue, 19 Jul 2022 22:27:13 +0200 Subject: [PATCH 16/21] Fix in date --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b7fbccf..ce8f7d7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,12 +6,12 @@ Under development ~~~~~~~~~~~~~~~~~~ * -2022.04.21 v05.7 +2022.07.19 v05.7 ~~~~~~~~~~~~~~~~ * Adds `DB_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from DB recording. * Enhances in read me and strings translations. -2022.04.21 v05.5 +2022.07.18 v05.5 ~~~~~~~~~~~~~~~~ * Fixes issues in case of error sending the mail. From 837cf4a99a14489a4f0b65a12afc9e43cd045e61 Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 20 Jul 2022 19:34:47 +0200 Subject: [PATCH 17/21] migrate --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 47a4726..58d017b 100644 --- a/README.rst +++ b/README.rst @@ -23,6 +23,10 @@ In settings.py:: # Record to database and send via SMTP. # IF errors happened you can see it in the Admin and resend it again. +Migrate:: + + $ python manage.py migrate + Configuration ============= From fad9c1bbbf9542509f460f6e4eddbb1ba2ad5386 Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 20 Jul 2022 20:48:20 +0200 Subject: [PATCH 18/21] Add verbose name --- README.rst | 2 +- db_email_backend/models.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 58d017b..2e3ad7d 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ In settings.py:: INSTALLED_APPS += ['db_email_backend'] EMAIL_BACKEND = 'db_email_backend.backend.DBEmailBackend' - # recrd the email message to database + # record the email message to database # or # EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' diff --git a/db_email_backend/models.py b/db_email_backend/models.py index da02b79..43c7f79 100644 --- a/db_email_backend/models.py +++ b/db_email_backend/models.py @@ -21,6 +21,10 @@ class Email(models.Model): def __str__(self): return '{} - {}, {}'.format(self.subject, self.to, self.create_date) + class Meta: + verbose_name = _('Email Log') + verbose_name_plural = _('Emails Log') + class EmailAlternative(models.Model): email = models.ForeignKey(Email, on_delete=models.CASCADE, related_name='alternatives') From 0b603d2a232112617887e5b369847b990054dcbb Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 8 Feb 2023 12:05:52 +0200 Subject: [PATCH 19/21] 2023.02.08 v05.8 ~~~~~~~~~~~~~~~~ * Adds arabic translation * Adds check for change permission to send email from admin --- CHANGELOG.rst | 5 ++ README.rst | 4 +- db_email_backend/__init__.py | 2 +- db_email_backend/admin.py | 6 ++ .../locale/ar/LC_MESSAGES/django.mo | Bin 0 -> 1088 bytes .../locale/ar/LC_MESSAGES/django.po | 63 ++++++++++++++++++ 6 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 db_email_backend/locale/ar/LC_MESSAGES/django.mo create mode 100644 db_email_backend/locale/ar/LC_MESSAGES/django.po diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ce8f7d7..62fef5f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,11 @@ Under development ~~~~~~~~~~~~~~~~~~ * +2023.02.08 v05.8 +~~~~~~~~~~~~~~~~ +* Adds arabic translation +* Adds check for change permission to send email from admin + 2022.07.19 v05.7 ~~~~~~~~~~~~~~~~ * Adds `DB_EMAIL_FILTER_FUNCTION_PATH` setting to allow filtering out email from DB recording. diff --git a/README.rst b/README.rst index 2e3ad7d..71c6bcc 100644 --- a/README.rst +++ b/README.rst @@ -18,8 +18,8 @@ In settings.py:: EMAIL_BACKEND = 'db_email_backend.backend.DBEmailBackend' # record the email message to database - # or - # EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' + # OR + EMAIL_BACKEND = 'db_email_backend.backend.SMTPDBEmailBackend' # Record to database and send via SMTP. # IF errors happened you can see it in the Admin and resend it again. diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index dc83d15..d23b09b 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,4 +1,4 @@ default_app_config = 'db_email_backend.apps.DBEmailBackendConfig' -VERSION = (0, 5, 7) +VERSION = (0, 5, 8) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index 9d6df5d..572f774 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -42,6 +42,12 @@ class EmailAdmin(admin.ModelAdmin): actions = ['send_mail'] + def get_actions(self, request): + actions = super().get_actions(request) + if not self.has_change_permission(request): + actions.pop('send_mail') + return actions + def get_fields(self, request, obj=None): fields = super().get_fields(request, obj) if not request.user.is_superuser: diff --git a/db_email_backend/locale/ar/LC_MESSAGES/django.mo b/db_email_backend/locale/ar/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..19466f98c3f28f464f3955289233615f882e24c5 GIT binary patch literal 1088 zcmb`CO=}ZD7{^DgZ>b`BPy~fRun4*}+iGdI-L`47slm+)Nm{6Q*=EOeHQ5cj8?hIm zn1(ht!Jb7>qNTQ!`a-|S?8S>04}J*$vzrn>fCCS|=jH#*{O9MTfh~e_9&ruPLR>`r zL5Pz)M#umtgF-XbV;Ve*x&$V`$KWZ@0)^h`^?mR(>Q}wG4IW4R2D|{i1-B3%5Moa` zLC95b2D}3lb*O0sjA(VkuF*N8`kXmtuhLyB&U_lBp67Z# z$#`y^l%b;04kIV5I1|LM3HQX^DA0-c@KZ zHUBWRpwT6*RL&Oii4tqLULfUs%QmICrsYc&SD}ewK`OBqw%C!<@T$;dEme|o1u31P z4+=^yd;3u#uO)QD52T7`IDX9tTu-5yMO~-)R8FH)S}v8#VKJ$~ zjJiVgRDNMmIGNJ**_u7)V!xlZpBGdr}2|Q)s|m26r1Z!*(WV^{L_c zEKnCK8EL$)6Skf(Ptu&KYub*b(D4;JNEB;L&!|Zm*Q@&qbsACWt7DTiPE|)$lXQkA zDW<9_BbDQH1nG!KZ_3H!v^q+MhT?ommBq$re, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-02-08 11:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +msgid "Send Email" +msgstr "ارسل الايميل" + +msgid "Email Backend" +msgstr "خلفية البريد الإلكتروني" + +msgid "Creation date" +msgstr "تاريخ الإنشاء" + +msgid "Subject" +msgstr "موضوع" + +msgid "Body" +msgstr "نص" + +msgid "content subtype" +msgstr "نوع المحتوى الفرعي" + +msgid "From" +msgstr "من" + +msgid "To" +msgstr "الى" + +msgid "CC" +msgstr "" + +msgid "BCC" +msgstr "" + +msgid "Headers" +msgstr "" + +msgid "Succeeded?" +msgstr "نجاح؟" + +msgid "Errors" +msgstr "اخطاء" + +msgid "Email Log" +msgstr "سجل البريد الإلكتروني" + +msgid "Emails Log" +msgstr "سجل البريد الإلكتروني" From af172491b14f62c9a4ee7a15bd32e4c107365b99 Mon Sep 17 00:00:00 2001 From: Ramez Date: Wed, 8 Feb 2023 12:21:55 +0200 Subject: [PATCH 20/21] 2023.02.08 v05.9 ~~~~~~~~~~~~~~~~ * Adds arabic translation; Mark strings are translatable. * Adds check for change permission to send email from admin --- CHANGELOG.rst | 4 ++-- MANIFEST.in | 1 + db_email_backend/__init__.py | 2 +- db_email_backend/admin.py | 4 ++-- .../locale/ar/LC_MESSAGES/django.mo | Bin 1088 -> 1201 bytes .../locale/ar/LC_MESSAGES/django.po | 12 +++++++++--- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 62fef5f..722d412 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,9 +6,9 @@ Under development ~~~~~~~~~~~~~~~~~~ * -2023.02.08 v05.8 +2023.02.08 v05.9 ~~~~~~~~~~~~~~~~ -* Adds arabic translation +* Adds arabic translation; Mark strings are translatable. * Adds check for change permission to send email from admin 2022.07.19 v05.7 diff --git a/MANIFEST.in b/MANIFEST.in index 64ad321..88a438e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include README.md LICENSE +recursive-include db_email_backend/locale * \ No newline at end of file diff --git a/db_email_backend/__init__.py b/db_email_backend/__init__.py index d23b09b..baa2d30 100644 --- a/db_email_backend/__init__.py +++ b/db_email_backend/__init__.py @@ -1,4 +1,4 @@ default_app_config = 'db_email_backend.apps.DBEmailBackendConfig' -VERSION = (0, 5, 8) +VERSION = (0, 5, 9) __version__ = '.'.join(map(str, VERSION)) diff --git a/db_email_backend/admin.py b/db_email_backend/admin.py index 572f774..d9502ec 100644 --- a/db_email_backend/admin.py +++ b/db_email_backend/admin.py @@ -76,12 +76,12 @@ def send_mail(self, request, queryset): def attachment_count(self, instance): return instance.attachments.count() - attachment_count.short_description = 'Attachments' + attachment_count.short_description = _('Attachments') def alternative_count(self, instance): return instance.alternatives.count() - alternative_count.short_description = 'Alternatives' + alternative_count.short_description = _('Alternatives') def has_add_permission(self, request, obj=None): return False diff --git a/db_email_backend/locale/ar/LC_MESSAGES/django.mo b/db_email_backend/locale/ar/LC_MESSAGES/django.mo index 19466f98c3f28f464f3955289233615f882e24c5..b1a901e6b4c1ead19573feaccc706443dffbb904 100644 GIT binary patch delta 435 zcmXxgJxc>Y5Qp)-&oQVEA4Vj)PG&=ciFldZ{>^8A4zmOs{jB1 delta 321 zcmXZXu@1pd6vpw>mMSH}KqM?Cg8_rVV(|nviQQ(g6Nz+}=o{D!B7FgmVX{~Z9>V|Q zp5)7~_w=;)-dph=?Vj~qYE*@^$$`|!ho}agSq_J&nXz+*Mb>jH;Sxh!q2|}Fzrh0Q zty?GPvp!\n" "Language-Team: LANGUAGE \n" @@ -15,11 +15,17 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " -"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + msgid "Send Email" msgstr "ارسل الايميل" +msgid "Attachments" +msgstr "المرفقات" + +msgid "Alternatives" +msgstr "البدائل" + msgid "Email Backend" msgstr "خلفية البريد الإلكتروني" From a9ed9c4e0e65b25239faca5a389cacaa6f7c63dc Mon Sep 17 00:00:00 2001 From: Ivan Vedernikov Date: Thu, 3 Apr 2025 16:17:18 +0300 Subject: [PATCH 21/21] Create 0004_auto_20250403_1558.py refactor: add indexes on Email model fields --- .../migrations/0004_auto_20250403_1558.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 db_email_backend/migrations/0004_auto_20250403_1558.py diff --git a/db_email_backend/migrations/0004_auto_20250403_1558.py b/db_email_backend/migrations/0004_auto_20250403_1558.py new file mode 100644 index 0000000..87b9767 --- /dev/null +++ b/db_email_backend/migrations/0004_auto_20250403_1558.py @@ -0,0 +1,97 @@ +# Generated by Django 3.2.17 on 2025-04-03 12:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db_email_backend', '0003_auto_20220421_1249'), + ] + + operations = [ + migrations.AlterModelOptions( + name='email', + options={'verbose_name': 'Email Log', 'verbose_name_plural': 'Emails Log'}, + ), + migrations.AlterField( + model_name='email', + name='bcc', + field=models.TextField(blank=True, verbose_name='BCC'), + ), + migrations.AlterField( + model_name='email', + name='body', + field=models.TextField(blank=True, verbose_name='Body'), + ), + migrations.AlterField( + model_name='email', + name='cc', + field=models.TextField(blank=True, verbose_name='CC'), + ), + migrations.AlterField( + model_name='email', + name='content_subtype', + field=models.CharField(max_length=254, verbose_name='content subtype'), + ), + migrations.AlterField( + model_name='email', + name='create_date', + field=models.DateTimeField(auto_now_add=True, verbose_name='Creation date'), + ), + migrations.AlterField( + model_name='email', + name='from_email', + field=models.CharField(blank=True, max_length=254, verbose_name='From'), + ), + migrations.AlterField( + model_name='email', + name='headers', + field=models.TextField(blank=True, verbose_name='Headers'), + ), + migrations.AlterField( + model_name='email', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='email', + name='subject', + field=models.TextField(blank=True, verbose_name='Subject'), + ), + migrations.AlterField( + model_name='email', + name='to', + field=models.TextField(blank=True, verbose_name='To'), + ), + migrations.AlterField( + model_name='emailalternative', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='emailattachment', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AddIndex( + model_name='email', + index=models.Index(fields=['create_date'], name='db_email_ba_create__e261e8_idx'), + ), + migrations.AddIndex( + model_name='email', + index=models.Index(fields=['from_email'], name='db_email_ba_from_em_237457_idx'), + ), + migrations.AddIndex( + model_name='email', + index=models.Index(fields=['to'], name='db_email_ba_to_ccc278_idx'), + ), + migrations.AddIndex( + model_name='email', + index=models.Index(fields=['cc'], name='db_email_ba_cc_f5ca2c_idx'), + ), + migrations.AddIndex( + model_name='email', + index=models.Index(fields=['bcc'], name='db_email_ba_bcc_aa743c_idx'), + ), + ]