Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.
Starter project
Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working starter project that’s integrated with content already inside of your ButterCMS account.Django Starter Project
Hit the ground running with a pre-configured Django + ButterCMS setup.
Installation
- pip
- pipenv
- poetry
pip install buttercms-python
pipenv install buttercms-python
poetry add buttercms-python
settings.py:
# settings.py
import os
BUTTERCMS_API_TOKEN = os.environ.get('BUTTERCMS_API_TOKEN', 'your_api_token')
Initialize the client
Create a reusable client instance:# myapp/buttercms_client.py
from butter_cms import ButterCMS
from django.conf import settings
client = ButterCMS(settings.BUTTERCMS_API_TOKEN)
For complete SDK documentation including all available methods and configuration options, see the Python SDK Reference.
Pages
- Function-Based Views
- Class-Based Views
# myapp/views.py
from django.shortcuts import render
from django.http import Http404
from .buttercms_client import client
def landing_page(request, slug='home'):
try:
response = client.pages.get('landing-page', slug)
page = response['data']
except:
raise Http404("Page not found")
return render(request, 'landing.html', {'page': page})
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.landing_page, name='home'),
path('page/<slug:slug>/', views.landing_page, name='landing_page'),
]
# myapp/views.py
from django.views.generic import TemplateView
from django.http import Http404
from .buttercms_client import client
class LandingPageView(TemplateView):
template_name = 'landing.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
slug = self.kwargs.get('slug', 'home')
try:
response = client.pages.get('landing-page', slug)
context['page'] = response['data']
except:
raise Http404("Page not found")
return context
# myapp/urls.py
from django.urls import path
from .views import LandingPageView
urlpatterns = [
path('', LandingPageView.as_view(), name='home'),
path('page/<slug:slug>/', LandingPageView.as_view(), name='landing_page'),
]
<!-- templates/landing.html -->
{% extends 'base.html' %}
{% block content %}
<main>
<h1>{{ page.fields.headline }}</h1>
<p>{{ page.fields.subheadline }}</p>
{% if page.fields.hero_image %}
<img src="{{ page.fields.hero_image }}" alt="{{ page.fields.headline }}" />
{% endif %}
{{ page.fields.body|safe }}
</main>
{% endblock %}
Collections
- Function-Based Views
- Class-Based Views
# myapp/views.py
from django.shortcuts import render
from .buttercms_client import client
def brands(request):
response = client.content_fields.get(['brands'])
brands = response['data']['brands']
return render(request, 'brands.html', {'brands': brands})
# myapp/urls.py
urlpatterns = [
path('brands/', views.brands, name='brands'),
]
# myapp/views.py
from django.views.generic import TemplateView
from .buttercms_client import client
class BrandsView(TemplateView):
template_name = 'brands.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
response = client.content_fields.get(['brands'])
context['brands'] = response['data']['brands']
return context
# myapp/urls.py
from .views import BrandsView
urlpatterns = [
path('brands/', BrandsView.as_view(), name='brands'),
]
<!-- templates/brands.html -->
{% extends 'base.html' %}
{% block content %}
<main>
<h1>Our Brands</h1>
<ul>
{% for brand in brands %}
<li>
<img src="{{ brand.logo }}" alt="{{ brand.name }}" />
<h2>{{ brand.name }}</h2>
{{ brand.description|safe }}
</li>
{% endfor %}
</ul>
</main>
{% endblock %}
Dynamic components
Component renderer
# myapp/components.py
from django.template.loader import render_to_string
COMPONENT_TEMPLATES = {
'hero': 'components/hero.html',
'features': 'components/features.html',
'testimonials': 'components/testimonials.html',
'cta': 'components/cta.html',
}
def render_components(components):
"""Render a list of ButterCMS components to HTML."""
html_parts = []
for component in components:
template = COMPONENT_TEMPLATES.get(component['type'])
if template:
html = render_to_string(template, {'fields': component['fields']})
html_parts.append(html)
return ''.join(html_parts)
Example component template
<!-- templates/components/hero.html -->
<section class="hero">
<h1>{{ fields.headline }}</h1>
<p>{{ fields.subheadline }}</p>
{% if fields.button_label %}
<a href="{{ fields.button_url }}" class="btn">{{ fields.button_label }}</a>
{% endif %}
{% if fields.image %}
<img src="{{ fields.image }}" alt="{{ fields.headline }}" />
{% endif %}
</section>
Using in views
- Function-Based Views
- Class-Based Views
# myapp/views.py
from django.shortcuts import render
from django.http import Http404
from .buttercms_client import client
from .components import render_components
def component_page(request, slug):
try:
response = client.pages.get('landing-page', slug)
page = response['data']
except:
raise Http404("Page not found")
# Render components to HTML
components_html = render_components(page['fields'].get('body', []))
return render(request, 'component_page.html', {
'page': page,
'components_html': components_html,
})
# myapp/views.py
from django.views.generic import TemplateView
from django.http import Http404
from .buttercms_client import client
from .components import render_components
class ComponentPageView(TemplateView):
template_name = 'component_page.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
slug = self.kwargs.get('slug')
try:
response = client.pages.get('landing-page', slug)
page = response['data']
except:
raise Http404("Page not found")
context['page'] = page
context['components_html'] = render_components(
page['fields'].get('body', [])
)
return context
<!-- templates/component_page.html -->
{% extends 'base.html' %}
{% block content %}
<main>
{{ components_html|safe }}
</main>
{% endblock %}
Blog
- Blog Post List
- Single Blog Post
- Function-Based Views
- Class-Based Views
# myapp/views.py
from django.shortcuts import render
from .buttercms_client import client
def blog_list(request):
page = request.GET.get('page', 1)
response = client.posts.all({'page': page, 'page_size': 10})
return render(request, 'blog/list.html', {
'posts': response['data'],
'meta': response['meta'],
})
# myapp/urls.py
urlpatterns = [
path('blog/', views.blog_list, name='blog_list'),
]
# myapp/views.py
from django.views.generic import TemplateView
from .buttercms_client import client
class BlogListView(TemplateView):
template_name = 'blog/list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page = self.request.GET.get('page', 1)
response = client.posts.all({'page': page, 'page_size': 10})
context['posts'] = response['data']
context['meta'] = response['meta']
return context
# myapp/urls.py
from .views import BlogListView
urlpatterns = [
path('blog/', BlogListView.as_view(), name='blog_list'),
]
<!-- templates/blog/list.html -->
{% extends 'base.html' %}
{% block content %}
<main>
<h1>Blog</h1>
<ul>
{% for post in posts %}
<li>
<h2><a href="{% url 'blog_post' post.slug %}">{{ post.title }}</a></h2>
{{ post.summary|safe }}
<span>By {{ post.author.first_name }} {{ post.author.last_name }}</span>
</li>
{% endfor %}
</ul>
{% if meta.next_page %}
<a href="?page={{ meta.next_page }}">Next Page</a>
{% endif %}
</main>
{% endblock %}
- Function-Based Views
- Class-Based Views
# myapp/views.py
from django.shortcuts import render
from django.http import Http404
from .buttercms_client import client
def blog_post(request, slug):
try:
response = client.posts.get(slug)
post = response['data']
except:
raise Http404("Post not found")
return render(request, 'blog/post.html', {'post': post})
# myapp/urls.py
urlpatterns = [
path('blog/<slug:slug>/', views.blog_post, name='blog_post'),
]
# myapp/views.py
from django.views.generic import TemplateView
from django.http import Http404
from .buttercms_client import client
class BlogPostView(TemplateView):
template_name = 'blog/post.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
slug = self.kwargs.get('slug')
try:
response = client.posts.get(slug)
context['post'] = response['data']
except:
raise Http404("Post not found")
return context
# myapp/urls.py
from .views import BlogPostView
urlpatterns = [
path('blog/<slug:slug>/', BlogPostView.as_view(), name='blog_post'),
]
<!-- templates/blog/post.html -->
{% extends 'base.html' %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<p>
By {{ post.author.first_name }} {{ post.author.last_name }}
on {{ post.published|date:"F j, Y" }}
</p>
{% if post.featured_image %}
<img src="{{ post.featured_image }}" alt="{{ post.title }}" />
{% endif %}
{{ post.body|safe }}
<a href="{% url 'blog_list' %}">Back to Posts</a>
</article>
{% endblock %}
Caching
Use Django’s cache framework for better performance:# myapp/views.py
from django.core.cache import cache
from django.shortcuts import render
from .buttercms_client import client
def cached_landing_page(request, slug='home'):
cache_key = f'butter_page_landing-page_{slug}'
page = cache.get(cache_key)
if not page:
try:
response = client.pages.get('landing-page', slug)
page = response['data']
cache.set(cache_key, page, 3600) # Cache for 1 hour
except:
raise Http404("Page not found")
return render(request, 'landing.html', {'page': page})
Webhook cache invalidation
# myapp/views.py
from django.core.cache import cache
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import json
@csrf_exempt
@require_POST
def buttercms_webhook(request):
payload = json.loads(request.body)
webhook_type = payload.get('webhook_type', '')
if 'published' in webhook_type or 'updated' in webhook_type or 'deleted' in webhook_type:
# Clear all ButterCMS cache keys
cache.delete_many(cache.keys('butter_*'))
return JsonResponse({'status': 'ok'})
SEO
# myapp/context_processors.py
def seo_context(request):
"""Add SEO fields from ButterCMS page to context."""
return {}
# myapp/views.py
def landing_page_with_seo(request, slug='home'):
try:
response = client.pages.get('landing-page', slug)
page = response['data']
except:
raise Http404("Page not found")
seo = page['fields'].get('seo', {})
return render(request, 'landing.html', {
'page': page,
'seo_title': seo.get('title', page['fields'].get('headline', '')),
'seo_description': seo.get('description', ''),
'og_title': seo.get('og_title', seo.get('title', '')),
'og_description': seo.get('og_description', seo.get('description', '')),
'og_image': seo.get('og_image', ''),
})
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ seo_title|default:"My Site" }}</title>
<meta name="description" content="{{ seo_description }}">
<!-- Open Graph -->
<meta property="og:title" content="{{ og_title|default:seo_title }}">
<meta property="og:description" content="{{ og_description|default:seo_description }}">
{% if og_image %}
<meta property="og:image" content="{{ og_image }}">
{% endif %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Resources
Django Starter
Pre-configured starter project
Python SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation