mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +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:
		| @@ -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 | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|   | ||||
| @@ -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={}) { | ||||
|   | ||||
| @@ -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'), | ||||
| ] | ||||
|   | ||||
| @@ -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', | ||||
|         ] | ||||
|   | ||||
							
								
								
									
										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) | ||||
|  | ||||
|         # 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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user