django-sitemessage documentation¶
https://github.com/idlesign/django-sitemessage
Description¶
Reusable application for Django introducing a message delivery framework.
Features:
- Message Types - message classes exposing message composition logic (plain text, html, etc.).
- Messengers - clients for various protocols (smtp, jabber, twitter, telegram, facebook, vkontakte, etc.);
- Support for user defined message types.
- Support for user defined messenger types.
- Message prioritization.
- Message subscription/unsubscription system.
- Message grouping to prevent flooding.
- Message ‘read’ indication.
- Means for background message delivery and cleanup.
- Means to debug integration: test requisites, delivery log.
- Django Admin integration.
Currently supported messengers:
- SMTP;
- XMPP (requires
sleekxmpp
package); - Twitter (requires
twitter
package); - Telegram (requires
requests
package); - Facebook (requires
requests
package); - VKontakte (requires
requests
package).
Requirements¶
- Python 3.7+
- Django 2.0+
Table of Contents¶
Quickstart¶
- Add the sitemessage application to INSTALLED_APPS in your settings file (usually ‘settings.py’).
- Run ‘./manage.py syncdb’ to install sitemessage tables into database.
Note
When switching from an older version do not forget to upgrade your database schema.
That could be done with the following command issued in your Django project directory:
./manage.py migrate
Configure messengers for your project (create
sitemessages.py
in one of your apps):from sitemessage.toolbox import register_messenger_objects from sitemessage.messengers.smtp import SMTPMessenger from sitemessage.messengers.xmpp import XMPPSleekMessenger # We register two messengers to deliver emails and jabber messages. register_messenger_objects( SMTPMessenger('user1@host.com', 'user1', 'user1password', host='smtp.host.com', use_tls=True), XMPPSleekMessenger('user1@jabber.host.com', 'user1password', 'jabber.host.com'), ) # Or you may want to define your own message type for further usage. class MyMessage(MessageBase): alias = 'myxmpp' supported_messengers = ['xmppsleek'] @classmethod def create(cls, message: str): cls(message).schedule(cls.recipients('xmppsleek', ['a@some.tld', 'b@some.tld', ])) register_message_types(MyMessage)
Schedule messages for delivery when and where needed (e.g. in a view):
from sitemessage.shortcuts import schedule_email, schedule_jabber_message from .sitemessages import MyFbMessage def send_messages_view(request): ... # Suppose `user_model` is a recipient User Model instance. user1_model = ... # Schedule both email and jabber messages to delivery. schedule_email('Email from sitemessage.', [user1_model, 'user2@host.com']) schedule_jabber_message('Jabber message from sitetree', ['user1@jabber.host.com', 'user2@jabber.host.com']) ... # Or if you want to send your message type: MyMessage.create('Hi there!')
Periodically run Django management command from wherever you like (cli, cron, Celery, uWSGI, etc.):
./manage.py sitemessage_send_scheduled
Toolbox¶
sitemessage toolbox exposes some commonly used functions.
Defining message recipients¶
sitemessage.toolbox.recipients allows to define message recipients for various messengers, so that they could be passed into message scheduling functions:
from sitemessage.toolbox import recipients
from sitemessage.messengers.smtp import SMTPMessenger
from sitemessage.messengers.xmpp import XMPPSleekMessenger
# The first argument could be Messenger alias:
my_smtp_recipients = recipients('smtp', ['user1@host.com', 'user2@host.com']),
# or a Messenger class itself:
my_jabber_recipients = recipients(XMPPSleekMessenger, ['user1@jabber.host.com', 'user2@jabber.host.com']),
# Second arguments accepts either Django User model instance or an actual address:
user1_model = ...
my_smtp_recipients = recipients(SMTPMessenger, [user1_model, 'user2@host.com'])
# You can also merge recipients from several messengers:
my_recipients = my_smtp_recipients + my_jabber_recipients
Scheduling messages¶
sitemessage.toolbox.schedule_messages is a generic tool to schedule messages:
from sitemessage.toolbox import schedule_messages, recipients
# Let's import a built-in message type class we'll use.
from sitemessage.messages import EmailHtmlMessage
schedule_messages(
# You can pass one or several message objects:
[
# The first param of this Message Type is `subject`. The second may be either an html itself:
EmailHtmlMessage('Message subject 1', '<html><head></head><body>Some <b>text</b></body></html>'),
# or a dictionary
EmailHtmlMessage('Message subject 2', {'title': 'My message', 'entry': 'Some text.'}),
# NOTE: Different Message Types may expect different arguments.
],
# The same applies to recipients: add one or many as required:
recipients('smtp', ['user1@host.com', 'user2@host.com']),
# It's useful sometimes to know message sender in terms of Django users:
sender=request.user
)
Sending test messages¶
When your messengers are configured you can try and send a test message using sitemessage_probe management command:
./manage.py sitemessage_probe smtp --to someone@example.com
Or you can use sitemessage.toolbox.send_test_message function:
from sitemessage.toolbox import send_test_message
send_test_message('smtp', to='someone@example.com')
Sending messages¶
Scheduled messages are normally sent with the help of sitemessage_send_scheduled management command, that could be issued from wherever you like (cron, Celery, etc.):
./manage.py sitemessage_send_scheduled
Nevertheless you can directly use sitemessage.toolbox.send_scheduled_messages from sitemessage toolbox:
from sitemessage.toolbox import send_scheduled_messages
# Note that this might eventually raise UnknownMessengerError, UnknownMessageTypeError exceptions.
send_scheduled_messages()
# Or if you do not want sitemessage exceptions to be raised (that way scheduled messages
# with unknown message types or for which messengers are not configured won't be sent):
send_scheduled_messages(ignore_unknown_messengers=True, ignore_unknown_message_types=True)
# To send only messages of a certain priority use `priority` argument.
send_scheduled_messages(priority=10)
Cleanup sent messages and dispatches¶
You can delete sent dispatches and message from DB using sitemessage_cleanup:
./manage.py sitemessage_cleanup --ago 5
Or you can use sitemessage.toolbox.cleanup_sent_messages from sitemessage toolbox:
from sitemessage.toolbox import cleanup_sent_messages
# Remove all dispatches (but not messages) 5 days old.
cleanup_sent_messages(ago=5, dispatches_only=True)
# Delete all sent messages and dispatches.
cleanup_sent_messages()
Use sitemessage to send Django-generated e-mails¶
In settings.py of your project set EMAIL_BACKEND to a backend shipped with sitemessage.
EMAIL_BACKEND = 'sitemessage.backends.EmailBackend'
After that Django’s send_mail() function will schedule e-mails using sitemessage machinery.
Messages¶
sitemessage message classes expose message composition logic (plain text, html, etc.).
You can either use builtin classes or define your own.
Helper functions¶
sitemessage.toolbox.register_message_types(*message_types)
Registers message types (classes).
get_registered_message_types()
Returns registered message types dict indexed by their aliases.
get_registered_message_type(message_type)
Returns registered message type (class) by alias,
Builtin message types¶
Builtin message types are available from sitemessage.messages:
- sitemessage.messages.plain.PlainTextMessage
- sitemessage.messages.email.EmailTextMessage
- sitemessage.messages.email.EmailHtmlMessage
User defined message types¶
To define a message type one needs to inherit from sitemessage.messages.base.MessageBase (or a builtin message class), and to register it with sitemessage.toolbox.register_message_types (put these instructions into sitemessages.py in one of your apps):
from sitemessage.messages.base import MessageBase
from sitemessage.toolbox import register_message_types
from django.utils import timezone
class MyMessage(MessageBase):
# Message types could be addressed by aliases.
alias = 'mymessage'
# Message type title to show up in UI
title = 'Super message'
# Define a template path to build messages from.
# You can omit this setting and place your template under
# `templates/sitemessage/messages/` naming it as `mymessage__<messenger>.html`
# where <messenger> is a messenger alias, e.g. `smtp`.
template = 'mymessages/mymessage.html'
# Define a send retry limit for that message type.
send_retry_limit = 10
# If we don't want users to subscribe for messages of this type
# (see get_user_preferences_for_ui()) we just forbid such subscriptions.
allow_user_subscription = False
def __init__(self, text, date):
# Calling base class __init__ and passing message context
super(MyMessage, self).__init__({'text': text, 'date': date})
@classmethod
def get_template_context(cls, context):
"""Here we can add some data into template context
right before rendering.
"""
context.update({'greeting': 'Hi!'})
return context
@classmethod
def create(cls, text):
"""Let it be an alternative constructor - kind of a shortcut."""
# This recipient list is comprised of users subscribed to this message type.
recipients = cls.get_subscribers()
# Or we can build recipient list for a certain messenger manually.
# recipients = cls.recipients('smtp', 'someone@sowhere.local')
date_now = timezone.now().date().strftime('%d.%m.%Y')
cls(text, date_now).schedule(recipients)
register_message_types(MyMessage)
Note
Look through MessageBase
and other builtin message classes for more code examples.
Now, as long as our message type uses a template, let’s create it (mymessages/mymessage.html):
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>{{ greeting }}</title>
</head>
<body>
<h1>{{ greeting }}</h1>
{{ text }}
<hr>
{{ date }}
</body>
</html>
Note
The following context variables are available in templates by default:
SITE_URL - base site URL
message_model - message model data
dispatch_model - message dispatch model data
directive_unsubscribe - unsubscribe directive string (e.g. URL, command)
directive_mark_read - mark dispatch as read directive string (e.g. Url, command)
After that you can schedule and send messages of this new type:
from sitemessage.toolbox import schedule_messages, recipients
from myproject.sitemessages import MyMessage
# Scheduling message send via smtp.
schedule_messages(MyMessage('Some text', '17.06.2014'), recipients('smtp', 'user1@host.com'))
# Or we can use out shortcut method:
MyMessage.create('Some other text')
Messengers¶
sitemessage messenger classes implement clients for various protocols (smtp, jabber, etc.).
You can either use builtin classes or define your own.
Helper functions¶
sitemessage.toolbox.register_messenger_objects(*messengers)
Registers (configures) messengers.
sitemessage.toolbox.get_registered_messenger_objects()
Returns registered (configured) messengers dict indexed by messenger aliases.
sitemessage.toolbox.get_registered_messenger_object(messenger)
Returns registered (configured) messenger by alias,
Builtin messengers¶
Builtin messengers are available from sitemessage.messengers:
xmpp.XMPPSleekMessenger¶
aliased xmppsleek
Warning
Requires sleekxmpp
package.
from sitemessage.toolbox import schedule_messages, recipients
# Sending jabber message.
schedule_messages('Hello there!', recipients('xmppsleek', 'somebody@example.ru'))
twitter.TwitterMessenger¶
aliased twitter
Warning
Requires twitter
package.
from sitemessage.toolbox import schedule_messages, recipients
# Twitting example.
schedule_messages('My tweet.', recipients('twitter', ''))
# Tweet to somebody.
schedule_messages('Hey, that is my tweet for you.', recipients('twitter', 'idlesign'))
telegram.TelegramMessenger¶
aliased telegram
Warning
Requires requests
package and a registered Telegram Bot. See https://core.telegram.org/bots/api
To send messages to a channel, your bot needs to be and administrator of that channel.
from sitemessage.toolbox import schedule_messages, recipients
# Let's send a message to chat with ID 12345678.
# (To get chat IDs from `/start` command messages sent to our bot
# by users you can use get_chat_ids() method of Telegram messenger).
schedule_messages('Hi there!', recipients('telegram', '12345678'))
# Message to a channel mychannel
schedule_messages('Hi all!', recipients('telegram', '@mychannel'))
facebook.FacebookMessenger¶
aliased fb
Warning
Requires requests
package, registered FB application and page.
See FacebookMessenger
docstring for detailed instructions.
from sitemessage.toolbox import schedule_messages, recipients
# Schedule a message or a URL for FB timeline.
schedule_messages('Hi there!', recipients('fb', ''))
vkontakte.VKontakteMessenger¶
aliased vk
Warning
Requires requests
package, registered VK application and community page.
See VKontakteMessenger
docstring for detailed instructions.
from sitemessage.toolbox import schedule_messages, recipients
# Schedule a message or a URL for VK page wall. 1245 - user_id; use -12345 (with minus) to post to community wall.
schedule_messages('Hi there!', recipients('vk', '12345'))
Proxying¶
Some messengers (vk, fb, telegram) are able to use proxies (e.g. SOCKS5).
One may pass proxy argument to use proxies.
TelegramMessenger('token', proxy={'https': 'socks5://user:pass@host:port'})
Sending test messages¶
After a messenger is configured you can try whether it works properly using its send_test_message method:
from sitemessage.messengers.smtp import SMTPMessenger
msgr = SMTPMessenger('user1@host.com', 'user1', 'user1password', host='smtp.host.com', use_tls=True)
msgr.send_test_message('user1@host.com', 'This is a test message')
User defined messengers¶
To define a message type one needs to inherit from sitemessage.messengers.base.MessengerBase (or a builtin messenger class), and to register it with sitemessage.toolbox.register_messenger_objects (put these instructions into sitemessages.py in one of your apps):
from sitemessage.messengers.base import MessengerBase
from sitemessage.toolbox import register_messenger_objects
class MyMessenger(MessengerBase):
# Messengers could be addressed by aliases.
alias = 'mymessenger'
# Messenger title to show up in UI
title = 'Super messenger'
# If we don't want users to subscribe for messages from that messenger
# (see get_user_preferences_for_ui()) we just forbid such subscriptions.
allow_user_subscription = False
def __init__(self):
"""This messenger doesn't accept any configuration arguments.
Other may expect login, password, host, etc. to connect this messenger to a service.
"""
@classmethod
def get_address(cls, recipient):
address = recipient
if hasattr(recipient, 'username'):
# We'll simply get address from User object `username`.
address = '%s--address' % recipient.username
return address
def before_send(self):
"""We don't need that for now, but usually here will be messenger warm up (connect) code."""
def after_send(self):
"""We don't need that for now, but usually here will be messenger cool down (disconnect) code."""
def send(self, message_cls, message_model, dispatch_models):
"""This is the main sending method that every messenger must implement."""
# `dispatch_models` from sitemessage are models representing a dispatch
# of a certain message_model for a definite addressee.
for dispatch_model in dispatch_models:
# For demonstration purposes we won't send a dispatch anywhere,
# we'll just mark it as sent:
self.mark_sent(dispatch_model) # See also: self.mark_failed() and self.mark_error().
register_messenger_objects(MyMessenger())
Note
Look through MessengerBase
and other builtin messenger classes for more information and
code examples.
After that you can schedule and send messages with your messenger as usual:
from sitemessage.toolbox import schedule_messages, recipients
user2 = ... # Let's suppose it's an instance of Django user model.
# We'll just try to send PlainText message.
schedule_messages('Some plain text message', recipients('mymessenger', ['user1--address', user2]))
Exceptions¶
The following exception classes are used by sitemessage.
-
exception
sitemessage.exceptions.
MessengerException
¶ Base messenger exception.
-
exception
sitemessage.exceptions.
MessengerWarmupException
¶ This exception represents a delivery error due to a messenger warm up process failure.
-
exception
sitemessage.exceptions.
SiteMessageConfigurationError
¶ This error is raised on configuration errors.
-
exception
sitemessage.exceptions.
SiteMessageError
¶ Base class for sitemessage errors.
-
exception
sitemessage.exceptions.
UnknownMessageTypeError
¶ This error is raised when there’s a try to access an unknown message type.
-
exception
sitemessage.exceptions.
UnknownMessengerError
¶ This error is raised when there’s a try to access an unknown messenger.
Prioritizing messages¶
sitemessage supports message sending prioritization: any message might be given a positive number to describe its priority.
Note
It’s up to you to decide the meaning of priority numbers.
Prioritization is supported on the following two levels:
- You can define priority within your message type class.
from sitemessage.messages.base import MessageBase
class MyMessage(MessageBase):
alias = 'mymessage'
priority = 10 # Messages of this type will automatically have priority of 10.
...
2. Or you can override priority defined within message type, by supplying priority argument to messages scheduling functions.
from sitemessage.shortcuts import schedule_email
from sitemessage.toolbox import schedule_messages, recipients
schedule_email('Email from sitemessage.', 'user2@host.com', priority=1)
# or
schedule_messages('My message', recipients('smtp', 'user1@host.com'), priority=16)
After that you can use sitemessage_send_scheduled management command with –priority argument to send message when needed:
./manage.py sitemessage_send_scheduled --priority 10
Note
Use a scheduler (e.g cron, uWSGI cron/cron2, etc.) to send messages with different priorities on different days or intervals, and even simultaneously.
Grouping messages¶
sitemessage allows you to group messages in such a way that even if your application generates many messages (between send attempts) your user receives them as one.
from sitemessage.messages.base import MessageBase
class MyMessage(MessageBase):
...
# Define group ID at class level or as a @property
group_mark = 'groupme'
# In case your message has some complex context
# you may want to override 'merge_context' to add a new message
# context to the context already existing in message stored in DB
@classmethod
def merge_context(cls, context: dict, new_context: dict) -> dict:
merged = ... #
return merged
Recipients and subscriptions¶
Postponed dispatches¶
Note that when scheduling a message you can omit recipients parameter.
In that case no dispatch objects are created on scheduling, instead the process of creation is postponed until prepare_dispatches() function is called.
from sitemessage.toolbox import schedule_messages, prepare_dispatches
# Here `recipients` parameter is omitted ...
schedule_messages(MyMessage('Some text'))
# ... instead dispatches are created later.
prepare_dispatches()
prepare_dispatches() by default generates dispatches using recipients list comprised from users subscription preferences data (see below).
Handling subscriptions¶
sitemessage supports basic subscriptions mechanics, and that’s how it works.
sitemessage.toolbox.get_user_preferences_for_ui is able to generate user subscription preferences data, that could be rendered in HTML as table using sitemessage_prefs_table template tag.
Note
sitemessage_prefs_table tag support table layout customization through custom templates.
user_prefs_table-bootstrap.html - Bootstrap-style table.
{% sitemessage_prefs_table from subscr_prefs template “sitemessage/user_prefs_table-bootstrap.html” %}
This table in its turn could be placed in form tag to allow users to choose message types they want to receive using various messengers.
At last sitemessage.toolbox.set_user_preferences_from_request can process form data from a request and store subscription data into DB.
from django.shortcuts import render
from sitemessage.toolbox import set_user_preferences_from_request, get_user_preferences_for_ui
def user_preferences(self, request):
"""Let's suppose this simplified view handles user preferences."""
...
if request.POST:
# Process form data:
set_user_preferences_from_request(request)
...
# Prepare preferences data.
subscr_prefs = get_user_preferences_for_ui(request.user)
...
return render(request, 'user_preferences.html', {'subscr_prefs': subscr_prefs})
Note
get_user_preferences_for_ui allows messenger titles customization and both message types and messengers filtering. You can also forbid subscriptions on message type or messenger level by setting allow_user_subscription class attribute to False.
And that’s what is in a template used by the view above:
<!-- user_preferences.html -->
{% load sitemessage %}
<form method="post">
{% csrf_token %}
<!-- Create preferences table from `subscr_prefs` template variable. -->
{% sitemessage_prefs_table from subscr_prefs %}
<input type="submit" value="Save preferences" />
</form>
Note
You can get subscribers as recipients list right from your message type, using get_subscribers() method.
Handling unsubscriptions¶
sitemessage bundles some views, and one of those allows users to unsubscribe from certain message types just by visiting it.
Please refer to Bundled views section of this documentation.
After that, for example, your E-mail client (if it supports List-Unsubscribe header) will happily introduce you some button to unsubscribe from messages of that type.
Bundled views¶
sitemessage bundles some views, and one of those allows users to unsubscribe from certain message types, or mark messages read just by visiting pages linked to those views. So let’s configure your project to use those views:
from sitemessage.toolbox import get_sitemessage_urls
...
# Somewhere in your urls.py.
urlpatterns += get_sitemessage_urls() # Attaching sitemessage URLs.
Unsubscribe¶
Read Handling unsubscriptions to get some information on how unsubscription works.
Mark read¶
When bundled views are attached to your app you can mark messages as read.
For example if you put the following code in your HTML e-mail message template, the message dispatch in your DB will be marked read as soon as Mail Client will render <img> tag.
{% if directive_mark_read %}
<img src="{{ directive_mark_read }}">
{% endif %}
This allows to track whether a user has read a message.
Sitemessage for reusable applications¶
sitemessage
offers reusable applications authors an API to send messages in a way that
can be customized by project authors.
For applications authors¶
Use sitemessage.toolbox.get_message_type_for_app to return a registered message type object for your application.
Note
Project authors can override the above mentioned object to customize messages.
from sitemessage.toolbox import get_message_type_for_app, schedule_messages, recipients
def schedule_email(text, to, subject):
"""Suppose you're sending a notification and want to sent a plain text e-mail by default."""
# This says: give me a message type `email_plain` if not overridden.
message_cls = get_message_type_for_app('myapp', 'email_plain')
message_obj = message_cls(subject, text)
# And this actually schedules a message to send via `smtp` messenger.
schedule_messages(message_obj, recipients('smtp', to))
Note
It’s advisable for reusable applications authors to document which message types are used in the app by default, with which arguments, so that project authors may design their custom message classes accordingly.
For project authors¶
Use sitemessage.toolbox.override_message_type_for_app to override a given message type used by a certain application with a custom one.
Note
You’d probably need to know which message types are used in an app by default, and with which arguments, so that you may design your custom message classes accordingly (e.g. by subclassing the default type).
from sitemessage.toolbox import override_message_type_for_app
# This will override `email_plain` message type by `my_custom_email_plain` for `myapp` application.
override_message_type_for_app('myapp', 'email_plain', 'my_custom_email_plain')
Warning
Be sure to call override_message_type_for_app
beforehand. So that to the moment when a thirdparty app
will try to send a message, message type is overridden.
Get involved into django-sitemessage¶
Submit issues. If you spotted something weird in application behavior or want to propose a feature you can do that at https://github.com/idlesign/django-sitemessage/issues
Write code. If you are eager to participate in application development, fork it at https://github.com/idlesign/django-sitemessage, write your code, whether it should be a bugfix or a feature implementation, and make a pull request right from the forked project page.
Translate. If want to translate the application into your native language use Transifex: https://www.transifex.net/projects/p/django-sitemessage/.
Spread the word. If you have some tips and tricks or any other words in mind that you think might be of interest for the others — publish it.
Also¶
If the application is not what you want for messaging with Django, you might be interested in considering other choices at https://www.djangopackages.com/grids/g/notification/ or https://www.djangopackages.com/grids/g/newsletter/ or https://www.djangopackages.com/grids/g/email/.