diff --git a/src/backend/InvenTree/InvenTree/permissions.py b/src/backend/InvenTree/InvenTree/permissions.py
index e1a5bb6a131b..c15fdb7bf3c6 100644
--- a/src/backend/InvenTree/InvenTree/permissions.py
+++ b/src/backend/InvenTree/InvenTree/permissions.py
@@ -8,7 +8,11 @@
def get_model_for_view(view):
- """Attempt to introspect the 'model' type for an API view."""
+ """Attempt to introspect the 'model' type for an API view.
+
+ Arguments:
+ view: The view being accessed
+ """
if hasattr(view, 'get_permission_model'):
return view.get_permission_model()
@@ -43,8 +47,13 @@ class RolePermission(permissions.BasePermission):
For example, a DELETE action will be rejected unless the user has the "part.remove" permission
"""
- def has_permission(self, request, view):
- """Determine if the current user has the specified permissions."""
+ def has_permission(self, request, view) -> bool:
+ """Determine if the current user has the specified permissions.
+
+ Arguments:
+ request: The HTTP request
+ view: The view being accessed
+ """
user = request.user
# Superuser can do it all
@@ -65,7 +74,7 @@ def has_permission(self, request, view):
if hasattr(view, 'rolemap'):
rolemap.update(view.rolemap)
- permission = rolemap[request.method]
+ permission = rolemap.get(request.method)
# The required role may be defined for the view class
if role := getattr(view, 'role_required', None):
@@ -95,9 +104,13 @@ class RolePermissionOrReadOnly(RolePermission):
REQUIRE_STAFF = False
- def has_permission(self, request, view):
+ def has_permission(self, request, view) -> bool:
"""Determine if the current user has the specified permissions.
+ Arguments:
+ request: The HTTP request
+ view: The view being accessed
+
- If the user does have the required role, then allow the request
- If the user does not have the required role, but is authenticated, then allow read-only access
"""
@@ -125,16 +138,26 @@ class StaffRolePermissionOrReadOnly(RolePermissionOrReadOnly):
class IsSuperuser(permissions.IsAdminUser):
"""Allows access only to superuser users."""
- def has_permission(self, request, view):
- """Check if the user is a superuser."""
+ def has_permission(self, request, view) -> bool:
+ """Check if the user is a superuser.
+
+ Arguments:
+ request: The HTTP request
+ view: The view being accessed
+ """
return bool(request.user and request.user.is_superuser)
class IsSuperuserOrReadOnly(permissions.IsAdminUser):
"""Allow read-only access to any user, but write access is restricted to superuser users."""
- def has_permission(self, request, view):
- """Check if the user is a superuser."""
+ def has_permission(self, request, view) -> bool:
+ """Check if the user is a superuser.
+
+ Arguments:
+ request: The HTTP request
+ view: The view being accessed
+ """
return bool(
(request.user and request.user.is_superuser)
or request.method in permissions.SAFE_METHODS
@@ -144,8 +167,13 @@ def has_permission(self, request, view):
class IsStaffOrReadOnly(permissions.IsAdminUser):
"""Allows read-only access to any user, but write access is restricted to staff users."""
- def has_permission(self, request, view):
- """Check if the user is a superuser."""
+ def has_permission(self, request, view) -> bool:
+ """Check if the user is a superuser.
+
+ Arguments:
+ request: The HTTP request
+ view: The view being accessed
+ """
return bool(
(request.user and request.user.is_staff)
or request.method in permissions.SAFE_METHODS
diff --git a/src/backend/InvenTree/part/test_api.py b/src/backend/InvenTree/part/test_api.py
index 4d8af0584645..c4a655aed1ad 100644
--- a/src/backend/InvenTree/part/test_api.py
+++ b/src/backend/InvenTree/part/test_api.py
@@ -42,13 +42,7 @@
class PartImageTestMixin:
"""Mixin for testing part images."""
- roles = [
- 'part.change',
- 'part.add',
- 'part.delete',
- 'part_category.change',
- 'part_category.add',
- ]
+ roles = ['part.change', 'part.add', 'part.delete']
@classmethod
def setUpTestData(cls):
@@ -98,14 +92,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
'stock',
]
- roles = [
- 'part.change',
- 'part.add',
- 'part.delete',
- 'part_category.change',
- 'part_category.add',
- 'part_category.delete',
- ]
+ roles = ['part.change', 'part.add', 'part.delete']
def test_category_list(self):
"""Test the PartCategoryList API endpoint."""
@@ -636,7 +623,7 @@ class PartOptionsAPITest(InvenTreeAPITestCase):
Ensure that the required field details are provided!
"""
- roles = ['part.add', 'part_category.view']
+ roles = ['part.add', 'part.view']
def test_part(self):
"""Test the Part API OPTIONS."""
@@ -719,17 +706,9 @@ def test_part_label_translation(self):
def test_category(self):
"""Test the PartCategory API OPTIONS endpoint."""
- actions = self.getActions(reverse('api-part-category-list'))
-
- # actions should *not* contain 'POST' as we do not have the correct role
- self.assertNotIn('POST', actions)
-
- self.assignRole('part_category.add')
-
actions = self.getActions(reverse('api-part-category-list'))['POST']
name = actions['name']
-
self.assertTrue(name['required'])
self.assertEqual(name['label'], 'Name')
@@ -777,13 +756,7 @@ class PartAPITestBase(InvenTreeAPITestCase):
'stock',
]
- roles = [
- 'part.change',
- 'part.add',
- 'part.delete',
- 'part_category.change',
- 'part_category.add',
- ]
+ roles = ['part.change', 'part.add', 'part.delete']
class PartAPITest(PartAPITestBase):
@@ -2750,14 +2723,7 @@ class PartInternalPriceBreakTest(InvenTreeAPITestCase):
'stock',
]
- roles = [
- 'part.change',
- 'part.add',
- 'part.delete',
- 'part_category.change',
- 'part_category.add',
- 'part_category.delete',
- ]
+ roles = ['part.change', 'part.add', 'part.delete']
def test_create_price_breaks(self):
"""Test we can create price breaks at various quantities."""
@@ -2975,7 +2941,7 @@ class PartMetadataAPITest(InvenTreeAPITestCase):
'stock',
]
- roles = ['part.change', 'part_category.change']
+ roles = ['part.change']
def setUp(self):
"""Setup unit tets."""
diff --git a/src/backend/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py b/src/backend/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py
index 66064d768d76..c1543c60e18d 100644
--- a/src/backend/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py
+++ b/src/backend/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py
@@ -209,7 +209,7 @@ def test_assign_to_location(self):
data={'barcode': barcode, 'stocklocation': 1}, expected_code=403
)
- self.assignRole('stock_location.change')
+ self.assignRole('stock.change')
# Assign random barcode data to a StockLocation instance
response = self.assign(
diff --git a/src/backend/InvenTree/stock/test_api.py b/src/backend/InvenTree/stock/test_api.py
index d579edb78fd5..4e6ca294881b 100644
--- a/src/backend/InvenTree/stock/test_api.py
+++ b/src/backend/InvenTree/stock/test_api.py
@@ -44,14 +44,7 @@ class StockAPITestCase(InvenTreeAPITestCase):
'stock_tests',
]
- roles = [
- 'stock.change',
- 'stock.add',
- 'stock_location.change',
- 'stock_location.add',
- 'stock_location.delete',
- 'stock.delete',
- ]
+ roles = ['stock.change', 'stock.add', 'stock.delete']
class StockLocationTest(StockAPITestCase):
@@ -2362,7 +2355,7 @@ class StockMetadataAPITest(InvenTreeAPITestCase):
'stock_tests',
]
- roles = ['stock.change', 'stock_location.change']
+ roles = ['stock.change']
def metatester(self, apikey, model):
"""Generic tester."""
diff --git a/src/backend/InvenTree/users/ruleset.py b/src/backend/InvenTree/users/ruleset.py
index d35309c41de6..05d686480ba2 100644
--- a/src/backend/InvenTree/users/ruleset.py
+++ b/src/backend/InvenTree/users/ruleset.py
@@ -14,10 +14,8 @@ def __str__(self):
return str(self.value)
ADMIN = 'admin'
- PART_CATEGORY = 'part_category'
PART = 'part'
STOCKTAKE = 'stocktake'
- STOCK_LOCATION = 'stock_location'
STOCK = 'stock'
BUILD = 'build'
PURCHASE_ORDER = 'purchase_order'
@@ -29,10 +27,8 @@ def __str__(self):
# These are used to determine the permissions available to a group of users.
RULESET_CHOICES = [
(RuleSetEnum.ADMIN, _('Admin')),
- (RuleSetEnum.PART_CATEGORY, _('Part Categories')),
(RuleSetEnum.PART, _('Parts')),
(RuleSetEnum.STOCKTAKE, _('Stocktake')),
- (RuleSetEnum.STOCK_LOCATION, _('Stock Locations')),
(RuleSetEnum.STOCK, _('Stock Items')),
(RuleSetEnum.BUILD, _('Build Orders')),
(RuleSetEnum.PURCHASE_ORDER, _('Purchase Orders')),
@@ -85,11 +81,6 @@ def get_ruleset_models() -> dict:
'machine_machineconfig',
'machine_machinesetting',
],
- RuleSetEnum.PART_CATEGORY: [
- 'part_partcategory',
- 'part_partcategoryparametertemplate',
- 'part_partcategorystar',
- ],
RuleSetEnum.PART: [
'part_part',
'part_partpricing',
@@ -102,17 +93,20 @@ def get_ruleset_models() -> dict:
'part_partparameter',
'part_partrelated',
'part_partstar',
+ 'part_partcategory',
'part_partcategorystar',
+ 'part_partcategoryparametertemplate',
'company_supplierpart',
'company_manufacturerpart',
'company_manufacturerpartparameter',
],
RuleSetEnum.STOCKTAKE: ['part_partstocktake', 'part_partstocktakereport'],
- RuleSetEnum.STOCK_LOCATION: ['stock_stocklocation', 'stock_stocklocationtype'],
RuleSetEnum.STOCK: [
'stock_stockitem',
'stock_stockitemtracking',
'stock_stockitemtestresult',
+ 'stock_stocklocation',
+ 'stock_stocklocationtype',
],
RuleSetEnum.BUILD: [
'part_part',
diff --git a/src/frontend/src/components/nav/SearchDrawer.tsx b/src/frontend/src/components/nav/SearchDrawer.tsx
index cc9e0b8c9958..1d853a6950a6 100644
--- a/src/frontend/src/components/nav/SearchDrawer.tsx
+++ b/src/frontend/src/components/nav/SearchDrawer.tsx
@@ -248,7 +248,7 @@ export function SearchDrawer({
model: ModelType.partcategory,
parameters: {},
enabled:
- user.hasViewRole(UserRoles.part_category) &&
+ user.hasViewRole(UserRoles.part) &&
userSettings.isSet('SEARCH_PREVIEW_SHOW_CATEGORIES')
},
{
@@ -268,7 +268,7 @@ export function SearchDrawer({
model: ModelType.stocklocation,
parameters: {},
enabled:
- user.hasViewRole(UserRoles.stock_location) &&
+ user.hasViewRole(UserRoles.stock) &&
userSettings.isSet('SEARCH_PREVIEW_SHOW_LOCATIONS')
},
{
diff --git a/src/frontend/src/enums/Roles.tsx b/src/frontend/src/enums/Roles.tsx
index 4c2d3bbaa8d1..2169473b8d2d 100644
--- a/src/frontend/src/enums/Roles.tsx
+++ b/src/frontend/src/enums/Roles.tsx
@@ -7,12 +7,10 @@ export enum UserRoles {
admin = 'admin',
build = 'build',
part = 'part',
- part_category = 'part_category',
purchase_order = 'purchase_order',
return_order = 'return_order',
sales_order = 'sales_order',
stock = 'stock',
- stock_location = 'stock_location',
stocktake = 'stocktake'
}
@@ -34,8 +32,6 @@ export function userRoleLabel(role: UserRoles): string {
return t`Build Orders`;
case UserRoles.part:
return t`Parts`;
- case UserRoles.part_category:
- return t`Part Categories`;
case UserRoles.purchase_order:
return t`Purchase Orders`;
case UserRoles.return_order:
@@ -44,8 +40,6 @@ export function userRoleLabel(role: UserRoles): string {
return t`Sales Orders`;
case UserRoles.stock:
return t`Stock Items`;
- case UserRoles.stock_location:
- return t`Stock Location`;
case UserRoles.stocktake:
return t`Stocktake`;
default:
diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
index f0deb5cab62f..d9b11c5d1f42 100644
--- a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
+++ b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
@@ -180,7 +180,7 @@ export default function AdminCenter() {
label: t`Category Parameters`,
icon: ,
content: ,
- hidden: !user.hasViewRole(UserRoles.part_category)
+ hidden: !user.hasViewRole(UserRoles.part)
},
{
name: 'stocktake',
@@ -206,7 +206,7 @@ export default function AdminCenter() {
label: t`Location Types`,
icon: ,
content: ,
- hidden: !user.hasViewRole(UserRoles.stock_location)
+ hidden: !user.hasViewRole(UserRoles.stock)
},
{
name: 'plugin',
diff --git a/src/frontend/src/pages/part/CategoryDetail.tsx b/src/frontend/src/pages/part/CategoryDetail.tsx
index 289c0a405203..12221c397334 100644
--- a/src/frontend/src/pages/part/CategoryDetail.tsx
+++ b/src/frontend/src/pages/part/CategoryDetail.tsx
@@ -241,12 +241,12 @@ export default function CategoryDetail() {
tooltip={t`Category Actions`}
actions={[
EditItemAction({
- hidden: !id || !user.hasChangeRole(UserRoles.part_category),
+ hidden: !id || !user.hasChangeRole(UserRoles.part),
tooltip: t`Edit Part Category`,
onClick: () => editCategory.open()
}),
DeleteItemAction({
- hidden: !id || !user.hasDeleteRole(UserRoles.part_category),
+ hidden: !id || !user.hasDeleteRole(UserRoles.part),
tooltip: t`Delete Part Category`,
onClick: () => deleteCategory.open()
})
@@ -327,7 +327,7 @@ export default function CategoryDetail() {
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 9a4cf99740d9..d6e23c1c9dda 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -995,7 +995,7 @@ export default function PartDetail() {
requiredRole={UserRoles.part}
>
- {user.hasViewRole(UserRoles.part_category) && (
+ {user.hasViewRole(UserRoles.part) && (
editLocation.open()
}),
DeleteItemAction({
- hidden: !id || !user.hasDeleteRole(UserRoles.stock_location),
+ hidden: !id || !user.hasDeleteRole(UserRoles.stock),
tooltip: t`Delete Stock Location`,
onClick: () => deleteLocation.open()
})
@@ -365,7 +365,7 @@ export default function Stock() {
- {user.hasViewRole(UserRoles.stock_location) && (
+ {user.hasViewRole(UserRoles.stock) && (
) {
});
const tableActions = useMemo(() => {
- const can_add = user.hasAddRole(UserRoles.part_category);
- const can_edit = user.hasChangeRole(UserRoles.part_category);
+ const can_add = user.hasAddRole(UserRoles.part);
+ const can_edit = user.hasChangeRole(UserRoles.part);
return [
) {
const rowActions = useCallback(
(record: any): RowAction[] => {
- const can_edit = user.hasChangeRole(UserRoles.part_category);
+ const can_edit = user.hasChangeRole(UserRoles.part);
return [
RowEditAction({
diff --git a/src/frontend/src/tables/stock/LocationTypesTable.tsx b/src/frontend/src/tables/stock/LocationTypesTable.tsx
index 632a1741bd5a..84863125ae81 100644
--- a/src/frontend/src/tables/stock/LocationTypesTable.tsx
+++ b/src/frontend/src/tables/stock/LocationTypesTable.tsx
@@ -84,14 +84,14 @@ export default function LocationTypesTable() {
(record: any): RowAction[] => {
return [
RowEditAction({
- hidden: !user.hasChangeRole(UserRoles.stock_location),
+ hidden: !user.hasChangeRole(UserRoles.stock),
onClick: () => {
setSelectedLocationType(record.pk);
editLocationType.open();
}
}),
RowDeleteAction({
- hidden: !user.hasDeleteRole(UserRoles.stock_location),
+ hidden: !user.hasDeleteRole(UserRoles.stock),
onClick: () => {
setSelectedLocationType(record.pk);
deleteLocationType.open();
@@ -108,7 +108,7 @@ export default function LocationTypesTable() {
key='add-location-type'
tooltip={t`Add Location Type`}
onClick={() => newLocationType.open()}
- hidden={!user.hasAddRole(UserRoles.stock_location)}
+ hidden={!user.hasAddRole(UserRoles.stock)}
/>
];
}, [user]);
diff --git a/src/frontend/src/tables/stock/StockLocationTable.tsx b/src/frontend/src/tables/stock/StockLocationTable.tsx
index 20c7275f2315..f49a4bada218 100644
--- a/src/frontend/src/tables/stock/StockLocationTable.tsx
+++ b/src/frontend/src/tables/stock/StockLocationTable.tsx
@@ -132,8 +132,8 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) {
});
const tableActions = useMemo(() => {
- const can_add = user.hasAddRole(UserRoles.stock_location);
- const can_edit = user.hasChangeRole(UserRoles.stock_location);
+ const can_add = user.hasAddRole(UserRoles.stock);
+ const can_edit = user.hasChangeRole(UserRoles.stock);
return [
) {
const rowActions = useCallback(
(record: any): RowAction[] => {
- const can_edit = user.hasChangeRole(UserRoles.stock_location);
+ const can_edit = user.hasChangeRole(UserRoles.stock);
return [
RowEditAction({