mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 20:45:44 +00:00
Added first UI components for user managment (#5875)
* Added first UI components for user managment Ref #4962 * Add user roles to table and serializer * added key to AddItem actions * added ordering to group * style text * do not show unnecessary options * fix admi / superuser usage * switched to use BooleanColumn * Added active column * added user role change action * added user active change action * Added api change log * fixed logical error * added admin center to navigation * added groups to user serializer * added groups to the uI * Added user drawer * fixed active state * remove actions as they are not usable after refactor * move functions to drawer * added drawer lock state * added edit toggle * merge fix * renamed values * remove empty roles section * fix settings header * make title shorter to reducelayout shift when switching to server settings
This commit is contained in:
@ -2,10 +2,15 @@
|
||||
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 149
|
||||
INVENTREE_API_VERSION = 150
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
v150 -> 2023-11-07: https://github.com/inventree/InvenTree/pull/5875
|
||||
- Extended user API endpoints to enable ordering
|
||||
- Extended user API endpoints to enable user role changes
|
||||
- Added endpoint to create a new user
|
||||
|
||||
v149 -> 2023-11-07 : https://github.com/inventree/InvenTree/pull/5876
|
||||
- Add 'building' quantity to BomItem serializer
|
||||
- Add extra ordering options for the BomItem list API
|
||||
|
@ -6,6 +6,7 @@ from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -15,7 +16,7 @@ from djmoney.contrib.django_rest_framework.fields import MoneyField
|
||||
from djmoney.money import Money
|
||||
from djmoney.utils import MONEY_CLASSES, get_currency_field_name
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.fields import empty
|
||||
from rest_framework.serializers import DecimalField
|
||||
from rest_framework.utils import model_meta
|
||||
@ -302,6 +303,72 @@ class UserSerializer(InvenTreeModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ExendedUserSerializer(UserSerializer):
|
||||
"""Serializer for a User with a bit more info."""
|
||||
from users.serializers import GroupSerializer
|
||||
|
||||
groups = GroupSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta(UserSerializer.Meta):
|
||||
"""Metaclass defines serializer fields."""
|
||||
fields = UserSerializer.Meta.fields + [
|
||||
'groups',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'is_active'
|
||||
]
|
||||
|
||||
read_only_fields = UserSerializer.Meta.read_only_fields + [
|
||||
'groups',
|
||||
]
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Expanded validation for changing user role."""
|
||||
# Check if is_staff or is_superuser is in attrs
|
||||
role_change = 'is_staff' in attrs or 'is_superuser' in attrs
|
||||
request_user = self.context['request'].user
|
||||
|
||||
if role_change:
|
||||
if request_user.is_superuser:
|
||||
# Superusers can change any role
|
||||
pass
|
||||
elif request_user.is_staff and 'is_superuser' not in attrs:
|
||||
# Staff can change any role except is_superuser
|
||||
pass
|
||||
else:
|
||||
raise PermissionDenied(_("You do not have permission to change this user role."))
|
||||
return super().validate(attrs)
|
||||
|
||||
|
||||
class UserCreateSerializer(ExendedUserSerializer):
|
||||
"""Serializer for creating a new User."""
|
||||
def validate(self, attrs):
|
||||
"""Expanded valiadation for auth."""
|
||||
# Check that the user trying to create a new user is a superuser
|
||||
if not self.context['request'].user.is_superuser:
|
||||
raise serializers.ValidationError(_("Only superusers can create new users"))
|
||||
|
||||
# Generate a random password
|
||||
password = User.objects.make_random_password(length=14)
|
||||
attrs.update({'password': password})
|
||||
return super().validate(attrs)
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Send an e email to the user after creation."""
|
||||
instance = super().create(validated_data)
|
||||
|
||||
# Make sure the user cannot login until they have set a password
|
||||
instance.set_unusable_password()
|
||||
# Send the user an onboarding email (from current site)
|
||||
current_site = Site.objects.get_current()
|
||||
domain = current_site.domain
|
||||
instance.email_user(
|
||||
subject=_(f"Welcome to {current_site.name}"),
|
||||
message=_(f"Your account has been created.\n\nPlease use the password reset function to get access (at https://{domain})."),
|
||||
)
|
||||
return instance
|
||||
|
||||
|
||||
class InvenTreeAttachmentSerializerField(serializers.FileField):
|
||||
"""Override the DRF native FileField serializer, to remove the leading server path.
|
||||
|
||||
|
@ -6,14 +6,14 @@ import logging
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.urls import include, path, re_path
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import exceptions, permissions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from InvenTree.filters import InvenTreeSearchFilter
|
||||
from InvenTree.mixins import ListAPI, RetrieveAPI, RetrieveUpdateAPI
|
||||
from InvenTree.serializers import UserSerializer
|
||||
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||
from InvenTree.mixins import (ListAPI, ListCreateAPI, RetrieveAPI,
|
||||
RetrieveUpdateAPI, RetrieveUpdateDestroyAPI)
|
||||
from InvenTree.serializers import ExendedUserSerializer, UserCreateSerializer
|
||||
from users.models import ApiToken, Owner, RuleSet, check_user_role
|
||||
from users.serializers import GroupSerializer, OwnerSerializer
|
||||
|
||||
@ -112,11 +112,11 @@ class RoleDetails(APIView):
|
||||
return Response(data)
|
||||
|
||||
|
||||
class UserDetail(RetrieveAPI):
|
||||
class UserDetail(RetrieveUpdateDestroyAPI):
|
||||
"""Detail endpoint for a single user."""
|
||||
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
serializer_class = ExendedUserSerializer
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated
|
||||
]
|
||||
@ -130,19 +130,15 @@ class MeUserDetail(RetrieveUpdateAPI, UserDetail):
|
||||
return self.request.user
|
||||
|
||||
|
||||
class UserList(ListAPI):
|
||||
class UserList(ListCreateAPI):
|
||||
"""List endpoint for detail on all users."""
|
||||
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
serializer_class = UserCreateSerializer
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
]
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
InvenTreeSearchFilter,
|
||||
]
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
search_fields = [
|
||||
'first_name',
|
||||
@ -150,8 +146,18 @@ class UserList(ListAPI):
|
||||
'username',
|
||||
]
|
||||
|
||||
ordering_fields = [
|
||||
'email',
|
||||
'username',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'is_active',
|
||||
]
|
||||
|
||||
class GroupDetail(RetrieveAPI):
|
||||
|
||||
class GroupDetail(RetrieveUpdateDestroyAPI):
|
||||
"""Detail endpoint for a particular auth group"""
|
||||
|
||||
queryset = Group.objects.all()
|
||||
@ -161,7 +167,7 @@ class GroupDetail(RetrieveAPI):
|
||||
]
|
||||
|
||||
|
||||
class GroupList(ListAPI):
|
||||
class GroupList(ListCreateAPI):
|
||||
"""List endpoint for all auth groups"""
|
||||
|
||||
queryset = Group.objects.all()
|
||||
@ -170,15 +176,16 @@ class GroupList(ListAPI):
|
||||
permissions.IsAuthenticated,
|
||||
]
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
InvenTreeSearchFilter,
|
||||
]
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
search_fields = [
|
||||
'name',
|
||||
]
|
||||
|
||||
ordering_fields = [
|
||||
'name',
|
||||
]
|
||||
|
||||
|
||||
class GetAuthToken(APIView):
|
||||
"""Return authentication token for an authenticated user."""
|
||||
|
Reference in New Issue
Block a user