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} + + + )}