Skip to content

Commit

Permalink
Admin tweaks (#7248)
Browse files Browse the repository at this point in the history
* Update admin site

- Implement 'autocomplete' for more fields
- Improves admin loading time

* Add "admin" buttons to the PUI interface

* Only allow superuser access
  • Loading branch information
SchrodingersGat committed May 17, 2024
1 parent acb1ec4 commit dc741b6
Show file tree
Hide file tree
Showing 19 changed files with 169 additions and 26 deletions.
9 changes: 9 additions & 0 deletions src/backend/InvenTree/common/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
import common.models


@admin.register(common.models.ProjectCode)
class ProjectCodeAdmin(ImportExportModelAdmin):
"""Admin settings for ProjectCode."""

list_display = ('code', 'description')

search_fields = ('code', 'description')


class SettingsAdmin(ImportExportModelAdmin):
"""Admin settings for InvenTreeSetting."""

Expand Down
4 changes: 4 additions & 0 deletions src/backend/InvenTree/company/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ class AddressAdmin(ImportExportModelAdmin):

search_fields = ['company', 'country', 'postal_code']

autocomplete_fields = ['company']


class ContactResource(InvenTreeResource):
"""Class for managing Contact data import/export."""
Expand All @@ -237,3 +239,5 @@ class ContactAdmin(ImportExportModelAdmin):
list_display = ('company', 'name', 'role', 'email', 'phone')

search_fields = ['company', 'name', 'email']

autocomplete_fields = ['company']
6 changes: 3 additions & 3 deletions src/backend/InvenTree/order/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class PurchaseOrderAdmin(ImportExportModelAdmin):

inlines = [PurchaseOrderLineItemInlineAdmin]

autocomplete_fields = ('supplier',)
autocomplete_fields = ['supplier', 'project_code', 'contact', 'address']


class SalesOrderResource(
Expand Down Expand Up @@ -152,7 +152,7 @@ class SalesOrderAdmin(ImportExportModelAdmin):

search_fields = ['reference', 'customer__name', 'description']

autocomplete_fields = ('customer',)
autocomplete_fields = ['customer', 'project_code', 'contact', 'address']


class PurchaseOrderLineItemResource(PriceResourceMixin, InvenTreeResource):
Expand Down Expand Up @@ -317,7 +317,7 @@ class ReturnOrderAdmin(ImportExportModelAdmin):

search_fields = ['reference', 'customer__name', 'description']

autocomplete_fields = ['customer']
autocomplete_fields = ['customer', 'project_code', 'contact', 'address']


class ReturnOrderLineItemResource(PriceResourceMixin, InvenTreeResource):
Expand Down
4 changes: 3 additions & 1 deletion src/backend/InvenTree/part/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ class PartAdmin(ImportExportModelAdmin):
'category',
'default_location',
'default_supplier',
'bom_checked_by',
'creation_user',
]

inlines = [PartParameterInline]
Expand All @@ -260,7 +262,7 @@ class PartPricingAdmin(admin.ModelAdmin):

list_display = ('part', 'overall_min', 'overall_max')

autcomplete_fields = ['part']
autocomplete_fields = ['part']


class PartStocktakeAdmin(admin.ModelAdmin):
Expand Down
1 change: 1 addition & 0 deletions src/backend/InvenTree/stock/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class StockItemAdmin(ImportExportModelAdmin):
'sales_order',
'stocktake_user',
'supplier_part',
'consumed_by',
]


Expand Down
9 changes: 5 additions & 4 deletions src/frontend/src/components/buttons/ActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { ReactNode } from 'react';
import { notYetImplemented } from '../../functions/notifications';

export type ActionButtonProps = {
key?: string;
icon?: ReactNode;
text?: string;
color?: string;
tooltip?: string;
variant?: string;
size?: number | string;
radius?: number | string;
disabled?: boolean;
onClick?: any;
hidden?: boolean;
Expand All @@ -26,15 +26,16 @@ export function ActionButton(props: ActionButtonProps) {
return (
!hidden && (
<Tooltip
key={`tooltip-${props.key}`}
key={`tooltip-${props.text}`}
disabled={!props.tooltip && !props.text}
label={props.tooltip ?? props.text}
position={props.tooltipAlignment ?? 'left'}
>
<ActionIcon
key={`action-icon-${props.key}`}
key={`action-icon-${props.text}`}
disabled={props.disabled}
radius="xs"
p={17}
radius={props.radius ?? 'xs'}
color={props.color}
size={props.size}
onClick={props.onClick ?? notYetImplemented}
Expand Down
88 changes: 88 additions & 0 deletions src/frontend/src/components/buttons/AdminButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { t } from '@lingui/macro';
import { IconUserStar } from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';

import { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
import { base_url } from '../../main';
import { useLocalState } from '../../states/LocalState';
import { useUserState } from '../../states/UserState';
import { ModelInformationDict } from '../render/ModelType';
import { ActionButton } from './ActionButton';

export type AdminButtonProps = {
model: ModelType;
pk: number | undefined;
};

/*
* A button that is used to navigate to the admin page for the selected item.
*
* This button is only rendered if:
* - The admin interface is enabled for the server
* - The selected model has an associated admin URL
* - The user has "superuser" role
* - The user has at least read rights for the selected item
*/
export default function AdminButton(props: AdminButtonProps) {
const user = useUserState();

const enabled: boolean = useMemo(() => {
// Only users with superuser permission will see this button
if (!user || !user.isLoggedIn() || !user.isSuperuser()) {
return false;
}

// TODO: Check if the server has the admin interface enabled

const modelDef = ModelInformationDict[props.model];

// No admin URL associated with the model
if (!modelDef.admin_url) {
return false;
}

// No primary key provided
if (!props.pk) {
return false;
}

return true;
}, [user, props.model, props.pk]);

const openAdmin = useCallback(
(event: any) => {
const modelDef = ModelInformationDict[props.model];
const host = useLocalState.getState().host;

if (!modelDef.admin_url) {
return;
}

// TODO: Check the actual "admin" URL (it may be custom)
const url = `${host}/admin${modelDef.admin_url}${props.pk}/`;

if (event?.ctrlKey || event?.shiftKey) {
// Open the link in a new tab
window.open(url, '_blank');
} else {
window.open(url, '_self');
}
},
[props.model, props.pk]
);

return (
<ActionButton
icon={<IconUserStar />}
color="blue"
size="lg"
radius="sm"
variant="filled"
tooltip={t`Open in admin interface`}
hidden={!enabled}
onClick={openAdmin}
/>
);
}
34 changes: 23 additions & 11 deletions src/frontend/src/components/render/ModelType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ModelInformationInterface {
url_detail?: string;
api_endpoint: ApiEndpoints;
cui_detail?: string;
admin_url?: string;
}

export type ModelDict = {
Expand All @@ -23,7 +24,8 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/part',
url_detail: '/part/:pk/',
cui_detail: '/part/:pk/',
api_endpoint: ApiEndpoints.part_list
api_endpoint: ApiEndpoints.part_list,
admin_url: '/part/part/'
},
partparametertemplate: {
label: t`Part Parameter Template`,
Expand All @@ -45,39 +47,44 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/supplierpart',
url_detail: '/purchasing/supplier-part/:pk/',
cui_detail: '/supplier-part/:pk/',
api_endpoint: ApiEndpoints.supplier_part_list
api_endpoint: ApiEndpoints.supplier_part_list,
admin_url: '/company/supplierpart/'
},
manufacturerpart: {
label: t`Manufacturer Part`,
label_multiple: t`Manufacturer Parts`,
url_overview: '/manufacturerpart',
url_detail: '/purchasing/manufacturer-part/:pk/',
cui_detail: '/manufacturer-part/:pk/',
api_endpoint: ApiEndpoints.manufacturer_part_list
api_endpoint: ApiEndpoints.manufacturer_part_list,
admin_url: '/company/manufacturerpart/'
},
partcategory: {
label: t`Part Category`,
label_multiple: t`Part Categories`,
url_overview: '/part/category',
url_detail: '/part/category/:pk/',
cui_detail: '/part/category/:pk/',
api_endpoint: ApiEndpoints.category_list
api_endpoint: ApiEndpoints.category_list,
admin_url: '/part/partcategory/'
},
stockitem: {
label: t`Stock Item`,
label_multiple: t`Stock Items`,
url_overview: '/stock/item',
url_detail: '/stock/item/:pk/',
cui_detail: '/stock/item/:pk/',
api_endpoint: ApiEndpoints.stock_item_list
api_endpoint: ApiEndpoints.stock_item_list,
admin_url: '/stock/stockitem/'
},
stocklocation: {
label: t`Stock Location`,
label_multiple: t`Stock Locations`,
url_overview: '/stock/location',
url_detail: '/stock/location/:pk/',
cui_detail: '/stock/location/:pk/',
api_endpoint: ApiEndpoints.stock_location_list
api_endpoint: ApiEndpoints.stock_location_list,
admin_url: '/stock/stocklocation/'
},
stocklocationtype: {
label: t`Stock Location Type`,
Expand All @@ -95,7 +102,8 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/build',
url_detail: '/build/:pk/',
cui_detail: '/build/:pk/',
api_endpoint: ApiEndpoints.build_order_list
api_endpoint: ApiEndpoints.build_order_list,
admin_url: '/build/build/'
},
buildline: {
label: t`Build Line`,
Expand All @@ -111,7 +119,8 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/company',
url_detail: '/company/:pk/',
cui_detail: '/company/:pk/',
api_endpoint: ApiEndpoints.company_list
api_endpoint: ApiEndpoints.company_list,
admin_url: '/company/company/'
},
projectcode: {
label: t`Project Code`,
Expand All @@ -126,7 +135,8 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/purchasing/purchase-order',
url_detail: '/purchasing/purchase-order/:pk/',
cui_detail: '/order/purchase-order/:pk/',
api_endpoint: ApiEndpoints.purchase_order_list
api_endpoint: ApiEndpoints.purchase_order_list,
admin_url: '/order/purchaseorder/'
},
purchaseorderline: {
label: t`Purchase Order Line`,
Expand All @@ -139,7 +149,8 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/sales/sales-order',
url_detail: '/sales/sales-order/:pk/',
cui_detail: '/order/sales-order/:pk/',
api_endpoint: ApiEndpoints.sales_order_list
api_endpoint: ApiEndpoints.sales_order_list,
admin_url: '/order/salesorder/'
},
salesordershipment: {
label: t`Sales Order Shipment`,
Expand All @@ -154,7 +165,8 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/sales/return-order',
url_detail: '/sales/return-order/:pk/',
cui_detail: '/order/return-order/:pk/',
api_endpoint: ApiEndpoints.return_order_list
api_endpoint: ApiEndpoints.return_order_list,
admin_url: '/order/returnorder/'
},
address: {
label: t`Address`,
Expand Down
6 changes: 4 additions & 2 deletions src/frontend/src/pages/build/BuildDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';

import AdminButton from '../../components/buttons/AdminButton';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
Expand Down Expand Up @@ -347,8 +348,8 @@ export default function BuildDetail() {
});

const buildActions = useMemo(() => {
// TODO: Disable certain actions based on user permissions
return [
<AdminButton model={ModelType.build} pk={build.pk} />,
<ActionDropdown
key="barcode"
tooltip={t`Barcode Actions`}
Expand Down Expand Up @@ -386,7 +387,8 @@ export default function BuildDetail() {
}),
CancelItemAction({
tooltip: t`Cancel order`,
onClick: () => cancelBuild.open()
onClick: () => cancelBuild.open(),
hidden: !user.hasChangeRole(UserRoles.build)
// TODO: Hide if build cannot be cancelled
}),
DuplicateItemAction({
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/pages/company/CompanyDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';

import AdminButton from '../../components/buttons/AdminButton';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
import { DetailsImage } from '../../components/details/DetailsImage';
Expand All @@ -32,6 +33,7 @@ import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { companyFields } from '../../forms/CompanyForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
Expand Down Expand Up @@ -285,6 +287,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {

const companyActions = useMemo(() => {
return [
<AdminButton model={ModelType.company} pk={company.pk} />,
<ActionDropdown
key="company"
tooltip={t`Company Actions`}
Expand Down

0 comments on commit dc741b6

Please sign in to comment.