mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
Relation update (#8524)
* Add "note" field to PartRelated model * Improved API * Add field to serializer * Implement in RelatedPartTable * Bump API version
This commit is contained in:
parent
8f1a3a1ab7
commit
7fcf068f05
@ -1,13 +1,16 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 282
|
INVENTREE_API_VERSION = 283
|
||||||
|
|
||||||
"""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 = """
|
||||||
|
|
||||||
|
v283 - 2024-11-20 : https://github.com/inventree/InvenTree/pull/8524
|
||||||
|
- Adds "note" field to the PartRelated API endpoint
|
||||||
|
|
||||||
v282 - 2024-11-19 : https://github.com/inventree/InvenTree/pull/8487
|
v282 - 2024-11-19 : https://github.com/inventree/InvenTree/pull/8487
|
||||||
- Remove the "test statistics" API endpoints
|
- Remove the "test statistics" API endpoints
|
||||||
- This is now provided via a custom plugin
|
- This is now provided via a custom plugin
|
||||||
|
@ -1427,37 +1427,50 @@ class PartDetail(PartMixin, RetrieveUpdateDestroyAPI):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class PartRelatedList(ListCreateAPI):
|
class PartRelatedFilter(rest_filters.FilterSet):
|
||||||
"""API endpoint for accessing a list of PartRelated objects."""
|
"""FilterSet for PartRelated objects."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options."""
|
||||||
|
|
||||||
|
model = PartRelated
|
||||||
|
fields = ['part_1', 'part_2']
|
||||||
|
|
||||||
|
part = rest_filters.ModelChoiceFilter(
|
||||||
|
queryset=Part.objects.all(), method='filter_part', label=_('Part')
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_part(self, queryset, name, part):
|
||||||
|
"""Filter queryset to include only PartRelated objects which reference the specified part."""
|
||||||
|
return queryset.filter(Q(part_1=part) | Q(part_2=part)).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class PartRelatedMixin:
|
||||||
|
"""Mixin class for PartRelated API endpoints."""
|
||||||
|
|
||||||
queryset = PartRelated.objects.all()
|
queryset = PartRelated.objects.all()
|
||||||
serializer_class = part_serializers.PartRelationSerializer
|
serializer_class = part_serializers.PartRelationSerializer
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def get_queryset(self, *args, **kwargs):
|
||||||
"""Custom queryset filtering."""
|
"""Return an annotated queryset for the PartRelatedDetail endpoint."""
|
||||||
queryset = super().filter_queryset(queryset)
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
params = self.request.query_params
|
queryset = queryset.prefetch_related('part_1', 'part_2')
|
||||||
|
|
||||||
# Add a filter for "part" - we can filter either part_1 or part_2
|
|
||||||
part = params.get('part', None)
|
|
||||||
|
|
||||||
if part is not None:
|
|
||||||
try:
|
|
||||||
part = Part.objects.get(pk=part)
|
|
||||||
queryset = queryset.filter(Q(part_1=part) | Q(part_2=part)).distinct()
|
|
||||||
|
|
||||||
except (ValueError, Part.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class PartRelatedDetail(RetrieveUpdateDestroyAPI):
|
class PartRelatedList(PartRelatedMixin, ListCreateAPI):
|
||||||
"""API endpoint for accessing detail view of a PartRelated object."""
|
"""API endpoint for accessing a list of PartRelated objects."""
|
||||||
|
|
||||||
queryset = PartRelated.objects.all()
|
filterset_class = PartRelatedFilter
|
||||||
serializer_class = part_serializers.PartRelationSerializer
|
filter_backends = SEARCH_ORDER_FILTER
|
||||||
|
|
||||||
|
search_fields = ['part_1__name', 'part_2__name']
|
||||||
|
|
||||||
|
|
||||||
|
class PartRelatedDetail(PartRelatedMixin, RetrieveUpdateDestroyAPI):
|
||||||
|
"""API endpoint for accessing detail view of a PartRelated object."""
|
||||||
|
|
||||||
|
|
||||||
class PartParameterTemplateFilter(rest_filters.FilterSet):
|
class PartParameterTemplateFilter(rest_filters.FilterSet):
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2024-11-19 12:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0130_alter_parttesttemplate_part'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partrelated',
|
||||||
|
name='note',
|
||||||
|
field=models.CharField(blank=True, help_text='Note for this relationship', max_length=500, verbose_name='Note'),
|
||||||
|
),
|
||||||
|
]
|
@ -4680,6 +4680,13 @@ class PartRelated(InvenTree.models.InvenTreeMetadataModel):
|
|||||||
help_text=_('Select Related Part'),
|
help_text=_('Select Related Part'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
note = models.CharField(
|
||||||
|
max_length=500,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Note'),
|
||||||
|
help_text=_('Note for this relationship'),
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return a string representation of this Part-Part relationship."""
|
"""Return a string representation of this Part-Part relationship."""
|
||||||
return f'{self.part_1} <--> {self.part_2}'
|
return f'{self.part_1} <--> {self.part_2}'
|
||||||
|
@ -1505,7 +1505,7 @@ class PartRelationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
"""Metaclass defining serializer fields."""
|
"""Metaclass defining serializer fields."""
|
||||||
|
|
||||||
model = PartRelated
|
model = PartRelated
|
||||||
fields = ['pk', 'part_1', 'part_1_detail', 'part_2', 'part_2_detail']
|
fields = ['pk', 'part_1', 'part_1_detail', 'part_2', 'part_2_detail', 'note']
|
||||||
|
|
||||||
part_1_detail = PartSerializer(source='part_1', read_only=True, many=False)
|
part_1_detail = PartSerializer(source='part_1', read_only=True, many=False)
|
||||||
part_2_detail = PartSerializer(source='part_2', read_only=True, many=False)
|
part_2_detail = PartSerializer(source='part_2', read_only=True, many=False)
|
||||||
|
@ -10,14 +10,15 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useDeleteApiFormModal
|
useDeleteApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import type { TableColumn } from '../Column';
|
import type { TableColumn } from '../Column';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { type RowAction, RowDeleteAction } from '../RowActions';
|
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a table listing related parts for a given part
|
* Construct a table listing related parts for a given part
|
||||||
@ -45,6 +46,7 @@ export function RelatedPartTable({
|
|||||||
{
|
{
|
||||||
accessor: 'part',
|
accessor: 'part',
|
||||||
title: t`Part`,
|
title: t`Part`,
|
||||||
|
switchable: false,
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
const part = getPart(record);
|
const part = getPart(record);
|
||||||
return (
|
return (
|
||||||
@ -63,11 +65,16 @@ export function RelatedPartTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Part Description`,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
return getPart(record).description;
|
return getPart(record).description;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'note',
|
||||||
|
title: t`Note`,
|
||||||
|
sortable: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [partId]);
|
}, [partId]);
|
||||||
@ -102,6 +109,16 @@ export function RelatedPartTable({
|
|||||||
table: table
|
table: table
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const editRelatedPart = useEditApiFormModal({
|
||||||
|
url: ApiEndpoints.related_part_list,
|
||||||
|
pk: selectedRelatedPart,
|
||||||
|
title: t`Edit Related Part`,
|
||||||
|
fields: {
|
||||||
|
note: {}
|
||||||
|
},
|
||||||
|
table: table
|
||||||
|
});
|
||||||
|
|
||||||
const tableActions: ReactNode[] = useMemo(() => {
|
const tableActions: ReactNode[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
@ -116,6 +133,13 @@ export function RelatedPartTable({
|
|||||||
const rowActions = useCallback(
|
const rowActions = useCallback(
|
||||||
(record: any): RowAction[] => {
|
(record: any): RowAction[] => {
|
||||||
return [
|
return [
|
||||||
|
RowEditAction({
|
||||||
|
hidden: !user.hasChangeRole(UserRoles.part),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedRelatedPart(record.pk);
|
||||||
|
editRelatedPart.open();
|
||||||
|
}
|
||||||
|
}),
|
||||||
RowDeleteAction({
|
RowDeleteAction({
|
||||||
hidden: !user.hasDeleteRole(UserRoles.part),
|
hidden: !user.hasDeleteRole(UserRoles.part),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@ -131,6 +155,7 @@ export function RelatedPartTable({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{newRelatedPart.modal}
|
{newRelatedPart.modal}
|
||||||
|
{editRelatedPart.modal}
|
||||||
{deleteRelatedPart.modal}
|
{deleteRelatedPart.modal}
|
||||||
<InvenTreeTable
|
<InvenTreeTable
|
||||||
url={apiUrl(ApiEndpoints.related_part_list)}
|
url={apiUrl(ApiEndpoints.related_part_list)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user