mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +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:
parent
3f80a45cb5
commit
b02b6b2bba
@ -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,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user