Repository navigation
Conversation
…h the API Adds /api/v2/occurrences/sets/ so curators can save a list of occurrences as a named set, list a project's sets with their size, and pick one from a choices endpoint that mirrors the capture set picker. The project and the occurrences are set once, on create: both are refused on PATCH and there is no PUT. Occurrence ids are checked against the project in one query and the error names up to ten that are missing or outside it. The project for the create permission is read from the body only, so the project checked is the one written to. Public sets are offered to every project but cannot be changed here. Row permissions are resolved against the active project, as for taxa lists, without a query per row. Split from #1492. Part of #763. Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
Adds an occurrence_set_id query parameter to the occurrence list and the occurrence stats endpoints, named like collection_id and taxa_list_id. It is a filter backend rather than a FilterSet field so the export framework can run the same filter later. The value is parsed with SingleParamSerializer, so a non-integer returns 400. The filter only narrows: the active project and the project's default filters still apply, so a set never shows an occurrence the list would otherwise hide. Split from #1492. Part of #763. Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
… set under the request's project A public set, one with no project, was listed with a details link that returned 403 for every caller, because the generic object check refuses anything without a project. The detail queryset already limits a caller to sets of projects they can see plus public sets, so retrieve now needs no further check. Editing a public set is still refused. A set shared by two projects (only possible through the Django admin) reported edit permissions on its row from the project in the request, while the edit itself was checked against the set's first project, so the offered buttons could fail and a curator of the first project could edit a set another project also used. An edit is now checked against the request's project when the set belongs to it, and against the set's first project otherwise. Tests cover public reads for every kind of caller and both directions of the shared-set case. Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
…n to The occurrence list's "Create set" button reads the collection-level user_permissions from the set choices response, so a refactor of the paginator or the role grants could hide the button from curators or offer it to basic members without any test failing. This test fixes the answer for the owner, an ML data manager, a superuser, a basic member, a non-member and an anonymous visitor. Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
✅ Deploy Preview for antenna-preview canceled.
|
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Summary
This is for the people who curate data for model evaluation and retraining. With this change they can save a hand-picked list of occurrences as a named occurrence set through the API, list and choose a project's sets, and narrow the occurrence list to the members of one set, which is how a set is reviewed. The list of occurrences is fixed when the set is created, so a classifier scored on a set today and another scored on it next month were measured on the same occurrences.
It is the second step of the occurrence sets epic (#763). The work was split from #1492 by @mohamedelabbas1996, and it carries his API code and tests with the contract changes agreed in review: the filter is named
occurrence_set_id, membership can only be set on create, ids are validated in one query, and choices follow the capture set picker. His evaluation and retraining stacks can rebase onto it, with the small renames listed under "For the stacked branches" below.List of Changes
POST /api/v2/occurrences/sets/withname,description,project_id,occurrence_ids.OccurrenceSetPermissioncheckscreate_occurrenceseton the project named in the body.occurrence_idsis aListField(IntegerField)checked against the project in one query.PrimaryKeyRelatedField(many=True), which runs one query per id. A non-integer or unknownproject_idreturns 400.occurrence_idsandproject_idare refused on PATCH; PUT is not offered (405).GET /occurrences/sets/?project_id=(project required) andGET /occurrences/sets/choices/?project_id=.ChoicesPagination, names only.OccurrenceSetPermissionallows retrieve, since the queryset already limits what a caller can reach. Edits fall through tocheck_permission, which refuses an object with no project.project_idnames one of the set's projects, the edit check uses the caller's role in that project; otherwise the set's first project governs.OccurrenceSetFilterbackend readingoccurrence_set_id, added toOCCURRENCE_FILTER_BACKENDS, documented in the OpenAPI schema.Detailed Description
Permissions. Create is the only action without a saved object to check, so
OccurrenceSetPermissionhandles it: anonymous callers are refused,project_idis parsed from the request body withSingleParamSerializer(a non-integer gives 400), an unknown project gives 400, and otherwise the caller needscreate_occurrenceseton that project. Reading a set needs no object check: the detail queryset goes throughvisible_for_user(), so a caller only reaches sets of projects they can see, plus public sets, which anyone may read. An edit is checked against the project in the request when the set belongs to it, so that a set shared by two projects (possible only through the admin) can be edited by the curators of either, under their own project, which matches theuser_permissionson its row in that project's list. Without such a project,BaseModel.check_permissionchecks the set's first project and refuses a public set.OccurrenceSetViewSet.createskipsDefaultViewSet's unsaved-instance check, because both the project and the occurrences are many-to-many and cannot be put on an unsaved instance.Visibility. Listing needs no permission branch: the queryset goes through
visible_for_user(), which hides the sets of draft projects from non-members, and thenfor_project(), which adds public sets. A non-member listing a draft project sees only public sets.Per-row permissions. A set can belong to several projects, so the
user_permissionson each row are resolved against the active project withadd_m2m_object_permissions, the same helper taxa lists use. The project and the caller's permissions are looked up once per request, and the set's projects are prefetched, so the list does not grow by a query per row. A detail read withoutproject_iduses the set's own project, which keeps the offered actions in line with whatcheck_permissionallows (none for a public set).Counts.
occurrences_countcomes from thewith_occurrences_count()annotation when it is present. The create response uses the number of ids just added rather than running aCOUNT.Routing.
occurrences/setsis registered beforeoccurrences, likeoccurrences/stats, so the occurrence detail route does not readsetsas a primary key.What is measured and what is not. The query-count tests compare counts directly (create with 5 ids against 50, and list and choices with 1 set against 3) and require them to be equal. Removing the
projectsprefetch makes both list tests fail (14 against 12 and 16 against 14), which shows the tests detect a per-row query. No timings were taken on production-sized data. The filter adds one join through the membership table, and its cost on large projects has not been measured.For the stacked branches
The evaluation (#1493, #1506) and retraining (#1501 to #1504) branches build on #1492's version of this code. When they rebase onto this PR and the model PR below it:
occurrence_set_id, notoccurrence_set. In the evaluation branch this is the UI filter key (ui/src/utils/useFilters.ts,filter-control.tsx) and theoccurrence_set = RelatedIdFilter(field_name="evaluation_sets")FilterSet field, which this PR'sOccurrenceSetFilterbackend replaces.occurrence_sets, notevaluation_sets(for exampleoccurrence__evaluation_setsinami/ml/training/dataset.pyon the retraining branch).0100_occurrence_setand0101_grant_occurrence_set_permissions, as on the retraining branches. The evaluation branch still has them as 0098 and 0099.ami/main/test_occurrence_sets.py, which will conflict with this PR's version of the file. Take this PR's version and re-add any tests the branch needs on top of it.?occurrence_set=parameter ofml/training-data/summarytooccurrence_set_id, so every endpoint names a set the same way.OccurrenceSet.objects.for_project(),visible_for_user()and theoccurrences/setsroute are unchanged from #1492, so code that only uses those should keep working.How to Test
docker compose run --rm django python manage.py test ami.main.test_occurrence_sets ami.main.models_future.tests.test_occurrence_setsBy hand, as a user with the ML data manager role in a project:
POST /api/v2/occurrences/sets/with{"name": "Blind set", "project_id": <id>, "occurrence_ids": [<ids>]}returns 201 withoccurrences_count.GET /api/v2/occurrences/?project_id=<id>&occurrence_set_id=<set id>returns only those occurrences that the project's default filters keep.PATCH /api/v2/occurrences/sets/<set id>/with{"occurrence_ids": [...]}returns 400, and with{"name": "Renamed"}returns 200.GET /api/v2/occurrences/sets/choices/?project_id=<id>lists the project's sets and any public sets, most recently updated first.GET /api/v2/occurrences/sets/<public set id>/returns 200 for any caller, andPATCHon it returns 403, even for a superuser.Related Issues
Closes #1534
Part of #763
Split from #1492
Stacked on
feat/occurrence-set-model(the occurrence set model, migrations, permissions and Django admin). Retarget this PR tomainonce that one merges.🤖 Generated with Claude Code
https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh