Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 387
INVENTREE_API_VERSION = 388

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""

INVENTREE_API_TEXT = """

v388 -> 2025-08-28 : https://github.com/inventree/InvenTree/pull/8596
- Adds ReferenceSource and Reference endpoints to the API

v387 -> 2025-08-19 : https://github.com/inventree/InvenTree/pull/10188
- Adds "update_records" field to the DataImportSession API

Expand Down
2 changes: 2 additions & 0 deletions src/backend/InvenTree/common/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,7 @@ class NewsFeedEntryAdmin(admin.ModelAdmin):


admin.site.register(common.models.WebhookMessage, admin.ModelAdmin)
admin.site.register(common.models.Reference, admin.ModelAdmin)
admin.site.register(common.models.ReferenceSource, admin.ModelAdmin)
admin.site.register(common.models.EmailMessage, admin.ModelAdmin)
admin.site.register(common.models.EmailThread, admin.ModelAdmin)
113 changes: 113 additions & 0 deletions src/backend/InvenTree/common/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,117 @@ def perform_create(self, serializer):
path('', SelectionListList.as_view(), name='api-selectionlist-list'),
]


class ReferenceSourceList(ListCreateAPI):
"""List view for all reference sources."""

queryset = common.models.ReferenceSource.objects.all()
serializer_class = common.serializers.ReferenceSourceSerializer
permission_classes = [IsAuthenticatedOrReadScope, IsStaffOrReadOnlyScope]
filter_backends = SEARCH_ORDER_FILTER

ordering_fields = [
'name',
'description',
'slug',
'locked',
'active',
'source_plugin',
'created',
'last_updated',
]
search_fields = ['name', 'description', 'slug']


class ReferenceSourceDetail(RetrieveUpdateDestroyAPI):
"""Detail view for a particular reference source."""

queryset = common.models.ReferenceSource.objects.all()
serializer_class = common.serializers.ReferenceSourceSerializer
permission_classes = [IsAuthenticatedOrReadScope, IsStaffOrReadOnlyScope]


class ReferenceList(ListCreateAPI):
"""List view for all references."""

queryset = common.models.Reference.objects.all()
serializer_class = common.serializers.ReferenceSerializer
permission_classes = [IsAuthenticatedOrReadScope, IsStaffOrReadOnlyScope]
filter_backends = SEARCH_ORDER_FILTER

ordering_fields = [
'source',
'target',
'value',
'locked',
'created',
'last_updated',
'checked',
'last_checked',
]
search_fields = ['source', 'target', 'value']

def get_queryset(self):
"""Return prefetched queryset."""
queryset = (
super()
.get_queryset()
.prefetch_related('target_content_type', 'target_object_id')
)

return queryset


class ReferenceDetail(RetrieveUpdateDestroyAPI):
"""Detail view for a particular reference."""

queryset = common.models.Reference.objects.all()
serializer_class = common.serializers.ReferenceSerializer
permission_classes = [IsAuthenticatedOrReadScope, IsStaffOrReadOnlyScope]

def get_queryset(self):
"""Return prefetched queryset."""
queryset = (
super()
.get_queryset()
.prefetch_related('target_content_type', 'target_object_id')
)

return queryset


reference_urls = [
path(
'source/',
include([
path(
'<int:pk>/',
include([
path(
'',
ReferenceSourceDetail.as_view(),
name='api-reference-source-detail',
)
]),
),
path('', ReferenceSourceList.as_view(), name='api-reference-source-list'),
]),
),
# TODO add api endpoint to get all references for a target
path(
'',
include([
path(
'<int:pk>/',
include([
path('', ReferenceDetail.as_view(), name='api-reference-detail')
]),
),
path('', ReferenceList.as_view(), name='api-reference-list'),
]),
),
]

# API URL patterns
settings_api_urls = [
# User settings
Expand Down Expand Up @@ -1116,6 +1227,8 @@ def perform_create(self, serializer):
path('icons/', IconList.as_view(), name='api-icon-list'),
# Selection lists
path('selection/', include(selection_urls)),
# References
path('reference/', include(reference_urls)),
# Data output
path(
'data-output/',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# Generated by Django 4.2.23 on 2025-08-21 16:23

import InvenTree.models
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
('plugin', '0011_delete_notificationusersetting'),
('contenttypes', '0002_remove_content_type_name'),
('common', '0039_emailthread_emailmessage'),
]

operations = [
migrations.CreateModel(
name='ReferenceSource',
fields=[
(
'id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'metadata',
models.JSONField(
blank=True,
help_text='JSON metadata field, for use by external plugins',
null=True,
verbose_name='Plugin Metadata',
),
),
(
'name',
models.CharField(
help_text='Name of the reference source',
max_length=100,
unique=True,
verbose_name='Name',
),
),
(
'description',
models.CharField(
blank=True,
help_text='Description of the reference source',
max_length=250,
verbose_name='Description',
),
),
(
'locked',
models.BooleanField(
default=False,
help_text='Is this reference source locked?',
verbose_name='Locked',
),
),
(
'active',
models.BooleanField(
default=True,
help_text='Can this reference source be used?',
verbose_name='Active',
),
),
(
'source_string',
models.CharField(
blank=True,
help_text='Optional string identifying the source used for this reference source',
max_length=1000,
verbose_name='Source String',
),
),
(
'created',
models.DateTimeField(
auto_now_add=True,
help_text='Date and time that the reference source was created',
verbose_name='Created',
),
),
(
'last_updated',
models.DateTimeField(
auto_now=True,
help_text='Date and time that the reference source was last updated',
verbose_name='Last Updated',
),
),
(
'validation_pattern',
models.CharField(
blank=True,
help_text='Regular expression pattern to validate a reference',
max_length=250,
verbose_name='Validation Pattern',
),
),
(
'max_length',
models.PositiveIntegerField(
default=100,
help_text='Maximum length of the reference string',
verbose_name='Max Length',
),
),
(
'reference_is_unique_global',
models.BooleanField(
default=False,
help_text='Are references unique globally?',
verbose_name='Unique Globally',
),
),
(
'reference_is_link',
models.BooleanField(
default=False,
help_text='Are references required to be valid URIs as per RFC 3986?',
verbose_name='Reference is Link',
),
),
(
'source_plugin',
models.ForeignKey(
blank=True,
help_text='Plugin which provides the reference source',
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to='plugin.pluginconfig',
verbose_name='Source Plugin',
),
),
],
options={
'verbose_name': 'Reference Source',
'verbose_name_plural': 'Reference Sources',
},
bases=(InvenTree.models.PluginValidationMixin, models.Model),
),
migrations.CreateModel(
name='Reference',
fields=[
(
'id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'metadata',
models.JSONField(
blank=True,
help_text='JSON metadata field, for use by external plugins',
null=True,
verbose_name='Plugin Metadata',
),
),
(
'target_object_id',
models.PositiveIntegerField(
help_text='ID of the target object',
verbose_name='Target Object ID',
),
),
(
'value',
models.CharField(
help_text='Raw value of the reference',
max_length=255,
verbose_name='Value',
),
),
(
'locked',
models.BooleanField(
default=False,
help_text='Is this reference locked?',
verbose_name='Locked',
),
),
(
'created',
models.DateTimeField(
auto_now_add=True,
help_text='Date and time that the reference was created',
verbose_name='Created',
),
),
(
'last_updated',
models.DateTimeField(
auto_now=True,
help_text='Date and time that the reference was last updated',
verbose_name='Last Updated',
),
),
(
'checked',
models.BooleanField(
default=False,
help_text='Was this reference checked to be valid?',
verbose_name='Checked',
),
),
(
'last_checked',
models.DateTimeField(
blank=True,
help_text='Date and time that the reference was last checked',
null=True,
verbose_name='Last Checked',
),
),
(
'source',
models.ForeignKey(
help_text='Reference source that defined this reference',
on_delete=django.db.models.deletion.CASCADE,
to='common.referencesource',
verbose_name='Source',
),
),
(
'target_content_type',
models.ForeignKey(
help_text='Content type of the target object',
on_delete=django.db.models.deletion.CASCADE,
related_name='reference_target',
to='contenttypes.contenttype',
verbose_name='Target Content Type',
),
),
],
options={'abstract': False},
bases=(InvenTree.models.PluginValidationMixin, models.Model),
),
]
Loading
Loading