Skip to main content

Django library for separating the message content from transmission method

Project description

django-heralder

Latest PyPI version Tests Black codecov

Logo

A Django messaging library that features:

  • Class-based declaration and registry approach, like Django Admin
  • Supports multiple transmission methods (Email, SMS, Slack, etc) per message
  • Browser-based previewing of messages
  • Maintains a history of messaging sending attempts and can view these messages
  • Disabling notifications per user
  • Auto conversion of HTML emails to text versions

History

Django-Heralder is a fork of the legacy Django-Herald.

The v0.3.0 release of Django-Herald has the same features and is equal to Django-Heralder v0.3.0.

As of Django-Heralder v0.4.0, Django-Heralder has diverged from original project with new features and bug fixes.

Installation

Supported Django / Python Versions

We try to make Heralder support all versions of django that django supports + all versions in between.

For python, Heralder supports all versions of python that the above versions of django support.

As of Heralder v0.4.0, we support:

Django Python 3.9 Python 3.10 Python 3.11 Python 3.12 Python 3.13
3.2.x Y Y - - -
4.2.x Y Y Y Y -
5.2.x - Y Y Y Y

Installation

  1. Install Django-Heralder using your favority packagement management tool (e.g. pip, uv, etc.)

    pip install django-heralder
    
  2. In your settings.py file, add herald and django.contrib.sites to INSTALLED_APPS:

    # ... other settings ...
    
    INSTALLED_APPS = [
        # ... other apps ...
        'herald',
        'django.contrib.sites',
    ]   
    
  3. In your main project urls.py file, add Heralder's URL routes:

    from django.conf import settings
    from django.conf.urls import url, include
    
    urlpatterns = []
    
    if settings.DEBUG:
        urlpatterns = [
            url(r'^herald/', include('herald.urls')),
    ] + urlpatterns
    

Example Usage

  1. Create a notifications.py file in any django app. This is where your notification classes will live. Add a class like this:

    from herald import registry
    from herald.base import EmailNotification
    
    
    class WelcomeEmail(EmailNotification):  # extend from EmailNotification for emails
        template_name = 'welcome_email'  # name of template, without extension
        subject = 'Welcome'  # subject of email
    
        def __init__(self, user):  # optionally customize the initialization
            self.context = {'user': user}  # set context for the template rendering
            self.to_emails = [user.email]  # set list of emails to send to
    
        @staticmethod
        def get_demo_args():  # define a static method to return list of args needed to initialize class for testing
            from users.models import User
            return [User.objects.order_by('?')[0]]
    
    registry.register(WelcomeEmail)  # finally, register your notification class
    
    # Alternatively, a class decorator can be used to register the notification:
    
    @registry.register_decorator()
    class WelcomeEmail(EmailNotification):
        # ... more code ...
    
  2. Create templates for rendering the email using this file structure:

    templates/
        herald/
            text/
                welcome_email.txt
            html/
                welcome_email.html
    
  3. Test how your email looks by navigating to /herald/.

  4. Additionally, preview your email before you send:

    WelcomeEmail().preview(render_type="html")
    
  5. Send your email wherever you need in your code:

    WelcomeEmail(user).send()
    
  6. View the sent emails in django admin and even be able to resend it.

Setting template names

There's three different ways to specify templatename:

  1. A string without any slash, e.g. "welcome_email" and Herald will expect your file structure to be:

    templates/
        herald/
            text/
                welcome_email.txt
            html/
                welcome_email.html
    
  2. A string with slashes, e.g. "path/to/welcome_email" and Herald will expect your file structure to be:

    templates/
        path/
            to/
                welcome_email.txt
                welcome_email.html
    
  3. A dictionary with text and/or html keys with the path to the templates:

    {
        "text": "path/to/welcome_email_t.txt",
        "html": "path/to/welcome_email_h.html",
    }
    

    and Herald will expect your file structure to be:

    templates/
        path/
            to/
                welcome_email_t.txt
                welcome_email_h.html
    

Email options

The following options can be set on the email notification class. For Example:

class WelcomeEmail(EmailNotification):
    cc = ['test@example.com']
  • from_email: (str, default: settings.DEFAULT_FROM_EMAIL) email address of sender
  • subject: (str, default: ) email subject
  • to_emails: (List[str], default: None) list of email strings to send to
  • bcc: (List[str], default: None) list of email strings to send as bcc
  • cc: (List[str], default: None) list of email strings to send as cc
  • headers: (dict, default: None) extra headers to be passed along to the EmailMultiAlternatives object
  • reply_to: (List[str], default: None) list of email strings to send as the Reply-To emails
  • attachments: (list) list of attachments. See "Email Attachments" below for more info

Automatically Deleting Old Notifications

Herald can automatically delete old notifications whenever a new notification is sent.

To enable this, set the HERALD_NOTIFICATION_RETENTION_TIME setting to a timedelta instance.

For example:

HERALD_NOTIFICATION_RETENTION_TIME = timedelta(weeks=8)

Will delete all notifications older than 8 weeks every time a new notification is sent.

Manually Deleting Old Notifications

The delnotifs command is useful for purging the notification history.

The default usage will delete everything from sent during today:

python manage.py delnotifs

However, you can also pass arguments for start or end dates. end is up to, but not including that date.

  • if only end is specified, delete anything sent before the end date.
  • if only start is specified, delete anything sent since the start date.
  • if both start and end are specified, delete anything sent in between, not including the end date.
python manage.py delnotifs --start='2016-01-01' --end='2016-01-10'

Asynchronous Email Sending

If you are sending slightly different emails to a large number of people, it might take quite a while to process. By default, Django will process this all synchronously. For asynchronous support, we recommend django-celery-email. It is very straightfoward to setup and integrate: https://github.com/pmclanahan/django-celery-email

Custom SentNotification Model

To create a custom SentNotification model, inherit SentNotificationAbstract. From there you can add fields and methods as desired. To use this model through Heralder, set HERALD_SENT_NOTIFICATION_MODEL in your settings.py to the app.ModelName path. Then use get_sent_notification_model in herald.utils to retrieve the model.

Note that using a custom model should be implemented as early as possible, ideally at the beginning of the project. Otherwise data may be split between the default table and the newly created custom table. To mitigate this, data will have to either be dropped or migrated to the new table.

Example:

from herald.models import SentNotificationAbstract

class SentNotificationCompany(SentNotificationAbstract):
   company_name = models.CharField(max_length=32)
   
   class Meta(SentNotificationAbstract.Meta):
      abstract = False

In settings.py:

HERALD_SENT_NOTIFICATION_MODEL = "mpapp.SentNotificationCompany"

Using throughout your app:

from herald.utils import get_sent_notification_model

SentNotification = get_sent_notification_model()

herald.contrib.auth

Django has built-in support for sending password reset emails. If you would like to send those emails using herald, you can use the notification class in herald.contrib.auth.

First, add herald.contrib.auth to INSTALLED_APPS (in addition to herald).

Second, use the HeraldPasswordResetForm in place of django's built in PasswordResetForm. This step is entirely dependant on your project structure, but it essentially just involves changing the form class on the password reset view in some way:

# you may simply just need to override the password reset url like so:
url(r'^password_reset/$', password_reset, name='password_reset', {'password_reset_form': HeraldPasswordResetForm}),

# of if you are using something like django-authtools:
url(r'^password_reset/$', PasswordResetView.as_view(form_class=HeraldPasswordResetForm), name='password_reset'),

# or you may have a customized version of the password reset view:
class MyPasswordResetView(FormView):
    form_class = HeraldPasswordResetForm  # change the form class here

# or, you may have a custom password reset form already. In that case, you will want to extend from the HeraldPasswordResetForm:
class MyPasswordResetForm(HeraldPasswordResetForm):
    ...

# alternatively, you could even just send the notification wherever you wish, seperate from the form:
PasswordResetEmail(some_user).send()

Third, you may want to customize the templates for the email. By default, herald will use the registration/password_reset_email.html that is provided by django for both the html and text versions of the email. But you can simply override herald/html/password_reset.html and/or herald/text/password_reset.txt to suit your needs.

User Disabled Notifications

If you want to disable certain notifications per user, add a record to the UserNotification table and add notifications to the disabled_notifications many to many table.

For example:

user = User.objects.get(id=user.id)

notification = Notification.objects.get(notification_class=MyNotification.get_class_path())

# disable the notification
user.usernotification.disabled_notifications.add(notification)

By default, notifications can be disabled. You can put can_disable = False in your notification class and the system will populate the database with this default. Your Notification class can also override the verbose_name by setting it in your inherited Notification class. Like this:

class MyNotification(EmailNotification):
    can_disable = False
    verbose_name = "My Required Notification"

Email Attachments

To send attachments, assign a list of attachments to the attachments attribute of your EmailNotification instance, or override the get_attachments() method.

Each attachment in the list can be one of the following:

  1. A tuple which consists of the filename, the raw attachment data, and the mimetype. It is up to you to get the attachment data. Like this:

    raw_data = get_pdf_data()
    
    email.attachments = [
        ('Report.pdf', raw_data, 'application/pdf'),
        ('report.txt', 'text version of report', 'text/plain')
    ]
    email.send()
    
  2. A MIMEBase object. See the documentation for attachments under EmailMessage Objects/attachments in the Django documentation.

  3. A django File object.

Inline Attachments

Sometimes you want to embed an image directly into the email content. Do that by using a MIMEImage assigning a content id header to a MIMEImage, like this:

email = WelcomeEmail(user)
im = get_thumbnail(image_file.name, '600x600', quality=95)
my_image = MIMEImage(im.read()) # MIMEImage inherits from MIMEBase
my_image.add_header('Content-ID', '<{}>'.format(image_file.name))

You can refer to these images in your html email templates using the Content ID (cid) like this:

<img src="cid:{{image_file.name}}" />

You would of course need to add the "image_file" to your template context in the example above. You can also accomplish this using file operations. In this example we overrode the get_attachments method of an EmailNotification.

class MyNotification(EmailNotification):
    context = {'hello': 'world'}
    template_name = 'welcome_email'
    to_emails = ['somebody@example.com']
    subject = "My email test"
        
    def get_attachments(self):
        fp = open('python.jpeg', 'rb')
        img = MIMEImage(fp.read())
        img.add_header('Content-ID', '<{}>'.format('python.jpeg'))
        return [
            img,
        ]

And in your template you would refer to it like this, and you would not need to add anything to the context:

<img src="cid:python.jpeg" />

Missing Templates

By default, Heralder will raise an exception if a template is missing when true (default).

HERALD_RAISE_MISSING_TEMPLATES = True

If you do not want this behavior, set this setting to False.

HERALD_RAISE_MISSING_TEMPLATES = False

HTML2Text Support

Django Herald can auto convert your HTML emails to plain text when installed with hmtl2text optional package. Any email without a plain text version will be auto converted if you enable this feature.

  1. Install Heralder with html2text support:

    pip install django-heralder[html2text]
    
  2. Follow the regular installation instructions.

  3. In your settings.py file, add this configuration setting to enable the html2text feature:

    HERALD_HTML2TEXT_ENABLED = True
    
  4. You can customize the output of HTML2Text by setting a configuration dictionary. See HTML2Text Configuration for options:

    HERALD_HTML2TEXT_CONFIG = {
        # Key / value configuration of html2text 
        'ignore_images': True  # Ignores images in conversion
    }
    

Twilio

Heralder supports Twilio as a notification provider when the optional package is installed.

  1. Install Heralder with twilio support:

    pip install django-heralder[twilio]
    
  2. Follow the regular installation instructions.

  3. In your settings.py file, set your Twilio account SID, token, and default "from number". You can retrieve these values on Twilio Console. Security best practices recommend to NOT hard coding your SID or token in your source code. The example below:

    # Twilio configurations
    # values taken from `twilio console`
    TWILIO_ACCOUNT_SID = "your_account_sid"
    TWILIO_AUTH_TOKEN = "your_auth_token"
    TWILIO_DEFAULT_FROM_NUMBER = "+1234567890"
    

For reference, Twilio has some great tutorials for Python: Twilio Python Tutorial

Other MIME attachments

You can also attach any MIMEBase objects as regular attachments, but you must add a content-disposition header, or they will be inaccessible:

my_image.add_header('Content-Disposition', 'attachment; filename="python.jpg"')

Attachments can cause your database to become quite large, so you should be sure to run the management commands to purge the database of old messages.

Development

Running Tests

This will run with a SQLite3 in-memory database:

python runtests.py -v 2

Running Server to See Views

You will need to run migrations:

python manage.py migrate

Run server:

python manage.py runserver 0.0.0.0:8000

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_heralder-0.4.0.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_heralder-0.4.0-py2.py3-none-any.whl (25.7 kB view details)

Uploaded Python 2Python 3

File details

Details for the file django_heralder-0.4.0.tar.gz.

File metadata

  • Download URL: django_heralder-0.4.0.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.4

File hashes

Hashes for django_heralder-0.4.0.tar.gz
Algorithm Hash digest
SHA256 3eea26c00e9972683097475100154ff5b53065e57aeb1787e219b67d8477d74f
MD5 5224de1fbc26ed204b236d26856fca39
BLAKE2b-256 44dc5ecd09981b1e5f616f2b15cdaba901932c9c9095e043a424645cf8914424

See more details on using hashes here.

File details

Details for the file django_heralder-0.4.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_heralder-0.4.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 b4cb615c756363eef8908b8cf8bb80dc187d35337c58b38e4f70bfe4baf4ef73
MD5 474b7afc13977272a3ce2bc00cd9eb02
BLAKE2b-256 3f924263cce4f2ead0339857abcf199573b5a5ca961b00dcd04ceb478a48a389

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page