Django-Unicorn - An Introduction
In this post, we are going to dive into a new library that's gaining some popularity in the Django community - django-unicorn.
This library comes with the promise of helping to enhance normal Django views with automatic AJAX calls, two-way binding of data between templates and components, and dynamic updates to the DOM, among other features.
django-unicorn has the potential to be a very useful tool in the Django developer's toolbox, as it enables some of the highly-interactive UI functionality that you can achieve in modern JavaScript frameworks such as React.js, Vue.js and Angular, whilst still keeping your code entirely within the Django ecosystem.
There is much debate in the web development community about what percentage of sites really need that shiny front-end framework like React. There's an argument that most sites do not, and adding that extra complexity budget to your application is not always desirable. Tools like django-unicorn attempt to address this by providing some of the rich interactivity of modern JavaScript frameworks, but without adding a whole raft of extra complexity such as a Rest-Framework or django-ninja API, a separate process for your front-end, NPM, Webpack, Babel, etc.
Instead, you work within the Django ecosystem, using the tools you're already familiar with such as templates and views - with a small amount of additional magic.
Let's get started!
Objectives
- Learn about
django-unicorn, and the tools it provides you with. - Build a small component - a Movie List component - to demonstrate the interactive functionality.
Unicorn Concepts - Deep Dive
We are going to begin by outlining some of the concepts and the tools that Unicorn provides to enable rich, interactive interfaces. The example component we will build later in the tutorial will demonstrate these concepts, but it's worth reading the following first to get a high-level idea of the library.
Components
Components are the central concept in django-unicorn. A component defines the interactive functionality that you can embed into your Django templates, with a normal Python class defining the attributes, methods and internal logic. This is somewhat analogous to a component in React.js, in the sense that you are able to build up self-contained functionality that refers to a single concept or "component" in your user interface. We will see examples of components later in this tutorial.
Component classes are special Django views - they are classes that have an associated template (indeed, they inherit from TemplateView). The component class defines properties (i.e. fields on the class) that are available in this associated template. HTML <input> elements can be bound to these properties via a magic uniform:model attribute on the <input> element, referring to the correct property. Behind the scenes, django-unicorn makes transparent AJAX calls to update the value of these properties on the class whenever a change is triggered - for example, typing into a text field. This process maintains a consistent data model between the frontend UI and the backend class properties.
Methods defined on your component class can be triggered by events in the user interface, and perform any logic necessary to your component.
This gives you the ability to dynamically update the content of a field while typing into an <input type='text'> element, for example, and perform dynamic validation of the input. All without writing custom AJAX calls!
Component Templates
Component classes also have an associated template, that defines the HTML code associated with the component. These are normal Django templates that are automatically associated with the component, and have all the bells and whistles of normal Django templates - loops, conditionals, and template-tags and filters.
To draw an analogy with React:
- The template is similar to the returned JSX in a React component.
- The component class is similar to the behaviour (methods) and the state that's defined in a React component.
Data can be passed into the component from the template when using the {% unicorn 'name' %} tag. In the quiz app, we passed in 4 pieces of data for our question component, using the following template code:
{% unicorn 'question' qid=question.pk quiz_pk=quiz.pk next_qid=next_question.pk final=is_final_question %}
The passing of data in this way is loosely similar to passing data as props in React. The component can receive this data in its __init__(self, *args, **kwargs) method.
With all that said, this should not be taken as a strict comparison with JavaScript frameworks. django-unicorn is not as flexible and fully-featured - but it is a nice middle-ground between plain Django, and the complexity of Django and a separate frontend project.
Actions
Another important django-unicorn concept are Actions. Actions represent events that occur in your interface - such events can include:
- Change event when an input element changes - for example when typing into a text-field, or selecting a radio-button
- Click event when a button is clicked
- Submit event when a form is submitted
- Keyup and Keydown events - when the keyboard is active
Any DOM event can be supported.
This functionality allows you to react to user events on the interface, and trigger functions in your component class.
For example, to react to a change in the selected radio button in your form, you can wire up a change handler such as below (borrowed from the Quiz component in the linked video):
<!-- Radio button change action - triggers set_answer() method in component,
passing the model PK of the selected button -->
<input type="radio" unicorn:change="set_answer({{choices.1.pk}})" />
<!-- Button click action - triggers check_answer method in the component class -->
<button unicorn:click.prevent="check_answer">Check Answer</button>
The syntax unicorn:<event_type> defines the action - with optional modifiers such as .prevent on the button above (to prevent the default action) available too.
It can be seen above, in the radio button's change handler, that you can also pass arguments to the action methods defined in your component class.
Actions are a mechanism to respond to events and make things happen in your component. This is extremely convenient, and is a core feature of django-unicorn.
Integration with Models & QuerySets
django-unicorn enables you to integrate models and querysets into your components - simply storing them as fields on the component class.
You can bind input fields to particular fields on your model using the unicorn:model syntax - for example, let's say you have an ultra-simple Movie model, as below:
class Movie(models.Model):
name = models.CharField(max_length=128)And you want to dynamically display all the movies in your database on the UI. You can store a list of your existing movies as a field in the component, as so:
from django_unicorn.components import UnicornView, QuerySetType
from movies.models import Movie
class MovielistView(UnicornView):
movies: QuerySetType[Movie] = Movie.objects.all()
With the above setup, in your component template, you can then loop over the movies using the movies field, and you can wire up your user interface to add movies and delete movies dynamically. Actions such as click-events can trigger new movies being added, or could trigger single movies (or all movies) being deleted.
We will see a full example of this in action in the next section of this post, to make everything more concrete.
One caveat - be careful with model fields, though! When you have Django models (or querysets) as fields on your component, django-unicorn will serialize all of the model fields, exposing their values in your source code. So if you have sensitive fields on your model, be sure to exclude them - with querysets, you can specify the fields you want using the .values() method. For example, to exclude any additional fields from our Book model, we could use use Book.objects.values('name').
In the next section, we will put these concepts into practice and create a django-unicorn component from scratch.
Creating a Movie List component
Let's get down to brass tacks, and create a component. We are creating a movie favourites application, and we need to populate a list of movies we have seen - but we don't want to deal with lots of page refreshes, and would like to see dynamic DOM manipulation without writing much JavaScript, if possible.
Project Setup
Create a Django project called movielist (optionally within a Python virtual environment, or by copying into a Docker container) using the following commands.
django-admin startproject movielist
cd movielist
python manage.py startapp movies
With that done, we need to install the django-unicorn library. This can be done with pip.
pip install django-unicorn
Now, in your settings.py file, add the movies app we have just created to the INSTALLED_APPS list. You should also add django_unicorn to this list, as below.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'movies',
'django_unicorn',
]Add the above model to your movies/models.py file
class Movie(models.Model):
name = models.CharField(max_length=128)
And migrate to push the change to the database layer.
python manage.py makemigrations
python manage.py migrate
Now, create a templates directory in the movies directory, and create an index.html file in there, as below. This will be an ultra-simple template - we are not going to style things up much.
{% load unicorn %}
<html>
<head>
<title>Movielist</title>
<style>
html, body { margin: 0; padding: 0; }
.header{
height: 50px;
width: 100%;
background: lightblue;
display: flex;
align-items: center;
}
main {
margin-left: 30px;
}
</style>
{% unicorn_scripts %}
</head>
<body>
{% csrf_token %}
<div class="header">Excellent Movie App</div>
<main>
{% unicorn 'movielist' %}
</main>
</body>
</html>
On the first line we add {% load unicorn %}, which loads up django-unicorn's template tags. On line 20, within the <head> tag, we have a {% unicorn_scripts %} tag that essentially loads the JavaScript that enables django-unicorn to make AJAX calls behind the scenes.
We've also added the {% csrf_token %} tag to enable secure AJAX POST requests to the backend.
You can see on line 28 that we're referencing a unicorn component called movielist with the {% unicorn 'movielist' %} template tag. We need to actually create this component, now. We can do this with a very handy management command that django-unicorn provides us with - the startunicorn command.
python manage.py startunicorn movies movielistThe first argument is the name of the app - movies. The second is the name of the component - movielist.
This command will create a movies/components directory, with a file called movielist.py that contains your UnicornView - this is the class that defines the logic for your component. It will also create a templates/unicorn directory, containing movielist.html - this is the front-end UI code for your component, where you can use the magic unicorn attributes on HTML tags.
We can now set up a basic view, with an associated URL, to deliver the basic logic.
Let's create a simple TemplateView to do the job.
from django.shortcuts import render
from django.views.generic import TemplateView
class MovieView(TemplateView):
template_name = 'index.html'
This refers to the index.html file we created earlier, that contains the link to the unicorn component. Now, we can wire up our URLconf - we need to add 2 urls to the urlpatterns, as below.
from django.urls import include, path
from movies.views import MovieView
urlpatterns = [
path("unicorn/", include("django_unicorn.urls")),
path('', MovieView.as_view()),
]
We're done with wiring everything up - now we can build our movie list component!
Building the Component
With the set up sorted, let's build the component. Enter the Unicorn!

Let's start by building our component's template.
The template code we will write represents a simple interface, with 3 key components that we will focus on:
- An
<input>element that allows the user to type the name of a movie - A button that, when clicked, adds the movie name from the input element to the existing list of movies
- Another button that, when clicked, clears all of the movies.
When the buttons are clicked, they trigger actions, and the UI changes. This all can occur without a page reload, without the need to write JavaScript code to hook up AJAX requests, and without the need to define AJAX backend views to handle these operations. django-unicorn transparently takes care of all of this, which is very nice.
The template is defined below, with explanatory comments, in the movies/templates/unicorn/movielist.html file. An important note is that the template MUST have a root element in the component, surrounding the rest of the content. In the case below, it is the <div> element.
<div>
<h2>Movie List</h2>
<hr/>
<!-- Allow user to enter a new movie, and add to list.
The input is bound to a property called "name" on the component
Button connects to an 'add_movie' method on the component -->
<input type="text" unicorn:model="name" />
<button unicorn:click="add_movie">Add Movie</button>
<!-- Allow user to delete all the movies from their list.
Connects to an 'delete_all' method on the component -->
<button unicorn:click="delete_all">Delete All</button>
<!-- Show the existing movies, from the component's "movies" property -->
<ul>
{% for movie in movies %}
<li>{{ movie.name }}</li>
{% endfor %}
</ul>
</div>
Let's break down those 3 key, unicorn-specific elements in this template.
<input type="text" unicorn:model="name" />(Line 8) - We bind a text input element to our component class'snamefield. When we type into this text field,django-unicornwill update the component field's value dynamically and transparently via AJAX calls.<button unicorn:click="add_movie">Add Movie</button>(Line 9) - We attach a click action to the "Add Movie" button element, which dynamically calls a method namedadd_movieon our component class.<button unicorn:click="delete_all">Delete All</button>(Line 13) - We attach a click action to the "Delete All" button element, which dynamically calls a method nameddelete_allon our component class.
And on lines 16-20, we loop over the movies property that we will define in our component, and display each movie's name in the UI in an unordered list. As we update the list of movies using the above buttons and action-bindings, we will see this list dynamically changing.
This template demonstrates some of the key features django-unicorn provides - binding component class fields to HTML elements, binding actions to component methods, and transparently handling all of the AJAX-related plumbing to make the developer's life easier.
With that said, the final step is to create our component class that defines these properties and methods, and their associated logic. This component is located in the movies/components/movielist.py file, within the components directory created by the startunicorn management command.
Let's define the code.
from django_unicorn.components import UnicornView, QuerySetType
from movies.models import Movie
class MovielistView(UnicornView):
name: str = ''
movies: QuerySetType[Movie] = Movie.objects.none()
def mount(self):
""" On mount, populate the movies property w/ a QuerySet of all movies """
self.movies = Movie.objects.all()
def add_movie(self):
""" Create the new movie, get new list of all movies,
and clear the 'name' property """
Movie.objects.create(name=self.name)
self.movies = Movie.objects.all()
self.name = ''
def delete_all(self):
""" Delete all movies and reset 'movies' property """
Movie.objects.all().delete()
self.movies = Movie.objects.none()
Our component has two type-hinted properties:
name- the name of the movie being added. Defaults to an empty string, and is populated as the user types into the<input>field.movies- a queryset containing all movies in the database at any given time.
The movies property is populated when the component first loads, using the special mount() method on line 8. To further the React comparisons a bit, this is somewhat similar to React component lifecycle methods - componentDidMount() in class-based components, and the useEffect() hook in function-based components.
Now, let's look at the add_movie and delete_all methods. These are called on actions from the template - when the user clicks either of the buttons, the associated method fires in this component.
When add_movie() method is triggered, the value of the name property is used to create and save a new Movie model instance (line 15). Then, we re-populate the movies property using an ORM call (line 16). Finally, we clear out the value of the name property by resetting it to an empty string (line 17). This dynamically updates the input element on the frontend, because it is bound to this property via the unicorn:model="name" attribute.
In the delete_all() method, all Movie objects in the database are deleted (line 21), and the movies property on the component is reset to its default value (line 22).
This interactive functionality is shown in the GIF below.
The combination of the template we defined, and the component class, allows for a set of dynamic, interactive functionality that requires no page reloads, no custom JavaScript code, and no wiring up of backend AJAX requests.
This is very convenient, and I believe this library has the potential to be very useful for projects that don't require a fully-fledged frontend framework. Instead, we can drop django-unicorn in wherever we need some of this rich interactivity, and we can stay within the Django and Python ecosystem.
If you enjoyed this post, please subscribe to our YouTube channel and follow us on Twitter to keep up with our new content!
Please also consider buying us a coffee, to encourage us to create more posts and videos!
Additional
- Video - quiz app that uses django-unicorn.