The Cohort component is designed for displaying cohort analysis and retention data. While Django Unfold doesn’t include a dedicated cohort template, you can build cohort visualizations using the Table and Tracker components.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/unfoldadmin/django-unfold/llms.txt
Use this file to discover all available pages before exploring further.
What is Cohort Analysis?
Cohort analysis tracks groups of users over time to understand behavior patterns, retention, and engagement. Common use cases include:- User retention rates
- Customer lifetime value
- Feature adoption
- Subscription retention
Building Cohort Views
Using Table Component
# views.py
from django.views.generic import TemplateView
from unfold.views import UnfoldModelAdminViewMixin
from django.db.models import Count, Q
from datetime import timedelta
import calendar
class CohortAnalysisView(UnfoldModelAdminViewMixin, TemplateView):
title = "Cohort Analysis"
template_name = "admin/cohort_analysis.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Generate cohort data (simplified example)
cohorts = self.generate_cohort_data()
# Prepare table data
headers = ['Cohort'] + [f'Month {i}' for i in range(6)]
rows = []
for cohort_date, retention in cohorts.items():
row = [
cohort_date.strftime('%B %Y'),
*[self.format_retention_cell(r) for r in retention]
]
rows.append(row)
context['cohort_table'] = {
'headers': headers,
'rows': rows
}
return context
def generate_cohort_data(self):
from django.utils import timezone
from dateutil.relativedelta import relativedelta
cohorts = {}
current_date = timezone.now().date()
# Get last 6 months of cohorts
for i in range(6):
cohort_date = current_date - relativedelta(months=i)
cohort_start = cohort_date.replace(day=1)
# Get users who joined in this cohort
cohort_users = User.objects.filter(
date_joined__year=cohort_start.year,
date_joined__month=cohort_start.month
).values_list('id', flat=True)
total_users = len(cohort_users)
retention = []
# Calculate retention for each subsequent month
for month_offset in range(6):
period_start = cohort_start + relativedelta(months=month_offset)
period_end = period_start + relativedelta(months=1)
active_users = Activity.objects.filter(
user_id__in=cohort_users,
created_at__gte=period_start,
created_at__lt=period_end
).values('user_id').distinct().count()
retention_rate = (active_users / total_users * 100) if total_users > 0 else 0
retention.append(retention_rate)
cohorts[cohort_start] = retention
return cohorts
def format_retention_cell(self, rate):
from django.utils.html import format_html
# Color code based on retention rate
if rate >= 80:
bg_class = 'bg-green-100 dark:bg-green-900'
text_class = 'text-green-800 dark:text-green-200'
elif rate >= 50:
bg_class = 'bg-yellow-100 dark:bg-yellow-900'
text_class = 'text-yellow-800 dark:text-yellow-200'
else:
bg_class = 'bg-red-100 dark:bg-red-900'
text_class = 'text-red-800 dark:text-red-200'
return format_html(
'<div class="{} {} px-3 py-1 rounded text-center font-medium">{:.1f}%</div>',
bg_class, text_class, rate
)
{# templates/admin/cohort_analysis.html #}
{% extends "unfold/layouts/base.html" %}
{% load unfold %}
{% block content %}
<div class="container mx-auto">
{% component "unfold/components/card.html" with title="User Retention by Cohort" %}
{% component "unfold/components/table.html" with table=cohort_table card_included=1 %}
{% endcomponent %}
{% slot footer %}
<p class="text-sm text-gray-600">Percentage of users active in each subsequent month</p>
{% endslot %}
{% endcomponent %}
</div>
{% endblock %}
Cohort Heatmap
Using Tracker for Visual Cohorts
class CohortHeatmapView(UnfoldModelAdminViewMixin, TemplateView):
title = "Cohort Heatmap"
template_name = "admin/cohort_heatmap.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Generate cohort rows
cohort_rows = []
for cohort_date, retention in self.generate_cohort_data().items():
tracker_data = []
for rate in retention:
# Map retention rate to color intensity
if rate >= 80:
color = 'bg-green-700'
elif rate >= 60:
color = 'bg-green-500'
elif rate >= 40:
color = 'bg-green-300'
elif rate >= 20:
color = 'bg-yellow-400'
else:
color = 'bg-red-400'
tracker_data.append({
'color': color,
'tooltip': f"{rate:.1f}% retention"
})
cohort_rows.append({
'label': cohort_date.strftime('%B %Y'),
'data': tracker_data
})
context['cohort_rows'] = cohort_rows
return context
{# templates/admin/cohort_heatmap.html #}
{% extends "unfold/layouts/base.html" %}
{% load unfold %}
{% block content %}
<div class="container mx-auto">
{% component "unfold/components/card.html" with title="Cohort Retention Heatmap" %}
<div class="space-y-2">
{% for cohort in cohort_rows %}
<div class="flex items-center gap-4">
<div class="w-32 text-sm font-medium">
{{ cohort.label }}
</div>
{% component "unfold/components/tracker.html" with data=cohort.data class="flex-1" %}
{% endcomponent %}
</div>
{% endfor %}
</div>
{# Legend #}
<div class="mt-6 flex items-center gap-4 text-sm">
<span class="text-gray-600">Retention:</span>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-red-400 rounded"></div>
<span>0-20%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-yellow-400 rounded"></div>
<span>20-40%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-300 rounded"></div>
<span>40-60%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-500 rounded"></div>
<span>60-80%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-700 rounded"></div>
<span>80-100%</span>
</div>
</div>
{% endcomponent %}
</div>
{% endblock %}
Advanced Example: Multi-Metric Cohorts
class MultiMetricCohortView(UnfoldModelAdminViewMixin, TemplateView):
title = "Advanced Cohort Analysis"
template_name = "admin/multi_metric_cohort.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cohorts = self.generate_advanced_cohorts()
# Retention table
context['retention_table'] = self.format_cohort_table(
cohorts, 'retention', 'User Retention'
)
# Revenue per user table
context['revenue_table'] = self.format_cohort_table(
cohorts, 'revenue', 'Revenue per User'
)
return context
def generate_advanced_cohorts(self):
# Complex cohort calculation with multiple metrics
# Returns dict with retention, revenue, engagement, etc.
pass
def format_cohort_table(self, cohorts, metric, title):
headers = ['Cohort'] + [f'Month {i}' for i in range(6)]
rows = []
for cohort_date, data in cohorts.items():
row = [cohort_date.strftime('%b %Y')]
for value in data[metric]:
if metric == 'retention':
cell = self.format_retention_cell(value)
elif metric == 'revenue':
cell = self.format_revenue_cell(value)
else:
cell = str(value)
row.append(cell)
rows.append(row)
return {
'title': title,
'headers': headers,
'rows': rows
}
def format_revenue_cell(self, amount):
from django.utils.html import format_html
return format_html(
'<span class="font-mono">${:,.2f}</span>',
amount
)
Integration with Admin
# admin.py
from django.contrib import admin
from unfold.admin import ModelAdmin
@admin.register(User)
class UserAdmin(ModelAdmin):
def get_urls(self):
from django.urls import path
urls = super().get_urls()
custom_urls = [
path(
'cohort-analysis/',
self.admin_site.admin_view(
CohortAnalysisView.as_view(model_admin=self)
),
name='user_cohort_analysis',
),
path(
'cohort-heatmap/',
self.admin_site.admin_view(
CohortHeatmapView.as_view(model_admin=self)
),
name='user_cohort_heatmap',
),
]
return custom_urls + urls
Best Practices
- Date Granularity: Choose appropriate cohort periods (daily, weekly, monthly)
- Time Windows: Limit cohort analysis to relevant time periods
- Color Coding: Use consistent color schemes for metrics
- Performance: Cache cohort calculations for large datasets
- Context: Provide clear legends and explanations
Optimization Tips
from django.core.cache import cache
from django.views.decorators.cache import cache_page
class CohortAnalysisView(UnfoldModelAdminViewMixin, TemplateView):
# Cache cohort data for 1 hour
def get_context_data(self, **kwargs):
cache_key = 'cohort_analysis_data'
cohort_data = cache.get(cache_key)
if cohort_data is None:
cohort_data = self.generate_cohort_data()
cache.set(cache_key, cohort_data, 3600) # 1 hour
# Use cached data
# ...
Related Components
- Table - Display cohort data in tables
- Tracker - Visualize retention as heatmaps
- Chart - Line charts for retention curves
- Card - Organize cohort views