diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 3c06a7732e..18ee59224a 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,12 +1,15 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 362
+INVENTREE_API_VERSION = 363
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v363 -> 2025-07-04 : https://github.com/inventree/InvenTree/pull/9954
+ - Adds "user_detail" field to the ApiToken serializer
+
v362 -> 2025-07-02 : https://github.com/inventree/InvenTree/pull/9939
- Allow filtering of BuildItem API by "location" of StockItem
- Allow filtering of SalesOrderAllocation API by "location" of StockItem
diff --git a/src/backend/InvenTree/users/serializers.py b/src/backend/InvenTree/users/serializers.py
index 21df9bcb83..24fd2c3bab 100644
--- a/src/backend/InvenTree/users/serializers.py
+++ b/src/backend/InvenTree/users/serializers.py
@@ -134,46 +134,6 @@ def generate_roles_dict(roles) -> dict:
return role_dict
-class ApiTokenSerializer(InvenTreeModelSerializer):
- """Serializer for the ApiToken model."""
-
- in_use = serializers.SerializerMethodField(read_only=True)
- user = serializers.PrimaryKeyRelatedField(
- queryset=User.objects.all(), required=False
- )
-
- def get_in_use(self, token: ApiToken) -> bool:
- """Return True if the token is currently used to call the endpoint."""
- from InvenTree.middleware import get_token_from_request
-
- request = self.context.get('request')
- rq_token = get_token_from_request(request)
- return token.key == rq_token
-
- class Meta:
- """Meta options for ApiTokenSerializer."""
-
- model = ApiToken
- fields = [
- 'created',
- 'expiry',
- 'id',
- 'last_seen',
- 'name',
- 'token',
- 'active',
- 'revoked',
- 'user',
- 'in_use',
- ]
-
- def validate(self, data):
- """Validate the data for the serializer."""
- if 'user' not in data:
- data['user'] = self.context['request'].user
- return super().validate(data)
-
-
class GetAuthTokenSerializer(serializers.Serializer):
"""Serializer for the GetAuthToken API endpoint."""
@@ -248,6 +208,49 @@ class UserSerializer(InvenTreeModelSerializer):
)
+class ApiTokenSerializer(InvenTreeModelSerializer):
+ """Serializer for the ApiToken model."""
+
+ in_use = serializers.SerializerMethodField(read_only=True)
+ user = serializers.PrimaryKeyRelatedField(
+ queryset=User.objects.all(), required=False
+ )
+
+ def get_in_use(self, token: ApiToken) -> bool:
+ """Return True if the token is currently used to call the endpoint."""
+ from InvenTree.middleware import get_token_from_request
+
+ request = self.context.get('request')
+ rq_token = get_token_from_request(request)
+ return token.key == rq_token
+
+ class Meta:
+ """Meta options for ApiTokenSerializer."""
+
+ model = ApiToken
+ fields = [
+ 'created',
+ 'expiry',
+ 'id',
+ 'last_seen',
+ 'name',
+ 'token',
+ 'active',
+ 'revoked',
+ 'user',
+ 'user_detail',
+ 'in_use',
+ ]
+
+ def validate(self, data):
+ """Validate the data for the serializer."""
+ if 'user' not in data:
+ data['user'] = self.context['request'].user
+ return super().validate(data)
+
+ user_detail = UserSerializer(source='user', read_only=True)
+
+
class GroupSerializer(InvenTreeModelSerializer):
"""Serializer for a 'Group'."""
diff --git a/src/frontend/src/tables/settings/ApiTokenTable.tsx b/src/frontend/src/tables/settings/ApiTokenTable.tsx
index 268bd41546..d5d7cd6a0c 100644
--- a/src/frontend/src/tables/settings/ApiTokenTable.tsx
+++ b/src/frontend/src/tables/settings/ApiTokenTable.tsx
@@ -3,7 +3,7 @@ import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
-import { Badge, Code, Flex, Modal, Text } from '@mantine/core';
+import { Badge, Code, Flex, Modal, Paper, Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconCircleX } from '@tabler/icons-react';
import { useCallback, useMemo, useState } from 'react';
@@ -11,6 +11,7 @@ import { api } from '../../App';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { CopyButton } from '../../components/buttons/CopyButton';
import { StylishText } from '../../components/items/StylishText';
+import { RenderUser } from '../../components/render/User';
import { showApiErrorMessage } from '../../functions/notifications';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
@@ -99,7 +100,18 @@ export function ApiTokenTable({
}
];
if (!only_myself) {
- cols.push({ accessor: 'user', title: t`User`, sortable: true });
+ cols.push({
+ accessor: 'user',
+ title: t`User`,
+ sortable: true,
+ render: (record: any) => {
+ if (record.user_detail) {
+ return ;
+ } else {
+ return record.user;
+ }
+ }
+ });
}
return cols;
}, [only_myself]);
@@ -178,10 +190,12 @@ export function ApiTokenTable({
Tokens are only shown once - make sure to note it down.
-
- {token}
-
-
+
+
+ {token}
+
+
+
>
)}