Coder Social home page Coder Social logo

django-herald's Introduction

django-herald

Latest PyPI version Tests Black Coverage Status

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

Python/Django Support

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

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

So as of herald v0.3 we support django 3.2 and 4.x+, and python 3.6, 3.7, 3.8, 3.9, and 3.10.

Installation

  1. pip install django-herald
  2. Add herald and django.contrib.sites to INSTALLED_APPS.
  3. Add herald's URLS:
from django.conf import settings
from django.conf.urls import url, include

urlpatterns = []

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

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):
    ...
  1. Create templates for rendering the email using this file structure:

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

  3. Send your email wherever you need in your code:

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

Email options

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

class WelcomeEmail(EmailNotification):
    cc = ['[email protected]']
  • 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

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()
  1. A MIMEBase object. See the documentation for attachments under EmailMessage Objects/attachments in the Django documentation.

  2. 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 = ['[email protected]']
    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" />

HTML2Text Support

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

# Install html2text
pip install django-herald[html2text]

In your settings.py file:

HERALD_HTML2TEXT_ENABLED = True

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
}
HERALD_RAISE_MISSING_TEMPLATES = True

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

Twilio

# Install twilio
pip install django-herald[twilio]

You can retrieve these values on Twilio Console. Once you have retrieve the necessary ids, you can place those to your settings.py.

For reference, Twilio has some great tutorials for python. Twilio Python Tutorial

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

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.

Running Tests

python runtests.py

django-herald's People

Contributors

apoorvaeternity avatar georgmzimmer avatar jordanmkoncz avatar jproffitt avatar mands avatar mluard-worthwhile avatar npardington avatar peterfarrell avatar raiderrobert avatar seanbermejo avatar sudoguy avatar zulupro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-herald's Issues

Templates From Database

I'm building an application that allows users to customize email templates before sending out messages to users.

Given that the docs only allow for specifying a directory for the email template, is there a way to utilize this library for pulling a template record directly from the database instead?

Adding attachments is not well documented

As per the readme section, the attachment can be in the form of Django File object.
Now, I have created a Django File object as follows:

    with open('docs/my.pdf', 'r') as f:
        file = File(f)

I am having a hard time figuring out how to use this object in the code to send the email. Anything done outside the with block obviously creates an error as the file is closed.

Here is the full code:

@registry.register_decorator()
class SendThisMail(SomeBaseClass, EmailNotification):
    def __init__(self, user, my_modal: models.MyModal):
        super().__init__(user, my_modal)

        self.subject = "abc"

        with open('docs/my.pdf', 'r') as f:
            file = File(f)

            self.attachments = (
                'abc.pdf', file,
                'application/pdf')

        self.context = {
            'subject': self.subject,
            **self.context,
        }

        self.to_emails = [user.email]

def sendMail():
    SendThisMail(user, my_modal).send(user=my_user) # creates an error on this line as the file object is closed and inaccessible.

Can't send mails with attachments

I've been struggling for days to make it work but the lack of documentation and silent failures made it impossible to debug the issues. It seems that the mails are not being sent if I include an attachment in it.


def sendMail():
    SendThisMail(user, my_modal).send(user=my_user) # creates an error on this line as the file object is closed and inaccessible.

@registry.register_decorator()
class SendThisMail(SomeBaseClass, EmailNotification):
    def __init__(self, user, my_modal: models.MyModal):
        super().__init__(user, my_modal)

        self.subject = "abc"

        file = open('.staticfiles/assets/some.pdf', 'rb')

        self.attachments = [('attachment_1', File(file))]

        self.context = {
            **self.context,
            'subject': self.subject,
            'attachment': self.attachments,
        }

        self.to_emails = [user.email]

What's wrong with it?

View in Browser link

Would be nice if herald could provide a "view in browser" link so that users can view the email in a browser like most email services do like mail chimp, etc.

Since the email html is cached in the database, it shouldn't be too difficult to add that feature.

django-herald on django 3.2 or above raises ImproperlyConfigured exception

The issue occurs when herald.contrib.auth is added to INSTALLED_APPS. The issue only occurs in django versions 3.2 or above.
Here's the traceback when running the tests:

Traceback (most recent call last):
  File "/Users/apoorva/Desktop/django-herald/runtests.py", line 14, in <module>
    runtests()
  File "/Users/apoorva/Desktop/django-herald/runtests.py", line 10, in runtests
    execute_from_command_line(argv)
  File "/Users/apoorva/Desktop/venvdjango3/lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/Users/apoorva/Desktop/venvdjango3/lib/python3.9/site-packages/django/core/management/__init__.py", line 395, in execute
    django.setup()
  File "/Users/apoorva/Desktop/venvdjango3/lib/python3.9/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/apoorva/Desktop/venvdjango3/lib/python3.9/site-packages/django/apps/registry.py", line 91, in populate
    app_config = AppConfig.create(entry)
  File "/Users/apoorva/Desktop/venvdjango3/lib/python3.9/site-packages/django/apps/config.py", line 255, in create
    return app_config_class(app_name, app_module)
  File "/Users/apoorva/Desktop/venvdjango3/lib/python3.9/site-packages/django/apps/config.py", line 38, in __init__
    raise ImproperlyConfigured(
django.core.exceptions.ImproperlyConfigured: The app label 'herald.contrib.auth' is not a valid Python identifier.

Allow Herald to Support Multiple Python Webframeworks

At the moment, Herald is squarely a Django package. However, there may be some benefit in making it less dependent on Django specifically and more dependent on having specific components that adhere to Django's API interface for various things.

Should this repo be forked to work on that, or is there openness to supporting this as a long-running branch?

Configuring email host

Hello! I ask for help! It’s not clear from the description how to set up an email account from where messages will be sent. For example, I have a gmail mailbox. How do I send from there? Where can I set login parameters for my account? Thank!

Buglets in notification browsing view

When viewing the HTML content of an Email class, the encoding is not properly set (utf8 in my case) so the text is garbled.

In text view, it's the content-type which seems abnormal, text/text should be text/plain, so the content gets downloaded as unknown instead of displayed as raw text.

How to send notification via SMS and email

I'm wondering how you would recommended using django-herald so that notifications are sent via both SMS and email.

The users in my system will always have an email address, and 90% of them will also have a mobile phone number; both of these are set on my 'User' model. I will have many different types of notifications, and it would be good if I could define them once and have the notification always sent via email, and also via SMS if the user has a mobile phone number set. I plan to use Twilio for sending SMS messages.

Ideally I would want to be able to define the message/content of the notification in just one one place too, with the SMS message being just this plain text message, while the email gets rendered into a HTML email template with the message rendered in this template.

I'm trying to figure out the best way to approach this so that I don't have to create 2 copies of every notification type (one for email and one for SMS) and trigger 2 notifications every time I want to send a notification to a user. Any recommendations?

Cannot install herald without first installing django

It is impossible to install herald in in an environment that does not have django installed yet. This is because setup.py imports herald.__version__ which then imports from django.utils.module_loading import autodiscover_modules.

I think all we need to do is move the django import down inside the autodiscover function.

Support for push notifications

Hi ,
Have you thought about adding support for push notifications for app.
Basically it would be good to add a template for adding a transmission method.

Thanks
Anand

confusing 'delnotifs' command with optional start and end args

for delnotifs command with start and end args, doc says:

end is up to, but not including that date.

I'd think if I specify the end date being yesterday without start date, it will delete notifications sent before yesterday.

the actual code is

start_date = options['start'] if options['start'] else today

This will set the start date to be today, and end date to be yesterday, it's going to be an invalid range.

Suggestion:

  • if only end date is specified, delete anything sent before date_sent
  • if only start date is specified, delete anything sent after date_sent
  • if both start and end dates are specified, delete the range

Sending group email using `bcc` only not working

I'm trying to send an email to a group of users with the same template and context and they can't be in the same message
so I tried using bcc only with to_emails = []
but it's not sending any emails
here is the init:

 def __init__(self, obj, emails):
        self.context = {'obj': obj}
        self.bcc = emails
        self.to_emails = []

is there any way to make this work?

support for autogenerating text/ascii notifications

Hello everyone! Great project!

I'm using it in production for one our SaaS and it's working nicely.

One of the things I've just learned about today is https://pypi.python.org/pypi/html2text.

There is a big hassle in creating and maintaining two sets of templates, one for html and one for txt. Perhps we could integrate with html2text and auto-generate the text emails, when the txt template is not available?

I can contribute, but I would like to know how you feel about that, before starting it.

Separate notification content from the sending of the notification

Right now, each notification class basically does it all. It specifies WHAT to send and HOW to send it. I think it would be a much more flexible API if the notification classes just specified WHAT to send. Then we would have different backends that could be specified on the notification that tell HOW to send it.

That way you could, for example, have one notification that gets sent a bunch of different ways:

class BlogPostLikedNotification(Notification):
    backends = [EmailBackend, TwillioTextBacked, APNSBackend, GCMBackend]

Of course, that will come with a bunch of complications. Each backend will need separate info for who to send to, (the push notification backends need device identifiers, the twilio backend needs a phone number, and the email backend needs an email, etc.) and what to send. (The email backend needs html, the others just plain text, etc)

Something to think about... would of course be a huge refactor.

Increase field max_length for Notification.notification_class

The Notification model has a field notification_class = models.CharField(max_length=80, unique=True). This max_length restriction of 80 characters is causing an issue for me, because I'm trying to create a notification class that happens to be 83 characters long, so I'm getting an error django.db.utils.DataError: (1406, "Data too long for column 'notification_class' at row 1").

The SentNotification model also has a field notification_class, but with a less restrictive max_length of 255 characters.

Is there a reason why the Notification model has an inconsistent and much more restrictive max_length for its notification_class field than that of the SentNotification model? If not, can we increase it to match the SentNotification model, otherwise you have to artificially limit the names of your notification classes in order to fit within this restriction.

Template directory structure may need some work

Right now, you are forced to make sure your templates are in the following directory structure: herald/html/{notifcation_name}.html or herald/text/{notifcation_name}.txt.

First of all, that should definitely be overridable. Maybe rename NotificationBase.template_name to NotificationBase.template_base_name to avoid confusion, then add the ability to define absolute template paths per render_type. (but the current default would still remain). like the following:

class MyEmail(NotificationBase):
    template_names = {
        'text': 'my/custom/template/path.txt',
        'html': 'my/custom/template/path.html'
    }

Secondly, is the current default even a good default? Rather than herald/html/{notifcation_name}.html or herald/text/{notifcation_name}.txt should it maybe be {app name}/{notifcation_name}.html or {app name}/{notifcation_name}.txt similar to how the django generic class based views find templates by default (for example, {app_name}/{model_name}_create.html is the default for CreateViews.)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.