mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2025-11-04 22:56:56 +08:00
Implement on_after_update event handle
This commit is contained in:
@ -76,6 +76,26 @@ def on_after_forgot_password(user: User, token: str, request: Request):
|
|||||||
print(f"User {user.id} has forgot their password. Reset token: {token}")
|
print(f"User {user.id} has forgot their password. Reset token: {token}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### After update
|
||||||
|
|
||||||
|
This event handler is called after a successful update user request. It is called with **three arguments**:
|
||||||
|
|
||||||
|
* The **user** which was updated.
|
||||||
|
* The dictionary containing the updated fields.
|
||||||
|
* The original **`Request` object**.
|
||||||
|
|
||||||
|
It may be useful if you wish for example update your user in a data analytics or customer success platform.
|
||||||
|
|
||||||
|
You can define it as an `async` or standard method.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```py
|
||||||
|
@fastapi_users.on_after_update()
|
||||||
|
def on_after_update(user: User, updated_user_data: Dict[str, Any], request: Request):
|
||||||
|
print(f"User {user.id} has been updated with the following data: {updated_user_data}")
|
||||||
|
```
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
Check out a [full example](full_example.md) that will show you the big picture.
|
Check out a [full example](full_example.md) that will show you the big picture.
|
||||||
|
|||||||
@ -78,6 +78,10 @@ class FastAPIUsers:
|
|||||||
"""Add an event handler on successful forgot password request."""
|
"""Add an event handler on successful forgot password request."""
|
||||||
return self._on_event(Event.ON_AFTER_FORGOT_PASSWORD)
|
return self._on_event(Event.ON_AFTER_FORGOT_PASSWORD)
|
||||||
|
|
||||||
|
def on_after_update(self) -> Callable:
|
||||||
|
"""Add an event handler on successful update user request."""
|
||||||
|
return self._on_event(Event.ON_AFTER_UPDATE)
|
||||||
|
|
||||||
def get_oauth_router(
|
def get_oauth_router(
|
||||||
self, oauth_client: BaseOAuth2, state_secret: str, redirect_url: str = None
|
self, oauth_client: BaseOAuth2, state_secret: str, redirect_url: str = None
|
||||||
) -> EventHandlersRouter:
|
) -> EventHandlersRouter:
|
||||||
|
|||||||
@ -15,6 +15,7 @@ class ErrorCode:
|
|||||||
class Event(Enum):
|
class Event(Enum):
|
||||||
ON_AFTER_REGISTER = auto()
|
ON_AFTER_REGISTER = auto()
|
||||||
ON_AFTER_FORGOT_PASSWORD = auto()
|
ON_AFTER_FORGOT_PASSWORD = auto()
|
||||||
|
ON_AFTER_UPDATE = auto()
|
||||||
|
|
||||||
|
|
||||||
class EventHandlersRouter(APIRouter):
|
class EventHandlersRouter(APIRouter):
|
||||||
|
|||||||
@ -169,6 +169,7 @@ def get_user_router(
|
|||||||
|
|
||||||
@router.patch("/me", response_model=user_model)
|
@router.patch("/me", response_model=user_model)
|
||||||
async def update_me(
|
async def update_me(
|
||||||
|
request: Request,
|
||||||
updated_user: user_update_model, # type: ignore
|
updated_user: user_update_model, # type: ignore
|
||||||
user: user_db_model = Depends(get_current_active_user), # type: ignore
|
user: user_db_model = Depends(get_current_active_user), # type: ignore
|
||||||
):
|
):
|
||||||
@ -176,7 +177,13 @@ def get_user_router(
|
|||||||
models.BaseUserUpdate, updated_user,
|
models.BaseUserUpdate, updated_user,
|
||||||
) # Prevent mypy complain
|
) # Prevent mypy complain
|
||||||
updated_user_data = updated_user.create_update_dict()
|
updated_user_data = updated_user.create_update_dict()
|
||||||
return await _update_user(user, updated_user_data)
|
updated_user = await _update_user(user, updated_user_data)
|
||||||
|
|
||||||
|
await router.run_handlers(
|
||||||
|
Event.ON_AFTER_UPDATE, updated_user, updated_user_data, request
|
||||||
|
)
|
||||||
|
|
||||||
|
return updated_user
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/",
|
"/",
|
||||||
|
|||||||
@ -41,6 +41,10 @@ def fastapi_users(
|
|||||||
def on_after_forgot_password():
|
def on_after_forgot_password():
|
||||||
return request.param()
|
return request.param()
|
||||||
|
|
||||||
|
@fastapi_users.on_after_update()
|
||||||
|
def on_after_update():
|
||||||
|
return request.param()
|
||||||
|
|
||||||
return fastapi_users
|
return fastapi_users
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,7 @@ def test_app_client(mock_user_db, mock_authentication, event_handler) -> TestCli
|
|||||||
|
|
||||||
user_router.add_event_handler(Event.ON_AFTER_REGISTER, event_handler)
|
user_router.add_event_handler(Event.ON_AFTER_REGISTER, event_handler)
|
||||||
user_router.add_event_handler(Event.ON_AFTER_FORGOT_PASSWORD, event_handler)
|
user_router.add_event_handler(Event.ON_AFTER_FORGOT_PASSWORD, event_handler)
|
||||||
|
user_router.add_event_handler(Event.ON_AFTER_UPDATE, event_handler)
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(user_router)
|
app.include_router(user_router)
|
||||||
@ -334,17 +335,21 @@ class TestMe:
|
|||||||
|
|
||||||
@pytest.mark.router
|
@pytest.mark.router
|
||||||
class TestUpdateMe:
|
class TestUpdateMe:
|
||||||
def test_missing_token(self, test_app_client: TestClient):
|
def test_missing_token(self, test_app_client: TestClient, event_handler):
|
||||||
response = test_app_client.patch("/me")
|
response = test_app_client.patch("/me")
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
assert event_handler.called is False
|
||||||
|
|
||||||
def test_inactive_user(self, test_app_client: TestClient, inactive_user: UserDB):
|
def test_inactive_user(
|
||||||
|
self, test_app_client: TestClient, inactive_user: UserDB, event_handler
|
||||||
|
):
|
||||||
response = test_app_client.patch(
|
response = test_app_client.patch(
|
||||||
"/me", headers={"Authorization": f"Bearer {inactive_user.id}"}
|
"/me", headers={"Authorization": f"Bearer {inactive_user.id}"}
|
||||||
)
|
)
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
assert event_handler.called is False
|
||||||
|
|
||||||
def test_empty_body(self, test_app_client: TestClient, user: UserDB):
|
def test_empty_body(self, test_app_client: TestClient, user: UserDB, event_handler):
|
||||||
response = test_app_client.patch(
|
response = test_app_client.patch(
|
||||||
"/me", json={}, headers={"Authorization": f"Bearer {user.id}"}
|
"/me", json={}, headers={"Authorization": f"Bearer {user.id}"}
|
||||||
)
|
)
|
||||||
@ -353,7 +358,15 @@ class TestUpdateMe:
|
|||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["email"] == user.email
|
assert response_json["email"] == user.email
|
||||||
|
|
||||||
def test_valid_body(self, test_app_client: TestClient, user: UserDB):
|
assert event_handler.called is True
|
||||||
|
actual_user = event_handler.call_args[0][0]
|
||||||
|
assert actual_user.id == user.id
|
||||||
|
updated_fields = event_handler.call_args[0][1]
|
||||||
|
assert updated_fields == {}
|
||||||
|
request = event_handler.call_args[0][2]
|
||||||
|
assert isinstance(request, Request)
|
||||||
|
|
||||||
|
def test_valid_body(self, test_app_client: TestClient, user: UserDB, event_handler):
|
||||||
json = {"email": "king.arthur@tintagel.bt"}
|
json = {"email": "king.arthur@tintagel.bt"}
|
||||||
response = test_app_client.patch(
|
response = test_app_client.patch(
|
||||||
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
||||||
@ -363,7 +376,17 @@ class TestUpdateMe:
|
|||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["email"] == "king.arthur@tintagel.bt"
|
assert response_json["email"] == "king.arthur@tintagel.bt"
|
||||||
|
|
||||||
def test_valid_body_is_superuser(self, test_app_client: TestClient, user: UserDB):
|
assert event_handler.called is True
|
||||||
|
actual_user = event_handler.call_args[0][0]
|
||||||
|
assert actual_user.id == user.id
|
||||||
|
updated_fields = event_handler.call_args[0][1]
|
||||||
|
assert updated_fields == {"email": "king.arthur@tintagel.bt"}
|
||||||
|
request = event_handler.call_args[0][2]
|
||||||
|
assert isinstance(request, Request)
|
||||||
|
|
||||||
|
def test_valid_body_is_superuser(
|
||||||
|
self, test_app_client: TestClient, user: UserDB, event_handler
|
||||||
|
):
|
||||||
json = {"is_superuser": True}
|
json = {"is_superuser": True}
|
||||||
response = test_app_client.patch(
|
response = test_app_client.patch(
|
||||||
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
||||||
@ -373,7 +396,17 @@ class TestUpdateMe:
|
|||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["is_superuser"] is False
|
assert response_json["is_superuser"] is False
|
||||||
|
|
||||||
def test_valid_body_is_active(self, test_app_client: TestClient, user: UserDB):
|
assert event_handler.called is True
|
||||||
|
actual_user = event_handler.call_args[0][0]
|
||||||
|
assert actual_user.id == user.id
|
||||||
|
updated_fields = event_handler.call_args[0][1]
|
||||||
|
assert updated_fields == {}
|
||||||
|
request = event_handler.call_args[0][2]
|
||||||
|
assert isinstance(request, Request)
|
||||||
|
|
||||||
|
def test_valid_body_is_active(
|
||||||
|
self, test_app_client: TestClient, user: UserDB, event_handler
|
||||||
|
):
|
||||||
json = {"is_active": False}
|
json = {"is_active": False}
|
||||||
response = test_app_client.patch(
|
response = test_app_client.patch(
|
||||||
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
"/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
|
||||||
@ -383,8 +416,21 @@ class TestUpdateMe:
|
|||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response_json["is_active"] is True
|
assert response_json["is_active"] is True
|
||||||
|
|
||||||
|
assert event_handler.called is True
|
||||||
|
actual_user = event_handler.call_args[0][0]
|
||||||
|
assert actual_user.id == user.id
|
||||||
|
updated_fields = event_handler.call_args[0][1]
|
||||||
|
assert updated_fields == {}
|
||||||
|
request = event_handler.call_args[0][2]
|
||||||
|
assert isinstance(request, Request)
|
||||||
|
|
||||||
def test_valid_body_password(
|
def test_valid_body_password(
|
||||||
self, mocker, mock_user_db, test_app_client: TestClient, user: UserDB
|
self,
|
||||||
|
mocker,
|
||||||
|
mock_user_db,
|
||||||
|
test_app_client: TestClient,
|
||||||
|
user: UserDB,
|
||||||
|
event_handler,
|
||||||
):
|
):
|
||||||
mocker.spy(mock_user_db, "update")
|
mocker.spy(mock_user_db, "update")
|
||||||
current_hashed_passord = user.hashed_password
|
current_hashed_passord = user.hashed_password
|
||||||
@ -399,6 +445,14 @@ class TestUpdateMe:
|
|||||||
updated_user = mock_user_db.update.call_args[0][0]
|
updated_user = mock_user_db.update.call_args[0][0]
|
||||||
assert updated_user.hashed_password != current_hashed_passord
|
assert updated_user.hashed_password != current_hashed_passord
|
||||||
|
|
||||||
|
assert event_handler.called is True
|
||||||
|
actual_user = event_handler.call_args[0][0]
|
||||||
|
assert actual_user.id == user.id
|
||||||
|
updated_fields = event_handler.call_args[0][1]
|
||||||
|
assert updated_fields == {"password": "merlin"}
|
||||||
|
request = event_handler.call_args[0][2]
|
||||||
|
assert isinstance(request, Request)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.router
|
@pytest.mark.router
|
||||||
class TestListUsers:
|
class TestListUsers:
|
||||||
|
|||||||
Reference in New Issue
Block a user