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:
Matthias Mair 2024-04-23 00:05:43 +02:00 committed by GitHub
parent 3f80a45cb5
commit b02b6b2bba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 120 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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

View File

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