diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 2a8042f4f7..7f207ec394 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 93 +INVENTREE_API_VERSION = 94 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v94 -> 2023-02-10 : https://github.com/inventree/InvenTree/pull/4327 + - Adds API endpoints for the "Group" auth model + v93 -> 2023-02-03 : https://github.com/inventree/InvenTree/pull/4300 - Adds extra information to the currency exchange endpoint - Adds API endpoint for manually updating exchange rates diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index f42b9ee5c5..d71d9e3aba 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -704,6 +704,17 @@ class BaseInvenTreeSetting(models.Model): except Exception: pass + # Some other model types are hard-coded + hardcoded_models = { + 'auth.user': 'api-user-list', + 'auth.group': 'api-group-list', + } + + model_table = f'{model_class._meta.app_label}.{model_class._meta.model_name}' + + if url := hardcoded_models[model_table]: + return reverse(url) + return None def is_bool(self): diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 705eeb7f75..c7fc9452d1 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -11,6 +11,7 @@ modalShowSubmitButton, renderBuild, renderCompany, + renderGroup, renderManufacturerPart, renderOwner, renderPart, @@ -2073,6 +2074,9 @@ function renderModelData(name, model, data, parameters, options) { case 'user': renderer = renderUser; break; + case 'group': + renderer = renderGroup; + break; default: break; } diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index 97970f67d4..4c4e70122f 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -8,6 +8,7 @@ /* exported renderBuild, renderCompany, + renderGroup, renderManufacturerPart, renderOwner, renderPart, @@ -15,6 +16,7 @@ renderStockItem, renderStockLocation, renderSupplierPart, + renderUser, */ @@ -216,6 +218,17 @@ function renderPart(name, data, parameters={}, options={}) { return html; } + +// Renderer for "Group" model +// eslint-disable-next-line no-unused-vars +function renderGroup(name, data, parameters={}, options={}) { + + var html = `${data.name}`; + + return html; + +} + // Renderer for "User" model // eslint-disable-next-line no-unused-vars function renderUser(name, data, parameters={}, options={}) { diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index 1d3c942cef..7aa9a8cc02 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -1,6 +1,6 @@ """DRF API definition for the 'users' app""" -from django.contrib.auth.models import User +from django.contrib.auth.models import Group, User from django.core.exceptions import ObjectDoesNotExist from django.urls import include, path, re_path @@ -13,7 +13,7 @@ from rest_framework.views import APIView from InvenTree.mixins import ListAPI, RetrieveAPI, RetrieveUpdateAPI from InvenTree.serializers import UserSerializer from users.models import Owner, RuleSet, check_user_role -from users.serializers import OwnerSerializer +from users.serializers import GroupSerializer, OwnerSerializer class OwnerList(ListAPI): @@ -113,7 +113,9 @@ class UserDetail(RetrieveAPI): queryset = User.objects.all() serializer_class = UserSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = [ + permissions.IsAuthenticated + ] class MeUserDetail(RetrieveUpdateAPI, UserDetail): @@ -129,7 +131,9 @@ class UserList(ListAPI): queryset = User.objects.all() serializer_class = UserSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = [ + permissions.IsAuthenticated, + ] filter_backends = [ DjangoFilterBackend, @@ -143,6 +147,35 @@ class UserList(ListAPI): ] +class GroupDetail(RetrieveAPI): + """Detail endpoint for a particular auth group""" + + queryset = Group.objects.all() + serializer_class = GroupSerializer + permission_classes = [ + permissions.IsAuthenticated, + ] + + +class GroupList(ListAPI): + """List endpoint for all auth groups""" + + queryset = Group.objects.all() + serializer_class = GroupSerializer + permission_classes = [ + permissions.IsAuthenticated, + ] + + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + ] + + search_fields = [ + 'name', + ] + + class GetAuthToken(APIView): """Return authentication token for an authenticated user.""" @@ -185,6 +218,12 @@ user_urls = [ re_path(r'^.*$', OwnerList.as_view(), name='api-owner-list'), ])), - re_path(r'^(?P[0-9]+)/?$', UserDetail.as_view(), name='user-detail'), - path('', UserList.as_view()), + re_path(r'^group/', include([ + re_path(r'^(?P[0-9]+)/?$', GroupDetail.as_view(), name='api-group-detail'), + re_path(r'^.*$', GroupList.as_view(), name='api-group-list'), + ])), + + re_path(r'^(?P[0-9]+)/?$', UserDetail.as_view(), name='api-user-detail'), + + path('', UserList.as_view(), name='api-user-list'), ] diff --git a/InvenTree/users/serializers.py b/InvenTree/users/serializers.py index bde5cb87db..153fd02d8e 100644 --- a/InvenTree/users/serializers.py +++ b/InvenTree/users/serializers.py @@ -1,5 +1,6 @@ """DRF API serializers for the 'users' app""" +from django.contrib.auth.models import Group from rest_framework import serializers @@ -24,3 +25,16 @@ class OwnerSerializer(InvenTreeModelSerializer): 'name', 'label', ] + + +class GroupSerializer(InvenTreeModelSerializer): + """Serializer for a 'Group'""" + + class Meta: + """Metaclass defines serializer fields""" + + model = Group + fields = [ + 'pk', + 'name', + ] diff --git a/InvenTree/users/test_api.py b/InvenTree/users/test_api.py new file mode 100644 index 0000000000..1f4d466ba4 --- /dev/null +++ b/InvenTree/users/test_api.py @@ -0,0 +1,55 @@ +"""API tests for various user / auth API endpoints""" + +from django.contrib.auth.models import Group, User +from django.urls import reverse + +from InvenTree.api_tester import InvenTreeAPITestCase + + +class UserAPITests(InvenTreeAPITestCase): + """Tests for user API endpoints""" + + def test_user_api(self): + """Tests for User API endpoints""" + + response = self.get( + reverse('api-user-list'), + expected_code=200 + ) + + # Check the correct number of results was returned + self.assertEqual(len(response.data), User.objects.count()) + + for key in ['username', 'pk', 'email']: + self.assertIn(key, response.data[0]) + + # Check detail URL + pk = response.data[0]['pk'] + + response = self.get( + reverse('api-user-detail', kwargs={'pk': pk}), + expected_code=200 + ) + + self.assertIn('pk', response.data) + self.assertIn('username', response.data) + + def test_group_api(self): + """Tests for the Group API endpoints""" + + response = self.get( + reverse('api-group-list'), + expected_code=200, + ) + + self.assertIn('name', response.data[0]) + + self.assertEqual(len(response.data), Group.objects.count()) + + # Check detail URL + response = self.get( + reverse('api-group-detail', kwargs={'pk': response.data[0]['pk']}), + expected_code=200, + ) + + self.assertIn('name', response.data) diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py index 406b9e83d3..5243d111b9 100644 --- a/InvenTree/users/tests.py +++ b/InvenTree/users/tests.py @@ -231,7 +231,7 @@ class OwnerModelTest(InvenTreeTestCase): # self.assertEqual(response['owner_id'], group.pk) # own user detail - response_detail = self.do_request(reverse('user-detail', kwargs={'pk': self.user.id}), {}, 200) + response_detail = self.do_request(reverse('api-user-detail', kwargs={'pk': self.user.id}), {}, 200) self.assertEqual(response_detail['username'], self.username) response_me = self.do_request(reverse('api-user-me'), {}, 200)