Skip to content

feat(projects): allow supporting organisations alongside the lead one - #7314

Open
StepFPV wants to merge 2 commits into
hotosm:developfrom
StepFPV:feat/7008-multiple-organisations-per-project
Open

feat(projects): allow supporting organisations alongside the lead one#7314
StepFPV wants to merge 2 commits into
hotosm:developfrom
StepFPV:feat/7008-multiple-organisations-per-project

Conversation

@StepFPV

@StepFPV StepFPV commented Aug 16, 2026

Copy link
Copy Markdown

Closes #7008

Problem

A project can only ever be attributed to a single organisation, through projects.organisation_id. Joint activations — the ESA case in the issue, and anything co-funded or co-run — have no way to record the other organisations involved.

Approach

I deliberately kept this additive, because projects.organisation_id is read in a lot of places (permissions, project search, summaries, the admin listing, CSV export), and I did not want a feature PR to move a column that much of the backend depends on.

So:

  • projects.organisation_id is untouched and keeps its current meaning — it is the lead organisation.
  • A new project_organisations link table holds the supporting organisations.

That gives the two tiers the issue asks for ("one designated lead organisation and optional partner/supporting organisations") without rewriting how the lead is stored or resolved. Nothing in the existing read paths changes behaviour.

The shape follows the existing project ↔ campaign relationship (backend/api/projects/campaigns.py), which is the closest analogue in the codebase, rather than inventing a new pattern.

What's included

New endpoints:

POST   /projects/{project_id}/organisations/{organisation_id}/
GET    /projects/{project_id}/organisations/
DELETE /projects/{project_id}/organisations/{organisation_id}/
  • Alembic migration creating project_organisations (unique on project_id, organisation_id, ON DELETE CASCADE both sides, indexed both FKs). No data migration needed, since existing rows keep their lead organisation where it already is.
  • GET returns the lead organisation id plus the supporting organisations, with name/slug/logo inlined so a caller can render them without a request per organisation.
  • Validation: rejects attaching the lead organisation as a supporting one, and rejects the same organisation twice.
  • 16 integration tests under tests/api/integration/api/projects/test_organisations.py.

Permissions

Linking and unlinking go through the existing ProjectAdminService.is_user_action_permitted_on_project, so only admins, the project author, managers of the lead organisation, or a project-manager team can change the list.

A supporting organisation deliberately gains no rights over the projectis_user_action_permitted_on_project still consults only projects.organisation_id. That felt like the safe default rather than something to decide in a PR, but it is a product question and I am happy to change it if you would rather managers of a supporting organisation could manage the project too.

Two things I would like your call on

  1. Should GET be restricted for private/draft projects? I made it public to match GET /projects/{id}/campaigns/ and GET /projects/{id}/partners/, which are both public today. If you would rather this one is gated, it is a small change.
  2. Should supporting organisations appear in project search/summary responses? Doing that means touching the existing organisation JOINs, which is exactly what I avoided here. Happy to do it as a follow-up once the shape above looks right to you.

Testing

flake8 and black --check pass on the exact commands from .github/workflows/pr_test_backend.yml.

I was not able to run the integration tests locally (no working Postgres/PostGIS on my machine at the time of submission), so the tests in this PR are unverified by me and I am relying on CI to exercise them. Flagging that explicitly rather than implying they are green — if CI turns up failures I will fix them.

Two things I noticed while looking for where to put the tests, unrelated to this PR but possibly worth an issue:

  • tests/backend/ still imports Flask and create_app, which no longer exists after the FastAPI migration. It looks dead — CI only runs tests/api/. I wrote the tests in tests/api/.
  • requirements.txt is missing dependencies that pyproject.toml has (databases, asyncpg, httpx), so it cannot build a working environment on its own.

Follow-up

Frontend is intentionally not in this PR — I would rather agree the API shape with you first than build the UI on a contract that might change. Happy to open it as a second PR straight after.

Disclosure

Implementation was done with AI assistance (Claude Code), reviewed and submitted by me.

A project could only ever be attributed to a single organisation via
`projects.organisation_id`. Joint activations funded or run by several
organisations had no way to record the others.

Adds a `project_organisations` link table holding the supporting
organisations of a project. The lead organisation keeps living on
`projects.organisation_id`, so project permissions, search, summaries and
CSV exports are untouched and keep resolving exactly one lead.

New endpoints, mirroring the existing project-campaign ones:

  POST   /projects/{project_id}/organisations/{organisation_id}/
  GET    /projects/{project_id}/organisations/
  DELETE /projects/{project_id}/organisations/{organisation_id}/

Linking is restricted to project managers via the existing
`is_user_action_permitted_on_project` check, which still consults only the
lead organisation - a supporting organisation gains no rights over the
project. Attaching the lead organisation as a supporting one, or the same
organisation twice, is rejected.

Refs hotosm#7008
The three test classes repeated the same fixture body, which SonarCloud
flagged as 5.5% duplication on new code (limit 3%). Moves the common
arrangement onto a shared base class and adds small helpers for the
repeated auth headers and service calls.

Same 15 tests, no behaviour change.
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attach multiple organisations to a single project

1 participant