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 information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# 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."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
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
|
v190 - 2024-04-19 : https://github.com/inventree/InvenTree/pull/7024
|
||||||
- Adds "active" field to the Company API endpoints
|
- Adds "active" field to the Company API endpoints
|
||||||
- Allow company list to be filtered by "active" status
|
- Allow company list to be filtered by "active" status
|
||||||
|
@ -280,6 +280,8 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
# Special case for 'user' model
|
# Special case for 'user' model
|
||||||
if field_info['model'] == 'user':
|
if field_info['model'] == 'user':
|
||||||
field_info['api_url'] = '/api/user/'
|
field_info['api_url'] = '/api/user/'
|
||||||
|
elif field_info['model'] == 'contenttype':
|
||||||
|
field_info['api_url'] = '/api/contenttype/'
|
||||||
else:
|
else:
|
||||||
field_info['api_url'] = model.get_api_url()
|
field_info['api_url'] = model.get_api_url()
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@ -619,6 +620,38 @@ class FlagDetail(RetrieveAPI):
|
|||||||
return {key: value}
|
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 = [
|
settings_api_urls = [
|
||||||
# User settings
|
# User settings
|
||||||
path(
|
path(
|
||||||
@ -799,6 +832,21 @@ common_api_urls = [
|
|||||||
path('', AllStatusViews.as_view(), name='api-status-all'),
|
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 = [
|
admin_api_urls = [
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""JSON serializers for common components."""
|
"""JSON serializers for common components."""
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import OuterRef, Subquery
|
from django.db.models import OuterRef, Subquery
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -16,6 +17,7 @@ from InvenTree.serializers import (
|
|||||||
InvenTreeImageSerializerField,
|
InvenTreeImageSerializerField,
|
||||||
InvenTreeModelSerializer,
|
InvenTreeModelSerializer,
|
||||||
)
|
)
|
||||||
|
from plugin import registry as plugin_registry
|
||||||
from users.serializers import OwnerSerializer
|
from users.serializers import OwnerSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -303,6 +305,26 @@ class FlagSerializer(serializers.Serializer):
|
|||||||
return data
|
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):
|
class CustomUnitSerializer(InvenTreeModelSerializer):
|
||||||
"""DRF serializer for CustomUnit model."""
|
"""DRF serializer for CustomUnit model."""
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from http import HTTPStatus
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
@ -1339,3 +1340,46 @@ class CustomUnitAPITest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
for name in invalid_name_values:
|
for name in invalid_name_values:
|
||||||
self.patch(url, {'name': name}, expected_code=400)
|
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