diff --git a/docs/configuration/user-manager.md b/docs/configuration/user-manager.md index 5e33b6cb..acc5faef 100644 --- a/docs/configuration/user-manager.md +++ b/docs/configuration/user-manager.md @@ -263,3 +263,50 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): async def on_after_reset_password(self, user: User, request: Optional[Request] = None): print(f"User {user.id} has reset their password.") ``` + +#### `on_before_delete` + +Perform logic before user delete. + +For example, you may want to **valide user resource integrity** to see if any related user resource need to be marked inactive, or delete +them recursively. + +**Arguments** + +* `user` (`User`): the user to be deleted. +* `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. + +**Example** + +```py +from fastapi_users import BaseUserManager, UUIDIDMixin + + +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): + # ... + async def on_before_delete(self, user: User, request: Optional[Request] = None): + print(f"User {user.id} is going to be deleted") +``` + +#### `on_after_delete` + +Perform logic after user delete. + +For example, you may want to **send an email** to the administrator about the event. + +**Arguments** + +* `user` (`User`): the user to be deleted. +* `request` (`Optional[Request]`): optional FastAPI request object that triggered the operation. Defaults to None. + +**Example** + +```py +from fastapi_users import BaseUserManager, UUIDIDMixin + + +class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): + # ... + async def on_after_delete(self, user: User, request: Optional[Request] = None): + print(f"User {user.id} is successfully deleted") +``` diff --git a/fastapi_users/manager.py b/fastapi_users/manager.py index 8b63dfa3..f5d2eb82 100644 --- a/fastapi_users/manager.py +++ b/fastapi_users/manager.py @@ -403,13 +403,19 @@ class BaseUserManager(Generic[models.UP, models.ID]): await self.on_after_update(updated_user, updated_user_data, request) return updated_user - async def delete(self, user: models.UP) -> None: + async def delete( + self, + user: models.UP, + request: Optional[Request] = None, + ) -> None: """ Delete a user. :param user: The user to delete. """ + await self.on_before_delete(user, request) await self.user_db.delete(user) + await self.on_after_delete(user, request) async def validate_password( self, password: str, user: Union[schemas.UC, models.UP] @@ -516,6 +522,34 @@ class BaseUserManager(Generic[models.UP, models.ID]): """ return # pragma: no cover + async def on_before_delete( + self, user: models.UP, request: Optional[Request] = None + ) -> None: + """ + Perform logic before user delete. + + *You should overload this method to add your own logic.* + + :param user: The user to be deleted + :param request: Optional FastAPI request that + triggered the operation, defaults to None. + """ + return # pragma: no cover + + async def on_after_delete( + self, user: models.UP, request: Optional[Request] = None + ) -> None: + """ + Perform logic before user delete. + + *You should overload this method to add your own logic.* + + :param user: The user to be deleted + :param request: Optional FastAPI request that + triggered the operation, defaults to None. + """ + return # pragma: no cover + async def authenticate( self, credentials: OAuth2PasswordRequestForm ) -> Optional[models.UP]: diff --git a/tests/conftest.py b/tests/conftest.py index 7ad46060..9f0c3497 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -120,6 +120,8 @@ class UserManagerMock(BaseTestUserManager[models.UP]): on_after_forgot_password: MagicMock on_after_reset_password: MagicMock on_after_update: MagicMock + on_before_delete: MagicMock + on_after_delete: MagicMock _update: MagicMock @@ -475,6 +477,8 @@ def make_user_manager(mocker: MockerFixture): mocker.spy(user_manager, "on_after_forgot_password") mocker.spy(user_manager, "on_after_reset_password") mocker.spy(user_manager, "on_after_update") + mocker.spy(user_manager, "on_before_delete") + mocker.spy(user_manager, "on_after_delete") mocker.spy(user_manager, "_update") return user_manager diff --git a/tests/test_manager.py b/tests/test_manager.py index 0ab995f7..fcfddce7 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -532,6 +532,10 @@ class TestDelete: ): await user_manager.delete(user) + assert user_manager.on_before_delete.called is True + + assert user_manager.on_after_delete.called is True + @pytest.mark.asyncio @pytest.mark.manager