feat(projects): allow supporting organisations alongside the lead one - #7314
Open
StepFPV wants to merge 2 commits into
Open
feat(projects): allow supporting organisations alongside the lead one#7314StepFPV wants to merge 2 commits into
StepFPV wants to merge 2 commits into
Conversation
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.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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_idis 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_idis untouched and keeps its current meaning — it is the lead organisation.project_organisationslink 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:
project_organisations(unique onproject_id, organisation_id,ON DELETE CASCADEboth sides, indexed both FKs). No data migration needed, since existing rows keep their lead organisation where it already is.GETreturns the lead organisation id plus the supporting organisations, withname/slug/logoinlined so a caller can render them without a request per organisation.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 project —
is_user_action_permitted_on_projectstill consults onlyprojects.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
GETbe restricted for private/draft projects? I made it public to matchGET /projects/{id}/campaigns/andGET /projects/{id}/partners/, which are both public today. If you would rather this one is gated, it is a small change.Testing
flake8andblack --checkpass 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 andcreate_app, which no longer exists after the FastAPI migration. It looks dead — CI only runstests/api/. I wrote the tests intests/api/.requirements.txtis missing dependencies thatpyproject.tomlhas (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.