mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-07 06:00:57 +00:00
User token table (#9954)
* Tweak layout of Token dialog * Render user details in API token table * Bump API version
This commit is contained in:
@ -1,12 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# 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."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
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
|
v362 -> 2025-07-02 : https://github.com/inventree/InvenTree/pull/9939
|
||||||
- Allow filtering of BuildItem API by "location" of StockItem
|
- Allow filtering of BuildItem API by "location" of StockItem
|
||||||
- Allow filtering of SalesOrderAllocation API by "location" of StockItem
|
- Allow filtering of SalesOrderAllocation API by "location" of StockItem
|
||||||
|
@ -134,46 +134,6 @@ def generate_roles_dict(roles) -> dict:
|
|||||||
return role_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):
|
class GetAuthTokenSerializer(serializers.Serializer):
|
||||||
"""Serializer for the GetAuthToken API endpoint."""
|
"""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):
|
class GroupSerializer(InvenTreeModelSerializer):
|
||||||
"""Serializer for a 'Group'."""
|
"""Serializer for a 'Group'."""
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { apiUrl } from '@lib/functions/Api';
|
|||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { Trans } from '@lingui/react/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 { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconCircleX } from '@tabler/icons-react';
|
import { IconCircleX } from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
@ -11,6 +11,7 @@ import { api } from '../../App';
|
|||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { CopyButton } from '../../components/buttons/CopyButton';
|
import { CopyButton } from '../../components/buttons/CopyButton';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
|
import { RenderUser } from '../../components/render/User';
|
||||||
import { showApiErrorMessage } from '../../functions/notifications';
|
import { showApiErrorMessage } from '../../functions/notifications';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
@ -99,7 +100,18 @@ export function ApiTokenTable({
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
if (!only_myself) {
|
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 <RenderUser instance={record.user_detail} />;
|
||||||
|
} else {
|
||||||
|
return record.user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return cols;
|
return cols;
|
||||||
}, [only_myself]);
|
}, [only_myself]);
|
||||||
@ -178,10 +190,12 @@ export function ApiTokenTable({
|
|||||||
Tokens are only shown once - make sure to note it down.
|
Tokens are only shown once - make sure to note it down.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Flex>
|
<Paper p='sm'>
|
||||||
<Code>{token}</Code>
|
<Flex>
|
||||||
<CopyButton value={token} />
|
<Code>{token}</Code>
|
||||||
</Flex>
|
<CopyButton value={token} />
|
||||||
|
</Flex>
|
||||||
|
</Paper>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
Reference in New Issue
Block a user