Skip to content

Conversation

@mihow
Copy link
Collaborator

@mihow mihow commented Jan 28, 2026

Summary

Refactors taxa list endpoints to use a through model (TaxaListTaxon) instead of custom POST actions, following the same pattern as the project membership API for consistency and better RESTful design.

Changes

Backend

  • Created TaxaListTaxon through model (ami/main/models.py:3606)

    • ForeignKeys to TaxaList and Taxon
    • Unique constraint on (taxa_list, taxon)
    • Permission checks for authenticated users
  • Database migration (ami/main/migrations/0081_add_taxalist_taxon_through_model.py)

    • Creates through model table
    • Migrates existing M2M data (16,838 memberships preserved)
    • Updated TaxaList.taxa to use through parameter
  • Created serializers (ami/main/api/serializers.py:672)

    • TaxaListTaxonSerializer - Create/update with validation
    • TaxaListTaxonListSerializer - Simplified for list views
    • Validates taxon existence and uniqueness
    • Returns proper error messages for duplicates
  • Created TaxaListTaxonViewSet (ami/main/api/views.py:1633)

    • Nested routing under taxa lists
    • Standard CRUD operations
    • Custom delete_by_taxon action for convenience
  • Updated router (config/api_router.py:40)

    • Added nested router configuration
    • Routes: /taxa/lists/{taxalist_id}/taxa/
  • Removed deprecated actions

    • add_taxon() action from TaxaListViewSet
    • remove_taxon() action from TaxaListViewSet

Frontend

  • Updated useAddTaxaListTaxon hook

    • Changed from POST /taxa/lists/{id}/add_taxon/ to POST /taxa/lists/{id}/taxa/
  • Updated useRemoveTaxaListTaxon hook

    • Changed from POST /taxa/lists/{id}/remove_taxon/ to DELETE /taxa/lists/{id}/taxa/by-taxon/{taxon_id}/

Tests

  • Comprehensive test suite (ami/main/tests/test_taxa_list_taxa_api.py)
    • 16 test cases covering all CRUD operations
    • Permission checks
    • Error cases (duplicates, non-existent taxa)
    • Data migration verification
    • All tests passing ✅

API Changes

New Endpoints

  • GET /taxa/lists/{id}/taxa/ - List taxa in a taxa list
  • POST /taxa/lists/{id}/taxa/ - Add taxon (201 Created, 400 if duplicate)
  • GET /taxa/lists/{id}/taxa/{membership_id}/ - Retrieve membership details
  • DELETE /taxa/lists/{id}/taxa/{membership_id}/ - Remove by membership ID (204 No Content)
  • DELETE /taxa/lists/{id}/taxa/by-taxon/{taxon_id}/ - Remove by taxon ID (204 No Content, 404 if not found)

Removed Endpoints

  • POST /taxa/lists/{id}/add_taxon/ (deprecated)
  • POST /taxa/lists/{id}/remove_taxon/ (deprecated)

Benefits

  1. Proper HTTP semantics: POST creates (201), DELETE destroys (204), duplicates return 400
  2. RESTful design: Nested resources follow standard conventions
  3. Better error handling: Clear error messages for invalid operations
  4. Consistency: Matches the project membership API pattern
  5. Backward compatibility: M2M relationship still works through the through model
  6. Data integrity: Migration preserved all existing relationships
  7. Test coverage: Comprehensive test suite for all operations

Testing

# Run all tests
docker compose run --rm django python manage.py test ami.main.tests.test_taxa_list_taxa_api

# Verify data migration
docker compose exec django python manage.py shell -c "
from ami.main.models import TaxaList, TaxaListTaxon
for taxa_list in TaxaList.objects.all():
    assert taxa_list.taxa.count() == TaxaListTaxon.objects.filter(taxa_list=taxa_list).count()
print('✓ All data preserved')
"

Checklist

  • Backend implementation complete
  • Frontend hooks updated
  • Migration preserves existing data
  • Tests added and passing
  • API documentation updated in PR description
  • Follows project membership API pattern

🤖 Generated with Claude Code

@netlify
Copy link

netlify bot commented Jan 28, 2026

Deploy Preview for antenna-preview ready!

Name Link
🔨 Latest commit 4892c11
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6982abe0eeccac0008a31cf6
😎 Deploy Preview https://deploy-preview-1104--antenna-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 62 (🔴 down 4 from production)
Accessibility: 80 (no change from production)
Best Practices: 100 (no change from production)
SEO: 92 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/taxa-lists-through-model

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mihow mihow marked this pull request as draft January 28, 2026 00:58
… model)

Refactors taxa list endpoints to use nested routes and proper HTTP methods
while keeping the simple ManyToManyField (no through model).

**Backend changes:**
- Created TaxaListTaxonViewSet with nested routes under /taxa/lists/{id}/taxa/
- Simple serializers for input validation and output
- Removed deprecated add_taxon/remove_taxon actions from TaxaListViewSet
- Uses Django M2M .add() and .remove() methods directly

**Frontend changes:**
- Updated useAddTaxaListTaxon to POST to /taxa/ endpoint
- Updated useRemoveTaxaListTaxon to use DELETE method

**API changes:**
- POST /taxa/lists/{id}/taxa/ - Add taxon (returns 201, 400 for duplicates)
- DELETE /taxa/lists/{id}/taxa/by-taxon/{taxon_id}/ - Remove taxon (returns 204, 404 for non-existent)
- GET /taxa/lists/{id}/taxa/ - List taxa in list
- Removed POST /taxa/lists/{id}/add_taxon/ (deprecated)
- Removed POST /taxa/lists/{id}/remove_taxon/ (deprecated)

**Benefits:**
- Same API as project membership (consistency)
- No migration needed (keeps existing simple M2M)
- Proper HTTP semantics (POST=201, DELETE=204)
- RESTful nested resource design

**Tests:**
- Added comprehensive test suite (13 tests, all passing)
- Tests for CRUD operations, validation, and error cases

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@mihow mihow force-pushed the feat/taxa-lists-through-model branch from 3ab6b2f to 1f5e5d4 Compare January 28, 2026 01:10
@mihow mihow changed the title Refactor taxa list endpoints to use through model Refactor taxa list endpoints to follow the project-user membership API Jan 30, 2026
taxon_id: taxonId,
},
axios.delete(
`${API_URL}/${API_ROUTES.TAXA_LISTS}/${taxaListId}/taxa/by-taxon/${taxonId}/?project_id=${projectId}`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is by-taxon in URL intentional or Claude being a bit creative? I think we can skip!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha yeah good catch! Maybe its clarifying that you are passing the taxon ID and not the ID of the lookup table entry (the "membership ID" for project-users). But I agree to remove it!

@annavik
Copy link
Member

annavik commented Jan 30, 2026

Thanks Michael, I tested it out, works well and the new structure seems more aligned! Error handling works better now as well, for example I get an error when trying to remove a taxon that is not part of the list.

Some remaining things (perhaps separate from this PR):

  • Getting error 500 when creating new taxa list
  • It would be helpful if we had a way to only return direct taxa (see screenshot)

For example here, only genus "Vanessa" is a member, which makes it a bit confusing to see the species. When trying to remove them, I can see they are not part of the list.
Screenshot 2026-01-30 at 13 34 29

@mihow
Copy link
Collaborator Author

mihow commented Jan 31, 2026

Thanks for testing @annavik! I will look into those issues

mihow added 3 commits February 3, 2026 16:48
Fixes 500 error when creating taxa lists via the API. The error was caused
by attempting to directly assign many-to-many fields during model instantiation,
which Django does not allow.

Also addresses a security issue discovered during the fix: users were able to
assign taxa lists and processing services to arbitrary projects.

Changes:
- Make projects field read-only on TaxaListSerializer and ProcessingServiceSerializer
- Add perform_create() methods to handle m2m project assignment after instance creation
- Update UI to send project_id instead of project for proper backend detection
- Remove client-side project field from taxa list creation

Resources are now automatically assigned to the active project by the server,
preventing the m2m assignment error and ensuring proper project scoping.
@mihow
Copy link
Collaborator Author

mihow commented Feb 4, 2026

Added new include_descendants query param to the taxa list API! I set it to true by default since that is the current behavior. But I am fine to flip that.

New taxa management view only shows direct members
image

Existing taxa view still shows children/descendants
image

@mihow
Copy link
Collaborator Author

mihow commented Feb 4, 2026

  • I fixed the creation of taxa lists. This was interesting. We need to set the project that the taxa list belongs to. There could be a "security" issue here where you a user could add a list to a project they don't belong to. This is a wider issue though. You can't retrieve data from projects you don't belong to, but setting many-to-many relationships on other projects may need additional checks. I made a note of it and setup the structure so that should be easy to enforce when we do.

  • I also removed the "by-taxon" url prefix

@mihow mihow marked this pull request as ready for review February 4, 2026 02:19
@mihow mihow merged commit a86308f into feat/taxa-lists Feb 4, 2026
5 checks passed
@mihow mihow deleted the feat/taxa-lists-through-model branch February 4, 2026 02:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants