mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Add contenttype model to API (#7079)
* Add contenttype APIs * add plugin and id identifier * resolve via modelname * bump API version * differentiate model view ids * add API test * remove unneeded response
This commit is contained in:
		| @@ -1,11 +1,14 @@ | ||||
| """InvenTree API version information.""" | ||||
|  | ||||
| # InvenTree API version | ||||
| INVENTREE_API_VERSION = 190 | ||||
| INVENTREE_API_VERSION = 191 | ||||
| """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" | ||||
|  | ||||
| INVENTREE_API_TEXT = """ | ||||
|  | ||||
| v191 - 2024-04-22 : https://github.com/inventree/InvenTree/pull/7079 | ||||
|     - Adds API endpoints for Contenttype model | ||||
|  | ||||
| v190 - 2024-04-19 : https://github.com/inventree/InvenTree/pull/7024 | ||||
|     - Adds "active" field to the Company API endpoints | ||||
|     - Allow company list to be filtered by "active" status | ||||
|   | ||||
| @@ -280,6 +280,8 @@ class InvenTreeMetadata(SimpleMetadata): | ||||
|                 # Special case for 'user' model | ||||
|                 if field_info['model'] == 'user': | ||||
|                     field_info['api_url'] = '/api/user/' | ||||
|                 elif field_info['model'] == 'contenttype': | ||||
|                     field_info['api_url'] = '/api/contenttype/' | ||||
|                 else: | ||||
|                     field_info['api_url'] = model.get_api_url() | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| import json | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.http.response import HttpResponse | ||||
| from django.urls import include, path, re_path | ||||
| from django.utils.decorators import method_decorator | ||||
| @@ -619,6 +620,38 @@ class FlagDetail(RetrieveAPI): | ||||
|         return {key: value} | ||||
|  | ||||
|  | ||||
| class ContentTypeList(ListAPI): | ||||
|     """List view for ContentTypes.""" | ||||
|  | ||||
|     queryset = ContentType.objects.all() | ||||
|     serializer_class = common.serializers.ContentTypeSerializer | ||||
|     permission_classes = [permissions.IsAuthenticated] | ||||
|  | ||||
|  | ||||
| class ContentTypeDetail(RetrieveAPI): | ||||
|     """Detail view for a ContentType model.""" | ||||
|  | ||||
|     queryset = ContentType.objects.all() | ||||
|     serializer_class = common.serializers.ContentTypeSerializer | ||||
|     permission_classes = [permissions.IsAuthenticated] | ||||
|  | ||||
|  | ||||
| @extend_schema(operation_id='contenttype_retrieve_model') | ||||
| class ContentTypeModelDetail(ContentTypeDetail): | ||||
|     """Detail view for a ContentType model.""" | ||||
|  | ||||
|     def get_object(self): | ||||
|         """Attempt to find a ContentType object with the provided key.""" | ||||
|         model_ref = self.kwargs.get('model', None) | ||||
|         if model_ref: | ||||
|             qs = self.filter_queryset(self.get_queryset()) | ||||
|             try: | ||||
|                 return qs.get(model=model_ref) | ||||
|             except ContentType.DoesNotExist: | ||||
|                 raise NotFound() | ||||
|         raise NotFound() | ||||
|  | ||||
|  | ||||
| settings_api_urls = [ | ||||
|     # User settings | ||||
|     path( | ||||
| @@ -799,6 +832,21 @@ common_api_urls = [ | ||||
|             path('', AllStatusViews.as_view(), name='api-status-all'), | ||||
|         ]), | ||||
|     ), | ||||
|     # Contenttype | ||||
|     path( | ||||
|         'contenttype/', | ||||
|         include([ | ||||
|             path( | ||||
|                 '<int:pk>/', ContentTypeDetail.as_view(), name='api-contenttype-detail' | ||||
|             ), | ||||
|             path( | ||||
|                 '<str:model>/', | ||||
|                 ContentTypeModelDetail.as_view(), | ||||
|                 name='api-contenttype-detail-modelname', | ||||
|             ), | ||||
|             path('', ContentTypeList.as_view(), name='api-contenttype-list'), | ||||
|         ]), | ||||
|     ), | ||||
| ] | ||||
|  | ||||
| admin_api_urls = [ | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| """JSON serializers for common components.""" | ||||
|  | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.db.models import OuterRef, Subquery | ||||
| from django.urls import reverse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| @@ -16,6 +17,7 @@ from InvenTree.serializers import ( | ||||
|     InvenTreeImageSerializerField, | ||||
|     InvenTreeModelSerializer, | ||||
| ) | ||||
| from plugin import registry as plugin_registry | ||||
| from users.serializers import OwnerSerializer | ||||
|  | ||||
|  | ||||
| @@ -303,6 +305,26 @@ class FlagSerializer(serializers.Serializer): | ||||
|         return data | ||||
|  | ||||
|  | ||||
| class ContentTypeSerializer(serializers.Serializer): | ||||
|     """Serializer for ContentType models.""" | ||||
|  | ||||
|     pk = serializers.IntegerField(read_only=True) | ||||
|     app_label = serializers.CharField(read_only=True) | ||||
|     model = serializers.CharField(read_only=True) | ||||
|     app_labeled_name = serializers.CharField(read_only=True) | ||||
|     is_plugin = serializers.SerializerMethodField('get_is_plugin', read_only=True) | ||||
|  | ||||
|     class Meta: | ||||
|         """Meta options for ContentTypeSerializer.""" | ||||
|  | ||||
|         model = ContentType | ||||
|         fields = ['pk', 'app_label', 'model', 'app_labeled_name', 'is_plugin'] | ||||
|  | ||||
|     def get_is_plugin(self, obj) -> bool: | ||||
|         """Return True if the model is a plugin model.""" | ||||
|         return obj.app_label in plugin_registry.installed_apps | ||||
|  | ||||
|  | ||||
| class CustomUnitSerializer(InvenTreeModelSerializer): | ||||
|     """DRF serializer for CustomUnit model.""" | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ from http import HTTPStatus | ||||
| from unittest import mock | ||||
|  | ||||
| from django.contrib.auth import get_user_model | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.core.cache import cache | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.core.files.uploadedfile import SimpleUploadedFile | ||||
| @@ -1339,3 +1340,46 @@ class CustomUnitAPITest(InvenTreeAPITestCase): | ||||
|  | ||||
|         for name in invalid_name_values: | ||||
|             self.patch(url, {'name': name}, expected_code=400) | ||||
|  | ||||
|  | ||||
| class ContentTypeAPITest(InvenTreeAPITestCase): | ||||
|     """Unit tests for the ContentType API.""" | ||||
|  | ||||
|     def test_list(self): | ||||
|         """Test API list functionality.""" | ||||
|         response = self.get(reverse('api-contenttype-list'), expected_code=200) | ||||
|         self.assertEqual(len(response.data), ContentType.objects.count()) | ||||
|  | ||||
|     def test_detail(self): | ||||
|         """Test API detail functionality.""" | ||||
|         ct = ContentType.objects.first() | ||||
|         assert ct | ||||
|  | ||||
|         response = self.get( | ||||
|             reverse('api-contenttype-detail', kwargs={'pk': ct.pk}), expected_code=200 | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(response.data['app_label'], ct.app_label) | ||||
|         self.assertEqual(response.data['model'], ct.model) | ||||
|  | ||||
|         # Test with model name | ||||
|         response = self.get( | ||||
|             reverse('api-contenttype-detail-modelname', kwargs={'model': ct.model}), | ||||
|             expected_code=200, | ||||
|         ) | ||||
|         self.assertEqual(response.data['app_label'], ct.app_label) | ||||
|         self.assertEqual(response.data['model'], ct.model) | ||||
|  | ||||
|         # Test non-existent model | ||||
|         self.get( | ||||
|             reverse( | ||||
|                 'api-contenttype-detail-modelname', kwargs={'model': 'nonexistent'} | ||||
|             ), | ||||
|             expected_code=404, | ||||
|         ) | ||||
|  | ||||
|         # PK should not work on model name endpoint | ||||
|         self.get( | ||||
|             reverse('api-contenttype-detail-modelname', kwargs={'model': None}), | ||||
|             expected_code=404, | ||||
|         ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user