Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3812999
Add global "test template" table to admin center
SchrodingersGat Jul 1, 2025
720ef80
Tweaks
SchrodingersGat Jul 1, 2025
9afafa1
Add new PartTest model
SchrodingersGat Jul 1, 2025
2102ee9
Add data migration
SchrodingersGat Jul 1, 2025
4e2ed1b
remove the "part" field from PartTestTemplate model
SchrodingersGat Jul 1, 2025
2c6ee01
Add API endpoints and serializers
SchrodingersGat Jul 1, 2025
0ebfdc2
Add basic table
SchrodingersGat Jul 1, 2025
7c7aaac
Merge branch 'master' into test-refactor
SchrodingersGat Jul 4, 2025
51db9a2
Merge branch 'master' into test-refactor
SchrodingersGat Jul 5, 2025
2562c03
Fix asserts
SchrodingersGat Jul 5, 2025
b5f29eb
Merge branch 'master' into test-refactor
SchrodingersGat Jul 9, 2025
9c88f5b
Fix migrations
SchrodingersGat Feb 25, 2026
e2eab48
Bump API version
SchrodingersGat Feb 25, 2026
69142ca
Improve migration
SchrodingersGat Feb 25, 2026
91f2fe0
Merge commit '3d9104e9a5ac2cef817a4fb6635c298bc60bc95d' into test-ref…
SchrodingersGat Mar 1, 2026
60d5d20
Merge commit '2c67454163e6f4bf240448e4a9cd560d101accb5' into test-ref…
SchrodingersGat Mar 2, 2026
dd97381
Cleanup PartTestTemplateTable
SchrodingersGat Mar 2, 2026
69359a1
Table updates
SchrodingersGat Mar 2, 2026
4654c7b
Add info icon
SchrodingersGat Mar 2, 2026
ed2f88d
Merge commit '3bbdddf51debe80c2fdf6eb5d6507e4f949568b7' into test-ref…
SchrodingersGat Mar 2, 2026
4f06391
Frontend compilation
SchrodingersGat Mar 2, 2026
d5c4559
Display PartTest table in Category view
SchrodingersGat Mar 2, 2026
1c6dcb5
Tweak API filter
SchrodingersGat Mar 2, 2026
1122ef1
Table updates
SchrodingersGat Mar 2, 2026
04743a0
Merge commit '948818bc78f5368c32b415a670d3bed34ae7468f' into test-ref…
SchrodingersGat Mar 3, 2026
4440073
Add method to determine the tests associated with a given part instance
SchrodingersGat Mar 3, 2026
c3d9d01
Placeholder for tests
SchrodingersGat Mar 3, 2026
40f6d1b
Merge commit '2c8ef258941d1da2e5d8e0310c4f465c5fa72fe9' into test-ref…
SchrodingersGat Mar 10, 2026
f3b71d7
Merge commit '3a3816307e5fe8cd67bf8c6425248f17e972e49a' into test-ref…
SchrodingersGat Mar 21, 2026
fb941f7
Merge commit '953b77bed9cec9d14dd1b4e2cd4bd0c2384b22fb' into test-ref…
SchrodingersGat Mar 24, 2026
51e8088
Fix typos
SchrodingersGat Mar 24, 2026
3a713de
Mark "part" field as nullable in migration
SchrodingersGat Mar 24, 2026
7b6690c
isGeneratingSchema fix
SchrodingersGat Mar 24, 2026
06973cf
Debug output for reverse migration
SchrodingersGat Mar 24, 2026
ec230b7
Refactor / simplify
SchrodingersGat Mar 24, 2026
55eae37
Merge branch 'master' into test-refactor
SchrodingersGat Mar 25, 2026
93f28cf
Add playwright test for "Test Template" page in admin
SchrodingersGat Mar 25, 2026
d0f8df0
Playwright tests for inherited part test templates
SchrodingersGat Mar 25, 2026
d583d26
Who fixes the fixtures?
SchrodingersGat Mar 25, 2026
7ac1417
Merge branch 'test-refactor' of github.com:SchrodingersGat/InvenTree …
SchrodingersGat Mar 25, 2026
4e0219a
Adjust part functions for extract test and template information
SchrodingersGat Mar 26, 2026
9684699
Merge branch 'master' into test-refactor
SchrodingersGat Mar 26, 2026
3c62624
Fixes
SchrodingersGat Mar 26, 2026
903fb54
Merge commit 'b4f230753f12170d22f696d93326a82b0eef9013' into test-ref…
SchrodingersGat Mar 29, 2026
8fcdd54
Merge commit '67d6026637dbca15ce5dd873615da73cf5df721f' into test-ref…
SchrodingersGat Mar 30, 2026
f922c85
Updated unit test
SchrodingersGat Mar 30, 2026
87ba739
Merge commit '9c1d8c1b1dcba8f3b96e3eed32fade8d6c081095' into test-ref…
SchrodingersGat Apr 3, 2026
6e90564
Merge branch 'master' into test-refactor
SchrodingersGat Apr 3, 2026
cecd586
Merge commit '9324f7c9bb0ca219dc2cb4143f090c884c488c97' into test-ref…
SchrodingersGat Apr 19, 2026
b2d7942
Merge branch 'master' into test-refactor
SchrodingersGat Apr 21, 2026
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
2 changes: 1 addition & 1 deletion docs/docs/settings/error_codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ See [Override global settings](../settings/global.md#override-global-settings) f
#### INVE-I2
**Issue with filtering serializer or decorator - Backend**

An issue was detected with the application of a filtering serializer or decorator. This might lead to unexpected behaviour or performance issues. Therefore an issue is raised to make the developer aware of the possible issue. Look into the docstrings of enable_filter, FilterableSerializerField or FilterableSerializerMixin.
An issue was detected with the application of a filtering serializer or decorator. This might lead to unexpected behaviour or performance issues. Therefore an issue is raised to make the developer aware of the possible issue. Look into the docstrings of OptionalField, FilterableSerializerField or FilterableSerializerMixin.

This warning should only be raised during development and not in production, if you recently installed a plugin you might want to contact the plugin author.

Expand Down
7 changes: 6 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 479
INVENTREE_API_VERSION = 480

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

INVENTREE_API_TEXT = """

v480 -> 2026-04-19 : https://github.com/inventree/InvenTree/pull/9928
- Refactor of "PartTestTemplate" models and API endpoints
- Add "PartTest" API endpoint

v479 -> 2026-04-11 : https://github.com/inventree/InvenTree/pull/11723
- POST /api//notifications/readall/ now requires a POST action
- POST /api/admin/email/test/ - now returns a 200 on. a successful test
Expand Down
11 changes: 9 additions & 2 deletions src/backend/InvenTree/part/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,17 @@ class PartRelatedAdmin(admin.ModelAdmin):
class PartTestTemplateAdmin(admin.ModelAdmin):
"""Admin class for the PartTestTemplate model."""

list_display = ('part', 'test_name', 'required')
list_display = ['test_name']
readonly_fields = ['key']
search_fields = ['test_name']

autocomplete_fields = ('part',)

@admin.register(models.PartTest)
class PartTestAdmin(admin.ModelAdmin):
"""Admin class for the PartTest model."""

list_display = ('template', 'part', 'enabled', 'required')
autocomplete_fields = ('part', 'template')


@admin.register(models.BomItem)
Expand Down
91 changes: 68 additions & 23 deletions src/backend/InvenTree/part/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
PartRelated,
PartSellPriceBreak,
PartStocktake,
PartTest,
PartTestTemplate,
)

Expand Down Expand Up @@ -396,29 +397,7 @@ class Meta:
"""Metaclass options for this filterset."""

model = PartTestTemplate
fields = ['enabled', 'key', 'required', 'requires_attachment', 'requires_value']

part = rest_filters.ModelChoiceFilter(
queryset=Part.objects.filter(testable=True),
label='Part',
field_name='part',
method='filter_part',
)

def filter_part(self, queryset, name, part):
"""Filter by the 'part' field.

Note: If the 'include_inherited' query parameter is set,
we also include any parts "above" the specified part.
"""
include_inherited = str2bool(
self.request.query_params.get('include_inherited', True)
)

if include_inherited:
return queryset.filter(part__in=part.get_ancestors(include_self=True))
else:
return queryset.filter(part=part)
fields = ['key', 'requires_attachment', 'requires_value']

has_results = rest_filters.BooleanFilter(
label=_('Has Results'), method='filter_has_results'
Expand Down Expand Up @@ -471,6 +450,60 @@ class PartTestTemplateList(PartTestTemplateMixin, DataExportViewMixin, ListCreat
ordering = 'test_name'


class PartTestFilter(FilterSet):
"""Custom filterset class for the PartTestList endpoint."""

class Meta:
"""Metaclass options for this filterset."""

model = PartTest
fields = ['template', 'part', 'enabled', 'required']

for_part = rest_filters.ModelChoiceFilter(
queryset=Part.objects.all(), label=_('For Part'), method='filter_for_part'
)

def filter_for_part(self, queryset, name, part):
"""Filter 'PartTest' instances which associate with the provided 'Part'.

This is functionally different from the 'part' filter,
as it returns any PartTest instances which are associated with the provided part,
either directly or indirectly via the part's ancestors or categories.

Additionally, the results return at most one PartTest instance per PartTestTemplate,
with the most specific match being returned.
"""
return part.getPartTests(queryset=queryset)


class PartTestMixin:
"""Mixin class for the PartTest API endpoints."""

queryset = PartTest.objects.all()
serializer_class = part_serializers.PartTestSerializer

def get_queryset(self, *args, **kwargs):
"""Return an annotated queryset for the PartTestDetail endpoints."""
queryset = super().get_queryset(*args, **kwargs)
queryset = queryset.prefetch_related('template', 'part', 'part__pricing_data')
return queryset


class PartTestList(PartTestMixin, ListCreateAPI):
"""List endpoint for the PartTest model."""

filterset_class = PartTestFilter
filter_backends = SEARCH_ORDER_FILTER

search_fields = ['template__test_name', 'template__description']

ordering_fields = ['template', 'part', 'enabled', 'required']


class PartTestDetail(PartTestMixin, RetrieveUpdateDestroyAPI):
"""Detail endpoint for the PartTest model."""


class PartThumbs(ListAPI):
"""API endpoint for retrieving information on available Part thumbnails."""

Expand Down Expand Up @@ -1583,6 +1616,18 @@ class BomItemSubstituteDetail(RetrieveUpdateDestroyAPI):
),
]),
),
path(
'test/',
include([
path(
'<int:pk>/',
include([
path('', PartTestDetail.as_view(), name='api-part-test-detail')
]),
),
path('', PartTestList.as_view(), name='api-part-test-list'),
]),
),
# Base URL for part sale pricing
path(
'sale-price/',
Expand Down
72 changes: 59 additions & 13 deletions src/backend/InvenTree/part/fixtures/test_templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,114 @@
- model: part.parttesttemplate
pk: 1
fields:
part: 10000
test_name: Test strength of chair
key: 'teststrengthofchair'

- model: part.parttesttemplate
pk: 2
fields:
part: 10000
test_name: Apply paint
key: 'applypaint'

- model: part.parttesttemplate
pk: 3
fields:
part: 10000
test_name: Sew cushion
key: 'sewcushion'

- model: part.parttesttemplate
pk: 4
fields:
part: 10000
test_name: Attach legs
key: 'attachlegs'

- model: part.parttesttemplate
pk: 5
fields:
part: 10000
test_name: Record weight
key: 'recordweight'
required: false

# Add some tests for one of the variants
- model: part.parttesttemplate
pk: 6
fields:
part: 10003
test_name: Check chair is green
key: 'checkchairisgreen'
required: true

- model: part.parttesttemplate
pk: 8
fields:
part: 25
test_name: 'Temperature Test'
key: 'temperaturetest'
required: False

- model: part.parttesttemplate
pk: 9
fields:
part: 25
test_name: 'Settings Checksum'
key: 'settingschecksum'
required: False

- model: part.parttesttemplate
pk: 10
fields:
part: 25
test_name: 'Firmware Version'
key: 'firmwareversion'

# Link parts to test templates
- model: part.parttest
pk: 1
fields:
template: 1
part: 10000

- model: part.parttest
pk: 2
fields:
template: 2
part: 10000

- model: part.parttest
pk: 3
fields:
template: 3
part: 10000

- model: part.parttest
pk: 4
fields:
template: 4
part: 10000

- model: part.parttest
pk: 5
fields:
template: 5
part: 10000
required: false

- model: part.parttest
pk: 6
fields:
template: 6
part: 10003
required: true

- model: part.parttest
pk: 8
fields:
template: 8
part: 25
required: False

- model: part.parttest
pk: 9
fields:
template: 9
part: 25
required: False

- model: part.parttest
pk: 10
fields:
template: 10
part: 25
required: False
87 changes: 87 additions & 0 deletions src/backend/InvenTree/part/migrations/0148_parttest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Generated by Django 4.2.22 on 2025-07-01 13:32

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


class Migration(migrations.Migration):

dependencies = [
("part", "0147_remove_part_default_supplier"),
]

operations = [
# Mark the 'part' field on the PartTestTemplate model as nullable
# Note that this field will be deleted in migration part.0150
migrations.AlterField(
model_name="parttesttemplate",
name="part",
field=models.ForeignKey(
blank=True,
limit_choices_to={"testable": True},
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="test_templates",
to="part.part",
verbose_name="Part",
),
),
migrations.CreateModel(
name="PartTest",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"part",
models.ForeignKey(
blank=True,
help_text="Part to which this test applies",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="tests",
to="part.part",
verbose_name="Part",
),
),
(
"template",
models.ForeignKey(
help_text="Part test template to apply",
on_delete=django.db.models.deletion.CASCADE,
related_name="tests",
to="part.parttesttemplate",
verbose_name="Test Template",
),
),
(
"enabled",
models.BooleanField(
default=True,
help_text="Is this test enabled for the associated part?",
verbose_name="Enabled",
),
),
(
"required",
models.BooleanField(
default=True,
help_text="Is this test required to pass?",
verbose_name="Required",
),
)
],
options={
"verbose_name": "Part Test",
"unique_together": {("template", "part")},
},
bases=(InvenTree.models.PluginValidationMixin, models.Model),
),
]
Loading
Loading