* Add basic endpoint for group information

* Add hardcoded api-url lookup function for django models

* Adds JS function for rendering a 'group'

* Fix typo

* Add unit tests for new endpoints

* Increment API version

* JS linting
This commit is contained in:
Oliver 2023-02-11 07:09:59 +11:00 committed by GitHub
parent 06605e70c5
commit f4e8c05165
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 8 deletions

View File

@ -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

View File

@ -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):

View File

@ -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;
}

View File

@ -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 = `<span>${data.name}</span>`;
return html;
}
// Renderer for "User" model
// eslint-disable-next-line no-unused-vars
function renderUser(name, data, parameters={}, options={}) {

View File

@ -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<pk>[0-9]+)/?$', UserDetail.as_view(), name='user-detail'),
path('', UserList.as_view()),
re_path(r'^group/', include([
re_path(r'^(?P<pk>[0-9]+)/?$', GroupDetail.as_view(), name='api-group-detail'),
re_path(r'^.*$', GroupList.as_view(), name='api-group-list'),
])),
re_path(r'^(?P<pk>[0-9]+)/?$', UserDetail.as_view(), name='api-user-detail'),
path('', UserList.as_view(), name='api-user-list'),
]

View File

@ -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',
]

View File

@ -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)

View File

@ -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)