Decadence¶
Decadence is a library which enables creating dynamic, constantly updating webpages with Django. It consists of three elements:
- Serializable models - so you have a consistent way of displaying and accessing data
- Rendering API - exposes Django’s templating engine so you can use it from JS in your frontend
- Updates API - allows fields to be updated in real time over WebSocket protocol
You don’t need to use all of these elements if you don’t want to.
The main motivation behind this library is that you don’t have to switch over to a fancy JS framework nor duplicate rendering logic in both JS and Django templates for content loaded over AJAX/WebSocket protocols.
Getting Started¶
To use Decadence in your project, first install it using pip
pip install -e git://github.com/ksiazkowicz/django-decadence.git#egg=django-decadence
Then, add Decadence to INSTALLED_APPS
in settings.py
INSTALLED_APPS = {
...
"django_decadence",
...
}
If you want to use Updates API, you also need to update your routing.py
file
from django_decadence.consumers import UpdateConsumer
channel_routing = [
...
route_class(UpdateConsumer, path=r"^/updates$"),
...
]
For Rendering API, add following lines to your url_patterns:
url(r'^decadence/', include('django_decadence.urls')),
To be continued
Using Updates API¶
Updates API provides automatic content updates, for example, when post title is changed, this change will be broadcast to all users that are currently browsing the page.
General idea¶
Updates API is exposed over WebSocket protocol at
ws://localhost/updates
. New values are broadcast to Groups named in
this fashion: [namespace]-[object id]-[path]
:
- namespace - for example, a name of model we’re updating,
- object id - pk of this object,
- path - unique identifier that describes a specific field, for example title
For 23rd user’s like status , we’d use something like:
post-1-like.23
.
To be a part of such Group, client needs to send a subscription request.
{
"subscribe": true,
"group": "post-1-like.23",
}
Where subscribe
field can be either true or false (in case client
wants to unsubscribe specific group).
Whenever field was updated with new data, server will send a message that looks like this:
{
"type": "update_value",
"path": "post-1-like.23",
"value": "1"
}
type
- type of change Server asks client to apply. Available types are:update_value
- value (ex. innerHTML of an Element containing this field) should be updated tovalue
toggle_class
- class (ex.hidden
of an Element corresponding to this field) defined inclass
field should be either added or removed to an Elementupdate_attribute
- attribute (ex.data-liked
of an Element corresponding to this field) namedattribute_name
should be changed tovalue
path
- group name (field name)value
- new value
Optional parameters:
class
- name of class to toggle (only intoggle_class
)attribute_name
- name of attribute, which value will be changed
Usage (Django)¶
Template¶
There is also a template tag available at update_tags
called
updatable
.
Usage:
{% updatable post "content" %}
This tag will include <span>
element with all required data
attributes to enable automatic updates using UpdateListener
.
You can also add safe=True
as argument if you want string to be marked as safe.
Model¶
General idea is, that if you already use serialization features from Decadence in your model, you will usually send out a few “update_value” requests during save(). To avoid reimplementing this from scratch for each model and simplify integration, push_update method is provided.
To avoid updating certain fields (upload date for blog post?), you can add this field in model:
updates_excluded = []
Fields from this list will never get checked for changes and won’t trigger any errors.
By default, each change will result in update_value
request being
broadcast. In case you want to override this behaviour, you can define a
list of options with which update()
method will be called for
specific field. A common case could be, for example, updating an URL to
image if it changed. You can either define it as a list, or a method
which returns it.
# as method, in case you need to override default value or something, in this case field is called "is_hidden"
def updates_is_hidden(self):
return [{
"type_name": "toggle_class", # use toggle_class instead of
"field": "main-div", # in case you want to override path
"classname": "hidden",
"value": not self.is_hidden, # override value, optional
}, ]
# as list, in this case field is called "image"
updates_image = [{
"type_name": "update_attribute",
"attribute_name": "src",
}, ]
One final step is overriding save()
method, capturing data before
save()
and calling push_update()
with captured data as an
argument.
def save(self, *args, **kwargs):
original_data = None
if self.pk:
old = ExampleModel.objects.get(pk=self.pk)
original_data = old.serialize()
super(ExampleModel, self).save(*args, **kwargs)
self.push_update(original_data)
Low-level¶
To send out updates, you need to either override save()
method on a
model or use signals.
Example:
from django_decadence import update
...
update(type_name="update_value", path="post-1-like.23", value="1");
...
Usage (TypeScript)¶
To simplify the process of subscribing to specific fields, there is a client for Updates API available globally under window.UpdateListener.
UpdateListener
automatically captures all elements in document
that have data-update-group
attribute containing a valid Group name.
For dynamically created Elements, you need to call UpdateListener again:
window["UpdateListener"].init(element)
. Decadence does this
automatically.
Consumers¶
-
class
django_decadence.consumers.
UpdateConsumer
(message, **kwargs)¶ Update Consumer
Handles logic for adding user sessions to specific Groups. Handles subscribe/unsubscribe requests.
-
connect
(message, **kwargs)¶ Called when a WebSocket connection is opened.
-
disconnect
(message, **kwargs)¶ Called when a WebSocket connection is closed.
-
receive
(content, **kwargs)¶ Called with decoded JSON content.
-
Updater¶
-
django_decadence.helpers.
check_template_path
(path)¶ Checks whether the path is valid. Valid path is a path that: - is not outside DECADENCE_DIR (default: templates/includes/decadence) - …
-
django_decadence.helpers.
update
(type_name='update_value', path='', value='', classname='', attribute_name='')¶ Pushes out content updates to user through our update channel.
Parameters: - type_name –
type of content update:
- toggle_class - adds/remove given class from element, if value is
- true, it’s added
- update_attribute - replaces html attribute value with given value
- update_value - updates content of html element with given value
- path – name of update group
- value – new value
- classname – name of class to be added/removed to element (optional)
- attribute_name – name of attribute which value will be replaced (optional)
- type_name –
Models¶
-
class
django_decadence.models.
DecadenceModel
(*args, **kwargs)¶ Implements a generic model that supports Decadence-specific features like serialization.
-
push_update
(original_data={})¶ Compares changes between old serialization data and new, then pushes out updates through Updates API.
-
serialize
(user=None, fields=None)¶ Attempts to generate a JSON serializable dictionary based on current model
-
updates_excluded
= []¶ list of fields excluded from updates
-
-
class
django_decadence.models.
SerializableQuerySet
(model=None, query=None, using=None, hints=None)¶ QuerySet extended with Decadence serialization method
Views¶
-
class
django_decadence.views.
DecadenceListView
(**kwargs)¶ Custom ListView that adds “serialized” to context with Decadence-serialized queryset
-
class
django_decadence.views.
DecadenceTableView
(**kwargs)¶ Implements a Decadence-serialized paginated table view.
-
django_decadence.views.
generate_html
(request)¶ Generates HTML code from request. You should send a JSON through post this way: csrfmiddlewaretoken=token&data={“template”: “includes/decadence/cancer.html”, …} Data should have a template + context.