mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 03:56:43 +00:00
Group API (#4327)
* 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:
parent
06605e70c5
commit
f4e8c05165
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
|
|
||||||
# InvenTree API version
|
# 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
|
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
|
v93 -> 2023-02-03 : https://github.com/inventree/InvenTree/pull/4300
|
||||||
- Adds extra information to the currency exchange endpoint
|
- Adds extra information to the currency exchange endpoint
|
||||||
- Adds API endpoint for manually updating exchange rates
|
- Adds API endpoint for manually updating exchange rates
|
||||||
|
@ -704,6 +704,17 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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
|
return None
|
||||||
|
|
||||||
def is_bool(self):
|
def is_bool(self):
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
modalShowSubmitButton,
|
modalShowSubmitButton,
|
||||||
renderBuild,
|
renderBuild,
|
||||||
renderCompany,
|
renderCompany,
|
||||||
|
renderGroup,
|
||||||
renderManufacturerPart,
|
renderManufacturerPart,
|
||||||
renderOwner,
|
renderOwner,
|
||||||
renderPart,
|
renderPart,
|
||||||
@ -2073,6 +2074,9 @@ function renderModelData(name, model, data, parameters, options) {
|
|||||||
case 'user':
|
case 'user':
|
||||||
renderer = renderUser;
|
renderer = renderUser;
|
||||||
break;
|
break;
|
||||||
|
case 'group':
|
||||||
|
renderer = renderGroup;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
/* exported
|
/* exported
|
||||||
renderBuild,
|
renderBuild,
|
||||||
renderCompany,
|
renderCompany,
|
||||||
|
renderGroup,
|
||||||
renderManufacturerPart,
|
renderManufacturerPart,
|
||||||
renderOwner,
|
renderOwner,
|
||||||
renderPart,
|
renderPart,
|
||||||
@ -15,6 +16,7 @@
|
|||||||
renderStockItem,
|
renderStockItem,
|
||||||
renderStockLocation,
|
renderStockLocation,
|
||||||
renderSupplierPart,
|
renderSupplierPart,
|
||||||
|
renderUser,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -216,6 +218,17 @@ function renderPart(name, data, parameters={}, options={}) {
|
|||||||
return html;
|
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
|
// Renderer for "User" model
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
function renderUser(name, data, parameters={}, options={}) {
|
function renderUser(name, data, parameters={}, options={}) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""DRF API definition for the 'users' app"""
|
"""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.core.exceptions import ObjectDoesNotExist
|
||||||
from django.urls import include, path, re_path
|
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.mixins import ListAPI, RetrieveAPI, RetrieveUpdateAPI
|
||||||
from InvenTree.serializers import UserSerializer
|
from InvenTree.serializers import UserSerializer
|
||||||
from users.models import Owner, RuleSet, check_user_role
|
from users.models import Owner, RuleSet, check_user_role
|
||||||
from users.serializers import OwnerSerializer
|
from users.serializers import GroupSerializer, OwnerSerializer
|
||||||
|
|
||||||
|
|
||||||
class OwnerList(ListAPI):
|
class OwnerList(ListAPI):
|
||||||
@ -113,7 +113,9 @@ class UserDetail(RetrieveAPI):
|
|||||||
|
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = [
|
||||||
|
permissions.IsAuthenticated
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class MeUserDetail(RetrieveUpdateAPI, UserDetail):
|
class MeUserDetail(RetrieveUpdateAPI, UserDetail):
|
||||||
@ -129,7 +131,9 @@ class UserList(ListAPI):
|
|||||||
|
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = [
|
||||||
|
permissions.IsAuthenticated,
|
||||||
|
]
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
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):
|
class GetAuthToken(APIView):
|
||||||
"""Return authentication token for an authenticated user."""
|
"""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'^.*$', OwnerList.as_view(), name='api-owner-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
re_path(r'^(?P<pk>[0-9]+)/?$', UserDetail.as_view(), name='user-detail'),
|
re_path(r'^group/', include([
|
||||||
path('', UserList.as_view()),
|
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'),
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""DRF API serializers for the 'users' app"""
|
"""DRF API serializers for the 'users' app"""
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
@ -24,3 +25,16 @@ class OwnerSerializer(InvenTreeModelSerializer):
|
|||||||
'name',
|
'name',
|
||||||
'label',
|
'label',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSerializer(InvenTreeModelSerializer):
|
||||||
|
"""Serializer for a 'Group'"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass defines serializer fields"""
|
||||||
|
|
||||||
|
model = Group
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'name',
|
||||||
|
]
|
||||||
|
55
InvenTree/users/test_api.py
Normal file
55
InvenTree/users/test_api.py
Normal 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)
|
@ -231,7 +231,7 @@ class OwnerModelTest(InvenTreeTestCase):
|
|||||||
# self.assertEqual(response['owner_id'], group.pk)
|
# self.assertEqual(response['owner_id'], group.pk)
|
||||||
|
|
||||||
# own user detail
|
# 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)
|
self.assertEqual(response_detail['username'], self.username)
|
||||||
|
|
||||||
response_me = self.do_request(reverse('api-user-me'), {}, 200)
|
response_me = self.do_request(reverse('api-user-me'), {}, 200)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user