Getting Started with Directus and Django
Published April 4th, 2024
Django is a popular Python framework known for its "battery included" philosophy. In this tutorial, you will learn how to integrate Django with Directus, and build an application that uses the Django templating engine to display data from the API.
Before You Start
You will need:
- Python installed and a code editor on your computer.
- A Directus project - Use the quickstart guide to create a project if you dont already have one.
Create a Django Project
Open your terminal and run the following commands to set up a Django project:
mkdir my_django_site && cd my_django_site
django-admin startproject config .
python -m venv env
source env/bin/activate # On Windows use `env\Scripts\activate`
pip install django requests
mkdir my_django_site && cd my_django_site
django-admin startproject config .
python -m venv env
source env/bin/activate # On Windows use `env\Scripts\activate`
pip install django requests
Open the new Django project in your code editor of choice and activate your virtual environment and start your Django development server to run the application at http://localhost:8000
:
python manage.py runserver
python manage.py runserver
After you've started your server, create a Django app that will contain your views, integrations and URLs. Run the following command in your project directory:
python manage.py startapp blog
python manage.py startapp blog
Open the config/settings.py
, add your new app to the INSTALLED_APPS
list, and configure a templates directory:
INSTALLED_APPS = [
... # Other installed apps
'blog', # Add this line
]
TEMPLATES = [
{
...
"DIRS": [BASE_DIR / "templates"],
...
},
]
INSTALLED_APPS = [
... # Other installed apps
'blog', # Add this line
]
TEMPLATES = [
{
...
"DIRS": [BASE_DIR / "templates"],
...
},
]
Using Global Metadata and Settings
In your Directus project, navigate to Settings -> Data Model and create a new collection called global. Under the Singleton option, select 'Treat as a single object', as this collection will have just a single entry containing global website metadata.
Create two text input fields - one with the key of title and one description.
Navigate to the content module and enter the global collection. Collections will generally display a list of items, but as a singleton, it will launch directly into the one-item form. Enter information in the title and description field and hit save.
By default, new collections are not accessible to the public. Navigate to Settings -> Access Control -> Public and give Read access to the Global collection.
In your Django project, create a file named directus_integration.py
in the blog app directory to handle data fetching:
import requests
DIRECTUS_API_ENDPOINT = "YOUR_DIRECTUS_INSTANCE_API_ENDPOINT"
def get_global_settings():
response = requests.get(f"{DIRECTUS_API_ENDPOINT}/items/global")
return response.json()
def get_collection_items(collection):
response = requests.get(f"{DIRECTUS_API_ENDPOINT}/items/{collection}")
return response.json()
import requests
DIRECTUS_API_ENDPOINT = "YOUR_DIRECTUS_INSTANCE_API_ENDPOINT"
def get_global_settings():
response = requests.get(f"{DIRECTUS_API_ENDPOINT}/items/global")
return response.json()
def get_collection_items(collection):
response = requests.get(f"{DIRECTUS_API_ENDPOINT}/items/{collection}")
return response.json()
With the functions in place, you can now fetch global settings and pass them to your Django templates.
Lets now create a view for the home page. Django automatically creates a views.py
file after starting an app. Update the file:
from django.shortcuts import render
from .directus_integration import get_global_settings
def home_page(request):
global_settings = get_global_settings()
context = {
'title': global_settings['data']['title'],
'description': global_settings['data']['description']
}
return render(request, 'home.html', context)
from django.shortcuts import render
from .directus_integration import get_global_settings
def home_page(request):
global_settings = get_global_settings()
context = {
'title': global_settings['data']['title'],
'description': global_settings['data']['description']
}
return render(request, 'home.html', context)
Create a templates
directory in the root directory of our Django project (the root directory is where you have the manage.py file).
Create a home.html
file in your templates directory:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<header>
<h1>{{ title }}</h1>
</header>
<main>
<p>{{ description }}</p>
</main>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<header>
<h1>{{ title }}</h1>
</header>
<main>
<p>{{ description }}</p>
</main>
</body>
</html>
Creating Pages With Directus
In your Django project, set up a system to serve pages stored in a Directus collection called pages
. Each page in Directus will have a unique identifier that corresponds to its URL path.
In your Directus dashboard, navigate to Settings -> Data Model and create a new collection named pages
. Assign a "Manually Entered String" field called slug
for the URL of each page. Add a text field named title
and a Rich Text field for the content
.
In the Access Control settings allow the Public role to read the pages
collection.
In your views.py
, utilize the get_collection_items
function to get the content and serve it through a Django view:
from django.shortcuts import render
from django.http import JsonResponse
# Import the get_collection_items function from your integration script
from .directus_integration import get_collection_items
def page_view(request, slug):
pages = get_collection_items('pages')
page = next((p for p in pages['data'] if p['slug'] == slug), None)
if page:
return render(request, 'page.html', {'page': page})
else:
return JsonResponse({'error': 'Page not found'}, status=404)
from django.shortcuts import render
from django.http import JsonResponse
# Import the get_collection_items function from your integration script
from .directus_integration import get_collection_items
def page_view(request, slug):
pages = get_collection_items('pages')
page = next((p for p in pages['data'] if p['slug'] == slug), None)
if page:
return render(request, 'page.html', {'page': page})
else:
return JsonResponse({'error': 'Page not found'}, status=404)
Now you can create a Django template to render the page content. In your templates directory, create a file named page.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }}</title>
</head>
<body>
<main>
{{ page.content|safe }}
</main>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }}</title>
</head>
<body>
<main>
{{ page.content|safe }}
</main>
</body>
</html>
Now, when you visit http://localhost:8000/your-page-slug
, replacing your-page-slug
with any slug from your Directus pages collection, Django will serve the content of that page.
Creating Blog Posts
In the Directus Data Studio, create two collections:
- authors: with a field for the author's
name
. - posts: with fields for:
slug
(unique identifier for the URL path)title
- a text inputcontent
- a rich text fieldpublish_date
- a date fieldauthors
- a many-to-one relationship linking to the authors collection
Adjust Directus permissions to allow public reading of the authors
and posts
collections.
Create Listing
In the directus_integration.py
file, create the data fetching function:
def fetch_blog_posts():
response = requests.get(f"{DIRECTUS_API_ENDPOINT}/items/posts?fields=*,author.name&sort=-publish_date")
return response.json()
def fetch_blog_posts():
response = requests.get(f"{DIRECTUS_API_ENDPOINT}/items/posts?fields=*,author.name&sort=-publish_date")
return response.json()
In the views.py
file, create a function that imports and uses the fetch_blog_posts
function to display the list of posts:
from .directus_integration import get_collection_items,fetch_blog_posts
def blog_posts(request):
posts_data = fetch_blog_posts()
return render(request, 'blog_list.html', {'posts': posts_data['data']})
from .directus_integration import get_collection_items,fetch_blog_posts
def blog_posts(request):
posts_data = fetch_blog_posts()
return render(request, 'blog_list.html', {'posts': posts_data['data']})
Within the the templates
directory, create a blog_list.html
file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog Posts</title>
</head>
<body>
<h1>Blog</h1>
<ul>
{% for post in posts %}
<li>
<a href="{% url 'blog_detail' post.slug %}">{{ post.title }}</a>
<p>{{ post.publish_date }} by {{ post.author.name }}</p>
</li>
{% endfor %}
</ul>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog Posts</title>
</head>
<body>
<h1>Blog</h1>
<ul>
{% for post in posts %}
<li>
<a href="{% url 'blog_detail' post.slug %}">{{ post.title }}</a>
<p>{{ post.publish_date }} by {{ post.author.name }}</p>
</li>
{% endfor %}
</ul>
</body>
</html>
Create Single Post Page
Create another view in views.py
to handle individual blog posts:
def blog_post_detail(request, slug):
posts_data = fetch_blog_posts()
post = next((p for p in posts_data['data'] if p['slug'] == slug), None)
if post is not None:
return render(request, 'blog_detail.html', {'post': post})
else:
return JsonResponse({'error': 'Post not found'}, status=404)
def blog_post_detail(request, slug):
posts_data = fetch_blog_posts()
post = next((p for p in posts_data['data'] if p['slug'] == slug), None)
if post is not None:
return render(request, 'blog_detail.html', {'post': post})
else:
return JsonResponse({'error': 'Post not found'}, status=404)
Still within the templates
directory, create the blog_detail.html
template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ post.title }}</title>
</head>
<body>
<article>
<header>
<h1>{{ post.title }}</h1>
<p>Published on: {{ post.publish_date }} by {{ post.author.name }}</p>
</header>
<section>
{{ post.content | safe }}
</section>
</article>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ post.title }}</title>
</head>
<body>
<article>
<header>
<h1>{{ post.title }}</h1>
<p>Published on: {{ post.publish_date }} by {{ post.author.name }}</p>
</header>
<section>
{{ post.content | safe }}
</section>
</article>
</body>
</html>
Create a urls.py
file within the blog app directory and update it to include URL patterns for all views:
from django.urls import path
from .views import blog_posts, blog_post_detail
urlpatterns = [
path('', home_page, name='home'),
path('blog/', blog_posts, name='blog_list'),
path('blog/<slug:slug>/', blog_post_detail, name='blog_detail'),
# ... other URL patterns ...
]
from django.urls import path
from .views import blog_posts, blog_post_detail
urlpatterns = [
path('', home_page, name='home'),
path('blog/', blog_posts, name='blog_list'),
path('blog/<slug:slug>/', blog_post_detail, name='blog_detail'),
# ... other URL patterns ...
]
Include the app's URLs in the main project's config/urls.py
:
from django.urls import path, include
urlpatterns = [
path('', include('blog.urls')),
]
from django.urls import path, include
urlpatterns = [
path('', include('blog.urls')),
]
Add Navigation
In Django, the website's navigation is usually integrated into a base template that other templates extend. Let's add a navigation menu to your base Django template to link together the different pages of your site.
The navigation menu typically resides in a base template that other templates extend. Update your base template (base.html
) to include the navigation:
<nav>
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'about' %}">About</a>
<a href="{% url 'blog_list' %}">Blog</a>
</nav>
{% block content %}
{% endblock %}
<nav>
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'about' %}">About</a>
<a href="{% url 'blog_list' %}">Blog</a>
</nav>
{% block content %}
{% endblock %}
In your individual page templates, extend the base.html to inherit the navigation:
{% extends 'base.html' %}
{% block content %}
{% endblock %}
{% extends 'base.html' %}
{% block content %}
{% endblock %}
Utilize Django's URL names instead of hardcoded paths for navigation links:
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'home' %}">Home</a>
Next steps
Through this guide, you have established a Django project and integrated it with Directus to manage and serve content dynamically. Utilizing the rich features of Django's web framework and Directus's flexible CMS, you've created a system that not only handles global website settings but also powers a blog with listings and detailed post pages.
As you progress, you might consider refining the accessibility of your content. To achieve this, delve into Directus's permissions and roles to define more granular access control, ensuring that only appropriate data is available for each user role. Additionally, you can fine-tune your Django views and templates to render content based on the user's permissions, providing a secure and customized experience.