Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.base
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
CONTACT_EMAIL_SENDER=<change me>
DJANGO_ADMINS=name1 <email1>, name2 <email2>
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8888,http://127.0.0.1:8888
DJANGO_CUSTOM_ASSETS_DOMAIN=<change me>
DJANGO_DEBUG=true
DJANGO_EMAIL_DEBUG=false
Expand Down
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
FROM python:3.10-bullseye
MAINTAINER Open Knowledge Foundation
FROM python:3.12-slim-bookworm
LABEL org.opencontainers.image.authors="Open Knowledge Foundation"

WORKDIR /app
RUN apt-get update -y && apt-get upgrade -y
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
RUN apt-get install -y curl ca-certificates gnupg
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs
RUN apt-get install -y nginx
RUN apt-get install -y supervisor

Expand All @@ -27,10 +29,8 @@ COPY requirements.txt .
COPY deployment/gunicorn.config.py .

RUN pip install -r requirements.txt
RUN . /root/.nvm/nvm.sh && nvm install 16
RUN . /root/.nvm/nvm.sh && nvm use 16

ENV PORT 80
ENV PORT=80
EXPOSE $PORT

COPY docker-entrypoint.d /docker-entrypoint.d
Expand Down
92 changes: 92 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Makefile for the okfn.org Django CMS site.
# `make` (no target) shows this help.

IMAGE := okfn
NAME := okfn
PORT := 8888

# Detect if the container is currently running.
RUNNING := $(shell docker ps --filter name=^/$(NAME)$$ --format '{{.Names}}' 2>/dev/null)

.PHONY: help build run stop restart bash logs test check lint shell migrate css deps-compile clean

help:
@echo "Targets:"
@echo " make build Build the Docker image ($(IMAGE))."
@echo " make run Start the container detached on port $(PORT)."
@echo " make stop Stop the running container."
@echo " make restart Stop + run."
@echo " make bash Open an interactive shell inside the container."
@echo " make logs Tail the container logs (Ctrl-C to exit)."
@echo " make test Run Django's test suite inside the container."
@echo " make check Run 'manage.py check'."
@echo " make lint Run flake8 against the project code."
@echo " make shell Open the Django shell (REPL)."
@echo " make migrate Apply pending migrations."
@echo " make css Compile Tailwind/PostCSS to static/css/styles.css."
@echo " make deps-compile Recompile requirements.txt + requirements.dev.txt from .in files."
@echo " make clean Stop the container and prune dangling images."

build:
docker build -t $(IMAGE) .

run:
docker run -d --rm --name $(NAME) -p $(PORT):80 $(IMAGE)
@echo "Container '$(NAME)' running on http://localhost:$(PORT)"

stop:
-docker stop $(NAME)

restart: stop run

# Use exec if the container is running, otherwise spin up a one-shot.
bash:
ifeq ($(RUNNING),$(NAME))
docker exec -it $(NAME) bash
else
docker run --rm -it -w /app --entrypoint bash $(IMAGE)
endif

logs:
docker logs -f $(NAME)

test:
ifeq ($(RUNNING),$(NAME))
docker exec $(NAME) python manage.py test
else
docker run --rm --entrypoint python $(IMAGE) manage.py test
endif

check:
ifeq ($(RUNNING),$(NAME))
docker exec $(NAME) python manage.py check --settings=foundation.settings
else
docker run --rm --entrypoint python $(IMAGE) manage.py check --settings=foundation.settings
endif

# flake8 is in requirements.dev.txt, not in the production image, so install it
# on the fly. The .flake8 config sets max-line-length=120 and ignores W503.
lint:
ifeq ($(RUNNING),$(NAME))
docker exec $(NAME) sh -c "pip install -q flake8 && flake8 --config=/app/.flake8 ."
else
docker run --rm -w /app --entrypoint sh $(IMAGE) -c "pip install -q flake8 && flake8 --config=/app/.flake8 ."
endif

shell:
docker exec -it $(NAME) python manage.py shell

migrate:
docker exec $(NAME) python manage.py migrate

# Run on the host — needs Node 20 + a local node_modules (`npm install`).
css:
npm run build

# Run on the host — needs `uv` installed locally.
deps-compile:
uv pip compile requirements.in -o requirements.txt
uv pip compile requirements.dev.in -o requirements.dev.txt

clean: stop
-docker image prune -f
60 changes: 45 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,32 @@ If we need to make global styling or data model changes, then we need to edit th

## Prerequisites and assumptions

You must have the following installed:
The fastest path is **Docker only** — see "Quick start with Make" below. You don't need Python or Node on the host for that flow.

- Python 3.10
- Node JS 16
If you want to run things directly on the host (faster dev loop, but more setup):

- Python 3.12
- Node 20
- [`uv`](https://github.com/astral-sh/uv) — used to compile the `requirements.txt` lock files

The [/Dockerfile](/Dockerfile) (used for staging/production) and the [requirements file](/requirements.txt)
(built with `pip-tools`) in this repo shows you the application dependencies.

## Quick start with Make

The repo ships a `Makefile` that wraps the most common Docker operations. From a clean checkout:

```bash
make build # build the image
make run # start the container on http://localhost:8888
make logs # follow logs (Ctrl-C to detach)
make test # run Django's test suite inside the container
make stop # stop the container
```

Run `make` with no arguments to see all available targets (`bash`, `shell`, `lint`, `check`, `migrate`,
`css`, `deps-compile`, `clean`, etc.).

# Development

## Database
Expand Down Expand Up @@ -59,14 +77,14 @@ DB_PORT=5432

Prepare the app:

Make sure to have the correct node version:
Make sure to have the correct Node version (20):

```bash
nvm install 16
nvm use 16
nvm install 20
nvm use 20
```

Create a Python 3.10 local environment (e.g. `python3.10 -m venv ~/okf-website-env`)
Create a Python 3.12 local environment (e.g. `python3.12 -m venv ~/okf-website-env`)

```bash
pip install -r requirements.txt
Expand All @@ -82,11 +100,20 @@ Start the server:
python manage.py runserver
```

Another option is to use Docker.
Another option is to use Docker — wrapped by the Makefile:

```bash
make build
make run # http://localhost:8888
make stop
```

Or the raw commands:

```bash
docker build -t okfn .
docker run -d -p 8888:80 okfn
docker run -d --rm --name okfn -p 8888:80 okfn
docker stop okfn
```

## File uploads
Expand All @@ -111,7 +138,7 @@ of how it works: [Installing Tailwind CSS as a PostCSS plugin](https://tailwindc

The css build is done by `PostCSS` and the configuration files for it are `tailwind.config.cjs` and `postcss.config.cjs`.

Running `npm run build` will compile our main `styles.css` file and place it in `static/css/styles.css`. (It then will be collected by
Running `npm run build` (or `make css`) will compile our main `styles.css` file and place it in `static/css/styles.css`. (It then will be collected by
Django when building the Dockerfile)

**Remember:** Tailwind CSS works by scanning all of our HTML files, JavaScript components, and any other templates
Expand Down Expand Up @@ -162,12 +189,15 @@ For more info read this [doc](/docs/cloud/google-deploy.md).

## Dependency Management

Dependencies are managed with [pip-tools](https://github.com/jazzband/pip-tools).
Add new packages to `requirements.in` / `requirements.dev.in`
and compile `requirements.txt` / `requirements.dev.txt` with
`uv pip compile requirements.in -o requirements.txt` (previously `pip-compile`).
Dependencies are managed with [`uv`](https://github.com/astral-sh/uv) (formerly
[pip-tools](https://github.com/jazzband/pip-tools)). Add new packages to
`requirements.in` / `requirements.dev.in` and recompile the lock files:

```bash
make deps-compile # regenerates both requirements.txt files
```

You can run `pip list --outdated` to see outdated packages.
You can also run `pip list --outdated` to see outdated packages.

## Changelog

Expand Down
7 changes: 7 additions & 0 deletions foundation/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ def _parse_email_list(varname):
DEFAULT_FROM_EMAIL = 'noreply@%s' % ALLOWED_HOSTS[0]
SERVER_EMAIL = 'admin-noreply@%s' % ALLOWED_HOSTS[0]

# Origins (scheme + host[:port]) browsers may POST to. Required since Django 4
# for any cross-origin POST, including admin login from a non-default port.
# Set via DJANGO_CSRF_TRUSTED_ORIGINS (comma-separated).
CSRF_TRUSTED_ORIGINS = []
if env.get('DJANGO_CSRF_TRUSTED_ORIGINS'):
CSRF_TRUSTED_ORIGINS = env.get('DJANGO_CSRF_TRUSTED_ORIGINS').split(',')

INSTALLED_APPS = (
# CMS admin theme
'djangocms_admin_style',
Expand Down
39 changes: 39 additions & 0 deletions foundation/tests/test_home.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Smoke tests for the home page and a few critical entry points.

These run against `foundation.tests.urls`, the slim URL conf swapped in by
`foundation/test_settings.py` when `manage.py test` is invoked. That conf
mounts `cms.urls` at the root, so `/` is served by Django CMS just like in
production — but without the surrounding i18n/sitemap/sendemail patterns.

The test DB is empty, so there are no CMS Page objects. The point of these
tests is to catch regressions in routing, middleware, and template loading,
not to assert specific page content.
"""
from django.test import TestCase


class HomePageTests(TestCase):
def test_home_does_not_error(self):
response = self.client.get("/")
self.assertLess(
response.status_code,
500,
f"GET / returned {response.status_code}; expected < 500",
)

def test_home_returns_html_when_successful(self):
response = self.client.get("/")
if response.status_code == 200:
self.assertIn("text/html", response["Content-Type"])


class AdminEntryPointTests(TestCase):
def test_admin_redirects_anonymous_user(self):
response = self.client.get("/admin/")
self.assertEqual(response.status_code, 302)
self.assertIn("/admin/login/", response["Location"])

def test_admin_login_page_renders(self):
response = self.client.get("/admin/login/")
self.assertEqual(response.status_code, 200)
self.assertContains(response, "csrfmiddlewaretoken")
5 changes: 5 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ django-filer==3.4.4
django-markdown-deux==1.0.6
django-mptt==0.18.0
django-pagedown==2.2.1
# Pinned directly so we get a 4.x release that uses `importlib.metadata`
# instead of the deprecated `pkg_resources`. django-filer pulls it in
# transitively but doesn't pin a specific version, so without this we'd
# resolve to an old 3.x release that breaks on Python 3.12.
django-polymorphic==4.11.2
django-reversion==5.1.0
django-sekizai==4.1.0
django-storages==1.14.6
Expand Down
7 changes: 5 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ django-mptt==0.18.0
# via -r requirements.in
django-pagedown==2.2.1
# via -r requirements.in
django-polymorphic==3.1.0
# via django-filer
django-polymorphic==4.11.2
# via
# -r requirements.in
# django-filer
django-ranged-response==0.2.0
# via django-simple-captcha
django-reversion==5.1.0
Expand Down Expand Up @@ -215,6 +217,7 @@ typing-extensions==4.15.0
# -r requirements.in
# beautifulsoup4
# django-countries
# django-polymorphic
urllib3==2.6.3
# via
# -r requirements.in
Expand Down
Loading