Reference Documentation

After understanding the basic concepts of tidings from the Introduction, these docstrings make a nice comprehensive reference.

events

class tidings.events.Event[source]

Abstract base class for events

An Event represents, simply, something that occurs. A Watch is a record of someone’s interest in a certain type of Event, distinguished by Event.event_type.

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.

Event subclasses can optionally represent a more limited scope of interest by populating the Watch.content_type field and/or adding related WatchFilter rows holding name/value pairs, the meaning of which is up to each individual subclass. NULL values are considered wildcards.

Event subclass instances must be pickleable so they can be shuttled off to celery tasks.

classmethod _activation_email(watch, email)[source]

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.

classmethod _activation_url(watch)[source]

Return a URL pointing to a view which activates a watch.

TODO: provide generic implementation of this before liberating. Generic implementation could involve a setting to the default reverse() path, e.g. 'tidings.activate_watch'.

_mails(users_and_watches)[source]

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.

_users_watching(**kwargs)[source]

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)[source]

Return an iterable of (User/EmailUser, [Watch objects]) tuples watching the event.

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 Watch objects 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 _mails() method of EventUnion based on the kinds of watches found.

“Watching the event” means having a Watch whose event_type is self.event_type, whose content_type is self.content_type or NULL, whose object_id is object_id or NULL, and whose WatchFilter rows match as follows: each name/value pair given in filters must 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.
classmethod _validate_filters(filters)[source]

Raise a TypeError if filters contains any keys inappropriate to this event class.

classmethod _watches_belonging_to_user(user_or_email, object_id=None, **filters)[source]

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.

classmethod description_of_watch(watch)[source]

Return a description of the Watch which can be used in emails.

For example, “changes to English articles”

filters = {}

Possible filter keys, for validation only. For example: set(['color', 'flavor'])

fire(exclude=None, delay=True)[source]

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_save signal 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 fire().

Parameters:
  • 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.
classmethod is_notifying(user_or_email_, object_id=None, **filters)[source]

Return whether the user/email is watching this event (either active or inactive watches), conditional on meeting the criteria in filters.

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 AnonymousUser always returns False. This means you can always pass it request.user in a view and get a sensible response.

classmethod notify(user_or_email_, object_id=None, **filters)[source]

Start notifying the given user or email address when this event occurs and meets the criteria given in filters.

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 is_notifying().

Send an activation email if an anonymous watch is created and TIDINGS_CONFIRM_ANONYMOUS_WATCHES is True. If the activation request fails, raise a ActivationRequestFailed exception.

Calling notify() twice for an anonymous user will send the email each time.

classmethod stop_notifying(user_or_email_, **filters)[source]

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 is_notifying().

class tidings.events.EventUnion(*events)[source]

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()
__init__(*events)[source]
Parameters:events – the events of which to take the union
_mails(users_and_watches)[source]

Default implementation calls the _mails() of my first event but may pass it any of my events as self.

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 EventUnion and override this method.

_users_watching(**kwargs)[source]

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.

class tidings.events.InstanceEvent(instance, *args, **kwargs)[source]

Abstract superclass for watching a specific instance of a Model.

Subclasses must specify an event_type and should specify a content_type.

__init__(instance, *args, **kwargs)[source]

Initialize an InstanceEvent

Parameters:instance – the instance someone would have to be watching in order to be notified when this event is fired.
_users_watching(**kwargs)[source]

Return users watching this instance.

classmethod is_notifying(user_or_email, instance)[source]

Check if the watch created by notify exists.

classmethod notify(user_or_email, instance)[source]

Create, save, and return a watch which fires when something happens to instance.

classmethod stop_notifying(user_or_email, instance)[source]

Delete the watch created by notify.

exception tidings.events.ActivationRequestFailed(msgs)[source]

Raised when activation request fails, e.g. if email could not be sent

models

class tidings.models.EmailUser(email='')[source]

An anonymous user identified only by email address.

This is based on Django’s AnonymousUser, so you can use the is_authenticated property to tell that this is an anonymous user.

class tidings.models.NotificationsMixin(*args, **kwargs)[source]

Mixin for notifications models that adds watches as a generic relation.

So we get cascading deletes for free, yay!

class tidings.models.Watch(*args, **kwargs)[source]

The registration of a user’s interest in a certain event

At minimum, specifies an event_type and thereby an Event subclass. May also specify a content type and/or object ID and, indirectly, any number of WatchFilters.

exception DoesNotExist
exception MultipleObjectsReturned
activate()[source]

Enable this watch so it actually fires.

Return self to support method chaining.

content_type

Optional reference to a content type:

email

Email stored only in the case of anonymous users:

event_type

Key used by an Event to find watches it manages:

is_active

Active watches receive notifications, inactive watches don’t.

secret

Secret for activating anonymous watch email addresses.

unsubscribe_url()[source]

Return the absolute URL to visit to delete me.

class tidings.models.WatchFilter(*args, **kwargs)[source]

Additional key/value pairs that pare down the scope of a watch

exception DoesNotExist
exception MultipleObjectsReturned
value

Either an int or the hash of an item in a reasonably small set, which is indicated by the name field. See comments by hash_to_unsigned() for more on what is reasonably small.

tidings.models.multi_raw(query, params, models, model_to_fields)[source]

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 models sequence of classes. For example:

[(<User such-and-such>, <Watch such-and-such>), ...]

tasks

tidings.tasks.claim_watches(user)

Attach any anonymous watches having a user’s email to that user.

Call this from your user registration process if you like.

utils

tidings.utils.hash_to_unsigned(data)[source]

If data is a string or unicode string, return an unsigned 4-byte int hash of it. If data is already an int that fits those parameters, return it verbatim.

If data is an int outside that range, behavior is undefined at the moment. We rely on the PositiveIntegerField on WatchFilter to 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 WatchFilters are 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 Event subclass and pass ints when specifying filter values.

tidings.utils.emails_with_users_and_watches(subject, template_path, vars, users_and_watches, from_email='nobody@example.com', **extra_kwargs)[source]

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 vars plus a user and watches key for each pair in users_and_watches

Parameters:
  • 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

views

tidings.views.unsubscribe(request, watch_id)[source]

Unsubscribe from (i.e. delete) the watch of ID watch_id.

Expects an s querystring 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 head_title and a content block in a base.html template.

The template extension can be changed from the default html using the setting TIDINGS_TEMPLATE_EXTENSION.