Skip to content
Open
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
57 changes: 41 additions & 16 deletions django/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ WeFa (Web Factory) delivers a set of modular Django apps that cover recurring we

## Features

- Shared utilities that power the higher-level apps (`nside_wefa.common`)
- Plug-and-play Django REST Framework authentication configuration (token and JWT) (`nside_wefa.authentication`)
- Legal consent tracking with automatic user onboarding and templated documents (`nside_wefa.legal_consent`)
- Per-user locale persistence with a public discovery endpoint (`nside_wefa.locale`)
- Append-only audit log with REST endpoints, model-registration UX, built-in event sources, and optional tamper-evident hash chain — built on `django-auditlog` (`nside_wefa.audit`)
- Shared utilities that power the higher-level apps ([`nside_wefa.common`](nside_wefa/common/))
- Plug-and-play Django REST Framework authentication configuration (token and JWT) ([`nside_wefa.authentication`](nside_wefa/authentication/README.md))
- Legal consent tracking with automatic user onboarding and templated documents ([`nside_wefa.legal_consent`](nside_wefa/legal_consent/README.md))
- Per-user locale persistence with a public discovery endpoint ([`nside_wefa.locale`](nside_wefa/locale/README.md))
- Append-only audit log with REST endpoints, model-registration UX, built-in event sources, and optional tamper-evident hash chain — built on `django-auditlog` ([`nside_wefa.audit`](nside_wefa/audit/README.md))
- Abstract attachment model with versioning, pluggable storage (S3/local/SFTP via `django-storages`), python-magic content-type sniffing, and a generic CRUD endpoint factory ([`nside_wefa.attachments`](nside_wefa/attachments/README.md))
- System checks and sensible defaults so configuration mistakes surface early
- A runnable [demo project](demo/README.md) showing how to consume every app end-to-end

## Installation

Expand All @@ -40,25 +42,35 @@ nside-wefa>=0.3.0

## Included Apps

### Common
### [Common](nside_wefa/common/)

Foundational helpers shared across the toolkit. You rarely interact with it directly, but it must be installed before the other apps.

### Authentication
### [Authentication](nside_wefa/authentication/README.md)

Automatically wires Django REST Framework authentication classes, URLs, and dependency checks. See `nside_wefa/authentication/README.md` for the full guide.
Automatically wires Django REST Framework authentication classes, URLs, and dependency checks. See the [authentication README](nside_wefa/authentication/README.md) for the full guide.

### Legal Consent
### [Legal Consent](nside_wefa/legal_consent/README.md)

Tracks acceptance of privacy and terms documents with templating support and REST endpoints. See `nside_wefa/legal_consent/README.md` for details.
Tracks acceptance of privacy and terms documents with templating support and REST endpoints. See the [legal consent README](nside_wefa/legal_consent/README.md) for details.

### Locale
### [Locale](nside_wefa/locale/README.md)

Persists each user's preferred locale and exposes the supported locales for the project over REST. See `nside_wefa/locale/README.md` for details.
Persists each user's preferred locale and exposes the supported locales for the project over REST. See the [locale README](nside_wefa/locale/README.md) for details.

### Audit
### [Audit](nside_wefa/audit/README.md)

Wraps `django-auditlog` to give every product an append-only audit store with four ergonomic ways to register models, REST endpoints (`/audit/events/` for staff, `/audit/me/` for end users), built-in event sources for `auth` / `legal_consent` / `locale`, an optional SHA-256 tamper-evident chain, and management commands for purge / verify / GDPR export. See `nside_wefa/audit/README.md` for details.
Wraps `django-auditlog` to give every product an append-only audit store with four ergonomic ways to register models, REST endpoints (`/audit/events/` for staff, `/audit/me/` for end users), built-in event sources for `auth` / `legal_consent` / `locale`, an optional SHA-256 tamper-evident chain, and management commands for purge / verify / GDPR export. See the [audit README](nside_wefa/audit/README.md) for details.

### [Attachments](nside_wefa/attachments/README.md)

Provides an abstract `Attachment` base model that consumer apps subclass to add file-attachment semantics to their tables. Versioning, storage routing through `django-storages`, whitelist-only content-type sniffing via `python-magic`, streaming size enforcement, hashing, and a generic CRUD endpoint factory in either *singleton* or *multi* mode. See the [attachments README](nside_wefa/attachments/README.md) for details.

### [Demo project](demo/README.md)

A runnable Django project that consumes every WeFa app and ships two illustrative consumer apps (`demo.profiles` for avatars, `demo.documents` for versioned PDFs / spreadsheets). Use it as a worked example when integrating the toolkit into a new project. See the [demo README](demo/README.md) for the reading order and quick start.

The Vue demo playground in [`../vue/`](../vue/README.md) talks to this Django demo over HTTP — its dev client is hard-wired to `http://localhost:8000`, so spin up `manage.py runserver` here before running `npm run dev` on the Vue side.

## Quick Start

Expand All @@ -77,6 +89,7 @@ Wraps `django-auditlog` to give every product an append-only audit store with fo
"nside_wefa.legal_consent",
"nside_wefa.locale",
"nside_wefa.audit",
"nside_wefa.attachments",
]
```

Expand Down Expand Up @@ -121,15 +134,25 @@ NSIDE_WEFA = {
"DEFAULT": "en",
},
"AUDIT": {
# All keys are optional. See nside_wefa/audit/README.md.
# All keys are optional. See the audit README.
# Track third-party models you can't edit:
# "MODELS": {"auth.Group": {"include_fields": ["name"]}},
# "TAMPER_EVIDENT": True, # SHA-256 hash chain (opt-in)
# "RETENTION_DAYS": 365, # used by wefa_audit_purge
},
"ATTACHMENTS": {
# All keys are optional. See the attachments README.
# "STORAGE": "default", # alias into settings.STORAGES
# "MAX_FILE_SIZE": 10 * 1024 * 1024, # global cap; subclasses may override
# "UPLOAD_PATH_PREFIX": "attachments/",
# "HASH_ALGORITHM": "sha256",
# "CONTENT_TYPE_SNIFF_BYTES": 2048,
},
}
```

Per-app settings reference: [authentication](nside_wefa/authentication/README.md), [legal consent](nside_wefa/legal_consent/README.md), [locale](nside_wefa/locale/README.md), [audit](nside_wefa/audit/README.md), [attachments](nside_wefa/attachments/README.md).

Validation happens through Django system checks. Run `python manage.py check` to surface configuration issues early.

## Requirements
Expand All @@ -138,6 +161,8 @@ Validation happens through Django system checks. Run `python manage.py check` to
- Django >= 6.0.4
- Django REST Framework >= 3.14.0
- djangorestframework-simplejwt >= 5.5.1 (if you enable JWT support)
- django-storages >= 1.14 (used by `nside_wefa.attachments`)
- python-magic >= 0.4.27 (used by `nside_wefa.attachments`; needs `libmagic` on the host)

## Local Development

Expand All @@ -150,7 +175,7 @@ source .venv/bin/activate
pip install -e .[dev]
```

Run the demo project:
Run the [demo project](demo/README.md):

```bash
python manage.py migrate
Expand Down
96 changes: 96 additions & 0 deletions django/demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# WeFa Demo Project

A minimal Django project that consumes every app shipped by the
`nside-wefa` package. Its purpose is **educational** — it shows the
shortest path from a fresh Django installation to a working integration
of the toolkit.

If you're new to the package, read the demo's source files alongside
each app's README to see how the documented APIs are wired up in
practice.

## What's inside

The demo project is a regular Django project with two demo apps that
illustrate consumer-side wiring:

| Folder | What it shows |
|---|---|
| [`demo/settings.py`](settings.py) | Installing every WeFa app, populating the `NSIDE_WEFA` settings dict, declaring `STORAGES` for the attachments app. |
| [`demo/urls.py`](urls.py) | Mounting each app's URLs at a stable prefix and including the demo apps' URL trees. |
| [`demo/profiles/`](profiles/) | Demo app — adds an `Avatar` model to users using the **singleton, non-versioned** mode of [`nside_wefa.attachments`](../nside_wefa/attachments/README.md). |
| [`demo/documents/`](documents/) | Demo app — adds a `Document` model accepting PDFs and Excel files using the **multi, versioned** mode of [`nside_wefa.attachments`](../nside_wefa/attachments/README.md). |
| [`demo/conftest.py`](conftest.py) | Shared pytest fixtures: per-test `MEDIA_ROOT` redirect, byte builders for PNG/PDF, two-user setup. |

Each demo app contains heavily commented `models.py`, `urls.py`, and a
matching `tests/test_*.py` file. The comments explain *why* each line
is there and what the alternatives look like, rather than restating
*what* the code does — they're written as reading material for
developers working through the toolkit for the first time.

## Per-app reading order

The fastest way to learn the toolkit is to read each app's README and
then jump to the matching demo wiring:

1. [`nside_wefa.common`](../nside_wefa/common/) — foundational helpers
(`get_section`, system check primitives). No demo app of its own;
the other demos rely on it transparently.
2. [`nside_wefa.authentication`](../nside_wefa/authentication/README.md) —
wired in `demo/settings.py` and `demo/urls.py`.
3. [`nside_wefa.legal_consent`](../nside_wefa/legal_consent/README.md) —
wired in `demo/settings.py` and `demo/urls.py`.
4. [`nside_wefa.locale`](../nside_wefa/locale/README.md) —
wired in `demo/settings.py` and `demo/urls.py`.
5. [`nside_wefa.audit`](../nside_wefa/audit/README.md) —
wired in `demo/settings.py` and `demo/urls.py`.
6. [`nside_wefa.attachments`](../nside_wefa/attachments/README.md) —
wired in `demo/settings.py`; demo apps live at
[`demo/profiles/`](profiles/) and [`demo/documents/`](documents/).

## Running the demo

From the `django/` directory:

```bash
uv sync --all-extras # install runtime + dev dependencies
uv run python manage.py migrate
uv run python manage.py createsuperuser
uv run python manage.py runserver
```

A few interesting URLs once the server is up:

| URL | What it does |
|---|---|
| `/admin/` | Django admin — log in with the superuser. |
| `/auth/` | Authentication endpoints exposed by `nside_wefa.authentication`. |
| `/legal-consent/agreement/` | Per-user consent state (login required). |
| `/locale/` | List supported locales. |
| `/audit/events/` | Audit event log (staff only). |
| `/me/avatar/` | Demo: singleton avatar (POST to upload, GET to read, GET `/download/` to stream). |
| `/documents/` | Demo: multi-document store (POST to upload v1, POST `/<id>/versions/` to bump, GET `/<id>/versions/` for history). |

## Running the demo's tests

```bash
uv run pytest demo/
```

The demo tests are HTTP-level — they exercise the same endpoints a
frontend would call and exist primarily as worked examples for
consumers writing their own test suites against the toolkit. See
[`demo/profiles/tests/test_avatar.py`](profiles/tests/test_avatar.py)
and [`demo/documents/tests/test_documents.py`](documents/tests/test_documents.py).

## Notes for readers

- The demo intentionally uses `FileSystemStorage` and SQLite. Swap
these for S3 / Postgres in a real product by editing
[`STORAGES`](settings.py) and [`DATABASES`](settings.py).
- Several of the per-app READMEs include *additional* configuration
knobs the demo doesn't exercise. Consult them when adapting a demo
pattern to a real product.
- The demo project is **not** packaged on PyPI — only the
`nside_wefa.*` apps are. The demo lives in the source tree to make
the toolkit easier to learn and to drive the test suite.
98 changes: 98 additions & 0 deletions django/demo/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Shared pytest fixtures for the demo apps.

Fixtures are scoped to ``demo/`` — they auto-apply to any test under
this tree. The patterns shown here are the same ones a real consumer
would use when writing tests against their own ``Attachment`` subclass:

- :func:`isolated_media` redirects ``MEDIA_ROOT`` to a per-test temp
directory so test runs never leak files into the project's persistent
media folder.
- The byte fixtures are real magic-byte headers. python-magic relies on
the leading bytes of the stream to determine the MIME, so synthetic
payloads must look like the real format.
- :func:`upload_file` is a small builder that returns a Django
:class:`SimpleUploadedFile` — what views expect on the request.
"""

from __future__ import annotations

from typing import Callable

import pytest
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile


# A 1×1 transparent PNG. Just enough bytes for libmagic to identify
# the file as image/png.
PNG_HEADER = (
b"\x89PNG\r\n\x1a\n"
b"\x00\x00\x00\rIHDR"
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89"
b"\x00\x00\x00\rIDATx\x9cc\xfa\xcf\x00\x00\x00\x02\x00\x01\xe2!\xbc\x33"
b"\x00\x00\x00\x00IEND\xaeB`\x82"
)

# The smallest PDF that's also a valid one — enough for libmagic to
# detect application/pdf.
PDF_HEADER = (
b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"
b"2 0 obj\n<< /Type /Pages /Count 0 /Kids [] >>\nendobj\n"
b"xref\n0 3\n0000000000 65535 f \n"
b"0000000015 00000 n \n0000000060 00000 n \n"
b"trailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n110\n%%EOF\n"
)


@pytest.fixture(autouse=True)
def isolated_media(tmp_path, settings):
"""Redirect MEDIA_ROOT to a tmp dir so test files don't persist."""
settings.MEDIA_ROOT = str(tmp_path / "media")
return tmp_path / "media"


@pytest.fixture
def png_bytes() -> bytes:
return PNG_HEADER


@pytest.fixture
def pdf_bytes() -> bytes:
return PDF_HEADER


@pytest.fixture
def upload_file() -> Callable[..., SimpleUploadedFile]:
"""Return a builder for ``SimpleUploadedFile`` payloads.

The third arg (``content_type``) is what the client *claims*. The
attachments library ignores it — it sniffs the actual MIME from the
bytes via python-magic — but the field is still useful when writing
tests that exercise the "client lies about type" branch.
"""

def _build(
name: str,
content: bytes,
content_type: str = "application/octet-stream",
) -> SimpleUploadedFile:
return SimpleUploadedFile(name=name, content=content, content_type=content_type)

return _build


@pytest.fixture
def user(db):
"""A logged-in test user."""
return get_user_model().objects.create_user(
username="alice", email="alice@example.com", password="x"
)


@pytest.fixture
def other_user(db):
"""A second user, used to assert tenant isolation."""
return get_user_model().objects.create_user(
username="bob", email="bob@example.com", password="x"
)
Empty file.
7 changes: 7 additions & 0 deletions django/demo/documents/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class DocumentsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "demo.documents"
verbose_name = "Demo Documents"
Loading
Loading