Octopus is a lightweight AJAX pull framework for django that uses a
purely declarative syntax to load or refresh pages modularly.
Click here to see a full-featured demo..
The content of the demo hasn't been updated to match the changes with
version 0.4. It behaves the same from the end-user perspective,
but the information is no longer accurate
- Quick start
- Changelog (Recent changes only)
- How does it work?
- Requirements
- Installation
- Usage
- Configuration
- Todo
1. pip install django-octopus
2. settings.py
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
...
'octopus',
...
)
You may need to restart your development server to detect the template
tags.
3. Add javascript:
<script src="{% static 'octopus/custom-jquery.js' %}"></script>
<script src="{% static 'octopus/octopus.js' %}"></script>
4. Define your views:
from octopus.views import OctopusDetailView, OctopusListView
def YourDetailView(OctopusDetailView):
model = YourModel
template_name = "<template name>"
base_template = '<some base template>'
def YourListView(OctopusListView):
model = YourModel
template_name = "<template name>"
base_template = '<some base template>'
where template_name
extends base_template
See Usage for more detail information on using generic views
or on writing your own functional views.
5a. When extending a template, use the context variable base_template
instead of a string literal:
Do this:
{% extends base_template %}
Not this:
{% extends 'base.html' %}
5b. In the same template, create a {% block fragment %} and optionally
a {% block title %}
{% block title %}<title>Detail View </title>{% endblock fragment %}
{% block fragment %}<div> some content </div>{% endblock fragment %}
6. In another template, link to the first template.
Manually:
<a href="{% url 'detail' object.id %}"
class="octopus-link"
data-oc-target="#container"
>Link Text</a>
or with a template tag:
{% a 'detail' object.id target='#container' text='Link Text' %}
Where #container
is the selector of a DOM element that already
exists in your template
See Usage for details on what these parameters mean
and other parameters and their default values.
7. Putting it all together, a template might look like
this
- Added tag to clear certain elements when clicked
- Fixed title insertion and HTML parsin
- Stopped propagation on clicks
- Fixed fadeout conflict when insert=self
This is an aggressive update that breaks backwards compatibility in
just about every way, but seeing that no one really uses it, the fallout
should be minimal :)
- Proxy templates are no longer used to switch between full and ajax
requests. You now just define blocks namedfragment
in your templates. - Titles are included directly in the response. Before, titles were
defined on the link itself. Now atitle
block is supported to define
title content - While loading, the target element fades to 30% opacity
-
removed support for python < 3.6
-
due to changes in django, new installed apps are required:
INSTALLED_APPS = ( ... 'django.contrib.auth', 'django.contrib.contenttypes' ... )
-
changed the template tags' signatures to resemble the typical order of
html tag attributes:<a href='/home' data-oc-target="#main">text</text>
{% a '/home' target='#main' text='text' %}
-
Changed default value for
multi
toTrue
-
Switched to using data-* html attributes
- Added javascript build commands: see
BUILD_INSTRUCTIONS.md
for details - Includes custom build of jQuery to use only the required components
*Trivial javascript improvements, still needs a lot of work before a
1.0 release. Jasmine tests are still missing
-
Python 3.6+
-
Django, v1.8+
-
jQuery 1.11+ (A custom build is provided that only uses the
minimum required components)
1. Install Octopus:
pip install django-octopus
2. Add to INSTALLED_APPS:
settings.py
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
...
'octopus'
)
A RunTimeError
will be thrown if auth and contenttypes are not loaded
3. Include the javascript to your templates.
jQuery is required. If you're not using it already, a custom build is
provided that only uses the css
, ajax
, events
, and effects
components of jQuery.
If you're new to this, jQuery should be loaded before octopus.js
<script src="{% static 'octopus/custom-jquery.js' %}"></script>
<script src="{% static 'octopus/octopus.js' %}"></script>
In the client, you create links or forms with various
data-attributes to determine how content should be handled
once it is retrieved from the server. All behavior is defined
declaratively meaning it's not necessary to write any javascript.
When a user navigates directly to a url, the page is rendered in full
like expected, but when a link is clicked or a form submitted, it is
intercepted and routed to the server via ajax where your django
app then returns only the content of your template defined in a
{% block fragment %}
The response is then inserted into the target node(s) defined on the
clicked element. Browser states are updated to preserve full
back/forward functionality.
There's three main steps:
-
Create your view
-
Create your link or form
-
Create your template(s)
Generic views have been created for the following class-based views:
Octopus View | Django View |
---|---|
OctopusTemplateView | TemplateView |
OctopusDetailView | DetailView |
OctopusListView | ListView |
OctopusArchiveIndexView | ArchiveIndexView |
OctopusDayArchiveView | DayArchiveView |
OctopusTodayArchiveView | TodayArchiveView |
OctopusDateDetailView | DateDetailView |
OctopusWeekArchiveView | WeekArchiveView |
OctopusMonthArchiveView | MonthArchiveView |
OctopusYearArchiveView | YearArchiveView |
OctopusCreateView | CreateView |
OctopusUpdateView | UpdateView |
OctopusDeleteView | DeleteView |
OctopusFormView | FormView |
You use these as you would normal CBV's. The only additional requirement
is that you need to define base_template
in addition to template_name
from octopus.views import OctopusDetailView
from yourapp.models import YouModel
class YourDetailView(OctopusDetailView):
model = YourModel
base_template = "base.html"
template_name = "template.html"
base_template
will be sent as a context variable and should be used
in the extends
tag instead of the string literal. More on that later.
If you are using the update views, you should be sure your
success_url
also returns a template fragment
The only caveat is to wrap your template definition in an is_ajax()
method:
def your_vew(request, *args, **kwargs):
...
if request.is_ajax():
base_template = "octopus/ajax.html"
else:
base_template = "base.html"
...
context['base_template'] = base_template
octopus/ajax.html
is a real template and should be included exactly as
written above
You can convert your existing CBV's into OctopusViews like so:
from octopus.views import AjaxResponseMixin
class YourView(AjaxResponseMixin, YourCustomBaseView):
base_name = "weeedoo.html"
template_name = "weeewoo.html"
Be sure to load the tentacles
tags
{% load tentacles %}
Here is the method signature of the tag:
a(href: str,
*href_args,
text: str,
target: str,
insert: str='replace',
method: str='get',
multi: bool=True,
**kwargs) -> dict:
-
href: the name of the url as defined in your urlconfs or a
hard-coded url -
href_args: parameters will be passed to
django.urls.resolvers.reverse
-
text: the visible, clickable text of the link.
-
target: the selector of the html node(s) that will receive the
server response. E.g.,main
,#container
,.container
-
method: the HTTP Method to use when making the request.
defaultget
-
insert: how the incoming text will be inserted into the document
values:replace
,prepend
,append
,self
default:replace
note:self
is similar toreplace
, but rather than overwriting everything in the entire node,self
replaces just the object in question. This is particularly useful for editing forms. -
multi: whether a link may be click multiple times default: True
-
kwargs: arbitrary parameters will be converted to html attributes
Here is a minimal example:
{% load tentacles %}
{% a '/home' target='#main' text='click me' %}
Here is a full example
{% a "detail" object.id target="#container" text="Link Text" method="post" insert="prepend" id="cantelope" class="inline-block article" data-some-random-attr='1' %}
The syntax for a form is similar to the link tag:
{% load tentacles %}
{% form request.path form=form_instance text="Button Text" %}
A couple things to note.
-
a form instance is must be passed
-
target
becomes optional because the default insertion method
insert=self
is more useful for forms and supercedes anything
defined by target.target
can still be used as long asinsert
is
redefined to be anything other thanself
-
request.path
is passed tourl_name
. You can still pass named urls and
their arguments to the tag if you want to, but when dealing with the generic
edit views, if you userequest.path
you won't have to create individual forms
for each view.
make sure you include'django.core.context_processors.request',
inTEMPLATE_CONTEXT_PROCESSORS
The rest of the arguments remain the same. The only difference are couple of
the default values:
- Kwargs
-
method: Default:
post
-
replace: Default
self
-
Note: the template uses form.as_p()
The template tags create standard html tags. If you want to create a link on an image or need some extra data attributes, you will have to build the links yourself.
But it's not hard. Non-standard attributes (target, method, multi) are
prefixed with data-oc-
The only requirements are:
-
href="..."
-
data-oc-target="..."
-
class="octopus-link ..."
Then you can set anything defined in Template Tags
If you want to render something besides form.as_p(), you'll have to create your own form. Beside the standard required attributes for forms, the only thing unique to octopus is to add a hook for the javascript:
class="octopus-form ..."
and in adition to setting method=
you need to set data-oc-method=
Then you can set anything defined in Template Tags
A simple template schema that uses both links and forms might look something like this:
base_template = 'base.html':
{% load static %}
{% load tentacles %}
<!doctype html>
<html>
<head>
{% block title %}
<title>Document</title>
{% endblock title %}
</head>
<body>
{% a 'create' text='create object' target='main' %}
<main>
{% block fragment %}
{% endblock fragment %}
</main>
...
<footer>
<script src="{% static 'octopus/custom-jquery.js' %}" type="text/javascript"></script>
<script src="{% static 'octopus/both.js' %}" type="text/javascript"></script>
</footer>
</body>
</html>
model_form.html:
{% extends base_template %}
{% load tentacles %}
{% block fragment %}
{% form request.path text='Submit' form=form_instance %}
{% endblock %}
So far there is only one setting:
The determines whether hard-coded links may be passed to the template tag
Default: True
-
Allow user more control over prepend, append, and replace behaviors: speed, effects, etc.
-
Allow settings to be set per view rather than globally