Advanced Features

The Quickstart and Basic Usage guide covers the common use cases of Django Entity Event. In addition to the basic uses for creating, storing, and querying events, there are some more advanced uses supported for making Django Entity Event more efficient and flexible.

This guide will cover the following advanced use cases:

  • Dynamically loading context using context_loader
  • Customizing the behavior of only_following by sub-classing Medium.

Rendering Events

Django Entity Event comes complete with a rendering system for events. This is accomplished by the setup of two different models:

  1. RenderingStyle: Defines a style of rendering.
  2. ContextRenderer: Defines the templates used for rendering, which rendering style it is, which source or source group it renders, and hints for fetching model PKs that are in event contexts.

When these models are in place, Medium models can be configured to point to a rendering_style of their choice. Events that have sources or source groups that match those configured in associated ContextRenderer models can then be rendered using the render method on the medium.

The configuration and rendering is best explained using a complete example. First, let’s imagine that we are storing events that have contexts with information about Django User models. These events have a source called user_logged_in and track every time a user logs in. An example context is as follows:

{
    'user': 1, # The PK of the Django User model
    'login_time': 'Jan 10, 2014', # The time the user logged in
}

Now let’s say we have a Django template, user_logged_in.html that looks like the following:

User {{ user.username }} logged in at {{ login_time }}

In order to render the event with this template, we first set up a rendering style. This rendering style is pretty short and could probably be displayed in many places that want to display short messages (like a notification bar). So, we can make a short rendering style as followings:

short_rendering_style = RenderingStyle.objects.create(
    name='short',
    display_name='Short Rendering Style')

Now that we have our rendering style, we need to create a context renderer that has information about what templates, source, rendering style, and context hints to use when rendering the event. In our case, it would look like the following:

context_renderer = ContextRenderer.objects.create(
    render_style=RenderingStyle.objects.get(name='short'),
    name='short_login_renderer',
    html_template_path='my_template_dir/user_logged_in.html',
    source=Source.objects.get(name='user_logged_in'),
    context_hints={
        'user': {
            'app_name': 'auth',
            'model_name': 'User',
        }
    }
)

In the above, we set up the context renderer to use the short rendering style, pointed it to our html template that we created, and also pointed it to the source of the event. As you can see from the html template, we want to reach inside of the Django User object and display the username field. In order to retrieve this information, we have told our context renderer to treat the user key from the event context as a PK to a Django User model that resides in the auth app.

With this information, we can now render the event using whatever medium we have set up in Django Entity Event.

notification_medium = Medium.objects.get(name='notification')
events = notification_medium.events()

# Assume that two events were returned that have the following contexts
# e1.context = {
#    'user': 1, # Points to Jeff's user object
#    'login_time': 'January 1, 2015',
# }
# e1.context = {
#     'user': 2, # Points to Wes's user object
#     'login_time': 'February 28, 2015',
# }
#
# Pass the events into the medium's render method
rendered_events = notification_medium.render(events)

# The results are a dictionary keyed on each event. The keys point to a tuple
# of text and html renderings.
print(rendered_events[0][1])
'jeff logged in at January 1, 2015'
print(rendered_events[1][1])
'wes logged in at February 28, 2015'

With the notion of rendering styles, the notification medium and any medium that can display short messages can utilize the renderings of the events. Other rendering styles can still be made for more complex renderings such as emails with special styling. For more advanced options on how to perform prefetch and select_relateds in the fetched contexts, view ContextRenderer.

Advanced Template Rendering Options

Along with the basic rendering capabilities, Django Entity Event comes with several other options and configurations for making rendering more robust.

Passing Additional Context to Templates

Sometimes mediums need to have subtle differences in the rendering of their contexts. For example, headers might need to be added above and below a message or images might need to be displayed. For cases such as this, mediums come with an additional_context variable. Anything in this variable will always be passed into the context when events are rendered for that particular medium.

Using a Default Rendering Style

It can be cumbersome to set up context renderers for every particular rendering style when it isn’t necessary. For example, sometimes tailored emails need a special rendering style, however, many events can be rendered in an email just fine with a simpler rendering style. For these cases, a user can set a Django setting called DEFAULT_ENTITY_EVENT_RENDERING_STYLE that points to the name of the default rendering style to use. If this variable is set and an appropriate context loader cannot be fetched for an event during rendering, the default rendering style will be used instead for that event (if it has been configured).

Serialized Context Data

If your display mechanism needs access to the context data of the event this can be accomplished by calling: Event.get_serialized_context method on the Event model. This will return a serializer safe version of the context that is used to generate the event output. This is useful if you want to make a completely custom rendering on the display device or you need additional context information about the event that occurred.

Customizing Only-Following Behavior

In the quickstart, we discussed the use of “only following” subscriptions to ensure that users only see the events that they are interested in. In this discussion, we mentioned that by default, entities follow themselves, and their super entities. This following relationship is defined in two methods on the Medium model: Medium.followers_of and Medium.followed_by. These two methods are inverses of each other and are used by the code that fetches events to determine the semantics of “only following” subscriptions.

It is possible to customize the behavior of these types of subscriptions by concretely inheriting from Medium, and overriding these two functions. For example, we could define a type of medium that provides the opposite behavior, where entities follow themselves and their sub-entities.

from entity import Entity, EntityRelationship
from entity_event import Medium

class FollowSubEntitiesMedium(Medium):
    def followers_of(self, entities):
        if isinstance(entities, Entity):
            entities = Entity.objects.filter(id=entities.id)
        super_entities = EntityRelationship.objects.filter(
            sub_entity__in=entities).values_list('super_entity')
        followed_by = Entity.objects.filter(
            Q(id__in=entities) | Q(id__in=super_entities))
        return followed_by

    def followed_by(self, entities):
        if isinstance(entities, Entity):
            entities = Entity.objects.filter(id=entities.id)
        sub_entities = EntityRelationship.objects.filter(
            super_entity__in=entities).values_list('sub_entity')
        followers_of = Entity.objects.filter(
            Q(id__in=entities) | Q(id__in=sub_entities))
        return followers_of

With these methods overridden, the behavior of the methods FollowsubEntitiesMedium.events, FollowsubEntitiesMedium.entity_events, and FollowsubEntitiesMedium.events_targets should all behave as expected.

It is entirely possible to define more complex following relationships, potentially drawing on different source of information for what entities should follow what entities. The only important consideration is that the followers_of method must be the inverse of the followed_by method. That is, for any set of entities, it must hold that

followers_of(followed_by(entities)) == entities

and

followed_by(followers_of(entities)) == entities