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. AWatch
is a record of someone’s interest in a certain type ofEvent
, distinguished byEvent.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 theWatch.content_type
field and/or adding relatedWatchFilter
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 ofEventUnion
based on the kinds of watches found.“Watching the event” means having a Watch whose
event_type
isself.event_type
, whosecontent_type
isself.content_type
orNULL
, whoseobject_id
isobject_id
orNULL
, and whose WatchFilter rows match as follows: each name/value pair given infilters
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 callsfire()
.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 returnsFalse
. This means you can always pass itrequest.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
isTrue
. 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()
.
-
classmethod
-
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()
-
_mails
(users_and_watches)[source]¶ Default implementation calls the
_mails()
of my first event but may pass it any of my events asself
.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 acontent_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.
-
classmethod
is_notifying
(user_or_email, instance)[source]¶ Check if the watch created by notify exists.
-
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 ofWatchFilters
.-
exception
DoesNotExist
¶
-
exception
MultipleObjectsReturned
¶
-
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.
-
exception
-
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.
-
exception
-
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. Ifdata
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 thePositiveIntegerField
onWatchFilter
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 yourEvent
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 auser
andwatches
key for each pair inusers_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 acontent
block in abase.html
template.The template extension can be changed from the default
html
using the settingTIDINGS_TEMPLATE_EXTENSION
.