A lightweight AJAX pull framework for django.
Python JavaScript HTML Shell
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
bin
js
octopus
tests
.gitignore
.travis.yml
BUILD_INSTRUCTIONS.md
CHANGELOG.md
LICENSE
MANIFEST.in
README.markdown
package-lock.json
package.json
requirements.txt
setup.cfg
setup.py
tox.ini

README.markdown

Build Status

django-octopus

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

Contents

Quick start

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

Changelog (Recent Changes)

v0.4.1

New Features

  • Added tag to clear certain elements when clicked

Bug fixes

  • Fixed title insertion and HTML parsin
  • Stopped propagation on clicks
  • Fixed fadeout conflict when insert=self

v0.4

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 :)

New features

  • Proxy templates are no longer used to switch between full and ajax
    requests. You now just define blocks named fragment in your templates.
  • Titles are included directly in the response. Before, titles were
    defined on the link itself. Now a title block is supported to define
    title content
  • While loading, the target element fades to 30% opacity

Changes

  • 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 to True

  • Switched to using data-* html attributes

Javascript changes

  • 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

Requirements

  1. Python 3.6+

  2. Django, v1.8+

  3. jQuery 1.11+ (A custom build is provided that only uses the
    minimum required components)

Installation

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>

How does it work?

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.

Usage

There's three main steps:

  1. Create your view

  2. Create your link or form

  3. Create your template(s)

Views

Creating Class-Based Views

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

Creating Function-Based Views

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

Using the AjaxResponseMixin with existing views

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"

Template Tags

Be sure to load the tentacles tags

{% load tentacles %}    

a

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.
    default get

  • insert: how the incoming text will be inserted into the document
    values: replace, prepend, append, self
    default: replace
    note: self is similar to replace, 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' %}

form

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.

  1. a form instance is must be passed

  2. 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 as insert is
    redefined to be anything other than self

  3. request.path is passed to url_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 use request.path you won't have to create individual forms
    for each view.
    make sure you include 'django.core.context_processors.request', in TEMPLATE_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()

Manually Creating Elements

Creating Links

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

Creating Forms

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

Creating templates

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 %}

Configuration

So far there is only one setting:

OCTOPUS_ALLOW_MANUAL

The determines whether hard-coded links may be passed to the template tag

Default: True

Todo

  • Allow user more control over prepend, append, and replace behaviors: speed, effects, etc.

  • Allow settings to be set per view rather than globally