diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1c5df1a003..5185cbac91 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,14 +10,14 @@ exclude: |
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.6.0
+ rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.7.0
+ rev: v0.7.3
hooks:
- id: ruff-format
args: [--preview]
@@ -28,7 +28,7 @@ repos:
--preview
]
- repo: https://github.com/astral-sh/uv-pre-commit
- rev: 0.4.24
+ rev: 0.5.1
hooks:
- id: pip-compile
name: pip-compile requirements-dev.in
@@ -51,7 +51,7 @@ repos:
args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt, --python-version=3.11, --no-strip-extras, --generate-hashes]
files: contrib/container/requirements\.(in|txt)$
- repo: https://github.com/Riverside-Healthcare/djLint
- rev: v1.35.2
+ rev: v1.36.1
hooks:
- id: djlint-django
- repo: https://github.com/codespell-project/codespell
@@ -76,7 +76,7 @@ repos:
additional_dependencies: ["@biomejs/biome@1.9.4"]
files: ^src/frontend/.*\.(js|ts|tsx)$
- repo: https://github.com/gitleaks/gitleaks
- rev: v8.21.0
+ rev: v8.21.2
hooks:
- id: gitleaks
#- repo: https://github.com/jumanjihouse/pre-commit-hooks
diff --git a/docs/docs/start/advanced.md b/docs/docs/start/advanced.md
index 0d9032b105..7711eae331 100644
--- a/docs/docs/start/advanced.md
+++ b/docs/docs/start/advanced.md
@@ -27,7 +27,6 @@ The version information contains the following information extracted form the in
| Target | No | ubuntu:20.04 | environment: `INVENTREE_PKG_TARGET` |
| Active plugins | Yes | [{'name': 'InvenTreeBarcode', 'slug': 'inventreebarcode', 'version': '2.0.0'}] | instance |
-
### Installer codes
The installer code is used to identify the way InvenTree was installed. If you vendor InvenTree, you can and should set the installer code to your own value to make sure debugging goes smoothly.
@@ -68,11 +67,15 @@ Next you can start configuring the connection. Either use the config file or set
| `ldap.always_update_user` | `INVENTREE_LDAP_ALWAYS_UPDATE_USER` | Always update the user on each login, default: `true` |
| `ldap.cache_timeout` | `INVENTREE_LDAP_CACHE_TIMEOUT` | cache timeout to reduce traffic with LDAP server, default: `3600` (1h) |
| `ldap.group_search` | `INVENTREE_LDAP_GROUP_SEARCH` | Base LDAP DN for group searching; required to enable group features |
+| `ldap.group_object_class` | `INVENTREE_LDAP_GROUP_OBJECT_CLASS` | The string to pass to the LDAP group search `(objectClass=<...>)`, default: `groupOfUniqueNames` |
+| `ldap.mirror_groups` | `INVENTREE_LDAP_MIRROR_GROUPS` | If `True`, mirror a user's LDAP group membership in the Django database, default: `False` |
+| `ldap.group_type_class` | `INVENTREE_LDAP_GROUP_TYPE_CLASS` | The group class to be imported from `django_auth_ldap.config` as a string, default: `'GroupOfUniqueNamesType'`|
+| `ldap.group_type_class_args` | `INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS` | A `list` of positional args to pass to the LDAP group type class, default `[]` |
+| `ldap.group_type_class_kwargs` | `INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS` | A `dict` of keyword args to pass to the LDAP group type class, default `{'name_attr': 'cn'}` |
| `ldap.require_group` | `INVENTREE_LDAP_REQUIRE_GROUP` | If set, users _must_ be in this group to log in to InvenTree |
| `ldap.deny_group` | `INVENTREE_LDAP_DENY_GROUP` | If set, users _must not_ be in this group to log in to InvenTree |
| `ldap.user_flags_by_group` | `INVENTREE_LDAP_USER_FLAGS_BY_GROUP` | LDAP group to InvenTree user flag map, can be json if used as env, in yml directly specify the object. See config template for example, default: `{}` |
-
## Tracing support
Starting with 0.14.0 InvenTree supports sending traces, logs and metrics to OpenTelemetry compatible endpoints (both HTTP and gRPC). A [list of vendors](https://opentelemetry.io/ecosystem/vendors) is available on the project site.
@@ -99,4 +102,4 @@ If your InvenTree instance is used in a multi-site environment, you can enable m
| Environment Variable | Config Key | Description | Default |
| --- | --- | --- | --- |
| INVENTREE_SITE_MULTI | site_multi | Enable multiple sites | False |
-| INVENTREE_SITE_ID | site_id | Specify a fixed site ID | *Not specified* |
+| INVENTREE_SITE_ID | site_id | Specify a fixed site ID | _Not specified_ |
diff --git a/src/backend/InvenTree/InvenTree/helpers_mixin.py b/src/backend/InvenTree/InvenTree/helpers_mixin.py
index d3f5c06d06..fee2ace924 100644
--- a/src/backend/InvenTree/InvenTree/helpers_mixin.py
+++ b/src/backend/InvenTree/InvenTree/helpers_mixin.py
@@ -62,7 +62,7 @@ class ClassValidationMixin:
if len(missing_attributes) > 0:
errors.append(
- f"did not provide the following attributes: {', '.join(missing_attributes)}"
+ f'did not provide the following attributes: {", ".join(missing_attributes)}'
)
if len(missing_overrides) > 0:
missing_overrides_list = []
@@ -75,7 +75,7 @@ class ClassValidationMixin:
else:
missing_overrides_list.append(base_implementation.__name__)
errors.append(
- f"did not override the required attributes: {', '.join(missing_overrides_list)}"
+ f'did not override the required attributes: {", ".join(missing_overrides_list)}'
)
if len(errors) > 0:
diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py
index e69094096c..7e294b8ac8 100644
--- a/src/backend/InvenTree/InvenTree/settings.py
+++ b/src/backend/InvenTree/InvenTree/settings.py
@@ -360,8 +360,8 @@ AUTHENTICATION_BACKENDS = CONFIG.get(
# LDAP support
LDAP_AUTH = get_boolean_setting('INVENTREE_LDAP_ENABLED', 'ldap.enabled', False)
if LDAP_AUTH:
+ import django_auth_ldap.config
import ldap
- from django_auth_ldap.config import GroupOfUniqueNamesType, LDAPSearch
AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend')
@@ -412,7 +412,7 @@ if LDAP_AUTH:
AUTH_LDAP_BIND_PASSWORD = get_setting(
'INVENTREE_LDAP_BIND_PASSWORD', 'ldap.bind_password'
)
- AUTH_LDAP_USER_SEARCH = LDAPSearch(
+ AUTH_LDAP_USER_SEARCH = django_auth_ldap.config.LDAPSearch(
get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'),
ldap.SCOPE_SUBTREE,
str(
@@ -439,12 +439,38 @@ if LDAP_AUTH:
'INVENTREE_LDAP_CACHE_TIMEOUT', 'ldap.cache_timeout', 3600, int
)
- AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+ AUTH_LDAP_MIRROR_GROUPS = get_boolean_setting(
+ 'INVENTREE_LDAP_MIRROR_GROUPS', 'ldap.mirror_groups', False
+ )
+ AUTH_LDAP_GROUP_OBJECT_CLASS = get_setting(
+ 'INVENTREE_LDAP_GROUP_OBJECT_CLASS',
+ 'ldap.group_object_class',
+ 'groupOfUniqueNames',
+ str,
+ )
+ AUTH_LDAP_GROUP_SEARCH = django_auth_ldap.config.LDAPSearch(
get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'),
ldap.SCOPE_SUBTREE,
- '(objectClass=groupOfUniqueNames)',
+ f'(objectClass={AUTH_LDAP_GROUP_OBJECT_CLASS})',
+ )
+ AUTH_LDAP_GROUP_TYPE_CLASS = get_setting(
+ 'INVENTREE_LDAP_GROUP_TYPE_CLASS',
+ 'ldap.group_type_class',
+ 'GroupOfUniqueNamesType',
+ str,
+ )
+ AUTH_LDAP_GROUP_TYPE_CLASS_ARGS = get_setting(
+ 'INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS', 'ldap.group_type_class_args', [], list
+ )
+ AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS = get_setting(
+ 'INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS',
+ 'ldap.group_type_class_kwargs',
+ {'name_attr': 'cn'},
+ dict,
+ )
+ AUTH_LDAP_GROUP_TYPE = getattr(django_auth_ldap.config, AUTH_LDAP_GROUP_TYPE_CLASS)(
+ *AUTH_LDAP_GROUP_TYPE_CLASS_ARGS, **AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS
)
- AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType(name_attr='cn')
AUTH_LDAP_REQUIRE_GROUP = get_setting(
'INVENTREE_LDAP_REQUIRE_GROUP', 'ldap.require_group'
)
diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py
index 0f17f9a3d4..bf9ad84477 100644
--- a/src/backend/InvenTree/InvenTree/tests.py
+++ b/src/backend/InvenTree/InvenTree/tests.py
@@ -1034,14 +1034,14 @@ class TestVersionNumber(TestCase):
# Check that the current .git values work too
git_hash = str(
- subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8'
+ subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']), 'utf-8'
).strip()
# On some systems the hash is a different length, so just check the first 6 characters
self.assertEqual(git_hash[:6], version.inventreeCommitHash()[:6])
d = (
- str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8')
+ str(subprocess.check_output(['git', 'show', '-s', '--format=%ci']), 'utf-8')
.strip()
.split(' ')[0]
)
diff --git a/src/backend/InvenTree/machine/machine_type.py b/src/backend/InvenTree/machine/machine_type.py
index 3239f5a149..1b8bdf4133 100644
--- a/src/backend/InvenTree/machine/machine_type.py
+++ b/src/backend/InvenTree/machine/machine_type.py
@@ -260,7 +260,7 @@ class BaseMachineType(
error_parts.append(
f'{config_type.name} settings: ' + ', '.join(missing)
)
- self.handle_error(f"Missing {' and '.join(error_parts)}")
+ self.handle_error(f'Missing {" and ".join(error_parts)}')
return
try:
diff --git a/src/backend/InvenTree/machine/models.py b/src/backend/InvenTree/machine/models.py
index c951adb59d..0e4852c64e 100755
--- a/src/backend/InvenTree/machine/models.py
+++ b/src/backend/InvenTree/machine/models.py
@@ -112,7 +112,7 @@ class MachineConfig(models.Model):
"""Get machine errors for django admin interface."""
return format_html_join(
mark_safe('
'), '{}', ((str(error),) for error in self.errors)
- ) or mark_safe(f"{_('No errors')}")
+ ) or mark_safe(f'{_("No errors")}')
@admin.display(description=_('Machine status'))
def get_machine_status(self):
diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py
index f29097e6e8..8578974711 100644
--- a/src/backend/InvenTree/order/models.py
+++ b/src/backend/InvenTree/order/models.py
@@ -470,7 +470,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
def __str__(self):
"""Render a string representation of this PurchaseOrder."""
- return f"{self.reference} - {self.supplier.name if self.supplier else _('deleted')}"
+ return f'{self.reference} - {self.supplier.name if self.supplier else _("deleted")}'
reference = models.CharField(
unique=True,
@@ -996,7 +996,7 @@ class SalesOrder(TotalPriceMixin, Order):
def __str__(self):
"""Render a string representation of this SalesOrder."""
- return f"{self.reference} - {self.customer.name if self.customer else _('deleted')}"
+ return f'{self.reference} - {self.customer.name if self.customer else _("deleted")}'
reference = models.CharField(
unique=True,
@@ -2194,7 +2194,7 @@ class ReturnOrder(TotalPriceMixin, Order):
def __str__(self):
"""Render a string representation of this ReturnOrder."""
- return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}"
+ return f'{self.reference} - {self.customer.name if self.customer else _("no customer")}'
reference = models.CharField(
unique=True,
diff --git a/src/backend/InvenTree/part/views.py b/src/backend/InvenTree/part/views.py
index 1ad7c2f31d..26f7c1fbd5 100644
--- a/src/backend/InvenTree/part/views.py
+++ b/src/backend/InvenTree/part/views.py
@@ -295,7 +295,7 @@ class PartImport(FileManagementFormView):
# Set alerts
if import_done:
- alert = f"{_('Part-Import')}
{_(f'Imported {import_done} parts')}"
+ alert = f'{_("Part-Import")}
{_(f"Imported {import_done} parts")}'
messages.success(self.request, alert)
if import_error:
error_text = '\n'.join([
@@ -304,7 +304,7 @@ class PartImport(FileManagementFormView):
])
messages.error(
self.request,
- f"{_('Some errors occurred:')}