mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Test template table (#6311)
* Add PartTestTemplateTable * Update PartTestTemplate API - Improve filtering options - Add sorting options - Add search options * Update table * Add placeholders for editing and deleting test templates * Update calls to DescriptionColumn * CORS fixes: - Update CORS headers in settings.py * Add / edit / delete templates * Fix for partId
This commit is contained in:
parent
edad000d8e
commit
2fbb8c757f
@ -367,6 +367,31 @@ class PartAttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI):
|
||||
serializer_class = part_serializers.PartAttachmentSerializer
|
||||
|
||||
|
||||
class PartTestTemplateFilter(rest_filters.FilterSet):
|
||||
"""Custom filterset class for the PartTestTemplateList endpoint."""
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options for this filterset."""
|
||||
|
||||
model = PartTestTemplate
|
||||
fields = ['required', 'requires_value', 'requires_attachment']
|
||||
|
||||
part = rest_filters.ModelChoiceFilter(
|
||||
queryset=Part.objects.filter(trackable=True),
|
||||
label='Part',
|
||||
field_name='part',
|
||||
method='filter_part',
|
||||
)
|
||||
|
||||
def filter_part(self, queryset, name, part):
|
||||
"""Filter by the 'part' field.
|
||||
|
||||
Note that for the 'part' field, we also include any parts "above" the specified part.
|
||||
"""
|
||||
variants = part.get_ancestors(include_self=True)
|
||||
return queryset.filter(part__in=variants)
|
||||
|
||||
|
||||
class PartTestTemplateDetail(RetrieveUpdateDestroyAPI):
|
||||
"""Detail endpoint for PartTestTemplate model."""
|
||||
|
||||
@ -375,45 +400,20 @@ class PartTestTemplateDetail(RetrieveUpdateDestroyAPI):
|
||||
|
||||
|
||||
class PartTestTemplateList(ListCreateAPI):
|
||||
"""API endpoint for listing (and creating) a PartTestTemplate.
|
||||
|
||||
TODO: Add filterset class for this view
|
||||
"""
|
||||
"""API endpoint for listing (and creating) a PartTestTemplate."""
|
||||
|
||||
queryset = PartTestTemplate.objects.all()
|
||||
serializer_class = part_serializers.PartTestTemplateSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
"""Filter the test list queryset.
|
||||
|
||||
If filtering by 'part', we include results for any parts "above" the specified part.
|
||||
"""
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
params = self.request.query_params
|
||||
|
||||
part = params.get('part', None)
|
||||
|
||||
# Filter by part
|
||||
if part:
|
||||
try:
|
||||
part = Part.objects.get(pk=part)
|
||||
queryset = queryset.filter(
|
||||
part__in=part.get_ancestors(include_self=True)
|
||||
)
|
||||
except (ValueError, Part.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Filter by 'required' status
|
||||
required = params.get('required', None)
|
||||
|
||||
if required is not None:
|
||||
queryset = queryset.filter(required=str2bool(required))
|
||||
|
||||
return queryset
|
||||
filterset_class = PartTestTemplateFilter
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
search_fields = ['test_name', 'description']
|
||||
|
||||
ordering_fields = ['test_name', 'required', 'requires_value', 'requires_attachment']
|
||||
|
||||
ordering = 'test_name'
|
||||
|
||||
|
||||
class PartThumbs(ListAPI):
|
||||
"""API endpoint for retrieving information on available Part thumbnails."""
|
||||
|
@ -20,25 +20,38 @@ export function PartColumn(part: any) {
|
||||
|
||||
export function BooleanColumn({
|
||||
accessor,
|
||||
title
|
||||
title,
|
||||
sortable,
|
||||
switchable
|
||||
}: {
|
||||
accessor: string;
|
||||
title: string;
|
||||
sortable?: boolean;
|
||||
switchable?: boolean;
|
||||
}): TableColumn {
|
||||
return {
|
||||
accessor: accessor,
|
||||
title: title,
|
||||
sortable: true,
|
||||
sortable: sortable ?? true,
|
||||
switchable: switchable ?? true,
|
||||
render: (record: any) => <YesNoButton value={record[accessor]} />
|
||||
};
|
||||
}
|
||||
|
||||
export function DescriptionColumn(): TableColumn {
|
||||
export function DescriptionColumn({
|
||||
accessor,
|
||||
sortable,
|
||||
switchable
|
||||
}: {
|
||||
accessor?: string;
|
||||
sortable?: boolean;
|
||||
switchable?: boolean;
|
||||
}): TableColumn {
|
||||
return {
|
||||
accessor: 'description',
|
||||
accessor: accessor ?? 'description',
|
||||
title: t`Description`,
|
||||
sortable: false,
|
||||
switchable: true
|
||||
sortable: sortable ?? false,
|
||||
switchable: switchable ?? true
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ export function CompanyTable({
|
||||
);
|
||||
}
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'website',
|
||||
title: t`Website`,
|
||||
|
@ -27,7 +27,7 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
|
||||
sortable: true,
|
||||
switchable: false
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'pathstring',
|
||||
title: t`Path`,
|
||||
|
@ -57,7 +57,7 @@ export default function PartParameterTemplateTable() {
|
||||
title: t`Units`,
|
||||
sortable: true
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'checkbox',
|
||||
title: t`Checkbox`
|
||||
|
@ -45,7 +45,7 @@ function partTableColumns(): TableColumn[] {
|
||||
sortable: true,
|
||||
title: t`Units`
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'category',
|
||||
title: t`Category`,
|
||||
|
@ -0,0 +1,149 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import { partTestTemplateFields } from '../../../forms/PartForms';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTable } from '../../../hooks/UseTable';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||
import { TableFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
export default function PartTestTemplateTable({ partId }: { partId: number }) {
|
||||
const table = useTable('part-test-template');
|
||||
const user = useUserState();
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessor: 'test_name',
|
||||
title: t`Test Name`,
|
||||
switchable: false,
|
||||
sortable: true
|
||||
},
|
||||
DescriptionColumn({
|
||||
switchable: false
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'required',
|
||||
title: t`Required`
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'requires_value',
|
||||
title: t`Requires Value`
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'requires_attachment',
|
||||
title: t`Requires Attachment`
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
name: 'required',
|
||||
label: t`Required`,
|
||||
description: t`Show required tests`
|
||||
},
|
||||
{
|
||||
name: 'requires_value',
|
||||
label: t`Requires Value`,
|
||||
description: t`Show tests that require a value`
|
||||
},
|
||||
{
|
||||
name: 'requires_attachment',
|
||||
label: t`Requires Attachment`,
|
||||
description: t`Show tests that require an attachment`
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
let can_edit = user.hasChangeRole(UserRoles.part);
|
||||
let can_delete = user.hasDeleteRole(UserRoles.part);
|
||||
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !can_edit,
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.part_test_template_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit Test Template`,
|
||||
fields: partTestTemplateFields(),
|
||||
successMessage: t`Template updated`,
|
||||
onFormSuccess: table.refreshTable
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !can_delete,
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.part_test_template_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete Test Template`,
|
||||
successMessage: t`Test Template deleted`,
|
||||
onFormSuccess: table.refreshTable
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
const addTestTemplate = useCallback(() => {
|
||||
let fields = partTestTemplateFields();
|
||||
|
||||
fields['part'].value = partId;
|
||||
|
||||
openCreateApiForm({
|
||||
url: ApiPaths.part_test_template_list,
|
||||
title: t`Create Test Template`,
|
||||
fields: fields,
|
||||
successMessage: t`Template created`,
|
||||
onFormSuccess: table.refreshTable
|
||||
});
|
||||
}, [partId]);
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
let can_add = user.hasAddRole(UserRoles.part);
|
||||
|
||||
return [
|
||||
<AddItemButton
|
||||
tooltip={t`Add Test Template`}
|
||||
onClick={addTestTemplate}
|
||||
disabled={!can_add}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiPaths.part_test_template_list)}
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
params: {
|
||||
part: partId
|
||||
},
|
||||
customFilters: tableFilters,
|
||||
customActionGroups: tableActions,
|
||||
rowActions: rowActions
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -52,7 +52,7 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode {
|
||||
title: t`Manufacturer Part Number`,
|
||||
sortable: true
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
LinkColumn()
|
||||
];
|
||||
}, [params]);
|
||||
|
@ -63,7 +63,7 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
|
||||
switchable: false
|
||||
// TODO: Display extra information if order is overdue
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'supplier__name',
|
||||
title: t`Supplier`,
|
||||
|
@ -57,7 +57,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
||||
title: t`Supplier Part`,
|
||||
sortable: true
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'manufacturer',
|
||||
|
||||
|
@ -76,7 +76,7 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
||||
accessor: 'customer_reference',
|
||||
title: t`Customer Reference`
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
LineItemsProgressColumn(),
|
||||
StatusColumn(ModelType.returnorder),
|
||||
ProjectCodeColumn(),
|
||||
|
@ -80,7 +80,7 @@ export function SalesOrderTable({ params }: { params?: any }) {
|
||||
accessor: 'customer_reference',
|
||||
title: t`Customer Reference`
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
LineItemsProgressColumn(),
|
||||
StatusColumn(ModelType.salesorder),
|
||||
ProjectCodeColumn(),
|
||||
|
@ -32,7 +32,7 @@ export default function ProjectCodeTable() {
|
||||
sortable: true,
|
||||
title: t`Project Code`
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
ResponsibleColumn()
|
||||
];
|
||||
}, []);
|
||||
|
@ -51,7 +51,7 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
|
||||
title: t`Name`,
|
||||
switchable: false
|
||||
},
|
||||
DescriptionColumn(),
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
accessor: 'pathstring',
|
||||
title: t`Path`,
|
||||
|
@ -58,6 +58,7 @@ export enum ApiPaths {
|
||||
part_attachment_list = 'api-part-attachment-list',
|
||||
part_parameter_list = 'api-part-parameter-list',
|
||||
part_parameter_template_list = 'api-part-parameter-template-list',
|
||||
part_test_template_list = 'api-part-test-template-list',
|
||||
|
||||
// Company URLs
|
||||
company_list = 'api-company-list',
|
||||
|
@ -164,3 +164,16 @@ export function partParameterTemplateFields(): ApiFormFieldSet {
|
||||
checkbox: {}
|
||||
};
|
||||
}
|
||||
|
||||
export function partTestTemplateFields(): ApiFormFieldSet {
|
||||
return {
|
||||
part: {
|
||||
hidden: true
|
||||
},
|
||||
test_name: {},
|
||||
description: {},
|
||||
required: {},
|
||||
requires_value: {},
|
||||
requires_attachment: {}
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, LoadingOverlay, Stack, Text } from '@mantine/core';
|
||||
import { Group, LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import {
|
||||
IconBookmarks,
|
||||
IconBuilding,
|
||||
@ -44,6 +44,7 @@ import { UsedInTable } from '../../components/tables/bom/UsedInTable';
|
||||
import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
|
||||
import PartTestTemplateTable from '../../components/tables/part/PartTestTemplateTable';
|
||||
import { PartVariantTable } from '../../components/tables/part/PartVariantTable';
|
||||
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
|
||||
import { ManufacturerPartTable } from '../../components/tables/purchasing/ManufacturerPartTable';
|
||||
@ -189,12 +190,14 @@ export default function PartDetail() {
|
||||
label: t`Sales Orders`,
|
||||
icon: <IconTruckDelivery />,
|
||||
hidden: !part.salable,
|
||||
content: part.pk && (
|
||||
content: part.pk ? (
|
||||
<SalesOrderTable
|
||||
params={{
|
||||
part: part.pk ?? -1
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
},
|
||||
{
|
||||
@ -211,7 +214,12 @@ export default function PartDetail() {
|
||||
name: 'test_templates',
|
||||
label: t`Test Templates`,
|
||||
icon: <IconTestPipe />,
|
||||
hidden: !part.trackable
|
||||
hidden: !part.trackable,
|
||||
content: part?.pk ? (
|
||||
<PartTestTemplateTable partId={part?.pk} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'related_parts',
|
||||
|
@ -151,6 +151,8 @@ export function apiEndpoint(path: ApiPaths): string {
|
||||
return 'part/related/';
|
||||
case ApiPaths.part_attachment_list:
|
||||
return 'part/attachment/';
|
||||
case ApiPaths.part_test_template_list:
|
||||
return 'part/test-template/';
|
||||
case ApiPaths.company_list:
|
||||
return 'company/';
|
||||
case ApiPaths.contact_list:
|
||||
|
Loading…
Reference in New Issue
Block a user