mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-18 04:33:48 +00:00
[UI] Page Load Improvements (#12334)
* Parallel initial requests * Remove debouncing * Refactor lazy loading of desktop / mobile view * Reduce initial waiting for loadable components * Fix duplication of API calls * Further reduce duplicate calls * Combine user roles into /user/me/ endpoint * lazy load global import drawer * lazy load table in plugin context * Patch ScanButton * Added playwright tests for login * Adjust thresholds
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 518
|
||||
INVENTREE_API_VERSION = 519
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v519 -> 2026-07-09 : https://github.com/inventree/InvenTree/pull/TODO
|
||||
- Adds optional "roles" and "permissions" fields to the /user/me/ API endpoint, via the "?roles=true" query parameter
|
||||
|
||||
v518 -> 2026-07-09 : https://github.com/inventree/InvenTree/pull/12341
|
||||
- Enable import of internal part prices via the API
|
||||
|
||||
|
||||
@@ -248,6 +248,24 @@ class MeUserDetail(RetrieveUpdateAPI, UserDetail):
|
||||
"""
|
||||
return None
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name='roles',
|
||||
type=bool,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description='Include the roles and permissions associated with the current user in the response',
|
||||
)
|
||||
]
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Retrieve details for the current user.
|
||||
|
||||
Pass '?roles=true' to also include the user's roles and permissions
|
||||
(previously only available via the separate '/user/me/roles/' endpoint).
|
||||
"""
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class UserList(ListCreateAPI):
|
||||
"""List endpoint for detail on all users.
|
||||
|
||||
@@ -78,37 +78,47 @@ class RoleSerializer(InvenTreeModelSerializer):
|
||||
|
||||
def get_roles(self, user: User) -> dict:
|
||||
"""Roles associated with the user."""
|
||||
roles = {}
|
||||
|
||||
# Cache the 'groups' queryset for the user
|
||||
groups = prefetch_rule_sets(user)
|
||||
|
||||
for ruleset in RULESET_CHOICES:
|
||||
role, _text = ruleset
|
||||
|
||||
permissions = []
|
||||
|
||||
for permission in RULESET_PERMISSIONS:
|
||||
if check_user_role(user, role, permission, groups=groups):
|
||||
permissions.append(permission)
|
||||
|
||||
if len(permissions) > 0:
|
||||
roles[role] = permissions
|
||||
else:
|
||||
roles[role] = None # pragma: no cover
|
||||
|
||||
return roles
|
||||
return get_user_roles(user)
|
||||
|
||||
def get_permissions(self, user: User) -> dict:
|
||||
"""Permissions associated with the user."""
|
||||
if user.is_superuser:
|
||||
permissions = Permission.objects.all()
|
||||
else:
|
||||
permissions = Permission.objects.filter(
|
||||
Q(user=user) | Q(group__user=user)
|
||||
).distinct()
|
||||
return get_user_permissions(user)
|
||||
|
||||
return generate_permission_dict(permissions)
|
||||
|
||||
def get_user_roles(user: User) -> dict:
|
||||
"""Return a dict of the roles associated with the given user."""
|
||||
roles = {}
|
||||
|
||||
# Cache the 'groups' queryset for the user
|
||||
groups = prefetch_rule_sets(user)
|
||||
|
||||
for ruleset in RULESET_CHOICES:
|
||||
role, _text = ruleset
|
||||
|
||||
permissions = []
|
||||
|
||||
for permission in RULESET_PERMISSIONS:
|
||||
if check_user_role(user, role, permission, groups=groups):
|
||||
permissions.append(permission)
|
||||
|
||||
if len(permissions) > 0:
|
||||
roles[role] = permissions
|
||||
else:
|
||||
roles[role] = None # pragma: no cover
|
||||
|
||||
return roles
|
||||
|
||||
|
||||
def get_user_permissions(user: User) -> dict:
|
||||
"""Return a dict of the permissions associated with the given user."""
|
||||
if user.is_superuser:
|
||||
permissions = Permission.objects.all()
|
||||
else:
|
||||
permissions = Permission.objects.filter(
|
||||
Q(user=user) | Q(group__user=user)
|
||||
).distinct()
|
||||
|
||||
return generate_permission_dict(permissions)
|
||||
|
||||
|
||||
def generate_permission_dict(permissions) -> dict:
|
||||
@@ -390,7 +400,7 @@ class UserSetPasswordSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class MeUserSerializer(ExtendedUserSerializer):
|
||||
class MeUserSerializer(FilterableSerializerMixin, ExtendedUserSerializer):
|
||||
"""API serializer specifically for the 'me' endpoint."""
|
||||
|
||||
class Meta(ExtendedUserSerializer.Meta):
|
||||
@@ -401,7 +411,11 @@ class MeUserSerializer(ExtendedUserSerializer):
|
||||
"""
|
||||
|
||||
# Remove the 'group_ids' field, as this is not relevant for the 'me' endpoint
|
||||
fields = [f for f in ExtendedUserSerializer.Meta.fields if f != 'group_ids']
|
||||
fields = [
|
||||
*(f for f in ExtendedUserSerializer.Meta.fields if f != 'group_ids'),
|
||||
'roles',
|
||||
'permissions',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
*ExtendedUserSerializer.Meta.read_only_fields,
|
||||
@@ -412,6 +426,31 @@ class MeUserSerializer(ExtendedUserSerializer):
|
||||
|
||||
profile = UserProfileSerializer(many=False, read_only=True)
|
||||
|
||||
# Roles and permissions are only computed (and included) when the
|
||||
# request explicitly asks for them via '?roles=true' - they require
|
||||
# extra queries, and most callers of this endpoint only want basic
|
||||
# user details. Shares a filter_name so both come back together, since
|
||||
# they were previously served as a single '/user/me/roles/' response.
|
||||
roles = OptionalField(
|
||||
serializer_class=serializers.SerializerMethodField,
|
||||
serializer_kwargs={'read_only': True},
|
||||
filter_name='roles',
|
||||
)
|
||||
|
||||
permissions = OptionalField(
|
||||
serializer_class=serializers.SerializerMethodField,
|
||||
serializer_kwargs={'allow_null': True, 'read_only': True},
|
||||
filter_name='roles',
|
||||
)
|
||||
|
||||
def get_roles(self, user: User) -> dict:
|
||||
"""Roles associated with the user."""
|
||||
return get_user_roles(user)
|
||||
|
||||
def get_permissions(self, user: User) -> dict:
|
||||
"""Permissions associated with the user."""
|
||||
return get_user_permissions(user)
|
||||
|
||||
# Redefine the fields from ExtendedUserSerializer, to ensure they are marked as read-only
|
||||
is_staff = serializers.BooleanField(
|
||||
label=_('Staff'),
|
||||
|
||||
Reference in New Issue
Block a user