2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-24 09:57:40 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into make-fields-filterable

This commit is contained in:
Matthias Mair
2025-10-13 08:40:04 +02:00
21 changed files with 191 additions and 76 deletions

View File

@@ -12,10 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `order_queryset` report helper function in [#10439](https://github.com/inventree/InvenTree/pull/10439)
- Added much more detailed status information for machines to the API endpoint (including backend and frontend changes) in [#10381](https://github.com/inventree/InvenTree/pull/10381)
- Added ability to partially complete and partially scrap build outputs in [#10499](https://github.com/inventree/InvenTree/pull/10499)
- Added support for Redis ACL user-based authentication in [#10551](https://github.com/inventree/InvenTree/pull/10551)
### Changed
- Changed site URL check to allow protocol mismatches if `INVENTREE_SITE_LAX_PROTOCOL` is set to `True` (default) in [#10454](https://github.com/inventree/InvenTree/pull/10454)
- Changed call signature of `get_global_setting` to use `environment_key` instead of `enviroment_key` in [#10557](https://github.com/inventree/InvenTree/pull/10557)
### Removed

View File

@@ -14,7 +14,7 @@ Only stable / production releases of InvenTree include the frontend panel. This
If you want to use the frontend panel, you can either:
- use a docker image that is version-tagged or the stable version
- use a package version that is from the stable or version stream - if you are and it is not working, run `sudo inventree run cli update` to re-run the upgrade
- use a package version that is from the stable or version stream - if you are and it is not working, run `sudo inventree run invoke update` to re-run the upgrade
- install node and yarn on the server to build the frontend with the [invoke](../start/invoke.md) task `int.frontend-build`
Raise an issue if none of these options work.

View File

@@ -309,6 +309,7 @@ The following cache settings are available:
| INVENTREE_CACHE_HOST | cache.host | Cache server host | *Not specified* |
| INVENTREE_CACHE_PORT | cache.port | Cache server port | 6379 |
| INVENTREE_CACHE_PASSWORD | cache.password | Cache server password | none |
| INVENTREE_CACHE_USER | cache.user | Cache server username | none |
| INVENTREE_CACHE_CONNECT_TIMEOUT | cache.connect_timeout | Cache connection timeout (seconds) | 3 |
| INVENTREE_CACHE_TIMEOUT | cache.timeout | Cache timeout (seconds) | 3 |
| INVENTREE_CACHE_TCP_KEEPALIVE | cache.tcp_keepalive | Cache TCP keepalive | True |

View File

@@ -37,7 +37,7 @@ The installer creates the following directories:
| `/opt/inventree/` | InvenTree application files |
| `/opt/inventree/data/` | InvenTree data files |
#### Performed steps
#### Performed Steps
The installer script performs the following functions:
@@ -111,7 +111,7 @@ To stop the automatic generation of an admin user, generate an empty file needs
By default, InvenTree is served internally on port 6000 and then proxied via Nginx. The config is placed in `/etc/nginx/sites-enabled/inventree.conf` and overwritten on each update. The location can be set with the environment variable `SETUP_NGINX_FILE`.
This only serves an HTTP version of InvenTree, to use HTTPS (recommended for production) or customize any further an additional config file should be used.
#### Extra python packages
#### Extra Python Packages
Extra python packages can be installed by setting the environment variable `SETUP_EXTRA_PIP`.
#### Database Options
@@ -120,25 +120,51 @@ The used database backend can be configured with environment variables (before t
## Moving Data
To change the data storage location, link the new location to `/opt/inventree/data`.
A rough outline of steps to achieve this could be:
- shut down the app service(s) `inventree` and webserver `nginx`
- copy data to the new location
- check everything was transferred successfully
- delete the old location
- create a symlink from the old location to the new one
- start up the services again
To change the data storage location, link the new location to `/opt/inventree/data`. A rough outline of steps to achieve this could be:
- Shut down the app service(s) `inventree` and webserver `nginx`
- Copy data to the new location
- Check everything was transferred successfully
- Delete the old location
- Create a symlink from the old location to the new one
- Start up the services again
## Updating InvenTree
To update InvenTree run `apt install --only-upgrade inventree` - this might need to be run as a sudo user.
To update InvenTree run the following command, which updates the InvenTree package to the latest version:
```bash
apt install --only-upgrade inventree
```
Note that this command may need to be run as a sudo user.
## Controlling InvenTree
### Services
InvenTree installs multiple services that can be controlled with your local system runner (`service` or `systemctl`).
The service `inventree` controls everything, `inventree-web` (the [InvenTree web server](./processes.md#web-server)) and `inventree-worker` the [background worker(s)](./processes.md#background-worker).
InvenTree installs multiple services that can be controlled with your local system runner (`service` or `systemctl`):
- `inventree` - The main InvenTree service that controls the web server and background worker(s)
- `inventree-web` - The InvenTree [web server](./processes.md#web-server) process(es)
- `inventree-worker` - The InvenTree [background worker(s)](./processes.md#background-worker) process(es)
#### Restarting Services
To restart the InvenTree services, use the following commands as necessary:
```bash
# Restart all InvenTree services
inventree restart
# Restart the web server only
inventree restart web
# Restart the worker only
inventree restart worker
```
### Scaling Workers
More instances of the worker can be instantiated from the command line. This is only meant for advanced users.
@@ -180,6 +206,26 @@ For example, to print InvenTree version information:
inventree run invoke version
```
### Viewing Logs
To view the logs of the InvenTree services, use the following commands:
```bash
inventree logs
```
To view just the tail of the logs, use:
```bash
inventree logs --tail
```
Or, to follow the logs in real-time:
```bash
inventree logs --follow
```
## Architecture
The packages are provided by [packager.io](https://packager.io/). They are built each time updates are pushed to GitHub and released about 10 minutes later. The local package index must be updated to see the new release in the package manager.

View File

@@ -132,9 +132,10 @@ nav:
- Docker:
- Introduction: start/docker.md
- Installation: start/docker_install.md
- Installer:
- Installer: start/installer.md
- Bare Metal:
- Introduction: start/install.md
- Installer: start/installer.md
- Production: start/bare_prod.md
- Development: start/bare_dev.md
- User Accounts: start/accounts.md

View File

@@ -1,12 +1,15 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 407
INVENTREE_API_VERSION = 408
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v408 -> 2025-10-13: https://github.com/inventree/InvenTree/pull/10561
- Allow search of assembly fields in BOM API endpoint
v407 -> 2025-10-09: https://github.com/inventree/InvenTree/pull/10538
- Breaking: Set error status code for plugin action call instead of just returning error data

View File

@@ -43,6 +43,11 @@ def cache_password():
return cache_setting('password', None)
def cache_user():
"""Return the cash username."""
return cache_setting('user', None)
def is_global_cache_enabled() -> bool:
"""Check if the global cache is enabled.
@@ -85,9 +90,10 @@ def get_cache_config(global_cache: bool) -> dict:
if global_cache:
# Build Redis URL with optional password
password = cache_password()
user = cache_user() or ''
if password:
redis_url = f'redis://:{password}@{cache_host()}:{cache_port()}/0'
redis_url = f'redis://{user}:{password}@{cache_host()}:{cache_port()}/0'
else:
redis_url = f'redis://{cache_host()}:{cache_port()}/0'

View File

@@ -76,8 +76,9 @@ def get_root_dir() -> Path:
def inventreeInstaller() -> Optional[str]:
"""Returns the installer for the running codebase - if set or detectable."""
# First look in the environment variables, e.g. if running in docker
load_version_file()
# First look in the environment variables, e.g. if running in docker
installer = os.environ.get('INVENTREE_PKG_INSTALLER', '')
if installer:
@@ -121,6 +122,11 @@ def get_testfolder_dir() -> Path:
return get_base_dir().joinpath('_testfolder').resolve()
def get_version_file() -> Path:
"""Returns the path of the InvenTree VERSION file. This does not ensure that the file exists."""
return get_root_dir().joinpath('VERSION').resolve()
def ensure_dir(path: Path, storage=None) -> None:
"""Ensure that a directory exists.
@@ -592,3 +598,28 @@ def check_config_dir(
pass
return
VERSION_LOADED = False
"""Flag to indicate if the VERSION file has been loaded in this process."""
def load_version_file():
"""Load the VERSION file if it exists and place the contents into the general execution environment.
Returns:
True if the VERSION file was loaded (now or previously), False otherwise.
"""
global VERSION_LOADED
if VERSION_LOADED:
return True
# Load the VERSION file if it exists
from dotenv import load_dotenv
version_file = get_version_file()
if version_file.exists():
load_dotenv(version_file)
VERSION_LOADED = True
return True
return False

View File

@@ -23,7 +23,6 @@ from django.http import Http404, HttpResponseGone
import structlog
from corsheaders.defaults import default_headers as default_cors_headers
from dotenv import load_dotenv
from InvenTree.cache import get_cache_config, is_global_cache_enabled
from InvenTree.config import (
@@ -80,11 +79,7 @@ BASE_DIR = config.get_base_dir()
# Load configuration data
CONFIG = config.load_config_data(set_cache=True)
# Load VERSION data if it exists
version_file = config.get_root_dir().joinpath('VERSION')
if version_file.exists():
load_dotenv(version_file)
config.load_version_file()
# Default action is to run the system in Debug mode
# SECURITY WARNING: don't run with debug turned on in production!

View File

@@ -670,6 +670,11 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True) -> b
Returns bool indicating if migrations are up to date
"""
from . import ready
if ready.isRunningMigrations() or ready.isRunningBackup():
# Migrations are already running!
return False
def set_pending_migrations(n: int):
"""Helper function to inform the user about pending migrations."""
@@ -720,6 +725,8 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True) -> b
if settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3':
raise e
logger.exception('Error during migrations: %s', e)
except Exception as e: # pragma: no cover
logger.exception('Error during migrations: %s', e)
else:
set_pending_migrations(0)

View File

@@ -269,7 +269,7 @@ def inventreeBranch():
branch = os.environ.get('INVENTREE_PKG_BRANCH', '')
if branch:
return branch
return ' '.join(branch.splitlines())
if main_branch is None:
return None
@@ -298,7 +298,7 @@ def inventree_identifier(override_announce: bool = False):
from common.settings import get_global_setting
if override_announce or get_global_setting(
'INVENTREE_ANNOUNCE_ID', enviroment_key='INVENTREE_ANNOUNCE_ID'
'INVENTREE_ANNOUNCE_ID', environment_key='INVENTREE_ANNOUNCE_ID'
):
return get_global_setting('INVENTREE_INSTANCE_ID', default='')
return None

View File

@@ -48,7 +48,7 @@ def currency_codes() -> list:
from common.settings import get_global_setting
codes = get_global_setting(
'CURRENCY_CODES', create=False, enviroment_key='INVENTREE_CURRENCY_CODES'
'CURRENCY_CODES', create=False, environment_key='INVENTREE_CURRENCY_CODES'
).strip()
if not codes:

View File

@@ -26,12 +26,12 @@ def global_setting_overrides() -> dict:
return {}
def get_global_setting(key, backup_value=None, enviroment_key=None, **kwargs):
def get_global_setting(key, backup_value=None, environment_key=None, **kwargs):
"""Return the value of a global setting using the provided key."""
from common.models import InvenTreeSetting
if enviroment_key:
value = environ.get(enviroment_key)
if environment_key:
value = environ.get(environment_key)
if value:
return value

View File

@@ -110,7 +110,7 @@ sentry_enabled: False
#sentry_dsn: https://custom@custom.ingest.sentry.io/custom
# OpenTelemetry tracing/metrics - disabled by default - refer to the documentation for full list of options
# This can be used to send tracing data, logs and metrics to OpenTelemtry compatible backends
# This can be used to send tracing data, logs and metrics to OpenTelemetry compatible backends
tracing:
enabled: false
@@ -142,9 +142,9 @@ allowed_hosts:
# use_x_forwarded_proto: true
# Cookie settings (nominally the default settings should be fine)
cookie:
secure: false
samesite: false
# cookie:
# secure: false
# samesite: false
# Cross Origin Resource Sharing (CORS) settings (see https://github.com/adamchainz/django-cors-headers)
cors:
@@ -203,7 +203,7 @@ remote_login_header: HTTP_REMOTE_USER
# - 'allauth.socialaccount.providers.github'
# Add specific settings for social account providers (if required)
# Refer to the djngo-allauth documentation for more details:
# Refer to the django-allauth documentation for more details:
# https://docs.allauth.org/en/latest/socialaccount/provider_configuration.html
# social_providers:
# github:

View File

@@ -1639,6 +1639,11 @@ class BomList(
search_fields = [
'reference',
'part__name',
'part__description',
'part__IPN',
'part__revision',
'part__keywords',
'sub_part__name',
'sub_part__description',
'sub_part__IPN',

View File

@@ -68,27 +68,29 @@ const AboutContent = ({
});
function fillTable(lookup: AboutLookupRef[], data: any, alwaysLink = false) {
return lookup.map((map: AboutLookupRef, idx) => (
<Table.Tr key={idx}>
<Table.Td>{map.title}</Table.Td>
<Table.Td>
<Group justify='space-between' gap='xs'>
{alwaysLink ? (
<Anchor href={data[map.ref]} target='_blank'>
{data[map.ref]}
</Anchor>
) : map.link ? (
<Anchor href={map.link} target='_blank'>
{data[map.ref]}
</Anchor>
) : (
data[map.ref]
)}
{map.copy && <CopyButton value={data[map.ref]} />}
</Group>
</Table.Td>
</Table.Tr>
));
return lookup
.filter((entry: AboutLookupRef) => !!data[entry.ref])
.map((entry: AboutLookupRef, idx) => (
<Table.Tr key={idx}>
<Table.Td>{entry.title}</Table.Td>
<Table.Td>
<Group justify='space-between' gap='xs'>
{alwaysLink ? (
<Anchor href={data[entry.ref]} target='_blank'>
{data[entry.ref]}
</Anchor>
) : entry.link ? (
<Anchor href={entry.link} target='_blank'>
{data[entry.ref]}
</Anchor>
) : (
data[entry.ref]
)}
{entry.copy && <CopyButton value={data[entry.ref]} />}
</Group>
</Table.Td>
</Table.Tr>
));
}
/* renderer */
if (isLoading) return <Trans>Loading</Trans>;

View File

@@ -106,14 +106,37 @@ export async function doBasicLogin(
}
})
.catch(async (err) => {
if (err?.response?.status == 401) {
await handlePossibleMFAError(err);
} else if (err?.response?.status == 409) {
notifications.hide('auth-login-error');
if (err?.response?.status) {
switch (err.response.status) {
case 401:
await handlePossibleMFAError(err);
break;
case 409:
notifications.show({
title: t`Already logged in`,
message: t`There is a conflicting session on the server for this browser. Please logout of that first.`,
color: 'red',
id: 'auth-login-error',
autoClose: false
});
break;
default:
notifications.show({
title: `${t`Login failed`} (${err.response.status})`,
message: t`Check your input and try again.`,
id: 'auth-login-error',
color: 'red'
});
break;
}
} else {
notifications.show({
title: t`Already logged in`,
message: t`There is a conflicting session on the server for this browser. Please logout of that first.`,
title: t`Login failed`,
message: t`No response from server.`,
color: 'red',
autoClose: false
id: 'login-error'
});
}
});

View File

@@ -238,17 +238,6 @@ export default function BuildDetail() {
icon: 'manufacturers',
hidden: !build.external
},
{
type: 'text',
name: 'purchase_order',
label: t`Purchase Order`,
icon: 'purchase_orders',
copy: true,
hidden: !build.external,
value_formatter: () => {
return 'TODO: external PO';
}
},
{
type: 'text',
name: 'reference',

View File

@@ -352,6 +352,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
// Reset the pagination state when the search term changes
useEffect(() => {
tableState.setPage(1);
tableState.clearSelectedRecords();
}, [
tableState.searchTerm,
tableState.filterSet.activeFilters,

View File

@@ -504,7 +504,10 @@ export function StockItemTable({
return {
items: table.selectedRecords,
model: ModelType.stockitem,
refresh: table.refreshTable,
refresh: () => {
table.clearSelectedRecords();
table.refreshTable();
},
filters: {
in_stock: true
}

View File

@@ -9,8 +9,8 @@ import { doLogin } from './login.js';
test('Login - Failures', async ({ page }) => {
const loginWithError = async () => {
await page.getByRole('button', { name: 'Log In' }).click();
await page.getByText('Login failed').waitFor();
await page.getByText('Check your input and try again').waitFor();
await page.getByText('Login failed', { exact: true }).waitFor();
await page.getByText('Check your input and try again').first().waitFor();
await page.locator('#login').getByRole('button').click();
};