mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-14 07:18:44 +00:00
Merge commit from fork
* Ensure the MeUserSerializer correctly marks fields as read-only * Bump API version * Add unit tests for the "me" endpoint * Additional unit tests * Add OPTIONS test
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 471
|
||||
INVENTREE_API_VERSION = 472
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v472 -> 2026-04-01 : https://github.com/inventree/InvenTree/pull/xxxx
|
||||
- Fixes writable fields on the user detail endpoint
|
||||
|
||||
v471 -> 2026-04-07 : https://github.com/inventree/InvenTree/pull/11685
|
||||
- Adds data importer support for the "SalesOrderShipment" model
|
||||
|
||||
|
||||
@@ -393,6 +393,9 @@ class MeUserSerializer(ExtendedUserSerializer):
|
||||
but ensures that certain fields are read-only.
|
||||
"""
|
||||
|
||||
# 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']
|
||||
|
||||
read_only_fields = [
|
||||
*ExtendedUserSerializer.Meta.read_only_fields,
|
||||
'is_active',
|
||||
@@ -402,6 +405,28 @@ class MeUserSerializer(ExtendedUserSerializer):
|
||||
|
||||
profile = UserProfileSerializer(many=False, read_only=True)
|
||||
|
||||
# Redefine the fields from ExtendedUserSerializer, to ensure they are marked as read-only
|
||||
is_staff = serializers.BooleanField(
|
||||
label=_('Staff'),
|
||||
help_text=_('Does this user have staff permissions'),
|
||||
required=False,
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
is_superuser = serializers.BooleanField(
|
||||
label=_('Superuser'),
|
||||
help_text=_('Is this user a superuser'),
|
||||
required=False,
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
is_active = serializers.BooleanField(
|
||||
label=_('Active'),
|
||||
help_text=_('Is this user account active'),
|
||||
required=False,
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
|
||||
def make_random_password(length=14):
|
||||
"""Generate a random password of given length."""
|
||||
|
||||
@@ -44,7 +44,7 @@ class UserAPITests(InvenTreeAPITestCase):
|
||||
)
|
||||
|
||||
def test_api_url(self):
|
||||
"""Test the 'api_url attribute in related API endpoints.
|
||||
"""Test the 'api_url' attribute in related API endpoints.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/10182
|
||||
"""
|
||||
@@ -129,6 +129,19 @@ class UserAPITests(InvenTreeAPITestCase):
|
||||
|
||||
self.assertIn('Only a superuser can adjust this field', str(response.data))
|
||||
|
||||
# Try again, but with superuser access
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
|
||||
response = self.post(
|
||||
url,
|
||||
data={**data, 'username': 'Superuser', 'is_superuser': True},
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
self.assertEqual(response.data['username'], 'Superuser')
|
||||
self.assertEqual(response.data['is_superuser'], True)
|
||||
|
||||
def test_user_detail(self):
|
||||
"""Test the UserDetail API endpoint."""
|
||||
user = User.objects.first()
|
||||
@@ -143,7 +156,7 @@ class UserAPITests(InvenTreeAPITestCase):
|
||||
self.get(url, expected_code=200)
|
||||
|
||||
# Let's try to update the user
|
||||
data = {'is_active': False, 'is_staff': False}
|
||||
data = {'is_active': True, 'is_staff': False}
|
||||
|
||||
self.patch(url, data=data, expected_code=403)
|
||||
|
||||
@@ -158,6 +171,26 @@ class UserAPITests(InvenTreeAPITestCase):
|
||||
|
||||
self.patch(url, data=data, expected_code=200)
|
||||
|
||||
# Try to change the "is_superuser" field - only a superuser can do this
|
||||
data['is_superuser'] = True
|
||||
response = self.patch(url, data=data, expected_code=403)
|
||||
|
||||
self.assertIn(
|
||||
'You do not have permission to perform this action', str(response.data)
|
||||
)
|
||||
|
||||
self.user.is_staff = True
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
|
||||
for val in [True, False]:
|
||||
data['is_staff'] = True
|
||||
data['is_superuser'] = val
|
||||
|
||||
response = self.patch(url, data=data, expected_code=200)
|
||||
self.assertEqual(response.data['is_superuser'], val)
|
||||
self.assertEqual(response.data['is_staff'], True)
|
||||
|
||||
# Try again, but logged out - expect no access to the endpoint
|
||||
self.logout()
|
||||
self.get(url, expected_code=401)
|
||||
@@ -229,6 +262,71 @@ class UserAPITests(InvenTreeAPITestCase):
|
||||
|
||||
self.assertEqual(len(data['permissions']), len(perms) + len(build_perms))
|
||||
|
||||
def test_me_endpoint(self):
|
||||
"""Test against the users /me/ endpoint."""
|
||||
url = reverse('api-user-me')
|
||||
|
||||
# Test endpoint options
|
||||
response = self.options(url, expected_code=200)
|
||||
|
||||
# Check that particular fields are present, and have the correct attributes
|
||||
fields = response.data['actions']['PUT']
|
||||
|
||||
for name in [
|
||||
'pk',
|
||||
'username',
|
||||
'email',
|
||||
'groups',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
]:
|
||||
self.assertIn(name, fields)
|
||||
|
||||
for name in ['is_active', 'is_staff', 'is_superuser']:
|
||||
self.assertTrue(fields[name]['read_only'])
|
||||
|
||||
# Perform a GET request against the endpoint
|
||||
response = self.get(url, expected_code=200)
|
||||
|
||||
for field in [
|
||||
'pk',
|
||||
'username',
|
||||
'email',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
]:
|
||||
self.assertIn(field, response.data)
|
||||
|
||||
# Change their own username
|
||||
for name in ['Henry', 'Sally']:
|
||||
response = self.patch(url, data={'username': name}, expected_code=200)
|
||||
self.assertEqual(response.data['username'], name)
|
||||
|
||||
# Defined starting point for the user
|
||||
for v in [True, False]:
|
||||
self.user.is_staff = v
|
||||
self.user.is_superuser = v
|
||||
self.user.save()
|
||||
|
||||
for key in ['is_staff', 'is_superuser']:
|
||||
for val in [True, False]:
|
||||
response = self.patch(url, data={key: val}, expected_code=200)
|
||||
|
||||
# Check that the field was *NOT CHANGED*
|
||||
self.assertEqual(response.data[key], v)
|
||||
|
||||
# Ensure we cannot change the "is_active" field either
|
||||
response = self.patch(url, data={'is_active': False}, expected_code=200)
|
||||
self.assertEqual(response.data['is_active'], True)
|
||||
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
|
||||
# User cannot fetch their own details if they are not active
|
||||
response = self.get(url, expected_code=401)
|
||||
|
||||
|
||||
class SuperuserAPITests(InvenTreeAPITestCase):
|
||||
"""Tests for user API endpoints that require superuser rights."""
|
||||
@@ -245,7 +343,7 @@ class SuperuserAPITests(InvenTreeAPITestCase):
|
||||
resp = self.put(url, {'password': 1}, expected_code=400)
|
||||
self.assertContains(resp, 'This password is too short', status_code=400)
|
||||
|
||||
# now with overwerite
|
||||
# now with overwrite
|
||||
resp = self.put(
|
||||
url, {'password': 1, 'override_warning': True}, expected_code=200
|
||||
)
|
||||
@@ -422,7 +520,7 @@ class UserTokenTests(InvenTreeAPITestCase):
|
||||
self.assertEqual(ApiToken.objects.count(), 1)
|
||||
|
||||
|
||||
class GroupDetialTests(InvenTreeAPITestCase):
|
||||
class GroupDetailTests(InvenTreeAPITestCase):
|
||||
"""Tests for the GroupDetail API endpoint."""
|
||||
|
||||
fixtures = ['users']
|
||||
|
||||
Reference in New Issue
Block a user