Metadata-Version: 2.4
Name: django-countdown
Version: 0.4.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: Funding, https://buymeacoffee.com/mpasternak
Project-URL: Homepage, https://github.com/iplweb/django-countdown
Project-URL: Documentation, https://iplweb.github.io/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 :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Framework :: Django :: 6.1
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"
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
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)
[![Docs](https://github.com/iplweb/django-countdown/actions/workflows/docs.yml/badge.svg)](https://iplweb.github.io/django-countdown/)
[![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.

**📖 Full documentation: <https://iplweb.github.io/django-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.
- **Self-healing wait** — the blocked page polls a status endpoint in the
  background and sends visitors back to the page they wanted the moment the
  site returns. It tells "still down for maintenance" apart from "nothing is
  answering while the container restarts", so a timer that runs out mid-deploy
  no longer strands anyone on the proxy's error page.
- **Per-Site configuration** — uses Django's `sites` framework, so each
  domain in a multi-tenant setup has its own independent countdown.
- **A command per verb** — `start_countdown` schedules a window,
  `show_countdown` reports where it stands (with `--json` for monitoring),
  `extend_countdown` and `shorten_countdown` move its boundaries when the plan
  slips, and `stop_countdown` reopens the site.
- **Dead man's switch** — `extend_countdown --at-least 5m` raises a floor
  rather than adding time, so a deploy loop can hold the site closed while it
  works and let it reopen by itself if the deploy dies.
- **Admin integration** — full Django admin support alongside the commands.

## Supported versions

| 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  |
| 6.1     | —    | —    | ✓    | ✓    | ✓    | Mainstream Apr 2027, extended Dec 2027  |

All 11 cells are exercised by the CI matrix on every push. Django is the only
runtime dependency.

## Installation

```bash
uv add django-countdown      # or: pip install django-countdown
```

Add the app, the middleware and the context processor to your settings:

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

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

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

Then `./manage.py migrate`, and include the banner in your base template:

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

Full walkthrough:
[Installation](https://iplweb.github.io/django-countdown/getting-started/installation/).

## Quick start

```bash
./manage.py start_countdown --banner +15m --service +30m \
    --message "Database upgrade" --noinput
```

Banner shows for 15 minutes, then the site returns 503 for 30 minutes, then
reopens by itself. Check on it with `./manage.py show_countdown`, and reopen
early with `./manage.py stop_countdown`. Use `--service indefinite` to stay
closed until you do. See
[Quickstart](https://iplweb.github.io/django-countdown/getting-started/quickstart/).

A working end-to-end example lives under [`example/`](./example/).

## Documentation

| | |
|---|---|
| [How it works](https://iplweb.github.io/django-countdown/guide/how-it-works/) | The state machine, who sees what, failure behaviour |
| [Countdown banner](https://iplweb.github.io/django-countdown/guide/banner/) | Including, styling and overriding the banner |
| [Blocked page](https://iplweb.github.io/django-countdown/guide/blocked-page/) | Three shipped variants and how to write your own |
| [Scheduling a countdown](https://iplweb.github.io/django-countdown/guide/management-command/) | Every option of `start_countdown` |
| [Managing a running countdown](https://iplweb.github.io/django-countdown/guide/managing-a-countdown/) | `show`, `stop`, `extend`, `shorten`, and the deploy patterns |
| [Multi-site setup](https://iplweb.github.io/django-countdown/guide/multisite/) | One countdown per domain |
| [Reference](https://iplweb.github.io/django-countdown/reference/settings/) | Settings, model, template context, template blocks |

## Development

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

See
[Contributing](https://iplweb.github.io/django-countdown/contributing/).

## License

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