Install in your project

Requirements

Supported environments are Python 3.13 and 3.14 with Django 6.0 and 6.1. Every combination is exercised on SQLite and PostgreSQL. Use the latest patch release of your Python and Django series. Other versions and databases are not part of the current compatibility promise. Django is the only runtime dependency; Sphinx, Ruff and database test drivers are development tools.

Install Django QBE 2

Install the stable release in your host project’s virtual environment:

python -m pip install django-qbe2

You do not need to upgrade a host project from Django 6.0 to Django 6.1 to use Django QBE 2. Pin a version in your application’s dependency file when you need reproducible deployments.

From the source checkout, you can always build and install a local wheel:

uv build

In your host project’s environment, install that wheel using its absolute path:

python -m pip install /absolute/path/to/django-qbe2/dist/django_qbe2-1.0.0-py3-none-any.whl

The distribution name is django-qbe2; the import and app name is django_qbe2. The wheel contains namespaced templates and static assets. It contains neither the demo database nor the host project’s settings.

Excel export is optional. Install the extra in the host project’s environment to show the Export Excel button alongside CSV:

python -m pip install 'django-qbe2[excel]'

Without this extra, Django QBE 2 continues to provide CSV exports and does not import openpyxl at runtime.

Configure Django

Keep Django Admin, auth, contenttypes, sessions, messages and staticfiles in your existing INSTALLED_APPS, and append the reporting app:

INSTALLED_APPS += ["django_qbe2"]

Keep your existing admin route and other routes, then append to your root URLconf:

from django.urls import include, path

urlpatterns += [path("qbe/", include("django_qbe2.urls"))]

Retain the standard session, authentication, messages and CSRF middleware. Templates need APP_DIRS=True and the standard request/auth/messages context processors used by Django Admin. The initial implementation uses the default admin.site for its login and context; it does not automatically adopt a custom AdminSite.

QBE includes migrations for saved queries, schedules and their user relationships, using your AUTH_USER_MODEL. Apply them with manage.py migrate when installing or upgrading. Your host project’s auth, sessions and business apps still need their normal migrations. In production, serve the packaged static files through your existing Django static-file setup:

python manage.py check
python manage.py migrate
python manage.py collectstatic

Register your models

The catalog starts empty. For a new installed app called sales, example models are shown below. If your application already has equivalent models, use those instead of replacing them.

sales/models.py
from django.conf import settings
from django.db import models


class Customer(models.Model):
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)


class Order(models.Model):
    class Status(models.TextChoices):
        OPEN = "open", "Open"
        CLOSED = "closed", "Closed"

    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    customer = models.ForeignKey(Customer, null=True, on_delete=models.SET_NULL)
    number = models.CharField(max_length=100)
    total = models.DecimalField(max_digits=12, decimal_places=2)
    status = models.CharField(max_length=10, choices=Status, default=Status.OPEN)

Put the report declarations in sales/qbe.py. The app discovers qbe.py modules at startup without fetching report data:

sales/qbe.py
from django_qbe2.registry import Report, register

from .models import Customer, Order


@register
class CustomerReport(Report):
    model = Customer
    fields = ("name",)

    def get_queryset(self, request):
        return Customer.objects.filter(owner=request.user)


@register
class OrderReport(Report):
    model = Order
    fields = ("number", "customer__name", "total", "status")
    field_labels = {"customer__name": "Customer", "total": "Total amount"}

    def get_queryset(self, request):
        return Order.objects.filter(owner=request.user)

Ensure sales is installed, create/apply its migrations if these are new models, and give authorized users active staff status plus sales.view_order and sales.view_customer permissions. Registering a related target is mandatory. Read Reports and permissions for the policy contract before adapting this example.

Note

These exact model and report files are installed into a temporary external Django consumer by the package test suite. The test checks the wheel outside the checkout with a UUID custom user, applies migrations, and verifies report HTML/CSV and shared saved queries preserve the executing user’s row policy.

Run manage.py check after changing report configuration. Invalid field paths, unsupported relations and missing target registrations are reported by system checks.