🌍 2027.djangocon.eu
📍 Innsbruck, Austria
📅 17–21 February 2027
Requirements: Docker and just (brew install just).
just build # creates .envs/.django if missing and builds the images
just up # django on http://localhost:8000, browser-sync on http://localhost:3000
just logs # follow the logs (or `just logs node`)
just downjust on its own lists every recipe. The node container watches djangocon/static/sass
and recompiles project.css on change — edit the SCSS, never the CSS. Other useful
recipes: just manage <cmd>, just shell, just lint (all pre-commit hooks),
just pytest, just assets (one-off SCSS/JS build), just collectstatic.
The site has no database and no models: every page is rendered from the files in
djangocon/content/. There is nothing to migrate.
Python 3.11+ (production runs 3.11) and Node 22 are required (see pyproject.toml / package.json).
python -m venv .venv && source .venv/bin/activate
pip install -r requirements/local.txt
npm ci && npm run build # or `npm run dev` to watch SCSS/JS
python manage.py runserverPython is linted and formatted by ruff, templates by
djLint, everything else by prettier — all wired through
pre-commit (pre-commit install once, or just lint to run everything).
Production settings live in config/settings/production.py and are driven by
environment variables: DJANGO_SECRET_KEY (required), DJANGO_ALLOWED_HOSTS
(default 2027.djangocon.eu), SENTRY_DSN (optional), DJANGO_SECURE_HSTS_SECONDS
(default one year), DJANGO_GA4_MEASUREMENT_ID (default
G-C4K64NWHSN; set it empty to turn analytics off). Static files are collected
into staticfiles/ by manage.py collectstatic, with fingerprinted names and
pre-compressed .gz copies. WhiteNoise serves them with a one-year cache when
requests reach Django; a front server that serves /static/ itself has to set
that cache header (fingerprinted names only) and serve the .gz copies
(gzip_static on in nginx) on its own. Run the app with
gunicorn (gunicorn config.wsgi).
/robots.txt and /sitemap.xml are both generated, not checked in. The sitemap
walks djangocon/content/ and lists every folder that renders a page, so adding
a page adds a sitemap entry with no extra step — see djangocon/site/sitemaps.py.
Google Analytics 4 is off unless DJANGO_GA4_MEASUREMENT_ID is set, so local
development and CI never send hits — including in Docker, where .envs/.django
does not set it. To see the banner locally, run with the variable set:
docker compose run --rm -e DJANGO_GA4_MEASUREMENT_ID=G-C4K64NWHSN -p 8000:8000 \
django python manage.py runserver 0.0.0.0:8000Where it is configured, gtag.js is still not loaded until the visitor accepts
analytics cookies: nothing is requested from Google and no cookie is written
before that. The choice is kept in localStorage under cookie-consent. This is
what the privacy guide
promises, so keep the two in step if you change the behaviour.
The tag is split in two, following Google's gtag.js install guide:
modules/analytics_head.html sets up the consent gate in <head> (so a returning
visitor who already accepted starts measuring immediately) and
modules/analytics_banner.html carries the banner markup at the end of <body>.
There is no <noscript> fallback: that belongs to Google Tag Manager, whose
noscript iframe still fires tags without JavaScript. GA4 measures purely in JS, so
a <noscript> block could not report anything.
No Python needed for any of this.
Page text lives in djangocon/content/<section>/<page>/*.md. Each file starts
with a small metadata block:
title: Grant Status
layout: simple
order: 2
order sets the position on the page, and layout picks a template from
djangocon/templates/modules/. A layout name that doesn't exist falls back to
simple rather than breaking the page. Adding a .md file to a folder adds a
section to that page. Use ##/### headings inside the body — the page title is
the only <h1>. Add published: false to park a section without deleting it.
The text search engines show under the page title comes from the page's first
paragraph that is at least 50 characters long. To write it yourself, add
description: (160 characters at most) to the metadata of the page's first
file.
Some layouts carry their own copy in the template rather than in the .md
(home_about, home_tickets, home_dates, past_edition, credits); edit
those under djangocon/templates/modules/.
Images go under djangocon/static/images/ and are referenced as
/static/images/... (the path is rewritten to the cached, fingerprinted URL
when the page renders). Keep each file under 300 KB, or the test suite fails.
For photos that usually means:
- save as WebP (or JPEG), never PNG;
- size it for where it is shown, at twice the displayed width at most. A photo
shown in a 400px column needs to be 800px wide, not 4000px. Offer a smaller
copy through
srcsetwhen it also shows on phones (seecontent/home/past_edition.mdfor an example); - give the
<img>itswidth,heightand, when it is not at the top of the page,loading="lazy".
Squoosh does the resizing and conversion in the
browser, or cwebp -q 78 -resize 800 0 in.png -o out.webp from the command
line.
Sponsors live in djangocon/content/sponsors.json, grouped by tier. Copy an
existing entry to add one. Empty tiers are hidden automatically. Set filter to
true when a dark logo needs inverting to white.
The menu lives in djangocon/content/navigation.json. Submenu URLs must match
a folder under content/ and end with a trailing slash. To hide an item without
deleting it, move it into the _disabled block.
As a contributor, you can help us keep the Django community open and inclusive. Please read and follow our Code of Conduct.
Get started contributing by reading our Contributing guidelines.
To contribute to this project, please follow these steps:
- Fork the Repo
- Clone the Repo to your local machine
- Follow "Local Development"
- make changes and submit a PR( we will review)
This project is licensed under the MIT License - see the LICENSE.md file for details.