Skip to content
Draft
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
49 changes: 29 additions & 20 deletions .github/workflows/lint_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,47 @@
branches: [main]
pull_request:

defaults:
run:
working-directory: django

jobs:
all:
runs-on: ubuntu-latest
env:
PYTHONPATH: ${{ github.workspace }}/django/
DJANGO_SETTINGS_MODULE: config.settings.test
TEST_PGUSER: postgres
TEST_PGPASSWORD: postgres
TEST_PGPORT: 5431
TEST_PGDATABASE: test_docurba
SECRET_KEY: test_secret_key
# Default supabase URL: https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=access-method&access-method=postgres
DATABASE_URL: postgresql://postgres:postgres@localhost:54322/postgres
UPSTREAM_NUXT: http://localhost:3000

steps:
- uses: actions/checkout@v4

- name: 🛠️ Installation de la CLI Supabase
uses: supabase/setup-cli@b60b5899c73b63a2d2d651b1e90db8d4c9392f51
with:
version: 2.60.0
- name: Set DATABASE_URL
run: |
echo "TEST_DATABASE_URL=postgres://${TEST_PGUSER}:${TEST_PGPASSWORD}@127.0.0.1:${TEST_PGPORT}/${TEST_PGDATABASE}" >> $GITHUB_ENV

- run: supabase db start
- uses: actions/checkout@v4 # TODO use sha

Check warning on line 25 in .github/workflows/lint_test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=MTES-MCT_Docurba&issues=AZ1TqHtaILKiuNOLJZ3E&open=AZ1TqHtaILKiuNOLJZ3E&pullRequest=1972

- uses: actions/setup-python@v5
- uses: actions/setup-python@v5 # TODO usa sha

Check warning on line 27 in .github/workflows/lint_test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=MTES-MCT_Docurba&issues=AZ1TqHtaILKiuNOLJZ3F&open=AZ1TqHtaILKiuNOLJZ3F&pullRequest=1972
with:
python-version-file: django/.python-version
- uses: astral-sh/setup-uv@v6

- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
activate-environment: true
- run: uv sync --locked --all-extras --dev --active
- run: pytest --create-db
- run: ruff format --diff .
- run: ruff check --output-format=github .
- run: python -m django makemigrations --check --dry-run --noinput || (echo "⚠ Migration manquante ⚠"; exit 1)

- run: uv sync --locked --all-extras --dev --active --project=django/

- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
with:
version: 2025.11.4 # [default: latest] mise version to install
install: true # [default: true] run `mise install`
cache: true # [default: true] cache mise using GitHub's cache
experimental: true # [default: false] enable experimental features

- name: 🛠️ Initialize the test database
run: mise start:tests

- run: pytest --cov django/tests/ django/tests/
- run: ruff format --diff django/
- run: ruff check --output-format=github django/
- run: django-admin makemigrations --check --dry-run --noinput || (echo "⚠ Migration manquante ⚠"; exit 1)
32 changes: 29 additions & 3 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,50 @@ node = "20"
dir = "{{config_root}}/nuxt"
run = "npm run dev"

[tasks."start:nuxt3"]
description="""
Serveur local Nuxt3.
Attention, le dépôt doit avoir été cloné et Mise doit avoir été installé avant.
Ensuite, assignez $NUXT3_PATH au chemin vers le dépôt.
"""
dir = "${NUXT3_PATH}"
run = "mise start"

[tasks."start:django"]
dir = "{{config_root}}/django"
run = "./manage.py runserver"

[tasks."start:supabase"]
description="""
Serveur local Supabase.
Serveur local Supabase contenant une interface web (Supabase studio), une base de données, un serveur SSO et un S3.
"""
dir = "{{config_root}}"
run = "supabase start"

[tasks.start]
depends = ["start:nuxt", "start:django", "start:supabase"]
[tasks."start:tests"]
description="""
Serveur PG initialisé avec le schéma de production et les migrations Django.
"""
dir = "{{config_root}}"
run = '''
#!/bin/bash
docker compose up test_db -d
RETRIES=15
until docker compose exec test_db bash -c '(psql -c "select 1 from public.only_exists_in_test;") > /dev/null 2>&1' || [ ${RETRIES} -eq 0 ]; do
echo "Waiting for Postgres server, $((RETRIES--)) remaining attempts..."
sleep 2
done
DJANGO_SETTINGS_MODULE=config.settings.test django-admin migrate
'''

[tasks."start"]
depends = ["start:nuxt", "start:django", "start:supabase", "start:nuxt3", "start:tests"]

[tasks."database:restore"]
description="""
Restauration locale de la dernière sauvegarde.
L'URL de la base de données n'est pas passée en paramètre pour éviter tout risque.
Il s'agit de l'URL du serveur local Supabase (lancé avec `supabase start` ou `mise start:supabase`).
"""
depends = ["start:supabase"]
dir = "{{config_root}}/django"
Expand Down
7 changes: 7 additions & 0 deletions django/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ export RCLONE_CONFIG="path_to_rclone_config_file"
# Review apps
export SUPABASE_PROJECT_ID=""
export SUPABASE_ACCESS_TOKEN=""

# Tests
export TEST_PGUSER=postgres
export TEST_PGPASSWORD=postgres
export TEST_PGPORT=5431
export TEST_PGDATABASE=test_docurba
export TEST_DATABASE_URL="postgres://${TEST_PGUSER}:${TEST_PGPASSWORD}@127.0.0.1:${TEST_PGPORT}/${TEST_PGDATABASE}"
2 changes: 1 addition & 1 deletion django/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

DATABASES = {
"default": {
**env.db("DATABASE_URL"),
**env.db("DATABASE_URL", default=""),
"CONN_MAX_AGE": env.int("CONN_MAX_AGE", 0),
"CONN_HEALTH_CHECK": True,
}
Expand Down
10 changes: 10 additions & 0 deletions django/config/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@
SESSION_COOKIE_SECURE = False
SECURE_SSL_REDIRECT = False
CREATE_UNMANAGED_TABLES = True
DATABASES = {
"default": {
**env.db("TEST_DATABASE_URL"),
"CONN_MAX_AGE": env.int("CONN_MAX_AGE", 0),
"CONN_HEALTH_CHECK": True,
"TEST": {
"MIRROR": "default",
},
},
}
18 changes: 0 additions & 18 deletions django/docurba/core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,9 @@ class CoreAppConfig(AppConfig):

def ready(self) -> None:
super().ready()
models.signals.pre_migrate.connect(create_unmanaged_tables, sender=self)
models.signals.post_migrate.connect(create_topics, sender=self)


def create_unmanaged_tables(*args: list[str, Any], **kwargs: dict[str, Any]) -> None: # noqa: ARG001
if not settings.CREATE_UNMANAGED_TABLES:
return

from docurba.core.models import ( # noqa: PLC0415
CommuneProcedure,
Event,
Procedure,
)

unmanaged_core_models = [Procedure, CommuneProcedure, Event]

for model in unmanaged_core_models:
with connection.schema_editor() as schema_editor:
schema_editor.create_model(model)


def create_topics(*args: list[str, Any], **kwargs: dict[str, Any]) -> None: # noqa: ARG001
from docurba.core.models import Topic # noqa: PLC0415

Expand Down
24 changes: 0 additions & 24 deletions django/docurba/users/apps.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,7 @@
from typing import Any

from django.apps import AppConfig
from django.conf import settings
from django.db import connection, models


class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "docurba.users"
verbose_name = "Utilisateurs"

def ready(self) -> None:
super().ready()
models.signals.pre_migrate.connect(create_unmanaged_tables, sender=self)


def create_unmanaged_tables(*args: list[str, Any], **kwargs: dict[str, Any]) -> None: # noqa: ARG001
if not settings.CREATE_UNMANAGED_TABLES:
return

from docurba.users.models import Profile, User # noqa: PLC0415

unmanaged_user_models = [User, Profile]

with connection.schema_editor() as schema_editor:
schema_editor.execute("CREATE SCHEMA auth;")

for model in unmanaged_user_models:
with connection.schema_editor() as schema_editor:
schema_editor.create_model(model)
6 changes: 1 addition & 5 deletions django/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@ dev = [
"pytest-django",
"ruff",
"pre-commit>=4.3.0", # https://github.com/pre-commit/pre-commit
"pytest-env>=1.5.0",
]

[tool.pytest.ini_options]
addopts = "--create-db --cov=. -x"
addopts = "--reuse-db -x --ds=config.settings.test"
xfail_strict = true
env = [
"DJANGO_SETTINGS_MODULE=config.settings.test",
]

[tool.ruff.lint]
select = ["ALL"]
Expand Down
4 changes: 2 additions & 2 deletions django/tests/core/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def test_topics_filter(self, admin_client: Client) -> None:
assertContains(response, procedure_with_topic.pk)

def test_collectivite_porteuse_type_filter(self, admin_client: Client) -> None:
polem = create_groupement(type=TypeCollectivite.POLEM)
cc = create_groupement(type=TypeCollectivite.CC)
polem = create_groupement(groupement_type=TypeCollectivite.POLEM)
cc = create_groupement(groupement_type=TypeCollectivite.CC)
procedure_polem = create_procedure(collectivite_porteuse=polem)
procedure_cc = create_procedure(collectivite_porteuse=cc)

Expand Down
7 changes: 6 additions & 1 deletion django/tests/surveys/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from docurba.core.models import Procedure
from docurba.surveys.models import ProcedureSurvey, Survey
from tests.factories import create_groupement
from tests.users.factories import create_user_and_profile


Expand All @@ -12,10 +13,14 @@ def test_fixtures(self) -> None:
_, profile = create_user_and_profile(
email="georges-eugene@haussmann.com", other_poste=["rédacteur", "maire"]
)
collectivite = create_groupement()
survey = Survey.objects.filter(name="zan_03_2026").first()
procedure = Procedure.objects.create()
ProcedureSurvey.objects.create(
survey=survey, procedure=procedure, respondant=profile
survey=survey,
procedure=procedure,
respondant=profile,
collectivite_code=collectivite,
)
assert procedure.surveys_answers.count() == 1
assert profile.surveys_answers.count() == 1
Expand Down
26 changes: 1 addition & 25 deletions django/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: docurba

services:
# Supabase PostgreSQL service used only in tests.
# Supabase's CLI only allows one database but we need two: one for dev and another one for tests.
# See mise.toml
# Highly inspired by https://github.com/supabase/supabase/blob/f128106801e474e685a384333f57d866421d655f/docker/docker-compose.yml.
# Documentation: https://supabase.com/docs/guides/self-hosting/docker
test_db:
container_name: supabase-db
image: supabase/postgres:15.1.1.13
restart: unless-stopped
volumes:
- ./docker/test_db/supabase_seeds/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
- ./docker/test_db/supabase_seeds/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
# Changes required for internal supabase data such as _analytics
- ./docker/test_db/supabase_seeds/_supabase.sql:/docker-entrypoint-initdb.d/migrations/97-_supabase.sql:Z
# Changes required for Analytics support
- ./docker/test_db/supabase_seeds/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z
# Seed data containing our own schema.
- ./docker/test_db/prod_schema_before_django_migrations.sql:/docker-entrypoint-initdb.d/seed.sql
ports:
- "${TEST_PGPORT}:5432"
environment:
PGUSER: ${TEST_PGUSER}
PGPASSWORD: ${TEST_PGPASSWORD}
PGDATABASE: ${TEST_PGDATABASE}
# Used by supabase seeds.
POSTGRES_USER: ${TEST_PGUSER}
POSTGRES_PASSWORD: ${TEST_PGPASSWORD}
POSTGRES_DB: ${TEST_PGDATABASE}
Loading
Loading