Repository: inventree/InvenTree

Issue:
[FR] :checkered_flag: Simple API endpoint to change user metadata
### Please verify that this feature request has NOT been suggested before.

- [X] I checked and didn't find similar feature request

### Problem statement

we currently provide an API endpoint for editing user data via a form. Fetching and changing endpoints are different and take different parameters.

### Suggested solution

I think it would be better to provide a RetrieveUpdateAPI endpoint under `/api/user/me` (somewhat a convention). This endpoint could provide and patch all the metadata for a user.

### Describe alternatives you've considered

N/A

### Examples of other systems

_No response_

### Do you want to develop this?

- [X] I want to develop this.


Discussion:
Rer #3901

Resolving patch:
diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py
--- a/InvenTree/users/api.py
+++ b/InvenTree/users/api.py
@@ -10,7 +10,7 @@
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
-from InvenTree.mixins import ListAPI, RetrieveAPI
+from InvenTree.mixins import ListAPI, RetrieveAPI, RetrieveUpdateAPI
 from InvenTree.serializers import UserSerializer
 from users.models import Owner, RuleSet, check_user_role
 from users.serializers import OwnerSerializer
@@ -116,6 +116,14 @@ class UserDetail(RetrieveAPI):
     permission_classes = (permissions.IsAuthenticated,)
 
 
+class MeUserDetail(RetrieveUpdateAPI, UserDetail):
+    """Detail endpoint for current user."""
+
+    def get_object(self):
+        """Always return the current user object"""
+        return self.request.user
+
+
 class UserList(ListAPI):
     """List endpoint for detail on all users."""
 
@@ -170,6 +178,7 @@ def delete(self, request):
 
     re_path(r'roles/?$', RoleDetails.as_view(), name='api-user-roles'),
     re_path(r'token/?$', GetAuthToken.as_view(), name='api-token'),
+    re_path(r'^me/', MeUserDetail.as_view(), name='api-user-me'),
 
     re_path(r'^owner/', include([
         path('<int:pk>/', OwnerDetail.as_view(), name='api-owner-detail'),


Test patch:
diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py
--- a/InvenTree/users/tests.py
+++ b/InvenTree/users/tests.py
@@ -216,9 +216,26 @@ def test_api(self):
         self.do_request(reverse('api-owner-list'), {})
         # user list with search
         self.do_request(reverse('api-owner-list'), {'search': 'user'})
-        # user detail
-        # TODO fix this test
-        # self.do_request(reverse('api-owner-detail', kwargs={'pk': self.user.id}), {})
+
+        # # owner detail - user
+        # response = self.do_request(reverse('api-owner-detail', kwargs={'pk': 1}), {})
+        # self.assertEqual(response['name'], self.username)
+        # self.assertEqual(response['label'], 'user')
+        # self.assertEqual(response['owner_id'], self.user.id)
+
+        # # owner detail - group
+        # group = self.user.groups.first()
+        # response = self.do_request(reverse('api-owner-detail', kwargs={'pk': 2}), {})
+        # self.assertEqual(response['name'], group.name)
+        # self.assertEqual(response['label'], 'group')
+        # self.assertEqual(response['owner_id'], group.pk)
+
+        # own user detail
+        response_detail = self.do_request(reverse('user-detail', kwargs={'pk': self.user.id}), {}, 200)
+        self.assertEqual(response_detail['username'], self.username)
+
+        response_me = self.do_request(reverse('api-user-me'), {}, 200)
+        self.assertEqual(response_detail, response_me)
 
     def test_token(self):
         """Test token mechanisms."""
@@ -242,3 +259,7 @@ def test_token(self):
         # token second delete
         response = self.client.delete(reverse('api-token'), {}, format='json')
         self.assertEqual(response.status_code, 400)
+
+        # test user is associated with token
+        response = self.do_request(reverse('api-user-me'), {}, 200)
+        self.assertEqual(response['username'], self.username)