Skip to content

Conversation

AlexVelezLl
Copy link
Member

Summary

  • Adds DeletedFacilityUserViewSet for soft-deleted users management
  • Adds tests for listing, hard deleting and restoring soft deleted users.
  • Fix identifier filter column id that was causing a 500 error when trying to sort users by id_number, and prevented that 500 errors are thrown when a worng sorting field is passed.

Comments

  • Since the trash users table (in the UI) is pretty much the same table as the users table, I have decided to inherit from the current FacilityUserViewSet to reuse all the current sorting, filtering, searching, pagination machinery without having to duplicate too much code. Is this is not a common pattern I can just duplicate the relevant parts of the code.
  • I tried to restore the users just by overriding the date_delted with a patch request, and it correctly restored the user, but the api was returning an error 404 because it was trying to query the just updated user, and it wasnt finding it because after the update, this user wasnt belonging to the current queryset (soft_delted_objects) anymore. Gemini suggested that we could override the get_object method, but it said that a cleaner option here was to implement a new action to better encapsulate the restore behavior. Im open to look for the best solution here.

References

Closes #13445.

Reviewer guidance

  • Run pytest.

@github-actions github-actions bot added DEV: backend Python, databases, networking, filesystem... APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) DEV: frontend labels Jun 20, 2025
@AlexVelezLl AlexVelezLl requested a review from rtibbles June 20, 2025 13:57
@AlexVelezLl
Copy link
Member Author

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Jun 20, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

coderabbitai bot commented Jun 20, 2025

Walkthrough

This change introduces a new DeletedFacilityUserViewSet to manage soft-deleted users, allowing for hard deletion and restoration. It updates the API routing to expose this viewset, adds comprehensive tests for its functionality, and aligns frontend column identifiers with backend field names. Minor enhancements are also made to the existing user viewset.

Changes

Files / Grouped Files Change Summary
kolibri/core/auth/api.py Added DeletedFacilityUserViewSet for soft-deleted user management; updated FacilityUserViewSet fields and sorting logic.
kolibri/core/auth/api_urls.py Registered DeletedFacilityUserViewSet with the API router.
kolibri/core/auth/test/test_api.py Added tests for soft-deleted user API: retrieval, ordering, hard deletion, and restoration scenarios.
kolibri/plugins/facility/assets/src/views/UserPage/index.vue Renamed user table column identifier from 'identifier' to 'id_number' for consistency with backend.

Assessment against linked issues

Objective Addressed Explanation
Introduce DeletedFacilityUserViewSet in correct module, using appropriate queryset and permissions (13445)
Include date_deleted in values and serializer, ensure PATCH can restore users, DELETE hard-deletes, and use correct queryset (13445)
Update serializer context to use all_objects where needed (13445)
Write comprehensive tests for fetch, delete, undelete, and integration with FacilityUserViewSet (13445)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
kolibri/core/auth/api.py (2)

529-538: Simplify control flow by removing unnecessary else clause.

The else clause after the return statement is unnecessary and can be simplified.

 def destroy(self, request, *args, **kwargs):
     if kwargs.get("pk"):
         # Single object deletion
         user = self.get_object()
         user.delete()
         return Response(status=status.HTTP_204_NO_CONTENT)
-    else:
-        # Bulk deletion
-        return self.bulk_destroy(request, *args, **kwargs)
+    # Bulk deletion
+    return self.bulk_destroy(request, *args, **kwargs)

545-553: Simplify dictionary key check.

Use direct membership testing instead of calling .keys() for better performance and readability.

 def allow_bulk_restore(self):
     """
     Hook to ensure that the bulk restore should be allowed.
     By default this checks that the restore is only applied to
     filtered querysets.
     """
     return any(
-        key in self.filterset_fields for key in self.request.query_params.keys()
+        key in self.filterset_fields for key in self.request.query_params
     )
kolibri/core/auth/test/test_api.py (1)

1119-1142: Consider using actual soft delete mechanism instead of direct database updates.

While the ordering logic is correct, directly updating date_deleted via database queries may not accurately reflect real-world usage. Consider using the actual soft delete endpoint to create the test data for more authentic testing.

-        for idx, user in enumerate(users):
-            models.FacilityUser.objects.filter(id=user.id).update(
-                date_deleted=timezone.now() - timedelta(days=idx + 1)
-            )
+        # Soft delete users at different times to create natural ordering
+        for idx, user in enumerate(users):
+            with patch('django.utils.timezone.now', return_value=timezone.now() - timedelta(days=idx + 1)):
+                self.client.delete(
+                    reverse("kolibri:core:facilityuser-detail", kwargs={"pk": user.pk})
+                )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4ad06e and b087aa4.

📒 Files selected for processing (4)
  • kolibri/core/auth/api.py (3 hunks)
  • kolibri/core/auth/api_urls.py (2 hunks)
  • kolibri/core/auth/test/test_api.py (1 hunks)
  • kolibri/plugins/facility/assets/src/views/UserPage/index.vue (1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
kolibri/core/auth/api.py

552-552: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🪛 Pylint (3.3.7)
kolibri/core/auth/api.py

[refactor] 520-520: Too many ancestors (9/7)

(R0901)


[refactor] 531-538: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Python unit tests for currently supported Python versions (3.12)
  • GitHub Check: Python unit tests on Windows Server (3.8)
  • GitHub Check: Python unit tests for currently supported Python versions (3.11)
  • GitHub Check: Python unit tests for currently supported Python versions (3.13)
  • GitHub Check: Python postgres unit tests
  • GitHub Check: Python unit tests on Mac OS (3.10)
  • GitHub Check: Python unit tests for currently supported Python versions (3.10)
  • GitHub Check: Python unit tests for currently supported Python versions (3.9)
  • GitHub Check: Python unit tests for EOL Python versions (3.7)
  • GitHub Check: Python unit tests for EOL Python versions (3.6)
  • GitHub Check: Python unit tests for EOL Python versions (3.8)
  • GitHub Check: All file linting
  • GitHub Check: Build WHL file / Build WHL
🔇 Additional comments (11)
kolibri/plugins/facility/assets/src/views/UserPage/index.vue (1)

234-234: LGTM! Frontend-backend field alignment fix.

This change correctly aligns the column identifier with the backend field name, ensuring consistent sorting and filtering behavior for the user identifier column.

kolibri/core/auth/api_urls.py (2)

5-5: LGTM! Proper import for new viewset.


41-43: LGTM! Standard router registration.

The routing configuration follows established patterns and correctly exposes the new DeletedFacilityUserViewSet endpoints.

kolibri/core/auth/api.py (5)

460-461: LGTM! Backend support for id_number sorting and filtering.

Adding "id_number" to both values and ordering_fields enables proper sorting and filtering functionality for the user identifier field, addressing the 500 error mentioned in the PR objectives.


508-510: LGTM! Improved error handling in consolidate method.

Using x.get(ordering_param, "") with a default value prevents KeyError exceptions when an invalid sorting field is passed, which addresses the 500 error prevention mentioned in the PR objectives.


520-527: Well-designed inheritance approach for code reuse.

Inheriting from FacilityUserViewSet effectively reuses existing functionality while appropriately extending the values and ordering fields with date_deleted for soft-deleted user management.


540-543: LGTM! Appropriate hard deletion for soft-deleted users.

The bulk destroy implementation correctly performs hard deletion (actual .delete()) instead of soft deletion, which is the expected behavior for managing soft-deleted users. The permission check prevents users from deleting themselves.


555-571: LGTM! Well-implemented restore functionality.

The restore action properly:

  • Validates that bulk restore is only allowed on filtered querysets
  • Checks for existence of users to restore
  • Updates date_deleted to None to restore users
  • Returns appropriate HTTP status codes

The implementation follows REST conventions and includes proper error handling.

kolibri/core/auth/test/test_api.py (3)

1094-1117: Excellent test coverage for soft-deleted user retrieval.

This test properly validates the core functionality of the new DeletedFacilityUserViewSet by creating users, soft-deleting them, and verifying they can be retrieved with proper date_deleted values.


1178-1195: Well-structured test for successful bulk hard delete.

This test properly validates the complete hard delete workflow: soft delete followed by hard delete, with correct assertions verifying the users are permanently removed.


1216-1226: Good error case validation for restore functionality.

This test properly validates that attempting to restore non-soft-deleted users returns an appropriate 404 error, ensuring the API behaves correctly in edge cases.

@AlexVelezLl AlexVelezLl force-pushed the deleted-facilityuser-viewset branch 2 times, most recently from d6142a9 to e3b46d4 Compare June 20, 2025 14:23
@AlexVelezLl
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Jun 20, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

github-actions bot commented Jun 20, 2025

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
kolibri/core/auth/api.py (3)

520-527: Consider the inheritance depth complexity.

The static analysis tool flagged "Too many ancestors (9/7)" for this class. While inheriting from FacilityUserViewSet provides good code reuse for filtering, sorting, and pagination, the deep inheritance hierarchy adds complexity.

The inheritance approach is reasonable for code reuse, but monitor if this becomes difficult to maintain. Consider composition patterns if the inheritance chain grows further.


544-560: Improve error message for bulk restore validation.

The allow_bulk_restore validation correctly prevents accidental bulk restoration of all deleted users by requiring filters. However, returning a 400 status with no error message could confuse API consumers.

        if not self.allow_bulk_restore():
            return Response(
+               {"detail": "Bulk restore requires query filters to prevent accidental restoration of all deleted users."},
                status=status.HTTP_400_BAD_REQUEST,
            )

562-567: Consider returning restoration count for better UX.

The restore action currently returns 204 No Content. Consider returning information about how many users were restored to provide better feedback to API consumers.

        users.update(date_deleted=None)
+       restored_count = users.count()

-       return Response(status=status.HTTP_204_NO_CONTENT)
+       return Response({"restored_count": restored_count}, status=status.HTTP_200_OK)
kolibri/core/auth/test/test_api.py (1)

13-13: Consider using unittest.mock for consistency.

For modern Python versions, consider importing from unittest.mock instead of the standalone mock package:

-from mock import patch
+from unittest.mock import patch
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b087aa4 and e3b46d4.

📒 Files selected for processing (2)
  • kolibri/core/auth/api.py (3 hunks)
  • kolibri/core/auth/test/test_api.py (2 hunks)
🧰 Additional context used
🪛 Pylint (3.3.7)
kolibri/core/auth/api.py

[refactor] 520-520: Too many ancestors (9/7)

(R0901)

⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: Python unit tests for currently supported Python versions (3.13)
  • GitHub Check: Python unit tests for currently supported Python versions (3.12)
  • GitHub Check: Python unit tests on Windows Server (3.8)
  • GitHub Check: Python unit tests on Mac OS (3.10)
  • GitHub Check: Python unit tests for currently supported Python versions (3.11)
  • GitHub Check: Python unit tests for currently supported Python versions (3.9)
  • GitHub Check: Python unit tests for currently supported Python versions (3.10)
  • GitHub Check: Python unit tests for EOL Python versions (3.6)
  • GitHub Check: Python unit tests for EOL Python versions (3.7)
  • GitHub Check: Python unit tests for EOL Python versions (3.8)
  • GitHub Check: Python postgres unit tests
  • GitHub Check: Build WHL file / Build WHL
🔇 Additional comments (12)
kolibri/core/auth/api.py (4)

460-460: LGTM: Fixes ordering by id_number field.

Adding id_number to ordering_fields resolves the 500 error when sorting users by this field, since it was already present in the values tuple but missing from ordering fields.


508-510: Good defensive programming to prevent KeyError.

Using .get(ordering_param, "") instead of direct dictionary access prevents 500 errors when invalid sorting fields are passed, which aligns with the PR objective.


529-542: Implementation correctly handles hard deletion with permission checks.

The destroy method properly implements hard deletion (.delete()) instead of soft deletion for the deleted users viewset, and perform_bulk_destroy includes the necessary permission check to prevent users from deleting themselves.


552-568: ```shell
#!/bin/bash

Display FacilityUserViewSet including its permission_classes and filter_backends

rg -n "class FacilityUserViewSet" -A50 -B5 kolibri/core/auth/api.py


</details>
<details>
<summary>kolibri/core/auth/test/test_api.py (8)</summary>

`1095-1118`: **Well-structured test for soft-deleted user retrieval.**

This test comprehensively covers the soft-deleted user listing functionality with proper setup, execution, and verification steps.

---

`1120-1150`: **Excellent use of mocking for time-based testing.**

This test properly uses `mock.patch` to control timestamps and verify date-based ordering functionality. The combination of mocked deletion times and manual database updates ensures reliable test results.

---

`1152-1169`: **Good security validation for bulk operations.**

This test properly verifies that bulk hard delete operations require explicit ID filtering, preventing accidental mass deletions.

---

`1171-1184`: **Correctly tests no-op behavior for hard delete.**

This test appropriately verifies that attempting to hard delete non-soft-deleted users results in a successful response but no actual deletion, implementing graceful handling of edge cases.

---

`1186-1203`: **Comprehensive test for complete hard deletion.**

This test properly validates the full hard delete workflow, ensuring soft-deleted users are permanently removed from the database.

---

`1205-1222`: **Consistent security validation for restore operations.**

This test appropriately mirrors the security requirements for hard delete operations, ensuring restore operations also require explicit ID filtering to prevent unintended bulk actions.

---

`1224-1234`: **Appropriate error handling for invalid restore.**

This test correctly validates that attempting to restore non-soft-deleted users returns a 404 status, providing clear feedback for invalid operations.

---

`1236-1256`: **Comprehensive restore functionality validation.**

This test thoroughly validates the user restoration workflow, confirming both the removal from soft-deleted state and successful restoration to active status.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Member

@nucleogenesis nucleogenesis left a comment

Choose a reason for hiding this comment

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

To your question about the approach - I think that your restore() method could probably just be renamed to update() and the default DRF API for the PATCH request should work as expected.


For the question of inheriting from the FacilityUserViewset class - this results in there being some unused routes provided for the PUT and POST handlers - so for example, I ran this through the API Explorer:

curl -X 'POST' \
  'http://localhost:8000/api/auth/deletedfacilityuser/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFToken: 0qHGvU9WO8WrJq077ROqHf8DEMDfZpiJeiBUnTjG0XHaqYxwl3M3CVID9gGjSa3o' \
  -d '{
  "username": "c",
  "full_name": "b",
  "password": "b",
  "facility": "54949ffd24603a3c8b886df966a83b76",
  "id_number": "b",
  "gender": "MALE",
  "birth_year": "1988",
  "extra_demographics": {}
}'

It returns a 404 BUT it also successfully creates the user (I suspect the 404 is related to what you ran into w/ the update() method override).

So we'll probably either want to build a new class that borrows some of the things used in FacilityUserViewSet -- or we could simply override those methods in this subclass to return 404 all the time. I'm sure there are other approaches available but these feel simplest -- keen to hear @rtibbles thoughts here as well.

Comment on lines 536 to 535
# Bulk deletion
return self.bulk_destroy(request, *args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

Should this be in an else: block for only when there isn't a pk? See Richard's comment on similar code in @ozer550 's PR

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh good catch. The if above is early returning a response already, so this is similar to have an else block. I will add the else clause to be more explicit on the intention. However, I think we can never get to this point, since it seems that if the pk arg isnt provided, it automatically just runs the bulk_destroy method, but Im probably missing something. Just added here for consistency with the FacilityUserViewSet implementation.

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't bother adding the else - early return pattern seems fine to me, and I have positively encouraged it elsewhere!

Copy link
Member

@rtibbles rtibbles left a comment

Choose a reason for hiding this comment

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

A few questions, one for @nucleogenesis about what the ordering should be, one for @AlexVelezLl about what the intended restrictions actually are.

Comment on lines 536 to 535
# Bulk deletion
return self.bulk_destroy(request, *args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't bother adding the else - early return pattern seems fine to me, and I have positively encouraged it elsewhere!

class DeletedFacilityUserViewSet(FacilityUserViewSet):
"""Viewset for managing soft-deleted FacilityUsers."""

order_by_field = "username"
Copy link
Member

Choose a reason for hiding this comment

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

I don't recall the details of the designs, @nucleogenesis are we ordering in the frontend by date deleted (most recent first) or by something else? Do we allow sorting by other fields? Would be good to have the order_by_field and ordering_fields set according to the needs of the frontend, but I don't see it detailed in the issue.

Copy link
Member

@nucleogenesis nucleogenesis Jun 24, 2025

Choose a reason for hiding this comment

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

Great catch Richard thank you for that - I failed to consider it when writing the issue.

The design for the trash page shows that we'll order them so that the longest-ago-deleted user is shown first (ie, the users in the most imminent danger of being permanently deleted). Although I can see a case for the inverse as well.

image

I think changing it to order by date_deleted in either direction will work for this PR then we'll be able to quickly make a change once it gets into user testing if QA et al think it should be the other direction by default.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! Will change this to -date_deleted then 👐.

+ f"?member_of={self.facility.id}"
)

self.assertEqual(response.status_code, 400)
Copy link
Member

Choose a reason for hiding this comment

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

This is only returning a 400 because the member_of filter would include the current super user, as such the naming of the test feels a little misleading. If the superuser doing the deletion was a user in a different facility to this one, then this would not return a 400, it would succeed.

Do we want a stronger guarantee than this (in which case we should update the implementation) or is this fine (in which case we should just update the test name)?

Copy link
Member Author

@AlexVelezLl AlexVelezLl Jun 24, 2025

Choose a reason for hiding this comment

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

Hi @rtibbles! The member_of filter here will not include the current superuser because the current superuser cannot be soft deleted, so it cant reach to this point. The implementation of the BulkDeleteMixin looks just for the filterset_fields attribute not for the fields described in the related Filterset class https://github.com/AlexVelezLl/kolibri/blob/e3b46d4b39e51d4d4feb76b1b5f2cc9625d93348/kolibri/core/mixins.py#L39 and since the only field we have in the filterset_fields is by_id https://github.com/AlexVelezLl/kolibri/blob/e3b46d4b39e51d4d4feb76b1b5f2cc9625d93348/kolibri/core/auth/api.py#L435, then the only required filter is by_ids any other filter will return a 400 error if by_ids is not present. I just double checked running the same tests but with a "user_type=learner" and the test is passing too.

Copy link
Member

Choose a reason for hiding this comment

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

Ah - that was my mistake for letting that addition of filterset_fields through. That wasn't needed, and will break functionality.

Copy link
Member

Choose a reason for hiding this comment

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

Can you revert that change - the FacilityUserViewset needs to be filterable by more than just by_ids.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, didnt know the intention was to be able to delete by other criteria other than by_ids 😅. Should we fix the BulkDeleteMixin then right? To look for other filters present in a related filterset class and not just the filterset_fields

@AlexVelezLl AlexVelezLl force-pushed the deleted-facilityuser-viewset branch 3 times, most recently from f89090c to dc68fa8 Compare June 25, 2025 15:31
@AlexVelezLl AlexVelezLl force-pushed the deleted-facilityuser-viewset branch 2 times, most recently from 6fe7b41 to f6394a2 Compare June 25, 2025 17:20
@AlexVelezLl AlexVelezLl force-pushed the deleted-facilityuser-viewset branch from f6394a2 to bbce1da Compare June 25, 2025 19:59
@AlexVelezLl
Copy link
Member Author

@nucleogenesis @rtibbles Just a heads up that this is ready for a re-review

@rtibbles rtibbles self-assigned this Jul 1, 2025
Copy link
Member

@rtibbles rtibbles left a comment

Choose a reason for hiding this comment

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

I think we might be able to simplify the allow_bulk_destroy method even further, as it's just essentially doing internal introspection now. But not a blocker.

Hook to ensure that the bulk restore should be allowed.
By default, these restrictions are the same as for bulk deletion,
"""
return self.allow_bulk_destroy(self.get_queryset())
Copy link
Member

Choose a reason for hiding this comment

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

I think, maybe we don't need this method at all, see below.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed!

"""
Restore soft-deleted FacilityUsers.
"""
if not self.allow_bulk_restore():
Copy link
Member

Choose a reason for hiding this comment

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

If we modify allow_bulk_destroy as per my comments below, this can just become:

if not self.allow_bulk_destroy():

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

@@ -1153,6 +1344,7 @@ def _make_request(self, user=None, url=None, params=None):

def test_user_list(self):
response = self._make_request(self.superuser)
self.maxDiff = None
Copy link
Member

Choose a reason for hiding this comment

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

Can remove this before we merge!

Copy link
Member Author

Choose a reason for hiding this comment

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

image

Copy link
Member Author

Choose a reason for hiding this comment

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

removed!

"""
Hook to ensure that the bulk destroy should be allowed.
By default this checks that the destroy is only applied to
filtered querysets.
"""
filter_fields = set()
Copy link
Member

Choose a reason for hiding this comment

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

This looks like the right change to me - sufficiently general to catch the use cases we need, but not going overboard!

@@ -28,22 +30,33 @@ class BulkDeleteMixin(object):

# Taken from https://github.com/miki725/django-rest-framework-bulk

def allow_bulk_destroy(self, qs, filtered):
def allow_bulk_destroy(self, qs):
Copy link
Member

Choose a reason for hiding this comment

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

Ah, so because we're not even looking at the filtered queryset this doesn't matter.

I wonder, could we just call qs = self.get_queryset() within this function and avoid having to pass it as an argument at all?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that makes sense

@AlexVelezLl AlexVelezLl requested a review from rtibbles July 2, 2025 19:26
@AlexVelezLl
Copy link
Member Author

Thanks @rtibbles! Merging :)

@AlexVelezLl AlexVelezLl merged commit 1f96c7d into learningequality:develop Jul 2, 2025
51 checks passed
@AlexVelezLl AlexVelezLl deleted the deleted-facilityuser-viewset branch July 23, 2025 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) DEV: backend Python, databases, networking, filesystem... DEV: frontend SIZE: medium
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bulk User: Add viewset for handling soft-deleted users
3 participants