Metadata-Version: 2.4
Name: django-countdown
Version: 0.2.0
Summary: Display a maintenance countdown banner and block access to a Django site when the countdown expires.
Author-email: Michał Pasternak <michal.dtz@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/iplweb/django-countdown
Project-URL: Repository, https://github.com/iplweb/django-countdown
Project-URL: Issues, https://github.com/iplweb/django-countdown/issues
Project-URL: Changelog, https://github.com/iplweb/django-countdown/blob/main/CHANGELOG.md
Keywords: django,countdown,maintenance,downtime,banner,sites
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: django>=5.2
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-django>=4.8; extra == "test"
Requires-Dist: pytest-mock>=3.12; extra == "test"
Requires-Dist: model_bakery>=1.17; extra == "test"
Provides-Extra: dev
Requires-Dist: pre-commit; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# django-countdown

[![Tests](https://github.com/iplweb/django-countdown/actions/workflows/tests.yml/badge.svg)](https://github.com/iplweb/django-countdown/actions/workflows/tests.yml)
[![Python Version](https://img.shields.io/pypi/pyversions/django-countdown.svg)](https://pypi.org/project/django-countdown/)
[![PyPI Version](https://img.shields.io/pypi/v/django-countdown.svg)](https://pypi.org/project/django-countdown/)
[![License](https://img.shields.io/pypi/l/django-countdown.svg)](LICENSE)

Display a maintenance countdown banner across a Django site, then block public
access (returning HTTP 503) when the countdown expires. Superusers retain
access during maintenance so they can finish the work and clear the countdown.

## Why?

Planned downtime is the worst kind of downtime to communicate badly. Users
land on a half-broken page mid-deploy, hit error logs, file support tickets,
and trust erodes. `django-countdown` lets you announce a maintenance window
*before* it starts (a countdown banner with a real timer), then *during* the
window swap public traffic for an explicit "we're in maintenance" page —
while leaving operators unblocked so they can actually finish the work.

## Features

- **Pre-maintenance banner** — an ultra-visible countdown banner inserted into
  templates via context processor, with a JS timer that ticks live.
- **Hard cutoff at expiry** — middleware returns HTTP 503 and renders a
  branded blocked page once the countdown lapses.
- **Superuser bypass** — admins keep working through the cutoff so they can
  fix the underlying issue and clear the countdown.
- **Maintenance window** — optional `maintenance_until` lets you set a target
  end-time; a second banner appears for superusers and the blocked page shows
  a live countdown to recovery.
- **Per-Site configuration** — uses Django's `sites` framework, so each
  domain in a multi-tenant setup has its own independent countdown.
- **Admin integration** — full Django admin support with status colors and a
  validator preventing past-dated countdowns.

## Supported versions

Authoritative upstream: <https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django>

| Django  | 3.10 | 3.11 | 3.12 | 3.13 | 3.14 | Status                                  |
|---------|------|------|------|------|------|-----------------------------------------|
| 5.2 LTS | ✓    | ✓    | ✓    | ✓    | ✓    | Active LTS (extended support Apr 2028)  |
| 6.0     | —    | —    | ✓    | ✓    | ✓    | Mainstream Aug 2026, extended Apr 2027  |

8 cells in total are exercised by the CI matrix on every push.

## Installation

### Using uv (recommended)

```bash
uv add django-countdown
```

### Using pip

```bash
pip install django-countdown
```

### Project configuration

Add to `INSTALLED_APPS` — the `django.contrib.sites` framework must also be
installed:

```python
INSTALLED_APPS = [
    # ...
    "django.contrib.sites",
    "django_countdown",
]

SITE_ID = 1
```

Add the blocking middleware **after** Django's auth middleware (it needs
`request.user`):

```python
MIDDLEWARE = [
    # ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django_countdown.middleware.CountdownBlockingMiddleware",
]
```

Add the context processor so the banner partials see the active countdown:

```python
TEMPLATES = [
    {
        # ...
        "OPTIONS": {
            "context_processors": [
                # ...
                "django_countdown.context_processors.countdown_context",
            ],
        },
    },
]
```

Run migrations:

```bash
./manage.py migrate
```

## Quick start

Create a `SiteCountdown` row (one per `django.contrib.sites.Site`) via the
Django admin, providing:

- `countdown_time` — when access starts being blocked.
- `maintenance_until` (optional) — when public access resumes.
- `message` — short banner headline (max 200 chars).
- `long_description` (optional) — extended copy shown on the blocked page.

The package exposes two template partials you can `{% include %}` from your
own base layout:

```django
{% include "django_countdown/countdown_banner.html" %}
```

Renders a banner before the countdown expires; switches to a subdued
"maintenance in progress" banner for superusers after expiry until
`maintenance_until` passes. Two context variables are populated by the
context processor: `active_countdown` (pre-maintenance) and
`maintenance_countdown` (during maintenance, superusers only).

The middleware automatically serves `django_countdown/blocked.html` for
non-superuser requests once the countdown has expired and maintenance is
ongoing.

A working end-to-end example lives under [`example/`](./example/) — start
there if you want to see the package wired up in a minimal Django project.

## Optional / template-level requirements

`countdown_banner.html` uses `{% load compress %}` (from
[django-compressor](https://django-compressor.readthedocs.io/)) to bundle its
CSS. If you include the banner partial in your templates, install
`django-compressor` and add it to `INSTALLED_APPS`, or fork the template and
drop the `{% compress %}` wrapping.

`blocked.html` references Foundation CSS and Foundation-Icons via
`{% static %}`. If you serve those files from your project's static pipeline
they will resolve; otherwise the blocked page degrades visually but still
renders the maintenance message and timer.

## Configuration

The package does not require any project-level Django settings. The middleware
exempts `/admin/`, `/static/`, and `/media/` URL prefixes from blocking by
default.

## Development

```bash
git clone https://github.com/iplweb/django-countdown.git
cd django-countdown
uv sync --all-extras
uv run pytest
```

## License

MIT — see [LICENSE](LICENSE) for details.
