Writing Your First Widget¶
In this tutorial, we will build a Slideshow widget. You probably want to read the Quickstart to get a Widgy site running before you do this one.
We currently have a static slideshow that we need to make editable. Users need to be able to add any number of slides. Users also want to be able to change the delay between each slide.
Here is the current slideshow HTML that is using jQuery Cycle2:
<div class="cycle-slideshow"
data-cycle-timeout="2000"
data-cycle-caption-template="{% templatetag openvariable %}alt{% templatetag closevariable %}">
<div class="cycle-caption"></div>
<img src="http://placekitten.com/800/300" alt="Cute cat">
<img src="http://placekitten.com/800/300" alt="Fuzzy kitty">
<img src="http://placekitten.com/800/300" alt="Another cute cat">
<img src="http://placekitten.com/800/300" alt="Awwww">
</div>
See also
templatetag
- This template tag allows inserting the
{{
and}}
characters needed by Cycle2.
1. Write the Models¶
The first step in writing a widget is to write the models. We are going to make a new Django app for these widgets.
$ python manage.py startapp slideshow
(Don’t forget to add slideshow
to your INSTALLED_APPS
).
Now let’s write the models. We need to make a Slideshow
model as the
container and a Slide
model that represents the individual images.
# slideshow/models.py
from django.db import models
import widgy
from widgy.models import Content
@widgy.register
class Slideshow(Content):
delay = models.PositiveIntegerField(default=2,
help_text="The delay in seconds between slides.")
accepting_children = True
editable = True
@widgy.register
class Slide(Content):
image = models.ImageField(upload_to='slides/', null=True)
caption = models.CharField(max_length=255)
editable = True
All widget classes inherit from widgy.models.base.Content
. This
creates the relationship with widgy.models.base.Node
and ensures that
all of the required methods are implemented.
In order to make a widget visible to Widgy, you have to add it to the registry.
There are two functions in the widgy
module that help with this,
widgy.register()
and widgy.unregister()
. You should use the
widgy.register()
class decorator on any model class that you wish to use
as a widget.
Both widgets need to have editable
set to
True
. This will make an edit button appear in the editor, allowing the
user to set the image
, caption
, and delay
values.
Slideshow
has accepting_children
set to
True
so that you can put a Slide
in it. The default implementation of
valid_parent_of()
checks
accepting_children
. We only need this until
we override valid_parent_of()
in Step 3.
Note
As you can see, the image
field is null=True
. It is necessary for
all fields in a widget either to be null=True
or to provide a default.
This is because when a widget is dragged onto a tree, it needs to be saved
without data.
CharFields
don’t need to be
null=True
because if they are non-NULL, the default is an empty string.
For most other field types, you must have null=True
or a default value.
Now we need to generate migration for this app.
$ python manage.py schemamigration --initial slideshow
And now run the migration.
$ python manage.py migrate
2. Write the Templates¶
After that, we need to write our templates. The templates are
expected to be named widgy/slideshow/slideshow/render.html
and
widgy/slideshow/slide/render.html
.
To create the slideshow template, add a file at
slideshow/templates/widgy/slideshow/slideshow/render.html
.
{% load widgy_tags %}
<div class="cycle-slideshow"
data-cycle-timeout="{{ self.get_delay_milliseconds }}"
data-cycle-caption-template="{% templatetag openvariable %}alt{% templatetag closevariable %}">
<div class="cycle-caption"></div>
{% for child in self.get_children %}
{% render child %}
{% endfor %}
</div>
For the slide, it’s slideshow/templates/widgy/slideshow/slide/render.html
.
<img src="{{ self.image.url }}" alt="{{ self.caption }}">
See also
Content.get_templates_hierarchy
- Documentation for how templates are discovered.
The current Slideshow
instance is available in the context as self
.
Because jQuery Cycle2 only accepts milliseconds instead of seconds for the
delay, we need to add a method to the Slideshow
class.
class Slideshow(Content):
# ...
def get_delay_milliseconds(self):
return self.delay * 1000
The Content
class mirrors several methods of the
TreeBeard API
, so you can call
get_children()
to get all the children. To
render a child Content
, use the
render()
template tag.
Caution
You might be tempted to include the HTML for each Slide
inside the
render template for Slideshow
. While this does work, it is a violation
of the single responsibility principle and makes it difficult for slides
(or subclasses thereof) to change how they are rendered.
3. Write the Compatibility¶
Right now, the Slideshow
and Slide
render and could be considered
complete; however, the way we have it, Slideshow
can accept any widget as a
child and a Slide
can go in any parent. To disallow this, we have to write
some Compatibility methods.
class Slideshow(Content):
def valid_parent_of(self, cls, obj=None):
# only accept Slides
return issubclass(cls, Slide)
class Slide(Content):
@classmethod
def valid_child_of(cls, parent, obj=None):
# only go in Slideshows
return isinstance(parent, Slideshow)
Done.
Addendum: Limit Number of Children¶
Say you want to limit the number of Slide
children to 3 for your
Slideshow
. You do so like this:
class Slideshow(Content):
def valid_parent_of(self, cls, obj=None):
if obj in self.get_children():
# If it's already one of our children, it is valid
return True
else:
# Make sure it's a Slide and that you aren't full
return (issubclass(cls, Slide) and
len(self.get_children()) < 3)