After understanding the basic concepts of tidings from the Introduction, these docstrings make a nice comprehensive reference.
Abstract base class for events
Fire an Event (
SomeEvent.fire()) from the code that causes the interesting event to occur. Fire it any time the event might have occurred. The Event will determine whether conditions are right to actually send notifications; don’t succumb to the temptation to do these tests outside the Event, because you’ll end up repeating yourself if the event is ever fired from more than one place.
Eventsubclasses can optionally represent a more limited scope of interest by populating the
Watch.content_typefield and/or adding related
WatchFilterrows holding name/value pairs, the meaning of which is up to each individual subclass. NULL values are considered wildcards.
Eventsubclass instances must be pickleable so they can be shuttled off to celery tasks.
Return an EmailMessage to send to anonymous watchers.
They are expected to follow the activation URL sent in the email to activate their watch, so you should include at least that.
Return a URL pointing to a view which
TODO: provide generic implementation of this before liberating. Generic implementation could involve a setting to the default
Return an iterable yielding an EmailMessage to send to each user.
Parameters: users_and_watches – an iterable of (User or EmailUser, [Watches]) pairs where the first element is the user to send to and the second is a list of watches (usually just one) that indicated the user’s interest in this event
emails_with_users_and_watches()can come in handy for generating mails from Django templates.
Return an iterable of Users and EmailUsers watching this event and the Watches that map them to it.
Each yielded item is a tuple: (User or EmailUser, [list of Watches]).
Default implementation returns users watching this object’s event_type and, if defined, content_type.
_users_watching_by_filter(object_id=None, exclude=None, **filters)¶
Of multiple Users/EmailUsers having the same email address, only one is returned. Users are favored over EmailUsers so we are sure to be able to, for example, include a link to a user profile in the mail.
The list of
Watchobjects includes both those tied to the given User (if there is a registered user) and to any anonymous Watch having the same email address. This allows you to include all relevant unsubscribe URLs in a mail, for example. It also lets you make decisions in the
EventUnionbased on the kinds of watches found.
“Watching the event” means having a Watch whose
NULL, and whose WatchFilter rows match as follows: each name/value pair given in
filtersmust be matched by a related WatchFilter, or there must be no related WatchFilter having that name. If you find yourself wanting the lack of a particularly named WatchFilter to scuttle the match, use a different event_type instead.
Parameters: exclude – If a saved user is passed in as this argument, that user will never be returned, though anonymous watches having the same email address may. A sequence of users may also be passed in.
Raise a TypeError if
filterscontains any keys inappropriate to this event class.
_watches_belonging_to_user(user_or_email, object_id=None, **filters)¶
Return a QuerySet of watches having the given user or email, having (only) the given filters, and having the event_type and content_type attrs of the class.
Matched Watches may be either confirmed and unconfirmed. They may include duplicates if the get-then-create race condition in
notify()allowed them to be created.
If you pass an email, it will be matched against only the email addresses of anonymous watches. At the moment, the only integration point planned between anonymous and registered watches is the claiming of anonymous watches of the same email address on user registration confirmation.
If you pass the AnonymousUser, this will return an empty QuerySet.
Return a description of the Watch which can be used in emails.
For example, “changes to English articles”
Possible filter keys, for validation only. For example:
Notify everyone watching the event.
We are explicit about sending notifications; we don’t just key off creation signals, because the receiver of a
post_savesignal has no idea what just changed, so it doesn’t know which notifications to send. Also, we could easily send mail accidentally: for instance, during tests. If we want implicit event firing, we can always register a signal handler that calls
- exclude – If a saved user is passed in, that user will not be notified, though anonymous notifications having the same email address may still be sent. A sequence of users may also be passed in.
- delay – If True (default), the event is handled asynchronously with Celery. This requires the pickle task serializer, which is no longer the default starting in Celery 4.0. If False, the event is processed immediately.
is_notifying(user_or_email_, object_id=None, **filters)¶
Return whether the user/email is watching this event (either active or inactive watches), conditional on meeting the criteria in
Count only watches that match the given filters exactly–not ones which match merely a superset of them. This lets callers distinguish between watches which overlap in scope. Equivalently, this lets callers check whether
notify()has been called with these arguments.
Implementations in subclasses may take different arguments–for example, to assume certain filters–though most will probably just use this. However, subclasses should clearly document what filters they supports and the meaning of each.
Passing this an
False. This means you can always pass it
request.userin a view and get a sensible response.
notify(user_or_email_, object_id=None, **filters)¶
Start notifying the given user or email address when this event occurs and meets the criteria given in
Return the created (or the existing matching) Watch so you can call
activate()on it if you’re so inclined.
Implementations in subclasses may take different arguments; see the docstring of
Send an activation email if an anonymous watch is created and
True. If the activation request fails, raise a ActivationRequestFailed exception.
notify()twice for an anonymous user will send the email each time.
Delete all watches matching the exact user/email and filters.
Delete both active and inactive watches. If duplicate watches exist due to the get-then-create race condition, delete them all.
Implementations in subclasses may take different arguments; see the docstring of
Fireable conglomeration of multiple events
Use this when you want to send a single mail to each person watching any of several events. For example, this sends only 1 mail to a given user, even if he was being notified of all 3 events:
EventUnion(SomeEvent(), OtherEvent(), ThirdEvent()).fire()
Parameters: events – the events of which to take the union
Default implementation calls the
_mails()of my first event but may pass it any of my events as
Use this default implementation when the content of each event’s mail template is essentially the same, e.g. “This new post was made. Enjoy.”. When the receipt of a second mail from the second event would add no value, this is a fine choice. If the second event’s email would add value, you should probably fire both events independently and let both mails be delivered. Or, if you would like to send a single mail with a custom template for a batch of events, just subclass
EventUnionand override this method.
InstanceEvent(instance, *args, **kwargs)¶
Abstract superclass for watching a specific instance of a Model.
Subclasses must specify an
event_typeand should specify a
__init__(instance, *args, **kwargs)¶
Initialize an InstanceEvent
Parameters: instance – the instance someone would have to be watching in order to be notified when this event is fired.
Return users watching this instance.
Check if the watch created by notify exists.
Create, save, and return a watch which fires when something happens to
Delete the watch created by notify.
Raised when activation request fails, e.g. if email could not be sent
An anonymous user identified only by email address.
This is based on Django’s AnonymousUser, so you can use the
is_authenticatedproperty (Django 1.10 or later) or
is_authenticatedmethod (Django 1.9 or earlier) to tell that this is an anonymous user.
Mixin for notifications models that adds watches as a generic relation.
So we get cascading deletes for free, yay!
The registration of a user’s interest in a certain event
Enable this watch so it actually fires.
selfto support method chaining.
Optional reference to a content type:
Email stored only in the case of anonymous users:
Key used by an Event to find watches it manages:
Active watches receive notifications, inactive watches don’t.
Secret for activating anonymous watch email addresses.
Return the absolute URL to visit to delete me.
Additional key/value pairs that pare down the scope of a watch
multi_raw(query, params, models, model_to_fields)¶
Scoop multiple model instances out of the DB at once, given a query that returns all fields of each.
Return an iterable of sequences of model instances parallel to the
modelssequence of classes. For example:
[(<User such-and-such>, <Watch such-and-such>), ...]
Attach any anonymous watches having a user’s email to that user.
Call this from your user registration process if you like.
datais a string or unicode string, return an unsigned 4-byte int hash of it. If
datais already an int that fits those parameters, return it verbatim.
datais an int outside that range, behavior is undefined at the moment. We rely on the
WatchFilterto scream if the int is too long for the field.
We use CRC32 to do the hashing. Though CRC32 is not a good general-purpose hash function, it has no collisions on a dictionary of 38,470 English words, which should be fine for the small sets that
WatchFiltersare designed to enumerate. As a bonus, it is fast and available as a built-in function in some DBs. If your set of filter values is very large or has different CRC32 distribution properties than English words, you might want to do your own hashing in your
Eventsubclass and pass ints when specifying filter values.
emails_with_users_and_watches(subject, template_path, vars, users_and_watches, email@example.com', **extra_kwargs)¶
Return iterable of EmailMessages with user and watch values substituted.
A convenience function for generating emails by repeatedly rendering a Django template with the given
watcheskey for each pair in
- template_path – path to template file
- vars – a map which becomes the Context passed in to the template
- extra_kwargs – additional kwargs to pass into EmailMessage constructor
Unsubscribe from (i.e. delete) the watch of ID
squerystring parameter matching the watch’s secret.
GET will result in a confirmation page (or a failure page if the secret is wrong). POST will actually delete the watch (again, if the secret is correct).
Uses these templates:
- tidings/unsubscribe.html - Asks user to confirm deleting a watch
- tidings/unsubscribe_error.html - Shown when a watch is not found
- tidings/unsubscribe_success.html - Shown when a watch is deleted
The shipped templates assume a
contentblock in a
The template extension can be changed from the default
htmlusing the setting