Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b181bb5ae | ||
|
|
145f4751c2 | ||
|
|
8a614f4501 | ||
|
|
140c65b26c | ||
|
|
2eccf13c93 | ||
|
|
19239c8621 | ||
|
|
c78b03b6ff | ||
|
|
ba7b776257 | ||
|
|
1e120c3589 | ||
|
|
6cbed50794 | ||
|
|
71c2f5ca73 | ||
|
|
a727c4e2f9 | ||
|
|
0460e81f9a | ||
|
|
9c6d16baba | ||
|
|
2a20eeb033 | ||
|
|
be8911eed3 | ||
|
|
d3d957e924 | ||
|
|
2600690fc1 | ||
|
|
0a2b53789a | ||
|
|
edc68b21ab | ||
|
|
2d14364a4d | ||
|
|
e4a6c1abfb | ||
|
|
20c7a5b5b8 | ||
|
|
0723c74567 | ||
|
|
c5548aabde | ||
|
|
dbb55a63fd | ||
|
|
c489423c07 | ||
|
|
bed83bc038 | ||
|
|
dc409c4efb | ||
|
|
1badf6557f | ||
|
|
3ca124c1a9 | ||
|
|
ae70c22485 | ||
|
|
f4186e73ff | ||
|
|
27fd2bcb8d | ||
|
|
a0cfdd72a5 | ||
|
|
0c4a637739 | ||
|
|
be9ec848c3 | ||
|
|
d7caddb135 | ||
|
|
efc8fb816d | ||
|
|
ffec087618 | ||
|
|
e98a612d9a | ||
|
|
3a18934b83 | ||
|
|
7028bb84ff | ||
|
|
d0c23bd523 | ||
|
|
92099bab3c | ||
|
|
744af5ba42 | ||
|
|
fa0d892a62 | ||
|
|
c443b4e9b8 | ||
|
|
2ffc2cb9fc | ||
|
|
8715935bb9 | ||
|
|
7920b0e670 | ||
|
|
3e35f439c0 | ||
|
|
c8b1bfb716 | ||
|
|
38b27271ac | ||
|
|
4c45716843 | ||
|
|
c224606d8d | ||
|
|
ee4e200cf3 | ||
|
|
bc5c306b6d | ||
|
|
3958e10875 | ||
|
|
7b592f157c | ||
|
|
be5814112d | ||
|
|
3b6b702bd5 | ||
|
|
3a4981056b | ||
|
|
5713cff1cb | ||
|
|
16b600af88 | ||
|
|
0746a1131f | ||
|
|
df6cbca197 | ||
|
|
0542f0608d | ||
|
|
bb5bd85716 | ||
|
|
74e368b85b | ||
|
|
5df42eda74 | ||
|
|
7c7d7b6a21 | ||
|
|
89e3d605c5 | ||
|
|
276041ae54 | ||
|
|
fcea1383d0 | ||
|
|
7d5429303e | ||
|
|
e590522909 | ||
|
|
a7ff1250ba | ||
|
|
f50d568b23 | ||
|
|
d5ee647c6e | ||
|
|
a76ec0a7b8 | ||
|
|
835c7784f9 | ||
|
|
f72efb804e | ||
|
|
468efbacfc | ||
|
|
18c2a934b8 | ||
|
|
a8f2a02d69 | ||
|
|
7b38fa30bb | ||
|
|
40fbb4d810 | ||
|
|
d06d80fb99 | ||
|
|
2799c4d1fe | ||
|
|
dab7223245 | ||
|
|
57a2de6ffc | ||
|
|
770f7a292e | ||
|
|
2c508feeec | ||
|
|
aa9958bf11 | ||
|
|
af4d9efd1d | ||
|
|
39d181ae5f | ||
|
|
ba9b5438b4 | ||
|
|
8cb808f613 | ||
|
|
0790dfff5b | ||
|
|
af6cce3aba | ||
|
|
d829d3a548 | ||
|
|
f3c1cc12af | ||
|
|
77f80385c9 | ||
|
|
246c084e6e | ||
|
|
682dd79326 | ||
|
|
5d21bf2679 | ||
|
|
f3e8482469 | ||
|
|
726e852b7b | ||
|
|
b048ca3a04 | ||
|
|
2fc7c7eb54 | ||
|
|
2dfe6b5f41 | ||
|
|
d12102ba96 | ||
|
|
dbb799a0e0 | ||
|
|
fc3d130888 | ||
|
|
7e943293c7 | ||
|
|
2bc2966d22 | ||
|
|
8e20bc53db | ||
|
|
901846272b | ||
|
|
a22e163b19 | ||
|
|
39f0054cd5 | ||
|
|
153bcc1d5c | ||
|
|
0997a18a62 | ||
|
|
91489e986c | ||
|
|
e1bf67b32c | ||
|
|
d7daf660ef | ||
|
|
46615e447b | ||
|
|
551da5a51f |
@@ -14,7 +14,7 @@ pool:
|
||||
strategy:
|
||||
matrix:
|
||||
Python39:
|
||||
PYTHON_VERSION: '3.9'
|
||||
PYTHON_VERSION: '3.11'
|
||||
maxParallel: 3
|
||||
|
||||
steps:
|
||||
|
||||
2
.github/actions/setup/action.yaml
vendored
@@ -57,7 +57,7 @@ runs:
|
||||
run: |
|
||||
python3 -m pip install -U pip
|
||||
pip3 install -U invoke wheel
|
||||
pip3 install 'uv>=0.9.4'
|
||||
pip3 install 'uv>=0.9.6'
|
||||
- name: Allow uv to use the system Python by default
|
||||
run: echo "UV_SYSTEM_PYTHON=1" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
10
.github/dependabot.yml
vendored
@@ -4,6 +4,8 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
dependencies:
|
||||
patterns:
|
||||
@@ -13,11 +15,15 @@ updates:
|
||||
directory: /contrib/container
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /.devcontainer
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directories:
|
||||
@@ -28,6 +34,8 @@ updates:
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
dependencies:
|
||||
patterns:
|
||||
@@ -41,6 +49,8 @@ updates:
|
||||
- /src/frontend
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
dependencies:
|
||||
patterns:
|
||||
|
||||
4
.github/workflows/check_translations.yaml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- l10
|
||||
|
||||
env:
|
||||
python_version: 3.9
|
||||
python_version: 3.11
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
14
.github/workflows/docker.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
docker: ${{ steps.filter.outputs.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Test Docker Image
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run Migration Tests
|
||||
@@ -153,11 +153,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set Up Python ${{ env.python_version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # pin@v6.0.0
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
|
||||
with:
|
||||
python-version: ${{ env.python_version }}
|
||||
- name: Version Check
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
|
||||
- name: Set up QEMU
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # pin@v3.6.0
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # pin@v3.7.0
|
||||
- name: Set up Docker Buildx
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # pin@v3.11.1
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
- name: Extract Docker metadata
|
||||
if: github.event_name != 'pull_request'
|
||||
id: meta
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # pin@v5.8.0
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # pin@v5.10.0
|
||||
with:
|
||||
images: |
|
||||
inventree/inventree
|
||||
|
||||
50
.github/workflows/qc_checks.yaml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
branches-ignore: ["l10*"]
|
||||
|
||||
env:
|
||||
python_version: 3.9
|
||||
python_version: 3.11
|
||||
node_version: 20
|
||||
# The OS version must be set per job
|
||||
server_start_sleep: 60
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
requirements: ${{ steps.filter.outputs.requirements }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
|
||||
@@ -82,11 +82,11 @@ jobs:
|
||||
if: needs.paths-filter.outputs.cicd == 'true' || needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.requirements == 'true' || needs.paths-filter.outputs.force == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ env.python_version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # pin@v6.0.0
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
|
||||
with:
|
||||
python-version: ${{ env.python_version }}
|
||||
cache: "pip"
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.requirements == 'true' || needs.paths-filter.outputs.force == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -126,11 +126,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ env.python_version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # pin@v6.0.0
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
|
||||
with:
|
||||
python-version: ${{ env.python_version }}
|
||||
- name: Check Config
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -249,7 +249,7 @@ jobs:
|
||||
version: ${{ needs.schema.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
name: Checkout Code
|
||||
with:
|
||||
repository: inventree/schema
|
||||
@@ -302,9 +302,9 @@ jobs:
|
||||
INVENTREE_LOG_LEVEL: WARNING
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: true
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
@@ -334,8 +334,8 @@ jobs:
|
||||
continue-on-error: true # continue if a step fails so that coverage gets pushed
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: [3.9]
|
||||
# python_version: [3.9, 3.12] # Disabled due to requirement issues
|
||||
python_version: [3.11]
|
||||
# python_version: [3.11, 3.14] # Disabled due to requirement issues
|
||||
|
||||
env:
|
||||
INVENTREE_DB_NAME: ./inventree.sqlite
|
||||
@@ -346,7 +346,7 @@ jobs:
|
||||
python_version: ${{ matrix.python_version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -397,7 +397,7 @@ jobs:
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
image: postgres:17
|
||||
env:
|
||||
POSTGRES_USER: inventree
|
||||
POSTGRES_PASSWORD: password
|
||||
@@ -410,7 +410,7 @@ jobs:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -458,7 +458,7 @@ jobs:
|
||||
- 3306:3306
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -492,7 +492,7 @@ jobs:
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
image: postgres:17
|
||||
env:
|
||||
POSTGRES_USER: inventree
|
||||
POSTGRES_PASSWORD: password
|
||||
@@ -500,7 +500,7 @@ jobs:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -534,7 +534,7 @@ jobs:
|
||||
INVENTREE_PLUGINS_ENABLED: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
name: Checkout Code
|
||||
@@ -588,7 +588,7 @@ jobs:
|
||||
if: needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
image: postgres:17
|
||||
env:
|
||||
POSTGRES_DB: inventree
|
||||
POSTGRES_USER: inventree_user
|
||||
@@ -613,7 +613,7 @@ jobs:
|
||||
VITE_COVERAGE_BUILD: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -663,7 +663,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -696,7 +696,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # pin@v2
|
||||
@@ -705,7 +705,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # pin@v3
|
||||
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # pin@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
||||
16
.github/workflows/release.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
python_version: 3.9
|
||||
python_version: 3.11
|
||||
|
||||
jobs:
|
||||
stable:
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Version Check
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
contents: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Build frontend
|
||||
run: cd src/frontend && npm run compile && npm run build
|
||||
- name: Create SBOM for frontend
|
||||
uses: anchore/sbom-action@8e94d75ddd33f69f691467e42275782e4bfefe84 # pin@v0
|
||||
uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # pin@v0
|
||||
with:
|
||||
artifact-name: frontend-build.spdx
|
||||
path: src/frontend
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip"
|
||||
|
||||
- name: Upload frontend
|
||||
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # pin@2.11.2
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: src/backend/InvenTree/web/static/frontend-build.zip
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
- name: Upload Attestation
|
||||
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # pin@2.11.2
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
asset_name: frontend-build.intoto.jsonl
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
INVENTREE_DEBUG: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
cd docs/site
|
||||
zip -r docs-html.zip *
|
||||
- name: Publish documentation
|
||||
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # pin@2.11.2
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: docs/site/docs-html.zip
|
||||
|
||||
4
.github/workflows/scorecard.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -67,6 +67,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
2
.github/workflows/stale.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # pin@v10.1.0
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # pin@v10.1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue seems stale. Please react to show this is still important."
|
||||
|
||||
8
.github/workflows/translations.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- master
|
||||
|
||||
env:
|
||||
python_version: 3.9
|
||||
python_version: 3.11
|
||||
node_version: 20
|
||||
|
||||
permissions:
|
||||
@@ -32,9 +32,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
|
||||
with:
|
||||
persist-credentials: true
|
||||
persist-credentials: false
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
echo "Resetting to HEAD~"
|
||||
git reset HEAD~ || true
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@08713f00a50548bfe39b37e8f44afb53e7a802d4 # pin@v2
|
||||
uses: crowdin/github-action@60debf382ee245b21794321190ad0501db89d8c1 # pin@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
5
.gitignore
vendored
@@ -37,6 +37,11 @@ local_settings.py
|
||||
*.backup
|
||||
*.old
|
||||
|
||||
# Files generated by profiling tools
|
||||
*.prof
|
||||
*.log
|
||||
*.sql
|
||||
|
||||
# Files used for testing
|
||||
inventree-demo-dataset/
|
||||
inventree-data/
|
||||
|
||||
@@ -20,9 +20,9 @@ before:
|
||||
- contrib/packager.io/before.sh
|
||||
dependencies:
|
||||
- curl
|
||||
- "python3.9 | python3.10 | python3.11 | python3.12 | python3.13 | python3.14"
|
||||
- "python3.9-venv | python3.10-venv | python3.11-venv | python3.12-venv | python3.13-venv | python3.14-venv"
|
||||
- "python3.9-dev | python3.10-dev | python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev"
|
||||
- "python3.11 | python3.12 | python3.13 | python3.14"
|
||||
- "python3.11-venv | python3.12-venv | python3.13-venv | python3.14-venv"
|
||||
- "python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev"
|
||||
- python3-pip
|
||||
- python3-cffi
|
||||
- python3-brotli
|
||||
@@ -35,8 +35,6 @@ dependencies:
|
||||
- jq
|
||||
- "libffi7 | libffi8"
|
||||
targets:
|
||||
ubuntu-20.04: true
|
||||
ubuntu-22.04: true
|
||||
ubuntu-24.04: true
|
||||
debian-11: true
|
||||
debian-12: true
|
||||
|
||||
@@ -10,7 +10,7 @@ exclude: |
|
||||
)$
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@@ -18,18 +18,18 @@ repos:
|
||||
exclude: mkdocs.yml
|
||||
- id: mixed-line-ending
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.13
|
||||
rev: v0.14.8
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
args: [--preview]
|
||||
- id: ruff
|
||||
- id: ruff-check
|
||||
args: [
|
||||
--fix,
|
||||
# --unsafe-fixes,
|
||||
--preview
|
||||
]
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.7.12
|
||||
rev: 0.9.16
|
||||
hooks:
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements-dev.in
|
||||
@@ -71,16 +71,16 @@ repos:
|
||||
src/frontend/vite.config.ts |
|
||||
)$
|
||||
- repo: https://github.com/biomejs/pre-commit
|
||||
rev: v2.0.0-beta.5
|
||||
rev: v2.3.8
|
||||
hooks:
|
||||
- id: biome-check
|
||||
additional_dependencies: ["@biomejs/biome@1.9.4"]
|
||||
files: ^src/frontend/.*\.(js|ts|tsx)$
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.27.2
|
||||
rev: v8.30.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
language_version: 1.23.6
|
||||
language_version: 1.25.4
|
||||
#- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||
# rev: 3.0.0
|
||||
# hooks:
|
||||
|
||||
7
.vscode/launch.json
vendored
@@ -11,7 +11,7 @@
|
||||
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
|
||||
"args": [
|
||||
"runserver",
|
||||
// "0.0.0.0:8000", // expose server in network (useful for testing with mobile app)
|
||||
"0.0.0.0:8000", // expose server in network (useful for testing with mobile app)
|
||||
// "--noreload" // disable auto-reload
|
||||
],
|
||||
"django": true,
|
||||
@@ -35,7 +35,8 @@
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
|
||||
"args": [
|
||||
"runserver"
|
||||
"runserver",
|
||||
"0.0.0.0:8000"
|
||||
],
|
||||
"django": true,
|
||||
"justMyCode": false
|
||||
@@ -44,7 +45,7 @@
|
||||
"name": "InvenTree invoke schema",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/.venv/lib/python3.9/site-packages/invoke/__main__.py",
|
||||
"program": "${workspaceFolder}/.venv/lib/python3.11/site-packages/invoke/__main__.py",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"dev.schema","--ignore-warnings"
|
||||
|
||||
29
CHANGELOG.md
@@ -5,7 +5,30 @@ All notable changes to this project will be documented in this file (starting wi
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased] - yyyy-mm-dd (in UTC)
|
||||
## Unreleased - YYYY-MM-DD
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [#10699](https://github.com/inventree/InvenTree/pull/10699) removes the `PartParameter` and `PartParameterTempalate` models (and associated API endpoints). These have been replaced with generic `Parameter` and `ParameterTemplate` models (and API endpoints). Any external client applications which made use of the old endpoints will need to be updated.
|
||||
|
||||
### Added
|
||||
|
||||
- Adds "Category" columns to BOM and Build Item tables and APIs in [#10722](https://github.com/inventree/InvenTree/pull/10772)
|
||||
- Adds generic "Parameter" and "ParameterTemplate" models (and associated API endpoints) in [#10699](https://github.com/inventree/InvenTree/pull/10699)
|
||||
- Adds parameter support for multiple new model types in [#10699](https://github.com/inventree/InvenTree/pull/10699)
|
||||
- Allows report generator to produce PDF input controls in [#10969](https://github.com/inventree/InvenTree/pull/10969)
|
||||
- UI overhaul of parameter management in [#10699](https://github.com/inventree/InvenTree/pull/10699)
|
||||
|
||||
### Changed
|
||||
|
||||
-
|
||||
|
||||
### Removed
|
||||
- Removed python 3.9 / 3.10 support as part of Django 5.2 upgrade in [#10730](https://github.com/inventree/InvenTree/pull/10730)
|
||||
- Removed the "PartParameter" and "PartParameterTemplate" models (and associated API endpoints) in [#10699](https://github.com/inventree/InvenTree/pull/10699)
|
||||
- Removed the "ManufacturerPartParameter" model (and associated API endpoints) [#10699](https://github.com/inventree/InvenTree/pull/10699)
|
||||
|
||||
## 1.1.0 - 2025-11-02
|
||||
|
||||
### Added
|
||||
|
||||
@@ -30,10 +53,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- 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
|
||||
|
||||
|
||||
## [1.0.0 ] - 2025-09-15
|
||||
## 1.0.0 - 2025-09-15
|
||||
|
||||
The first "stable" release following semver but not extensively other than the previous releases. The use of 1.0 indicates the stability that users already expect from InvenTree.
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ COPY --from=builder_stage ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${IN
|
||||
COPY --from=builder_stage /root/.local /root/.local
|
||||
|
||||
# Launch the production server
|
||||
CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"]
|
||||
CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"]
|
||||
|
||||
FROM builder_stage AS dev
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/ash
|
||||
#!/bin/bash
|
||||
|
||||
# exit when any command fails
|
||||
set -e
|
||||
|
||||
@@ -17,7 +17,7 @@ gunicorn>=22.0.0
|
||||
# LDAP required packages
|
||||
django-auth-ldap # Django integration for ldap auth
|
||||
python-ldap # LDAP auth support
|
||||
django<5.0 # Force lower to match main project
|
||||
django<6.0 # Force lower to match main project
|
||||
|
||||
# Upgraded python package installer
|
||||
uv
|
||||
|
||||
@@ -4,9 +4,9 @@ asgiref==3.10.0 \
|
||||
--hash=sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734 \
|
||||
--hash=sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e
|
||||
# via django
|
||||
django==4.2.25 \
|
||||
--hash=sha256:2391ab3d78191caaae2c963c19fd70b99e9751008da22a0adcc667c5a4f8d311 \
|
||||
--hash=sha256:9584cf26b174b35620e53c2558b09d7eb180a655a3470474f513ff9acb494f8c
|
||||
django==5.2.9 \
|
||||
--hash=sha256:16b5ccfc5e8c27e6c0561af551d2ea32852d7352c67d452ae3e76b4f6b2ca495 \
|
||||
--hash=sha256:3a4ea88a70370557ab1930b332fd2887a9f48654261cdffda663fef5976bb00a
|
||||
# via
|
||||
# -r contrib/container/requirements.in
|
||||
# django-auth-ldap
|
||||
@@ -53,77 +53,77 @@ packaging==25.0 \
|
||||
# via
|
||||
# gunicorn
|
||||
# mariadb
|
||||
psycopg[binary, pool]==3.2.11 \
|
||||
--hash=sha256:217231b2b6b72fba88281b94241b2f16043ee67f81def47c52a01b72ff0c086a \
|
||||
--hash=sha256:398bb484ed44361e041c8f804ed7af3d2fcefbffdace1d905b7446c319321706
|
||||
psycopg[binary, pool]==3.2.12 \
|
||||
--hash=sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b \
|
||||
--hash=sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee
|
||||
# via -r contrib/container/requirements.in
|
||||
psycopg-binary==3.2.11 \
|
||||
--hash=sha256:00221bfeb9594ca6e01207b032c300fa6f889d918bf0de47f4571c1f9f6e1578 \
|
||||
--hash=sha256:110a2036007230416fcc2c17bfe7aaa2c1fa9b6e9d21e2cd551523e3f6489759 \
|
||||
--hash=sha256:199f88a05dd22133eab2deb30348ef7a70c23d706c8e63fdc904234163c63517 \
|
||||
--hash=sha256:1db270e6bdbd183e3908cd9bb832506b99e1f2222a2fc2145f980c3ba1c8c30f \
|
||||
--hash=sha256:20d41bcd9ac289d44ac1f6151594f7883483b4ad14680a63e04b639dc90c3349 \
|
||||
--hash=sha256:23c77dbbffe8ba679213877f7204f4599bd545b65d2d69982fd685a3fea35b11 \
|
||||
--hash=sha256:260738ae222b41dbefd0d84cb2e150a112f90b41688630f57fdac487ab6d6f38 \
|
||||
--hash=sha256:27eb6367350b75fef882c40cd6f748bfd976db2f8651f7511956f11efc15154f \
|
||||
--hash=sha256:2a438fad4cc081b018431fde0e791b6d50201526edf39522a85164f606c39ddb \
|
||||
--hash=sha256:30e2c114d26554ae677088de5d4133cc112344d7a233200fdbf4a2ca5754c7ec \
|
||||
--hash=sha256:31f1d5630afa673c37a6327f8e3efa1f17d4e4e42972643b3478b52275233529 \
|
||||
--hash=sha256:32bd319a68420631a320bb450921c8320641621a92556c97b38b1e116010c344 \
|
||||
--hash=sha256:3bd2c8fb1dec6f93383fbaa561591fa3d676e079f9cb9889af17c3020a19715f \
|
||||
--hash=sha256:3f32b09fba85d9e239229bdc5b6254420c02054f6954fe7fbd1ecf1ca93009ed \
|
||||
--hash=sha256:478a68d50f34f6203642d245e2046d266c719ab4e593a1bb94c3be5f82e1aee1 \
|
||||
--hash=sha256:47f6cf8a1d02d25238bdb8741ac641ff0ec22b1c6ff6a2acd057d0da5c712842 \
|
||||
--hash=sha256:49d76391b225f72dd63fcab87937ccf307ae0f093b5a382eeacf05f19a57c176 \
|
||||
--hash=sha256:4cae9bdc482e36e825d5102a9f3010e729f33a4ca83fc8a1f439ba16eb61e1f1 \
|
||||
--hash=sha256:54a30f00a51b9043048b3e7ee806ffd31fc5fbd02a20f0e69d21306ff33dc473 \
|
||||
--hash=sha256:566d02a0b85b994e40b4f6276b3423c59e8157f10b73bd2e634f8e0a3dfb1890 \
|
||||
--hash=sha256:5768a9e7d393b2edd3a28de5a6d5850d054a016ed711f7044a9072f19f5e50d5 \
|
||||
--hash=sha256:581358e770a4536e546841b78fd0fe318added4a82443bf22d0bbe3109cf9582 \
|
||||
--hash=sha256:58997db1aa48a1119e26c1c2f893d1c92339bd3be5d1f25334f22eaeaeeca90e \
|
||||
--hash=sha256:58d8f9f80ae79ba7f2a0509424939236220d7d66a4f8256ae999b882cc58065b \
|
||||
--hash=sha256:592fb928efe0674a7400af914bcf931eb5267d36237925947aaecf63bd9a91aa \
|
||||
--hash=sha256:5bc571786a256a2fa2d8f13b5ecf714020b753bc76c2fa6d308e46751946dc31 \
|
||||
--hash=sha256:5f6f948ff1cd252003ff534d7b50a2b25453b4212b283a7514ff8751bdb68c37 \
|
||||
--hash=sha256:5fb27dd9c52ae13cb4de90244207155b694f76a75a816115ead2d573f40e1e36 \
|
||||
--hash=sha256:6688807ed07436c18e9946d01372bc80b9d20b7732cde27de9313e0860910c84 \
|
||||
--hash=sha256:6b9632c42f76d5349e7dd50025cff02688eb760b258e891ad2c6428e7e4917d5 \
|
||||
--hash=sha256:720e19ff2d1c425b6be18bd20ba35010c7927e492bcfecbae1085a89caa7db7c \
|
||||
--hash=sha256:749d23fbfd642a7abfef5fc0f6ca185fa82a2c0f895e6eab42c3f2a5d88f6011 \
|
||||
--hash=sha256:7608c9fa58b85426093ab8777080e8f134d89915c05c51fa270e7aee317f2b38 \
|
||||
--hash=sha256:766089fdaa8af1b5f7e2ec9fd7ad190c865e226b4fb0e7b1bd8dbcd62b5b923e \
|
||||
--hash=sha256:7744b4ed1f3b76fe37de7e9ef98014482fe74b6d3dfe1026cc4cfb4b4404e74f \
|
||||
--hash=sha256:7b3c5474dbad63bcccb8d14d4d4c7c19f1dc6f8e8c1914cbc771d261cf8eddca \
|
||||
--hash=sha256:81e57d1f00af9b7414c8d00ac77892b3786ddd69a23c27dee47cae8fd3543b07 \
|
||||
--hash=sha256:82fe30afbdd66fbdad583b02baad5c15930a3dc8a3756d2ae15fc874e9be8ec8 \
|
||||
--hash=sha256:8792e502a16a0b28d9fd23571fe492271a00c4b2b55f6c0b8377e47032758cd3 \
|
||||
--hash=sha256:91268f04380964a5e767f8102d05f1e23312ddbe848de1a9514b08b3fc57d354 \
|
||||
--hash=sha256:9b4b0fc4e774063ae64c92cc57e2b10160150de68c96d71743218159d953869d \
|
||||
--hash=sha256:9bdc762600fcc8e4ad3224734a4e70cc226207fd8f2de47c36b115efeed01782 \
|
||||
--hash=sha256:9ea3ebe1706fd78d6ac0dd1cf692a77cfacd5ba967c82128f9863a5e48f63c47 \
|
||||
--hash=sha256:9f12a34bddaeffa7840a61163595ec0d70a9db855896865dcfbb731510014484 \
|
||||
--hash=sha256:a3a59d404e1fb8ec47116f66f5adf48a8993a8aac0dad0395a923155fd55ee38 \
|
||||
--hash=sha256:b051aa1e67f0d03ccdb4503d716f22da56229896526f0aa721e5a199baa9e5d4 \
|
||||
--hash=sha256:b2fa94ce40bc4b408149d83a6204fc5e53c3e9d3cd5b749de2e7e9671a049cf7 \
|
||||
--hash=sha256:c45f61202e5691090a697e599997eaffa3ec298209743caa4fd346145acabafe \
|
||||
--hash=sha256:c594c199869099c59c85b9f4423370b6212491fb929e7fcda0da1768761a2c2c \
|
||||
--hash=sha256:d27f51b8ce291da4af749ef850adb4520bfe52c2ff4677402c719ff35af03f00 \
|
||||
--hash=sha256:d59db908d9baaa057a43dd5aa8352f3e3de4b8c57f295172d5fe521e97d6c39d \
|
||||
--hash=sha256:d7e490848d7bedf6c1d2180233a33d31d554a1b0823f80a0236ebb7d3b6caf12 \
|
||||
--hash=sha256:e3b6328bc2f3ca233f9a5f08d266089b96a534eca9ee4e45cb92d0a8d4629d9c \
|
||||
--hash=sha256:e3f5887019dfb094c60e7026968ca3a964ca16305807ba5e43f9a78483767d5f \
|
||||
--hash=sha256:e7575ca710277cc3e9257ff803a3e0e3cb7cc1b7851639cb783a7cd55ebfc815 \
|
||||
--hash=sha256:e7f4dff472a529c9027f294c8842ab535bbed7e2928fe1f4fc28b27f2463d6d5 \
|
||||
--hash=sha256:eab6959fade522e586b8ec37d3fe337ce10861965edef3292f52e66e36dc375d \
|
||||
--hash=sha256:f5e7415b5d0f58edf2708842c66605092df67f3821161d861b09695fc326c4de \
|
||||
--hash=sha256:f72146ad5b69ea177c2707578e5a4a9422b79e50d5a80992dabc5619b0929771 \
|
||||
--hash=sha256:fa2aa5094dc962967ca0978c035b3ef90329b802501ef12a088d3bac6a55598e \
|
||||
--hash=sha256:fe5e3648e855df4fba1d70c18aef18c9880ea8d123fdfae754c18787c8cb37b3 \
|
||||
--hash=sha256:ff64883cff66fe797cd958c0ff7f53fc36a28239b9e0dc80189ce1c03ce47153
|
||||
psycopg-binary==3.2.12 \
|
||||
--hash=sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d \
|
||||
--hash=sha256:0afb71a99871a41dd677d207c6a988d978edde5d6a018bafaed4f9da45357055 \
|
||||
--hash=sha256:100fdfee763d701f6da694bde711e264aca4c2bc84fb81e1669fb491ce11d219 \
|
||||
--hash=sha256:13cd057f406d2c8063ae8b489395b089a7f23c39aff223b5ea39f0c4dd640550 \
|
||||
--hash=sha256:15e226f0d8af85cc8b2435b2e9bc6f0d40febc79eef76cf20fceac4d902a6a7b \
|
||||
--hash=sha256:16db2549a31ccd4887bef05570d95036813ce25fd9810b523ba1c16b0f6cfd90 \
|
||||
--hash=sha256:1c1dbeb8e97d00a33dfa9987776ce3d1c1e4cc251dfbd663b8f9e173f5c89d17 \
|
||||
--hash=sha256:1d7cedecbe0bb60a2e72b1613fba4072a184a6472d6cc9aa99e540217f544e3e \
|
||||
--hash=sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc \
|
||||
--hash=sha256:26b5927b5880b396231ab6190ee5c8fb47ed3f459b53504ed5419faaf16d3bfb \
|
||||
--hash=sha256:294f08b014f08dfd3c9b72408f5e1a0fd187bd86d7a85ead651e32dbd47aa038 \
|
||||
--hash=sha256:2aa80ca8d17266507bef853cecefa7d632ffd087883ee7ca92b8a7ea14a1e581 \
|
||||
--hash=sha256:2d55009eeddbef54c711093c986daaf361d2c4210aaa1ee905075a3b97a62441 \
|
||||
--hash=sha256:310c95a68a9b948b89d6d187622757d57b6c26cece3c3f7c2cbb645ee36531b2 \
|
||||
--hash=sha256:32b3e12d9441508f9c4e1424f4478b1a518a90a087cd54be3754e74954934194 \
|
||||
--hash=sha256:356b4266e5cde7b5bbcf232f549dedf7fbed4983daa556042bdec397780e044d \
|
||||
--hash=sha256:385c7b5cfffac115f413b8e32c941c85ea0960e0b94a6ef43bb260f774c54893 \
|
||||
--hash=sha256:3c1e38b1eda54910628f68448598139a9818973755abf77950057372c1fe89a6 \
|
||||
--hash=sha256:3e9c9e64fb7cda688e9488402611c0be2c81083664117edcc709d15f37faa30f \
|
||||
--hash=sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b \
|
||||
--hash=sha256:489b154891f1c995355adeb1077ee3479e9c9bada721b93270c20243bbad6542 \
|
||||
--hash=sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39 \
|
||||
--hash=sha256:49582c3b6d578bdaab2932b59f70b1bd93351ed4d594b2c97cea1611633c9de1 \
|
||||
--hash=sha256:58ed30d33c25d7dc8d2f06285e88493147c2a660cc94713e4b563a99efb80a1f \
|
||||
--hash=sha256:5b6e505618cb376a7a7d6af86833a8f289833fe4cc97541d7100745081dc31bd \
|
||||
--hash=sha256:66a031f22e4418016990446d3e38143826f03ad811b9f78f58e2afbc1d343f7a \
|
||||
--hash=sha256:6a898717ab560db393355c6ecf39b8c534f252afc3131480db1251e061090d3a \
|
||||
--hash=sha256:7130effd0517881f3a852eff98729d51034128f0737f64f0d1c7ea8343d77bd7 \
|
||||
--hash=sha256:72fd979e410ba7805462817ef8ed6f37dd75f9f4ae109bdb8503e013ccecb80b \
|
||||
--hash=sha256:77690f0bf08356ca00fc357f50a5980c7a25f076c2c1f37d9d775a278234fefd \
|
||||
--hash=sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5 \
|
||||
--hash=sha256:7b9a99ded7d19b24d3b6fa632b58e52bbdecde7e1f866c3b23d0c27b092af4e3 \
|
||||
--hash=sha256:802bd01fb18a0acb0dea491f69a9a2da6034f33329a62876ab5b558a1fb66b45 \
|
||||
--hash=sha256:8335d989a4e94df2ccd8a1acbba9d03c4157ea8d73b65b79d447c6dc10b001d8 \
|
||||
--hash=sha256:89b3c5201ca616d69ca0c3c0003ca18f7170a679c445c7e386ebfb4f29aa738e \
|
||||
--hash=sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e \
|
||||
--hash=sha256:909de94de7dd4d6086098a5755562207114c9638ec42c52d84c8a440c45fe084 \
|
||||
--hash=sha256:940ac69ef6e89c17b3d30f3297a2ad03efdd06a4b1857f81bc533a9108a90eb9 \
|
||||
--hash=sha256:95f2806097a49bfd57e0c6a178f77b99487c53c157d9d507aee9c40dd58efdb4 \
|
||||
--hash=sha256:9c674887d1e0d4384c06c822bc7fcfede4952742e232ec1e76b5a6ae39a3ddd4 \
|
||||
--hash=sha256:9fdf3a0c24822401c60c93640da69b3dfd4d9f29c3a8d797244fe22bfe592823 \
|
||||
--hash=sha256:ab02b7d138768fd6ac4230e45b073f7b9fd688d88c04f24c34df4a250a94d066 \
|
||||
--hash=sha256:acb1811219a4144539f0baee224a11a2aa323a739c349799cf52f191eb87bc52 \
|
||||
--hash=sha256:bfd632f7038c76b0921f6d5621f5ba9ecabfad3042fa40e5875db11771d2a5de \
|
||||
--hash=sha256:ce68839da386f137bc8d814fdbeede8f89916b8605e3593a85b504a859243af9 \
|
||||
--hash=sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3 \
|
||||
--hash=sha256:dc68094e00a5a7e8c20de1d3a0d5e404a27f522e18f8eb62bbbc9f865c3c81ef \
|
||||
--hash=sha256:deeb06b7141f3a577c3aa8562307e2747580ae43d705a0482603a2c1f110d046 \
|
||||
--hash=sha256:e0b5ccd03ca4749b8f66f38608ccbcb415cbd130d02de5eda80d042b83bee90e \
|
||||
--hash=sha256:ea049c8d33c4f4e6b030d5a68123c0ccd2ffb77d4035f073db97187b49b6422f \
|
||||
--hash=sha256:ea9751310b840186379c949ede5a5129b31439acdb929f3003a8685372117ed8 \
|
||||
--hash=sha256:ec82fa5134517af44e28a30c38f34384773a0422ffd545fd298433ea9f2cc5a9 \
|
||||
--hash=sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675 \
|
||||
--hash=sha256:ef40601b959cc1440deaf4d53472ab54fa51036c37189cf3fe5500559ac25347 \
|
||||
--hash=sha256:ef92d5ba6213de060d1390b1f71f5c3b2fbb00b4d55edee39f3b07234538b64a \
|
||||
--hash=sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3 \
|
||||
--hash=sha256:f33c9e12ed05e579b7fb3c8fdb10a165f41459394b8eb113e7c377b2bd027f61 \
|
||||
--hash=sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a \
|
||||
--hash=sha256:f6ba1fe35fd215813dac4544a5ffc90f13713b29dd26e9e5be97ba53482bf6d6 \
|
||||
--hash=sha256:f7c81bc60560be9eb3c23601237765069ebfa9881097ce19ca6b5ea17c5faa8f \
|
||||
--hash=sha256:f8107968a9eadb451cfa6cf86036006fdde32a83cd39c26c9ca46765e653b547 \
|
||||
--hash=sha256:f821e0c8a8fdfddfa71acb4f462d7a4c5aae1655f3f5e078970dbe9f19027386
|
||||
# via psycopg
|
||||
psycopg-pool==3.2.6 \
|
||||
--hash=sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5 \
|
||||
--hash=sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7
|
||||
psycopg-pool==3.2.7 \
|
||||
--hash=sha256:4b47bb59d887ef5da522eb63746b9f70e2faf967d34aac4f56ffc65e9606728f \
|
||||
--hash=sha256:a77d531bfca238e49e5fb5832d65b98e69f2c62bfda3d2d4d833696bdc9ca54b
|
||||
# via psycopg
|
||||
pyasn1==0.6.1 \
|
||||
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
|
||||
@@ -229,26 +229,26 @@ typing-extensions==4.15.0 \
|
||||
# via
|
||||
# psycopg
|
||||
# psycopg-pool
|
||||
uv==0.9.4 \
|
||||
--hash=sha256:03a85b02e6ccf1b705ce78bd98da78c90d5a0d0f941756ee842825d850cada2f \
|
||||
--hash=sha256:0840346084d28aa5345eeabcb7f9e727448b56b3b399300447a9155066909925 \
|
||||
--hash=sha256:253133f7f2eac8fed10ad601c56ddcd13d8d81d9343ed9e95873d19b149199f2 \
|
||||
--hash=sha256:2feb2adc0a2eb41a757b9cef3226f649452423badf20d68d177b6649342d021d \
|
||||
--hash=sha256:39f6b459fdabc80c0afc080ba8bce86e048afa799bc6c5c372f78b14195cf49c \
|
||||
--hash=sha256:3e1b5df83e96a8128b81a9f2bd72a4db752f691515914471b76df994339d2c35 \
|
||||
--hash=sha256:42012fcfdbaec08e1c009bbdbf96296b05e0e86feb83e1182d9335ae86a288d2 \
|
||||
--hash=sha256:57582a149de7788a83f998ddad2dfc50a328aae7a474fbb1617c73a9e2b42ebf \
|
||||
--hash=sha256:610a219a6d92cc56c1a24888118a5ae1b07233b93dde0565d64fe198a2c7c376 \
|
||||
--hash=sha256:787cf63c2f5c97cc6b30915632351eac655fcd4ec19620bc67cbd6855975817b \
|
||||
--hash=sha256:79efd533016d9bf077056cac72e68fa501e9d0e09576a2c375f7c286d19be9d6 \
|
||||
--hash=sha256:9ee7695b6632b74ea62d67fcef732e519d1fdb3f9ecf81c99bfd5a354ff925fb \
|
||||
--hash=sha256:aa0e144df0276945cbe49e30b577cf51e19b808e5ca55e23b8a1a354857e1629 \
|
||||
--hash=sha256:b7f7d3fd51627fbcca06cf75d327e060db924d4ca054e1e934b71682d58f1f51 \
|
||||
--hash=sha256:c353be83686f769bf50e6c6bc8591ad59752b492c6bb51296e378e55521482f5 \
|
||||
--hash=sha256:d89f88df09d571f6d06228b32a6a71100905eb64343247317d363bcd774ee870 \
|
||||
--hash=sha256:dcbcc963232e13e279002844e983cd6d0f53560e75d8a3f7a68e7d68a6021235 \
|
||||
--hash=sha256:df3288f85bd6bfb4b8722bb7223d6723de7c32d213596573d92803f89af9007c \
|
||||
--hash=sha256:fa33399d5e3e31b753910cfaa6f87022736339cadb140c8896dccb7c6a855e32
|
||||
uv==0.9.8 \
|
||||
--hash=sha256:0f03bc413c933dbf850ad0dc2dba3df6b80c860a5c65cd767add49da19dadef0 \
|
||||
--hash=sha256:14670bf55ecb5cfd0f3654fbf51c58a21dec3ad8ab531079b3ed8599271dc77b \
|
||||
--hash=sha256:1b8b5bdcda3e10ea70b618d0609acddc5c725cb58d4caf933030ddedd7c2e98f \
|
||||
--hash=sha256:40253d00c1e900a0a61b132b1e0dd4aa83575cfd5302d3671899b6de29b1ef67 \
|
||||
--hash=sha256:50d130c46d97d7f10675ebea8608b7b4722c84b5745cd1bb0c8ae6d7984c05d5 \
|
||||
--hash=sha256:543693def38fa41b9706aba391111fe8d9dd6be86899d76f9581faf045ac1cb6 \
|
||||
--hash=sha256:5af28f1645eb3c50fd34a78508792db2d0799816f4eb5f55e1e6e2c724dfb125 \
|
||||
--hash=sha256:6a01d7cd41953ffac583139b10ad1df004a67c0246a6b694eb5bcdbc8c99deaf \
|
||||
--hash=sha256:6df2e16f6df32018047c60bab2c0284868ad5c309addba9183ea2eeb71746bf0 \
|
||||
--hash=sha256:7038a552159f2291dd0d1f4f66a36261b5f3ed5fcd92e2869186f8e910b2c935 \
|
||||
--hash=sha256:75671150d6eb9d5ee829e1fdb8cf86b8e44a66d27cbb996fe807e986c4107b5d \
|
||||
--hash=sha256:87c3b65b6d5fcbdeab199d54c74fbf75de19cb534a690c936c5616478a038576 \
|
||||
--hash=sha256:99b18bfe92c33c3862b65d74677697e799763e669e0064685f405e7e27517f25 \
|
||||
--hash=sha256:9f2f3576c4518ff4f15e48dbca70585a513523c4738bc8cc2e48b20fd1190ce3 \
|
||||
--hash=sha256:a4010b3fdabbb3c4f2cf2f7aa3bf6002d00049dcbc54ce0ee5ada32a933b2290 \
|
||||
--hash=sha256:bb0f8e83c2a2fc5a802e930cc8a7b71ab068180300a3f27ba38037f9fcb3d430 \
|
||||
--hash=sha256:cdbfadca9522422ab9820f5ada071c9c5c869bcd6fee719d20d91d5ec85b2a7d \
|
||||
--hash=sha256:d93a2227d23e81ab3a16c30363559afc483e8aca40ea9343b3f326a9a41718c9 \
|
||||
--hash=sha256:f52c6a99197028a314d4c1825f7ccb696eb9a88b822d2e2f17046266c75e543e
|
||||
# via -r contrib/container/requirements.in
|
||||
wheel==0.45.1 \
|
||||
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \
|
||||
|
||||
@@ -214,66 +214,72 @@ ruamel-yaml==0.18.15 \
|
||||
--hash=sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701 \
|
||||
--hash=sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700
|
||||
# via jc
|
||||
ruamel-yaml-clib==0.2.14 \
|
||||
--hash=sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78 \
|
||||
--hash=sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d \
|
||||
--hash=sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9 \
|
||||
--hash=sha256:16a60d69f4057ad9a92f3444e2367c08490daed6428291aa16cefb445c29b0e9 \
|
||||
--hash=sha256:18c041b28f3456ddef1f1951d4492dbebe0f8114157c1b3c981a4611c2020792 \
|
||||
--hash=sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e \
|
||||
--hash=sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca \
|
||||
--hash=sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb \
|
||||
--hash=sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d \
|
||||
--hash=sha256:275f938692013a3883edbd848edde6d9f26825d65c9a2eb1db8baa1adc96a05d \
|
||||
--hash=sha256:27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182 \
|
||||
--hash=sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98 \
|
||||
--hash=sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1 \
|
||||
--hash=sha256:4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8 \
|
||||
--hash=sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee \
|
||||
--hash=sha256:5ac5ff9425d8acb8f59ac5b96bcb7fd3d272dc92d96a7c730025928ffcc88a7a \
|
||||
--hash=sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e \
|
||||
--hash=sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052 \
|
||||
--hash=sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27 \
|
||||
--hash=sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352 \
|
||||
--hash=sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83 \
|
||||
--hash=sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f \
|
||||
--hash=sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32 \
|
||||
--hash=sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e \
|
||||
--hash=sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad \
|
||||
--hash=sha256:81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02 \
|
||||
--hash=sha256:83bbd8354f6abb3fdfb922d1ed47ad8d1db3ea72b0523dac8d07cdacfe1c0fcf \
|
||||
--hash=sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6 \
|
||||
--hash=sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a \
|
||||
--hash=sha256:94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31 \
|
||||
--hash=sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d \
|
||||
--hash=sha256:9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8 \
|
||||
--hash=sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29 \
|
||||
--hash=sha256:a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543 \
|
||||
--hash=sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27 \
|
||||
--hash=sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68 \
|
||||
--hash=sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a \
|
||||
--hash=sha256:aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451 \
|
||||
--hash=sha256:b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4 \
|
||||
--hash=sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6 \
|
||||
--hash=sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54 \
|
||||
--hash=sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023 \
|
||||
--hash=sha256:d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8 \
|
||||
--hash=sha256:d8354515ab62f95a07deaf7f845886cc50e2f345ceab240a3d2d09a9f7d77853 \
|
||||
--hash=sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70 \
|
||||
--hash=sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85 \
|
||||
--hash=sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9 \
|
||||
--hash=sha256:e1d1735d97fd8a48473af048739379975651fab186f8a25a9f683534e6904179 \
|
||||
--hash=sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c \
|
||||
--hash=sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640 \
|
||||
--hash=sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2 \
|
||||
--hash=sha256:f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4 \
|
||||
--hash=sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4 \
|
||||
--hash=sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259 \
|
||||
--hash=sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59
|
||||
ruamel-yaml-clib==0.2.15 \
|
||||
--hash=sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490 \
|
||||
--hash=sha256:04d21dc9c57d9608225da28285900762befbb0165ae48482c15d8d4989d4af14 \
|
||||
--hash=sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6 \
|
||||
--hash=sha256:0ba6604bbc3dfcef844631932d06a1a4dcac3fee904efccf582261948431628a \
|
||||
--hash=sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9 \
|
||||
--hash=sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d \
|
||||
--hash=sha256:1bb7b728fd9f405aa00b4a0b17ba3f3b810d0ccc5f77f7373162e9b5f0ff75d5 \
|
||||
--hash=sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3 \
|
||||
--hash=sha256:27dc656e84396e6d687f97c6e65fb284d100483628f02d95464fd731743a4afe \
|
||||
--hash=sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c \
|
||||
--hash=sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc \
|
||||
--hash=sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf \
|
||||
--hash=sha256:3cb75a3c14f1d6c3c2a94631e362802f70e83e20d1f2b2ef3026c05b415c4900 \
|
||||
--hash=sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a \
|
||||
--hash=sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa \
|
||||
--hash=sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6 \
|
||||
--hash=sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd \
|
||||
--hash=sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25 \
|
||||
--hash=sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600 \
|
||||
--hash=sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf \
|
||||
--hash=sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642 \
|
||||
--hash=sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614 \
|
||||
--hash=sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf \
|
||||
--hash=sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000 \
|
||||
--hash=sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb \
|
||||
--hash=sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690 \
|
||||
--hash=sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e \
|
||||
--hash=sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137 \
|
||||
--hash=sha256:5d3c9210219cbc0f22706f19b154c9a798ff65a6beeafbf77fc9c057ec806f7d \
|
||||
--hash=sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401 \
|
||||
--hash=sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f \
|
||||
--hash=sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2 \
|
||||
--hash=sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471 \
|
||||
--hash=sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed \
|
||||
--hash=sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524 \
|
||||
--hash=sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60 \
|
||||
--hash=sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef \
|
||||
--hash=sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043 \
|
||||
--hash=sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03 \
|
||||
--hash=sha256:923816815974425fbb1f1bf57e85eca6e14d8adc313c66db21c094927ad01815 \
|
||||
--hash=sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77 \
|
||||
--hash=sha256:a8220fd4c6f98485e97aea65e1df76d4fed1678ede1fe1d0eed2957230d287c4 \
|
||||
--hash=sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d \
|
||||
--hash=sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467 \
|
||||
--hash=sha256:badd1d7283f3e5894779a6ea8944cc765138b96804496c91812b2829f70e18a7 \
|
||||
--hash=sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e \
|
||||
--hash=sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec \
|
||||
--hash=sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4 \
|
||||
--hash=sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd \
|
||||
--hash=sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff \
|
||||
--hash=sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c \
|
||||
--hash=sha256:da3d6adadcf55a93c214d23941aef4abfd45652110aed6580e814152f385b862 \
|
||||
--hash=sha256:dcc7f3162d3711fd5d52e2267e44636e3e566d1e5675a5f0b30e98f2c4af7974 \
|
||||
--hash=sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922 \
|
||||
--hash=sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a \
|
||||
--hash=sha256:e9fde97ecb7bb9c41261c2ce0da10323e9227555c674989f8d9eb7572fc2098d \
|
||||
--hash=sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262 \
|
||||
--hash=sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144 \
|
||||
--hash=sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1 \
|
||||
--hash=sha256:fd4c928ddf6bce586285daa6d90680b9c291cfd045fc40aad34e445d57b1bf51 \
|
||||
--hash=sha256:fe239bdfdae2302e93bd6e8264bd9b71290218fff7084a9db250b55caaccf43f
|
||||
# via ruamel-yaml
|
||||
urllib3==2.5.0 \
|
||||
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
||||
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
||||
urllib3==2.6.0 \
|
||||
--hash=sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f \
|
||||
--hash=sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1
|
||||
# via requests
|
||||
xmltodict==1.0.2 \
|
||||
--hash=sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649 \
|
||||
|
||||
@@ -15,7 +15,7 @@ root_command() {
|
||||
no_call=${args[--no-call]}
|
||||
dry_run=${args[--dry-run]}
|
||||
|
||||
REQS="wget apt-transport-https"
|
||||
REQS="wget apt-transport-https curl gpg"
|
||||
|
||||
function do_call() {
|
||||
if [[ $dry_run ]]; then
|
||||
@@ -64,7 +64,7 @@ root_command() {
|
||||
Ubuntu)
|
||||
if [[ $VER == "24.04" ]]; then
|
||||
SUPPORTED=true
|
||||
if [[ $VER == "22.04" ]]; then
|
||||
elif [[ $VER == "22.04" ]]; then
|
||||
SUPPORTED=true
|
||||
elif [[ $VER == "20.04" ]]; then
|
||||
SUPPORTED=true
|
||||
|
||||
@@ -5,7 +5,7 @@ publisher=${args[publisher]}
|
||||
no_call=${args[--no-call]}
|
||||
dry_run=${args[--dry-run]}
|
||||
|
||||
REQS="wget apt-transport-https"
|
||||
REQS="wget apt-transport-https curl gpg"
|
||||
|
||||
function do_call() {
|
||||
if [[ $dry_run ]]; then
|
||||
@@ -54,7 +54,7 @@ case "$OS" in
|
||||
Ubuntu)
|
||||
if [[ $VER == "24.04" ]]; then
|
||||
SUPPORTED=true
|
||||
if [[ $VER == "22.04" ]]; then
|
||||
elif [[ $VER == "22.04" ]]; then
|
||||
SUPPORTED=true
|
||||
elif [[ $VER == "20.04" ]]; then
|
||||
SUPPORTED=true
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
Color_Off='\033[0m'
|
||||
On_Red='\033[41m'
|
||||
PYTHON_FROM=9
|
||||
PYTHON_TO=14
|
||||
PYTHON_FROM=11
|
||||
PYTHON_TO=15
|
||||
|
||||
function detect_docker() {
|
||||
if [ -n "$(grep docker </proc/1/cgroup)" ]; then
|
||||
@@ -61,7 +61,7 @@ function detect_python() {
|
||||
echo "# POI07| No python environment found - using environment variable: ${SETUP_PYTHON}"
|
||||
fi
|
||||
|
||||
# Try to detect a python between 3.9 and 3.12 in reverse order
|
||||
# Try to detect a python between lowest and highest supported in reverse order
|
||||
if [ -z "$(which ${SETUP_PYTHON})" ]; then
|
||||
echo "# POI07| Trying to detecting python3.${PYTHON_FROM} to python3.${PYTHON_TO} - using newest version"
|
||||
for i in $(seq $PYTHON_TO -1 $PYTHON_FROM); do
|
||||
@@ -79,7 +79,7 @@ function detect_python() {
|
||||
echo "${On_Red}"
|
||||
echo "# POI07| Python ${SETUP_PYTHON} not found - aborting!"
|
||||
echo "# POI07| Please ensure python can be executed with the command '$SETUP_PYTHON' by the current user '$USER'."
|
||||
echo "# POI07| If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.10'."
|
||||
echo "# POI07| If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.11'."
|
||||
echo "${Color_Off}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -26,7 +26,7 @@ export DATA_DIR=${APP_HOME}/data
|
||||
export SETUP_NGINX_FILE=${SETUP_NGINX_FILE:-/etc/nginx/sites-enabled/inventree.conf}
|
||||
export SETUP_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
|
||||
export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
|
||||
export SETUP_PYTHON=${SETUP_PYTHON:-python3.9}
|
||||
export SETUP_PYTHON=${SETUP_PYTHON:-python3.11}
|
||||
export SETUP_ADMIN_NOCREATION=${SETUP_ADMIN_NOCREATION:-false}
|
||||
# SETUP_DEBUG can be set to get debug info
|
||||
# SETUP_EXTRA_PIP can be set to install extra pip packages
|
||||
|
||||
@@ -4,28 +4,104 @@
|
||||
|
||||
This repository hosts the [official documentation](https://inventree.readthedocs.io/) for [InvenTree](https://github.com/inventree/inventree), an open source inventory management system.
|
||||
|
||||
To serve this documentation locally (e.g. for development), you will need to have Python 3 installed on your system.
|
||||
## Prerequisites
|
||||
|
||||
## Setup
|
||||
InvenTree uses [MkDocs](https://www.mkdocs.org/) to convert [Markdown](https://www.mkdocs.org/user-guide/writing-your-docs/#writing-with-markdown) format `.md` files into HTML suitable for viewing in a web browser.
|
||||
|
||||
Run the following commands from the top-level project directory:
|
||||
!!! info "Prerequisites"
|
||||
To build and serve this documentation locally (e.g. for development), you will need:
|
||||
|
||||
* Python 3 installed on your system.
|
||||
* An existing InvenTree installation containing the virtual environment that was created during installation.
|
||||
|
||||
These instructions assume you followed the [InvenTree bare metal installation instructions](./docs/start/install.md), so you'll have an `inventree` user, a home directory at `/home/inventree`, the InvenTree source code cloned from [GitHub](https://github.com/inventree/inventree) into `/home/inventree/src`, and a virtual environment at `/home/inventree/env`. If you installed InvenTree some other way, this might vary, and you'll have to adjust these instructions accordingly.
|
||||
|
||||
!!! warning "Your InvenTree install will be updated!"
|
||||
Some of the commands that follow will make changes to your install, for example, by running any pending database migrations. There's a small risk this may cause issues with your existing installation. If you can't risk this, consider setting up a separate InvenTree installation specifically for documentation development.
|
||||
|
||||
## Building the documentation locally
|
||||
|
||||
To build the documentation locally, run these commands as the `inventree` user:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/inventree/inventree
|
||||
$ cd /home/inventree
|
||||
$ source env/bin/activate
|
||||
```
|
||||
|
||||
!!! info "(env) prefix"
|
||||
The shell prompt should now display the `(env)` prefix, showing that you are operating within the context of the python virtual environment
|
||||
|
||||
You can now install the additional packages needed by mkdocs:
|
||||
|
||||
```
|
||||
$ cd src
|
||||
$ pip install --require-hashes -r docs/requirements.txt
|
||||
```
|
||||
|
||||
## Serve Locally
|
||||
## Build Documentation
|
||||
|
||||
To serve the pages locally, run the following command (from the top-level project directory):
|
||||
Before serving the documentation, you will need to build the API schema files from the source code, so they can be included in the documentation:
|
||||
|
||||
```
|
||||
invoke build-docs
|
||||
```
|
||||
|
||||
!!! tip
|
||||
This command is only required when building the documentation for the first time, or when changes have been made to the API schema.
|
||||
|
||||
## Serve Local files
|
||||
|
||||
```
|
||||
$ invoke build-docs
|
||||
```
|
||||
|
||||
You will see output similar to this (truncated for brevity):
|
||||
```
|
||||
Running InvenTree database migrations...
|
||||
Exporting definitions...
|
||||
Exporting settings definition to '/home/inventree/src/docs/generated/inventree_settings.json'...
|
||||
Exported InvenTree settings definitions to '/home/inventree/src/docs/generated/inventree_settings.json'
|
||||
Exported InvenTree tag definitions to '/home/inventree/src/docs/generated/inventree_tags.yml'
|
||||
Exported InvenTree filter definitions to '/home/inventree/src/docs/generated/inventree_filters.yml'
|
||||
Exported InvenTree report context definitions to '/home/inventree/src/docs/generated/inventree_report_context.json'
|
||||
Exporting definitions complete
|
||||
Exporting schema file to '/home/inventree/src/docs/generated/schema.yml'
|
||||
|
||||
Schema export completed: /home/inventree/src/docs/generated/schema.yml
|
||||
Documentation build complete, but mkdocs not requested
|
||||
```
|
||||
|
||||
If that worked, you can now generate the HTML format documentation pages:
|
||||
|
||||
```
|
||||
$ mkdocs build -f docs/mkdocs.yml
|
||||
```
|
||||
|
||||
## Viewing the generated output
|
||||
|
||||
To view the documentation locally, run the following command to start the MkDocs webpage server:
|
||||
|
||||
```
|
||||
$ mkdocs serve -f docs/mkdocs.yml -a localhost:8080
|
||||
```
|
||||
|
||||
## Edit Documentation Files
|
||||
Alternatively, you can use the `invoke` command:
|
||||
|
||||
Once the server is running, it will monitor the documentation files for any changes, and update the served pages.
|
||||
```
|
||||
invoke dev.docs-server
|
||||
```
|
||||
|
||||
To see all the available options:
|
||||
|
||||
```
|
||||
invoke dev.docs-server --help
|
||||
```
|
||||
|
||||
You can then point your web browser at http://localhost:8080/
|
||||
|
||||
## Editing the Documentation Files
|
||||
|
||||
Once the server is running, it will monitor the documentation files for any changes, and regenerate the HTML pages.
|
||||
|
||||
### Admonitions
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ response = request.get('http://localhost:8080/api/part/', data=data, headers=hea
|
||||
|
||||
InvenTree has built-in support for using [oAuth2](https://oauth.net/2/) and OpenID Connect (OIDC) for authentication to the API. This enables using the instance as a very limited identity provider.
|
||||
|
||||
A default application using a public client with PKCE enabled ships with each instance. Intended to be used with the python api and configured with very wide scopes this can also be used for quick tests - the cliend_id is `zDFnsiRheJIOKNx6aCQ0quBxECg1QBHtVFDPloJ6`.
|
||||
A default application using a public client with PKCE enabled ships with each instance. Intended to be used with the python api and configured with very wide scopes this can also be used for quick tests - the client_id is `zDFnsiRheJIOKNx6aCQ0quBxECg1QBHtVFDPloJ6`.
|
||||
|
||||
#### Managing applications
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ print("Minimum stock:", part.minimum_stock)
|
||||
|
||||
### Adding Parameters
|
||||
|
||||
Each [part](../../part/index.md) can have multiple [parameters](../../part/parameter.md). For the example of the sofa (above) *length* and *weight* make sense. Each parameter has a parameter template that combines the parameter name with a unit. So we first have to create the parameter templates and afterwards add the parameter values to the sofa.
|
||||
Each [part](../../part/index.md) can have multiple [parameters](../../concepts/parameters.md). For the example of the sofa (above) *length* and *weight* make sense. Each parameter has a parameter template that combines the parameter name with a unit. So we first have to create the parameter templates and afterwards add the parameter values to the sofa.
|
||||
|
||||
```python
|
||||
from inventree.part import Parameter
|
||||
|
||||
@@ -73,7 +73,7 @@ the *Scan Items Into Location* action allows you to scan items into the selected
|
||||
|
||||
### Stock Item Actions
|
||||
|
||||
From the [Stock Item detail page](./stock.md#stock-item-detail-view), the following barcode actions may be available:
|
||||
From the [Stock Item detail page](./stock.md#details-tab), the following barcode actions may be available:
|
||||
|
||||
{{ image("app/barcode_stock_item_actions.png", "Stock item barcode actions") }}
|
||||
|
||||
|
||||
BIN
docs/docs/assets/images/barcode/barcode_allocate_stock.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/docs/assets/images/barcode/barcode_field.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/docs/assets/images/barcode/barcode_field_dialog.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/docs/assets/images/barcode/barcode_field_filled.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/docs/assets/images/concepts/attachments-tab.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/docs/assets/images/concepts/parameter-tab.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/docs/assets/images/concepts/parameter-template.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/docs/assets/images/concepts/parametric-parts.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
docs/docs/assets/images/report/report_merge.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
docs/docs/assets/images/stock/build_order_allocations.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
docs/docs/assets/images/stock/sales_order_allocations.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/docs/assets/images/stock/serial_edit.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/docs/assets/images/stock/serial_edit_error.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/docs/assets/images/stock/stock_detail.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
docs/docs/assets/images/stock/stock_overview.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
@@ -69,6 +69,27 @@ To access this page, select *Scan Barcode* from the main navigation menu:
|
||||
{{ image("barcode/barcode_nav_menu.png", "Barcode menu item") }}
|
||||
{{ image("barcode/barcode_scan_page.png", "Barcode scan page") }}
|
||||
|
||||
## Barcodes in Forms
|
||||
|
||||
The InvenTree user interface supports direct scanning of barcodes within certain forms in the web UI. This means that any form field which points to a model which supports barcodes can accept barcode input. If barcode scanning is supported for a particular field, a barcode icon will be displayed next to the input field:
|
||||
|
||||
{{ image("barcode/barcode_field.png", "Barcode form field") }}
|
||||
|
||||
To scan a barcode into a form field, click this barcode icon. A barcode scanning dialog will be displayed, allowing the user to scan a barcode using their preferred input method:
|
||||
|
||||
{{ image("barcode/barcode_field_dialog.png", "Barcode field scan dialog") }}
|
||||
|
||||
Once scanned, the form field will be automatically populated with the correct item.
|
||||
|
||||
{{ image("barcode/barcode_field_filled.png", "Barcode field populated") }}
|
||||
|
||||
Any field which supports barcode input will have this functionality, such as allocating stock items to an order:
|
||||
|
||||
{{ image("barcode/barcode_allocate_stock.png", "Allocate stock via barcode") }}
|
||||
|
||||
### User Configuration
|
||||
|
||||
By default, barcode scanning in form fields is disabled. Each user can enable this feature via their [user preferences](../settings/user.md#display-settings).
|
||||
|
||||
## App Integration
|
||||
|
||||
@@ -76,8 +97,15 @@ Barcode scanning is a key feature of the [companion mobile app](../app/barcode.m
|
||||
|
||||
## Barcode History
|
||||
|
||||
If enabled, InvenTree can retain logs of the most recent barcode scans. This can be very useful for debugging or auditing purpopes.
|
||||
If enabled, InvenTree can retain logs of the most recent barcode scans. This can be very useful for debugging or auditing purposes.
|
||||
|
||||
Refer to the [barcode settings](../settings/global.md#barcodes) to enable barcode history logging.
|
||||
|
||||
The barcode history can be viewed via the admin panel in the web interface.
|
||||
|
||||
## Barcode Settings
|
||||
|
||||
There are a number of settings which control the behavior of barcodes within InvenTree. For more information, refer to the links below:
|
||||
|
||||
- [Global Barcode Settings](../settings/global.md#barcodes)
|
||||
- [User Preferences for Barcode Scanning](../settings/user.md#display-settings)
|
||||
|
||||
18
docs/docs/concepts/attachments.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Attachments
|
||||
---
|
||||
|
||||
## Attachments
|
||||
|
||||
An *attachment* is a file which has been uploaded and linked to a specific object within InvenTree. Attachments can be used to store additional documentation, images, or other relevant files associated with various InvenTree models.
|
||||
|
||||
!!! note "Business Logic"
|
||||
Attachments are not used for any core business logic within InvenTree. They are intended to provide additional metadata for objects, which can be useful for documentation, reference, or reporting purposes.
|
||||
|
||||
Parameters can be associated with various InvenTree models.
|
||||
|
||||
### Attachments Tab
|
||||
|
||||
Any model which supports attachments will have an "Attachments" tab on its detail page. This tab displays all attachments associated with that object:
|
||||
|
||||
{{ image("concepts/attachments-tab.png", "Order Attachments Example") }}
|
||||
@@ -1,17 +1,21 @@
|
||||
---
|
||||
title: Part Parameters
|
||||
title: Parameters
|
||||
---
|
||||
|
||||
## Part Parameters
|
||||
## Parameters
|
||||
|
||||
A part *parameter* describes a particular "attribute" or "property" of a specific part.
|
||||
A *parameter* describes a particular "attribute" or "property" of a specific object in InvenTree. Parameters allow for flexible and customizable data to be stored against various InvenTree models.
|
||||
|
||||
Part parameters are located in the "Parameters" tab, on each part detail page.
|
||||
There is no limit for the number of part parameters and they are fully customizable through the use of [parameter templates](#parameter-templates).
|
||||
!!! note "Business Logic"
|
||||
Parameters are not used for any core business logic within InvenTree. They are intended to provide additional metadata for objects, which can be useful for documentation, filtering, or reporting purposes.
|
||||
|
||||
Here is an example of parameters for a capacitor:
|
||||
Parameters can be associated with various InvenTree models.
|
||||
|
||||
{{ image("part/part_parameters_example.png", "Part Parameters Example") }}
|
||||
### Parameter Tab
|
||||
|
||||
Any model which supports parameters will have a "Parameters" tab on its detail page. This tab displays all parameters associated with that object:
|
||||
|
||||
{{ image("concepts/parameter-tab.png", "Part Parameters Example") }}
|
||||
|
||||
## Parameter Templates
|
||||
|
||||
@@ -22,13 +26,16 @@ Parameter templates are used to define the different types of parameters which a
|
||||
| Name | The name of the parameter template (*must be unique*) |
|
||||
| Description | Optional description for the template |
|
||||
| Units | Optional units field (*must be a valid [physical unit](#parameter-units)*) |
|
||||
| Model Type | The InvenTree model to which this parameter template applies (e.g. Part, Company, etc). If this is left blank, the template can be used for any model type. |
|
||||
| Choices | A comma-separated list of valid choices for parameter values linked to this template. |
|
||||
| Checkbox | If set, parameters linked to this template can only be assigned values *true* or *false* |
|
||||
| Selection List | If set, parameters linked to this template can only be assigned values from the linked [selection list](#selection-lists) |
|
||||
|
||||
{{ image("concepts/parameter-template.png", "Parameters Template") }}
|
||||
|
||||
### Create Template
|
||||
|
||||
Parameter templates are created and edited via the [settings interface](../settings/global.md).
|
||||
Parameter templates are created and edited via the [admin interface](../settings/admin.md).
|
||||
|
||||
To create a template:
|
||||
|
||||
@@ -54,11 +61,11 @@ Select the parameter `Template` you would like to use for this parameter, fill-o
|
||||
|
||||
## Parametric Tables
|
||||
|
||||
Parametric tables gather all parameters from all parts inside a particular [part category](./index.md#part-category) to be sorted and filtered.
|
||||
Parametric tables gather all parameters from all objects of a particular type, to be sorted and filtered.
|
||||
|
||||
To access a category's parametric table, click on the "Parameters" tab within the category view:
|
||||
Tables views which support parametric filtering and sorting will have a "Parametric View" button above the table:
|
||||
|
||||
{{ image("part/parametric_table_tab.png", "Parametric Table Tab") }}
|
||||
{{ image("concepts/parametric-parts.png", "Parametric Parts Table") }}
|
||||
|
||||
### Sorting by Parameter Value
|
||||
|
||||
@@ -139,7 +146,7 @@ Parameter sorting takes unit conversion into account, meaning that values provid
|
||||
|
||||
### Selection Lists
|
||||
|
||||
Selection Lists can be used to add a large number of predefined values to a parameter template. This can be useful for parameters which must be selected from a large predefined list of values (e.g. a list of standardised colo codes). Choices on templates are limited to 5000 characters, selection lists can be used to overcome this limitation.
|
||||
Selection Lists can be used to add a large number of predefined values to a parameter template. This can be useful for parameters which must be selected from a large predefined list of values (e.g. a list of standardized color codes). Choices on templates are limited to 5000 characters, selection lists can be used to overcome this limitation.
|
||||
|
||||
It is possible that plugins lock selection lists to ensure a known state.
|
||||
|
||||
@@ -59,9 +59,9 @@ The [unit of measure](../part/index.md#units-of-measure) field for the [Part](..
|
||||
|
||||
The [supplier part](../part/index.md/#supplier-parts) model uses real-world units to convert between supplier part quantities and internal stock quantities. Unit conversion rules ensure that only compatible unit types can be supplied
|
||||
|
||||
### Part Parameter
|
||||
### Parameter
|
||||
|
||||
The [part parameter template](../part/parameter.md#parameter-templates) model can specify units of measure, and part parameters can be specified against these templates with compatible units
|
||||
The [parameter template](../concepts/parameters.md#parameter-templates) model can specify units of measure, and part parameters can be specified against these templates with compatible units
|
||||
|
||||
## Custom Units
|
||||
|
||||
|
||||
56
docs/docs/develop/index.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: InvenTree Development
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
If you are interested in contributing to InvenTree, then this section is for you! Here you will find information about the architecture of InvenTree, how to set up a development environment, and guidelines for contributing code or documentation.
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
Read the [architecture overview](./architecture.md) to understand the high-level architecture of InvenTree, including how requests are processed, the backend and frontend architecture, and various components of the system.
|
||||
|
||||
### Contribution Guide
|
||||
|
||||
Start with the [contribution guide](./contributing.md) to understand how to get involved with the InvenTree project.
|
||||
|
||||
### Devcontainer Setup
|
||||
|
||||
We provide a [devcontainer](./devcontainer.md) configuration to help you quickly set up a development environment using vscode.
|
||||
|
||||
### Frontend Development
|
||||
|
||||
For information on developing the InvenTree frontend, refer to the [frontend development guide](./react-frontend.md).
|
||||
|
||||
## Profiling Tools
|
||||
|
||||
The InvenTree project supports integrated profiling tools to help developers analyze and optimize performance. Note that the following tools are intended for development use only and should not be enabled in production environments. In fact, they are explicitly disabled unless the server is running in [debug mode](../start/index.md#debug-mode).
|
||||
|
||||
### Django Silk
|
||||
|
||||
[django-silk](https://silk.readthedocs.io/en/latest/) is a profiling tool that can be used to monitor and analyze the performance of Django applications. It provides insights into SQL queries, request/response times, and more.
|
||||
|
||||
To enable django-silk profiling, ensure that the `debug_silk` option is set to `True` in your [config file](../start/config.md#configuration-file). Alternative, you can set the `INVENTREE_DEBUG_SILK` environment variable to enable this feature.
|
||||
|
||||
Once enabled, you can access the silk interface at the `/silk/` endpoint of your InvenTree instance.
|
||||
|
||||
!!! tip "Run Migrations"
|
||||
If you are enabling django-silk for the first time, you may need to run database migrations to create the necessary tables. You can do this by running `invoke migrate`.
|
||||
|
||||
#### Detailed Profiling
|
||||
|
||||
To enable detailed profiling in django-silk, set the `INVENTREE_DEBUG_SILK_PROFILING` environment variable to `True`, or set the `debug_silk_profiling` option to `True` in your config file. This will enable more granular profiling features within django-silk. Refer to the [django-silk documentation](https://github.com/jazzband/django-silk#profiling) for more information.
|
||||
|
||||
### Django QueryCount
|
||||
|
||||
Enabling the `INVENTREE_DEBUG_QUERYCOUNT` setting will log (to the terminal) the number of database queries executed for each page load. This can be useful for identifying performance bottlenecks in the InvenTree server. Note that this setting is only available if `INVENTREE_DEBUG` is also enabled.
|
||||
|
||||
### Database Logging
|
||||
|
||||
Enabling the `INVENTREE_DB_LOGGING` setting will log all database queries to the terminal. This can be useful for debugging database-related issues.
|
||||
|
||||
### Internal Profiling Tools
|
||||
|
||||
In addition to the above third-party tools, InvenTree includes some internal profiling tools that can be enabled in debug mode. These tools can be used to provide additional insights into the performance of various components of the InvenTree server.
|
||||
|
||||
These profiling tools can be found in `./src/backend/InvenTree/profiling.py`.
|
||||
@@ -98,7 +98,7 @@ Sometimes, users may encounter unexpected error messages when updating their Inv
|
||||
|
||||
The most common problem here is that the correct sequence of steps has not been followed:
|
||||
|
||||
1. Ensure that the InvenTree web server and background worker processes are *halted*
|
||||
1. Ensure that the InvenTree [web server](./start/processes.md#web-server) and [background worker](./start/processes.md#background-worker) processes are *halted*
|
||||
1. Update the InvenTree software (e.g. using git or docker, depending on installation method)
|
||||
1. Run the `invoke update` command
|
||||
1. Restart the web server and background worker processes
|
||||
@@ -150,7 +150,7 @@ or
|
||||
|
||||
### Background Worker "Not Running"
|
||||
|
||||
The background worker process must be started separately to the web-server application.
|
||||
The [background worker process](./start/processes.md#background-worker) must be started separately to the web-server application.
|
||||
|
||||
From the top-level source directory, run the following command from a separate terminal, while the server is already running:
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Before continuing, it is important that the difference between *untracked* and *
|
||||
|
||||
#### BOM Considerations
|
||||
|
||||
A [Bill of Materials](./bom.md) to generate an assembly may consist of a mixture of *untracked* and *tracked* components. The build order process can facilitate this, as documentated in the sections below.
|
||||
A [Bill of Materials](./bom.md) to generate an assembly may consist of a mixture of *untracked* and *tracked* components. The build order process can facilitate this, as documented in the sections below.
|
||||
|
||||
### Tracked Build Outputs
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Trackable Parts
|
||||
|
||||
## Stock Tracking
|
||||
|
||||
Denoting a part as *Trackble* changes the way that [stock items](../stock/index.md) associated with the particular part are handled in the database. A trackable part also has more restrictions imposed by the database scheme.
|
||||
Denoting a part as *Trackable* changes the way that [stock items](../stock/index.md) associated with the particular part are handled in the database. A trackable part also has more restrictions imposed by the database scheme.
|
||||
|
||||
For many parts in an InvenTree database, simply tracking current stock levels (and locations) is sufficient. However, some parts require more extensive tracking than simple stock level knowledge.
|
||||
|
||||
|
||||
@@ -39,12 +39,6 @@ A Part is defined in the system by the following parameters:
|
||||
|
||||
The Part view page organizes part data into sections, displayed as tabs. Each tab has its own function, which is described in this section.
|
||||
|
||||
### Parameters
|
||||
|
||||
Parts can have multiple defined parameters.
|
||||
|
||||
[Read about Part parameters](./parameter.md)
|
||||
|
||||
### Variants
|
||||
|
||||
If a part is a *Template Part* then the *Variants* tab will be visible.
|
||||
@@ -125,10 +119,18 @@ Related parts can be added and are shown under a table of the same name in the "
|
||||
|
||||
This feature can be enabled or disabled in the global part settings.
|
||||
|
||||
### Parameters
|
||||
|
||||
Parts can have multiple defined parameters.
|
||||
|
||||
[Read about parameters](../concepts/parameters.md).
|
||||
|
||||
### Attachments
|
||||
|
||||
The *Part Attachments* tab displays file attachments associated with the selected *Part*. Multiple file attachments (such as datasheets) can be uploaded for each *Part*.
|
||||
|
||||
[Read about attachments](../concepts/attachments.md).
|
||||
|
||||
### Notes
|
||||
|
||||
A part may have notes attached, which support markdown formatting.
|
||||
|
||||
@@ -21,7 +21,7 @@ The following builtin plugins are available in InvenTree:
|
||||
| Barcodes | [TME](./barcode_tme.md) | TME barcode support | No |
|
||||
| Data Export | [BOM Exporter](./bom_exporter.md) | Custom [exporter](../mixins/export.md) for BOM data | Yes |
|
||||
| Data Export | [InvenTree Exporter](./inventree_exporter.md) | Custom [exporter](../mixins/export.md) for InvenTree data | Yes |
|
||||
| Data Export | [Parameter Exporter](./part_parameter_exporter.md) | Custom [exporter](../mixins/export.md) for part parameter data | Yes |
|
||||
| Data Export | [Parameter Exporter](./parameter_exporter.md) | Custom [exporter](../mixins/export.md) for parameter data | Yes |
|
||||
| Data Export | [Stocktake Exporter](./stocktake_exporter.md) | Custom [exporter](../mixins/export.md) for stocktake data | No |
|
||||
| Events | [Auto Create Child Builds](./auto_create_builds.md) | Automatically create child build orders for sub-assemblies | No |
|
||||
| Events | [Auto Issue Orders](./auto_issue.md) | Automatically issue pending orders when target date is reached | No |
|
||||
|
||||
27
docs/docs/plugins/builtin/parameter_exporter.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Parameter Exporter
|
||||
---
|
||||
|
||||
## Parameter Exporter
|
||||
|
||||
The **Parameter Exporter** plugin provides custom export functionality for models which support custom [Parameter](../../concepts/parameters.md) data.
|
||||
|
||||
It utilizes the [ExporterMixin](../mixins/export.md) mixin to provide a custom export format for part parameter data.
|
||||
|
||||
In addition to the standard exported fields, this plugin also exports all associated parameter data for each row of the export.
|
||||
|
||||
### Activation
|
||||
|
||||
This plugin is a *mandatory* plugin, and is always enabled.
|
||||
|
||||
### Plugin Settings
|
||||
|
||||
This plugin has no configurable settings.
|
||||
|
||||
## Usage
|
||||
|
||||
This plugin is used in the same way as the [InvenTree Exporter Plugin](./inventree_exporter.md), but provides a custom export format for part parameter data.
|
||||
|
||||
When exporting parameter data, the *Parameter Exporter* plugin is available for selection in the export dialog. When selected, the plugin provides some additional export options to control the data export process.
|
||||
|
||||
{{ image("parameter_export_options.png", base="plugin/builtin", title="Parameter Export Options") }}
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
title: Part Parameter Exporter
|
||||
---
|
||||
|
||||
## Part Parameter Exporter
|
||||
|
||||
The **Part Parameter Exporter** plugin provides custom export functionality for [Part Parameter](../../part/parameter.md) data.
|
||||
|
||||
It utilizes the [ExporterMixin](../mixins/export.md) mixin to provide a custom export format for part parameter data.
|
||||
|
||||
### Activation
|
||||
|
||||
This plugin is a *mandatory* plugin, and is always enabled.
|
||||
|
||||
### Plugin Settings
|
||||
|
||||
This plugin has no configurable settings.
|
||||
|
||||
## Usage
|
||||
|
||||
This plugin is used in the same way as the [InvenTree Exporter Plugin](./inventree_exporter.md), but provides a custom export format for part parameter data.
|
||||
|
||||
When exporting part parameter data, the *Part Parameter Exporter* plugin is available for selection in the export dialog. When selected, the plugin provides some additional export options to control the data export process.
|
||||
|
||||
{{ image("parameter_export_options.png", base="plugin/builtin", title="Part Parameter Export Options") }}
|
||||
@@ -134,11 +134,11 @@ Validation of the Part IPN (Internal Part Number) field is exposed to custom plu
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
### Part Parameter Values
|
||||
### Parameter Values
|
||||
|
||||
[Part parameters](../../part/parameter.md) can also have custom validation rules applied, by implementing the `validate_part_parameter` method. A plugin which implements this method should raise a `ValidationError` with an appropriate message if the part parameter value does not match a required convention.
|
||||
[Parameters](../../concepts/parameters.md) can also have custom validation rules applied, by implementing the `validate_parameter` method. A plugin which implements this method should raise a `ValidationError` with an appropriate message if the parameter value does not match a required convention.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_part_parameter
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_parameter
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
|
||||
@@ -6,7 +6,7 @@ The InvenTree project is grateful to receive resources from various vendors free
|
||||
|
||||
Individuals and companies can also support via [GitHub sponsors](https://github.com/sponsors/inventree).
|
||||
|
||||
## Current supporters
|
||||
## Current Supporters
|
||||
- [DigitalOcean](https://inventree.org/digitalocean) - Cloud hosting provider, used to host the InvenTree demo instance
|
||||
- [Crowdin](https://crowdin.com/) - Translation platform, used to manage the [InvenTree translations](../develop/contributing.md#translations) across backend, frontend and app
|
||||
- [SonarQube Cloud](https://sonarcloud.io/) - Code quality and security analysis, used to track the code quality of the various components
|
||||
@@ -15,7 +15,8 @@ Individuals and companies can also support via [GitHub sponsors](https://github.
|
||||
- [Netlify](https://www.netlify.com/) - Static site hosting provider, used to test deploy the frontend and website
|
||||
- [Depot](https://depot.dev/?utm_source=inventree) - Docker build accelerator, used to build the multi-arch images for the InvenTree docker image
|
||||
|
||||
## Past supporters
|
||||
Non cromprehensive list of past supporters. The project stops consuming resources for various reasons, this does not mean they are not good resources.
|
||||
## Past Supporters
|
||||
|
||||
Non comprehensive list of past supporters. The project stops consuming resources for various reasons, this does not mean they are not good resources.
|
||||
- [Coveralls](https://coveralls.io/) - Code coverage as a service
|
||||
- [Deepsource](https://deepsource.io/) - Code quality and security analysis
|
||||
|
||||
@@ -273,7 +273,7 @@ To convert a currency value from one currency to another, use the `convert_curre
|
||||
!!! info "Data Types"
|
||||
The `money` parameter must be `Money` class instance. If not, an error will be raised.
|
||||
|
||||
#### create_currency
|
||||
### create_currency
|
||||
|
||||
Create a `currency` instance using the `create_currency` helper function. This returns a `Money` class instance based on the provided amount and currency type.
|
||||
|
||||
@@ -286,12 +286,13 @@ Create a `currency` instance using the `create_currency` helper function. This r
|
||||
|
||||
Simple mathematical operators are available, as demonstrated in the example template below. These operators can be used to perform basic arithmetic operations within the report template.
|
||||
|
||||
### Input Types
|
||||
|
||||
These mathematical functions accept inputs of various input types, and attempt to perform the operation accordingly. Note that any inputs which are provided as strings or numbers will be converted to `Decimal` class types before the operation is performed.
|
||||
!!! info "Input Types"
|
||||
These mathematical functions accept inputs of various input types, and attempt to perform the operation accordingly. Note that any inputs which are provided as strings or numbers will be converted to `Decimal` class types before the operation is performed.
|
||||
|
||||
### add
|
||||
|
||||
Add two numbers together using the `add` helper function:
|
||||
|
||||
::: report.templatetags.report.add
|
||||
options:
|
||||
show_docstring_description: false
|
||||
@@ -299,6 +300,8 @@ These mathematical functions accept inputs of various input types, and attempt t
|
||||
|
||||
### subtract
|
||||
|
||||
Subtract one number from another using the `subtract` helper function:
|
||||
|
||||
::: report.templatetags.report.subtract
|
||||
options:
|
||||
show_docstring_description: false
|
||||
@@ -306,6 +309,8 @@ These mathematical functions accept inputs of various input types, and attempt t
|
||||
|
||||
### multiply
|
||||
|
||||
Multiply two numbers together using the `multiply` helper function:
|
||||
|
||||
::: report.templatetags.report.multiply
|
||||
options:
|
||||
show_docstring_description: false
|
||||
@@ -313,6 +318,8 @@ These mathematical functions accept inputs of various input types, and attempt t
|
||||
|
||||
### divide
|
||||
|
||||
Divide one number by another using the `divide` helper function:
|
||||
|
||||
::: report.templatetags.report.divide
|
||||
options:
|
||||
show_docstring_description: false
|
||||
@@ -320,6 +327,8 @@ These mathematical functions accept inputs of various input types, and attempt t
|
||||
|
||||
### modulo
|
||||
|
||||
Perform a modulo operation using the `modulo` helper function:
|
||||
|
||||
::: report.templatetags.report.modulo
|
||||
options:
|
||||
show_docstring_description: false
|
||||
@@ -536,11 +545,11 @@ You can add asset images to the reports and labels by using the `{% raw %}{% ass
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
## Part Parameters
|
||||
## Parameters
|
||||
|
||||
If you need to load a part parameter for a particular Part, within the context of your template, you can use the `part_parameter` template tag:
|
||||
If you need to load a parameter value for a particular model instance, within the context of your template, you can use the `parameter` template tag:
|
||||
|
||||
::: report.templatetags.report.part_parameter
|
||||
::: report.templatetags.report.parameter
|
||||
options:
|
||||
show_docstring_description: false
|
||||
show_source: False
|
||||
@@ -553,7 +562,7 @@ The following example assumes that you have a report or label which contains a v
|
||||
{% raw %}
|
||||
{% load report %}
|
||||
|
||||
{% part_parameter part "length" as length %}
|
||||
{% parameter part "length" as length %}
|
||||
|
||||
Part: {{ part.name }}<br>
|
||||
Length: {{ length.data }} [{{ length.units }}]
|
||||
@@ -561,7 +570,7 @@ Length: {{ length.data }} [{{ length.units }}]
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
A [Part Parameter](../part/parameter.md) has the following available attributes:
|
||||
A [Parameter](../concepts/parameters.md) has the following available attributes:
|
||||
|
||||
| Attribute | Description |
|
||||
| --- | --- |
|
||||
@@ -569,7 +578,7 @@ A [Part Parameter](../part/parameter.md) has the following available attributes:
|
||||
| Description | The *description* of the parameter |
|
||||
| Data | The *value* of the parameter (e.g. "123.4") |
|
||||
| Units | The *units* of the parameter (e.g. "km") |
|
||||
| Template | A reference to a [PartParameterTemplate](../part/parameter.md#parameter-templates) |
|
||||
| Template | A reference to a [ParameterTemplate](../concepts/parameters.md#parameter-templates) |
|
||||
|
||||
## Rendering Markdown
|
||||
|
||||
|
||||
@@ -44,5 +44,45 @@ For example, rendering the name of a part (which is available in the particular
|
||||
</p></i>
|
||||
{% endraw %}
|
||||
```
|
||||
#### Rendering a single report vs. multiple report from selection
|
||||
Users can select multiple items such as `part`, `stockItem`,...etc to render from a report template. By default, the `merge` attribute of report template is disabled, which means an independent report will be generated for each item in the list of selected items. If `merge` is enabled, all selected items will be available in the `instances` context variable of the report template. Users are free to access them by indexing or in a loop. For more details, visit [context variable](./context_variables.md)
|
||||
|
||||
## Merging Reports
|
||||
|
||||
When rendering reports for multiple items, the default behaviour is that each item is rendered as a separate report. The chosen templeate is rendered multiple times, once for each item selected, and expects a single item in the context variable.
|
||||
|
||||
However, it is possible to merge multiple items into a single report document. This is achieved by enabling the `merge` attribute of the report template:
|
||||
|
||||
{{ image("report/report_merge.png", alt="Report Merge Option") }}
|
||||
|
||||
When the `merge` is enabled, all selected items are passed to the report template in the `instances` context variable, which is a list of all selected items. The user can then iterate over this list to render multiple items in a single report document.
|
||||
|
||||
### Instance Context
|
||||
|
||||
When rendering a single template against multiple *instances* of a particular model (e.g. multiple parts, multiple stock items, etc), each instance being rendered has its own unique context data.
|
||||
|
||||
Each "instance" is provided to the report template as a dictionary of context variables, which can be accessed using standard django template syntax.
|
||||
|
||||
Refer to the [context variable documentation](./context_variables.md) for more information about the available context variables for each model type.
|
||||
|
||||
The `instances` variable is provided as a list of all selected items, where each item in the list is a dictionary of context variables for that particular instance. Within the report template, the user can iterate over the `instances` list to render each item in turn.
|
||||
|
||||
### Example
|
||||
|
||||
As an example, let's consider a report template where we are printing multiple parts in a single report document.
|
||||
|
||||
When the `merge` option is enabled, the report template is provided with an `instances` variable, which is a list of all selected parts.
|
||||
|
||||
Each *instance* in the `instances` list is a dictionary of context variables for that particular part, which conforms to the standard [part context structure](./context_variables.md#part).
|
||||
|
||||
```django
|
||||
{% raw %}
|
||||
|
||||
{% for instance in instances %}
|
||||
Part Name: {{ instance.part.name }} <br>
|
||||
IPN: {{ instance.part.IPN }} <br>
|
||||
Description: {{ instance.part.description }} <br>
|
||||
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
!!! tip "Instance Prefix"
|
||||
Note that the context variable is prefixed with `instance.` when accessing variables for each item in the `instances` list.
|
||||
|
||||
@@ -13,6 +13,9 @@ We use the powerful [WeasyPrint](https://weasyprint.org/) PDF generation engine
|
||||
|
||||
Templates are rendered using standard HTML / CSS - if you are familiar with web page layout, you're ready to go!
|
||||
|
||||
HTML form elements (`input`, `select`, `textarea`, `button`) are converted into PDF form controls. Use CSS to limit which elements are converted; refer to the
|
||||
[WeasyPrint docs](https://doc.courtbouillon.org/weasyprint/stable/common_use_cases.html#include-pdf-forms) for further information.
|
||||
|
||||
### Template Language
|
||||
|
||||
Uploaded report template files are passed through the [django template rendering framework]({% include "django.html" %}/topics/templates/), and as such accept the same variable template strings as any other django template file. Different variables are passed to the report template (based on the context of the report) and can be used to customize the contents of the generated PDF.
|
||||
|
||||
@@ -4,7 +4,7 @@ title: InvenTree Multi Factor Authentication
|
||||
|
||||
## Multi Factor Authentication
|
||||
|
||||
InvenTree gives the option to use TOTP or statically generated backup tokens as an additional factor to password or SSO authentication. This is a widely adopted security feature on enterprise web services. We highly encourage to enable it if you expose your instance to the public internet.
|
||||
InvenTree gives the option to use [TOTP](https://en.wikipedia.org/wiki/Time-based_One-Time_Password) or statically generated backup tokens as an additional factor to password or SSO authentication. This is a widely adopted security feature on enterprise web services. We highly encourage to enable it if you expose your instance to the public internet.
|
||||
|
||||
As TOTP is an [open standard](https://datatracker.ietf.org/doc/html/rfc6238) there are a lot of different ways to hold your key and generate the time based tokens needed for authentication. That ranges from physical devices to password managers and mobile apps. We do not advertise any method but recommend to keep password and token generator separate from each other.
|
||||
|
||||
@@ -16,4 +16,4 @@ To make MFA mandatory for all users:
|
||||
|
||||
### Security Consideration
|
||||
|
||||
A user can lock themself out if they lose access to both the device with their TOTP app and their backup tokens. An admin can delete their tokens from the admin pages (they exist under the 'TOTP devices' / 'static devices' models) . This should be a last resort and only done by people knowledgeable about the [admin pages](../settings/admin.md) as changes there might circumvent InvneTrees business and security logic.
|
||||
A user can lock themselves out if they lose access to both the device with their TOTP app and their backup tokens. An admin can delete their tokens from the admin pages (they exist under the 'TOTP devices' / 'static devices' models) . This should be a last resort and only done by people knowledgeable about the [admin pages](../settings/admin.md) as changes there might circumvent InvenTree's business and security logic.
|
||||
|
||||
@@ -9,7 +9,7 @@ InvenTree can be configured to send emails to users, for various purposes.
|
||||
To enable this, email configuration settings must be supplied to the InvenTree [configuration options](../start/config.md#email-settings).
|
||||
|
||||
!!! info "Functionality might be degraded"
|
||||
Multiple functions of InvenTree require functioning email delivery, including *Password Reset*, *Notififications*, *Update Infos*
|
||||
Multiple functions of InvenTree require functioning email delivery, including *Password Reset*, *Notifications*, *Update Infos*
|
||||
|
||||
### Outgoing
|
||||
|
||||
|
||||
@@ -83,6 +83,24 @@ This is a security measure to prevent plugins from changing the core functionali
|
||||
|
||||
An error occurred when discovering or initializing a machine type from a plugin. This likely indicates a faulty or incompatible plugin.
|
||||
|
||||
#### INVE-E13
|
||||
|
||||
**Error reading InvenTree configuration file**
|
||||
|
||||
An error occurred while reading the InvenTree configuration file. This might be caused by a syntax error or invalid value in the configuration file.
|
||||
|
||||
#### INVE-E14
|
||||
|
||||
**Could not import Django**
|
||||
|
||||
Django is not installed in the current Python environment. This means that the InvenTree backend is not running within the correct [python virtual environment](../start/index.md#virtual-environment) or that the required Python packages were not installed correctly.
|
||||
|
||||
#### INVE-E15
|
||||
|
||||
**Python version not supported**
|
||||
|
||||
This error occurs attempting to run InvenTree on a version of Python which is older than the minimum required version. We [require Python {{ config.extra.min_python_version }} or newer](../start/index.md#python-requirements)
|
||||
|
||||
### INVE-W (InvenTree Warning)
|
||||
Warnings - These are non-critical errors which should be addressed when possible.
|
||||
|
||||
@@ -153,7 +171,7 @@ The warning text will show the recommended command for intended use.
|
||||
#### INVE-W10
|
||||
**Config not in recommended directory - Backend**
|
||||
|
||||
A configuration file is not in the recommended directory. This might lead to issues with the deployment method you are using. It might also lead to confusinon.
|
||||
A configuration file is not in the recommended directory. This might lead to issues with the deployment method you are using. It might also lead to confusion.
|
||||
|
||||
The warning text will show the recommended directory for your deployment method.
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ If this setting is enabled, users can reset their password via email. This requi
|
||||
|
||||
If this setting is enabled, users must have multi-factor authentication enabled to log in.
|
||||
|
||||
#### Auto Fil SSO Users
|
||||
#### Auto Fill SSO Users
|
||||
|
||||
Automatically fill out user-details from SSO account-data. If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over.
|
||||
|
||||
@@ -174,11 +174,14 @@ Configuration of label printing:
|
||||
{{ globalsetting("PART_COPY_TESTS") }}
|
||||
{{ globalsetting("PART_CATEGORY_PARAMETERS") }}
|
||||
{{ globalsetting("PART_CATEGORY_DEFAULT_ICON") }}
|
||||
{{ globalsetting("PART_PARAMETER_ENFORCE_UNITS") }}
|
||||
|
||||
#### Part Parameter Templates
|
||||
#### Parameter Templates
|
||||
|
||||
Refer to the section describing [how to create part parameter templates](../part/parameter.md#create-template).
|
||||
| Name | Description | Default | Units |
|
||||
| ---- | ----------- | ------- | ----- |
|
||||
{{ globalsetting("PARAMETER_ENFORCE_UNITS") }}
|
||||
|
||||
For more information on parameters, refer to the [parameter documentation](../concepts/parameters.md).
|
||||
|
||||
### Categories
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ The *Display Settings* screen shows general display configuration options:
|
||||
{{ usersetting("STICKY_HEADER") }}
|
||||
{{ usersetting("STICKY_TABLE_HEADER") }}
|
||||
{{ usersetting("SHOW_SPOTLIGHT") }}
|
||||
{{ usersetting("BARCODE_IN_FORM_FIELDS") }}
|
||||
{{ usersetting("DATE_DISPLAY_FORMAT") }}
|
||||
{{ usersetting("FORMS_CLOSE_USING_ESCAPE") }}
|
||||
{{ usersetting("DISPLAY_STOCKTAKE_TAB") }}
|
||||
|
||||
@@ -143,7 +143,7 @@ pip install django-storages[google]
|
||||
You will need to change the storage backend, which is set via the `INVENTREE_BACKUP_STORAGE` environment variable, or via `backup_storage` in the configuration file:
|
||||
|
||||
```yaml
|
||||
backup_stoage: storages.backends.gcloud.GoogleCloudStorage
|
||||
backup_storage: storages.backends.gcloud.GoogleCloudStorage
|
||||
```
|
||||
|
||||
### Configure Backend Options
|
||||
|
||||
@@ -92,7 +92,9 @@ The following debugging / logging options are available:
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_DEBUG | debug | Enable [debug mode](./index.md#debug-mode) | False |
|
||||
| INVENTREE_DEBUG_QUERYCOUNT | debug_querycount | Enable [query count logging](https://github.com/bradmontgomery/django-querycount) in the terminal | False |
|
||||
| INVENTREE_DEBUG_QUERYCOUNT | debug_querycount | Enable support for [django-querycount](../develop/index.md#django-querycount) middleware. | False |
|
||||
| INVENTREE_DEBUG_SILK | debug_silk | Enable support for [django-silk](../develop/index.md#django-silk) profiling tool. | False |
|
||||
| INVENTREE_DEBUG_SILK_PROFILING | debug_silk_profiling | Enable detailed profiling in django-silk | False |
|
||||
| INVENTREE_DB_LOGGING | db_logging | Enable logging of database messages | False |
|
||||
| INVENTREE_LOG_LEVEL | log_level | Set level of logging to terminal | WARNING |
|
||||
| INVENTREE_JSON_LOG | json_log | log as json | False |
|
||||
@@ -103,13 +105,7 @@ The following debugging / logging options are available:
|
||||
|
||||
Enabling the `INVENTREE_DEBUG` setting will turn on [Django debug mode]({% include "django.html" %}/ref/settings/#debug). This mode is intended for development purposes, and should not be enabled in a production environment. Read more about [InvenTree debug mode](./index.md#debug-mode).
|
||||
|
||||
### Query Count Logging
|
||||
|
||||
Enabling the `INVENTREE_DEBUG_QUERYCOUNT` setting will log the number of database queries executed for each page load. This can be useful for identifying performance bottlenecks in the InvenTree server. Note that this setting is only available if `INVENTREE_DEBUG` is also enabled.
|
||||
|
||||
### Database Logging
|
||||
|
||||
Enabling the `INVENTREE_DB_LOGGING` setting will log all database queries to the terminal. This can be useful for debugging database-related issues.
|
||||
In debug mode, there are additional [profiling tools](../develop/index.md#profiling-tools) available to help developers analyze and optimize performance.
|
||||
|
||||
## Server Access
|
||||
|
||||
@@ -266,6 +262,9 @@ The following database options can be configured:
|
||||
| INVENTREE_DB_HOST | database.HOST | Database host address (if required) | *Not specified* |
|
||||
| INVENTREE_DB_PORT | database.PORT | Database host port (if required) | *Not specified* |
|
||||
|
||||
!!! tip "Database Password"
|
||||
The value specified for `INVENTREE_DB_PASSWORD` should not contain comma `,` or colon `:` characters, otherwise the connection to the database may fail.
|
||||
|
||||
### PostgreSQL Settings
|
||||
|
||||
If running with a PostgreSQL database backend, the following additional options are available:
|
||||
@@ -318,6 +317,9 @@ The following cache settings are available:
|
||||
| INVENTREE_CACHE_KEEPALIVE_INTERVAL | cache.keepalive_interval | Cache keepalive interval | 1 |
|
||||
| INVENTREE_CACHE_USER_TIMEOUT | cache.user_timeout | Cache user timeout | 1000 |
|
||||
|
||||
!!! tip "Cache Password"
|
||||
The value specified for `INVENTREE_CACHE_PASSWORD` should not contain comma `,` or colon `:` characters, otherwise the connection to the cache server may fail.
|
||||
|
||||
## Email Settings
|
||||
|
||||
To enable [email functionality](../settings/email.md), email settings must be configured here, either via environment variables or within the configuration file.
|
||||
@@ -336,6 +338,10 @@ The following email settings are available:
|
||||
| INVENTREE_EMAIL_SENDER | email.sender | Sending email address | *Not specified* |
|
||||
| INVENTREE_EMAIL_PREFIX | email.prefix | Prefix for subject text | [InvenTree] |
|
||||
|
||||
### Email Backend
|
||||
|
||||
The default email implementation uses the Django STMP backend. This should be sufficient for most implementations, although other backends can be used if required. Note that selection of a different backend requires must use fully qualified module path, and requires advanced knowledge.
|
||||
|
||||
### Sender Email
|
||||
|
||||
The "sender" email address is the address from which InvenTree emails are sent (by default) and must be specified for outgoing emails to function:
|
||||
@@ -425,7 +431,7 @@ InvenTree provides allowance for additional sign-in options. The following optio
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_MFA_ENABLED | mfa_enabled | Enable or disable multi-factor authentication support for the InvenTree server | True |
|
||||
| INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp |
|
||||
| INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp,webauthn |
|
||||
|
||||
### Single Sign On
|
||||
|
||||
@@ -482,7 +488,7 @@ The INVENTREE_CUSTOMIZE environment variable must contain a json object with the
|
||||
the wanted values. Example:
|
||||
|
||||
```
|
||||
INVENTREE_CUSTOMIZE={"login_message":"Hallo Michi"}
|
||||
INVENTREE_CUSTOMIZE={"login_message":"Hello World"}
|
||||
```
|
||||
|
||||
This example sets a login message. Take care of the double quotes.
|
||||
|
||||
@@ -235,6 +235,21 @@ The [Caddy](./docker.md#ssl-certificates) container will automatically generate
|
||||
|
||||
Any persistent files generated by the Caddy container (such as certificates, etc) will be stored in the `caddy` directory within the external volume.
|
||||
|
||||
### Web Server Bind Address
|
||||
|
||||
By default, the Dockerized InvenTree web server binds to all available network interfaces and listens for IPv4 traffic on port 8000.
|
||||
This can be adjusted using the following environment variables:
|
||||
|
||||
| Environment Variable | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_WEB_ADDR | 0.0.0.0 |
|
||||
| INVENTREE_WEB_PORT | 8000 |
|
||||
|
||||
These variables are combined in the [Dockerfile]({{ sourcefile("contrib/container/Dockerfile") }}) to build the bind string passed to the InvenTree server on startup.
|
||||
|
||||
!!! tip "IPv6 Support"
|
||||
To enable IPv6/Dual Stack support, set `INVENTREE_WEB_ADDR` to `[::]` when you create/start the container.
|
||||
|
||||
### Demo Dataset
|
||||
|
||||
To quickly get started with a [demo dataset](../demo.md), you can run the following command:
|
||||
|
||||
@@ -123,11 +123,14 @@ By default, a production InvenTree installation is configured to run with [DEBUG
|
||||
|
||||
Running in DEBUG mode provides many handy development features, however it is strongly recommended *NOT* to run in DEBUG mode in a production environment. This recommendation is made because DEBUG mode leaks a lot of information about your installation and may pose a security risk.
|
||||
|
||||
So, for a production setup, you should set `INVENTREE_DEBUG=false` in the [configuration options](./config.md).
|
||||
So, for a production setup, you should ensure that `INVENTREE_DEBUG=false` in the [configuration options](./config.md).
|
||||
|
||||
!!! warning "Security Risk"
|
||||
Running InvenTree in DEBUG mode in a production environment is a significant security risk, and should be avoided at all costs.
|
||||
|
||||
### Turning Debug Mode off
|
||||
|
||||
When running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that when DEBUG mode is *disabled*, the proxy setup has to be configured to handle this.
|
||||
When running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that when DEBUG mode is *disabled* (which is the default for a production setup), the proxy setup has to be configured to handle this.
|
||||
|
||||
!!! info "Read More"
|
||||
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details
|
||||
|
||||
@@ -108,6 +108,27 @@ The Python packages required by the InvenTree server must be installed into the
|
||||
|
||||
```
|
||||
pip install --upgrade --ignore-installed invoke
|
||||
```
|
||||
|
||||
#### Install Python Bindings
|
||||
|
||||
Depending on your database the python bindings must also be installed (into your virtual environment).
|
||||
|
||||
For PostgreSQL install:
|
||||
|
||||
```
|
||||
pip3 install psycopg pgcli
|
||||
```
|
||||
|
||||
For MySQL install:
|
||||
|
||||
```
|
||||
pip3 install mysqlclient mariadb
|
||||
```
|
||||
|
||||
If all packages have been installed run:
|
||||
|
||||
```
|
||||
invoke install
|
||||
```
|
||||
|
||||
@@ -155,14 +176,6 @@ grant all privileges on database inventree to myuser;
|
||||
!!! info "Username / Password"
|
||||
You should change the username and password from the values specified above. This username and password will also be for the InvenTree database connection configuration.
|
||||
|
||||
#### Install Python Bindings
|
||||
|
||||
The PostgreSQL python binding must also be installed (into your virtual environment):
|
||||
|
||||
```
|
||||
pip3 install psycopg pgcli
|
||||
```
|
||||
|
||||
#### Install Postgresql client
|
||||
|
||||
If PostgreSQL and InvenTree are installed on separate servers / containers the PostgreSQL client has to be installed also where InvenTree is running.
|
||||
@@ -184,14 +197,6 @@ To run InvenTree with the MySQL or MariaDB backends, a number of extra packages
|
||||
sudo apt-get install mysql-server libmysqlclient-dev
|
||||
```
|
||||
|
||||
#### Install Python Bindings
|
||||
|
||||
Install the python bindings for MySQL (into the python virtual environment).
|
||||
|
||||
```
|
||||
pip3 install mysqlclient mariadb
|
||||
```
|
||||
|
||||
#### Create Database
|
||||
|
||||
Assuming the MySQL server is installed and running, login to the MySQL server as follows:
|
||||
|
||||
@@ -114,3 +114,86 @@ Once the migration process completes, the database records are now updated! Rest
|
||||
|
||||
!!! tip "Example: Docker"
|
||||
If running under docker, run `docker compose up -d`
|
||||
|
||||
## Migrating Between Incompatible Database Versions
|
||||
|
||||
There may be occasions when InvenTree data needs to be migrated between two database installations running *incompatible* versions of the database software. For example, InvenTree may be running on a Postgres database running on version 12, and the administrator wishes to migrate the data to a Postgres version 17 database.
|
||||
|
||||
!!! warning "Advanced Procedure"
|
||||
The following procedure is *advanced*, and should only be attempted by experienced database administrators. Always ensure that database backups are made before attempting any migration procedure.
|
||||
|
||||
Due to inherit incompatibilities between different major versions of database software, it is not always possible to directly migrate data between two different database versions. In such cases, the following procedure can be used as a workaround.
|
||||
|
||||
!!! warning "InvenTree Version"
|
||||
It is *crucial* that both InvenTree database installations are running the same version of InvenTree software! If this is not the case, data migration may fail, and there is a possibility that data corruption can occur. Ensure that the original database is up to date, by running `invoke update`.
|
||||
|
||||
The following instructions assume that the source (old) database is Postgres version 15, and the target (new) database is Postgres version 17. Additionally, it assumes that the InvenTree installation is running under [docker / docker compose](./docker.md), for simplicity. Adjust commands as required for other InvenTree configurations or database software.
|
||||
|
||||
The overall procedure is as follows:
|
||||
|
||||
### Backup Old Database
|
||||
|
||||
Run the following command to create a backup dump of the old database:
|
||||
|
||||
```
|
||||
docker compose run --rm inventree-server invoke backup
|
||||
```
|
||||
|
||||
This will create a database backup file in the InvenTree backup directory.
|
||||
|
||||
!!! tip "Secondary Backup"
|
||||
It may be prudent to create a secondary backup of the database, separate to the one created by InvenTree.
|
||||
|
||||
### Shutdown Old Database
|
||||
|
||||
Stop the old InvenTree installation, to ensure that the database is not being accessed during the migration process:
|
||||
|
||||
```
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Remove Old Database Files
|
||||
|
||||
The raw database files are incompatible between different major versions of Postgres. Thus, the old database files must be removed before starting the new database.
|
||||
|
||||
!!! warning "Data Loss"
|
||||
Ensure that a complete backup of the old database has been made before proceeding! Removing the database files will result in data loss if a backup does not exist.
|
||||
|
||||
```
|
||||
rm -rf ./path/to/database/*
|
||||
```
|
||||
|
||||
!!! info "Database Location"
|
||||
The location of the database files depends on how InvenTree was configured.
|
||||
|
||||
### Start New Database
|
||||
|
||||
Update the InvenTree docker configuration to use the new version of Postgres (e.g. `postgres:17`), and then start the InvenTree installation:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will initialize a new, empty database using the new version of Postgres.
|
||||
|
||||
### Run Database Migrations
|
||||
|
||||
Run the database migration process to ensure that the new database schema is correctly initialized:
|
||||
|
||||
```
|
||||
docker compose run --rm inventree-server invoke update
|
||||
```
|
||||
|
||||
### Restore Database Backup
|
||||
|
||||
Finally, restore the database backup created earlier into the new database:
|
||||
|
||||
```
|
||||
docker compose run --rm inventree-server invoke restore
|
||||
```
|
||||
|
||||
This will load the database records from the backup file into the new database.
|
||||
|
||||
### Caveats
|
||||
|
||||
The process described here is a *suggested* procedure for migrating between incompatible database versions. However, due to the complexity of database software, there may be unforeseen complications that arise during the process.
|
||||
|
||||
88
docs/docs/stock/availability.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Stock Availability
|
||||
---
|
||||
|
||||
## Stock Availability
|
||||
|
||||
Each stock item represents a physical quantity of a particular part in a specific location. Given the complexities of tracking stock across multiple locations, reservations, and allocations, it is important to understand whether a stock item is actually available for use.
|
||||
|
||||
There are multiple terms used in InvenTree to describe stock availability:
|
||||
|
||||
### Required
|
||||
|
||||
The *Required* quantity indicates the total number of a certain [part](../part/index.md) that is needed to fulfill all current orders, builds, or other commitments. This is independent of the actual stock levels.
|
||||
|
||||
### In Stock
|
||||
|
||||
The *In Stock* quantity represents the total number of items physically present in the stock location, regardless of their allocation or reservation status.
|
||||
|
||||
For example, if a stock item has a quantity of 100, it means there are 100 units of that part physically present in the specified location.
|
||||
|
||||
### Allocated
|
||||
|
||||
The *Allocated* quantity indicates the number of stock items that have been reserved for specific orders, builds, or other commitments. This number is specified against each stock item when it is allocated.
|
||||
|
||||
Note that the *Allocated* quantity is always less than or equal to the *In Stock* quantity.
|
||||
|
||||
For example, if a stock item has an *In Stock* quantity of 100, and 30 units have been allocated to fulfill orders, then the *Allocated* quantity is 30. In this case the *In Stock* quantity remains 100, until the alloacted quantity is actually consumed.
|
||||
|
||||
### Available
|
||||
|
||||
The *Available* quantity for a given stock item is the difference between the *In Stock* quantity and the *Allocated* quantity.
|
||||
|
||||
For example, if a stock item has an *In Stock* quantity of 100, and 30 units have been allocated, then the *Available* quantity is 70.
|
||||
|
||||
## Consumed or Depleted Stock
|
||||
|
||||
Once allocated stock quantities are consumed (e.g. when fulfilling an order or completing a build), the *In Stock* quantity of the stock item is reduced accordingly. This means that the *Available* quantity is also reduced.
|
||||
|
||||
### Delete on Deplete
|
||||
|
||||
Stock items can be designated with the `Delete on Deplete` flag. When this flag is set, if the stock quantity of the item reaches zero, the stock item will be automatically deleted from the system.
|
||||
|
||||
## Part Stock Levels
|
||||
|
||||
There are multiple ways to view the overall stock levels for a given part across all stock items and locations.
|
||||
|
||||
### Stock Overview
|
||||
|
||||
Each [part](../part/index.md) page displays an overview of the stocktotal stock levels across all locations. This page shows the total *In Stock*, *Allocated*, and *Available* quantities for the part, aggregated across all stock items:
|
||||
|
||||
{{ image("stock/stock_overview.png", title="Stock overview") }}
|
||||
|
||||
In the example above, the "Red Widget" part has the following stock levels:
|
||||
|
||||
| Metric | Quantity | Description |
|
||||
| ------ | -------- | ----------- |
|
||||
| In Stock | 138 | There are 138 physical units of the Red Widget part across all stock locations. |
|
||||
| Available | 123 | Only 123 units are available for allocation, as 15 units have already been allocated to fulfill orders. |
|
||||
| Required | 657 | A total of 657 units of the Red Widget part are needed to fulfill all current orders and builds. |
|
||||
| Deficit | 519 | There is a deficit of 519 units, meaning that the current available stock is insufficient to meet the required quantity. |
|
||||
|
||||
### Stock Details
|
||||
|
||||
The *Part Detail* view displays additional information regarding the stock levels for the part:
|
||||
|
||||
{{ image("stock/stock_detail.png", title="Stock detail") }}
|
||||
|
||||
Here we can see that:
|
||||
|
||||
- There are 138 units of the Red Widget part physically in stock across all locations.
|
||||
- Of this quantity, 15 units have been allocated to fulfill orders, leaving 123 units available for allocation.
|
||||
- A total of 657 units are required to fulfill all current orders and builds.
|
||||
- 645 units are required to fulfil outstanding build orders, of which 15 units have already been allocated.
|
||||
- 12 units are required to fulfil outstanding sales orders, none of which have been allocated yet.
|
||||
|
||||
### Allocations Tab
|
||||
|
||||
To view further details regarding which stock items have been allocated, the *Allocations* tab on the part view can be used.
|
||||
|
||||
This tab displays a complete overview of where the stock items for this part are allocated, against any open orders:
|
||||
|
||||
#### Build Order Allocations
|
||||
|
||||
{{ image("stock/build_order_allocations.png", title="Build order allocations") }}
|
||||
|
||||
#### Sales Order Allocations
|
||||
|
||||
{{ image("stock/sales_order_allocations.png", title="Sales order allocations") }}
|
||||
@@ -26,11 +26,15 @@ The *Stock Item* detail view shows information regarding the particular stock it
|
||||
|
||||
**Status** - Status of this stock item
|
||||
|
||||
### Stock Availability
|
||||
|
||||
InvenTree has a number of different mechanisms to determine whether stock is available for use. See the [Stock Availability](./availability.md) page for more information.
|
||||
|
||||
### Stock Tracking
|
||||
|
||||
Every time a *Stock Item* is adjusted, a *Stock Tracking* entry is automatically created. This ensures a complete history of the *Stock Item* is maintained as long as the item is in the system.
|
||||
|
||||
Each stock tracking historical item records the user who performed the action.
|
||||
Each stock tracking historical item records the user who performed the action. [Read more about stock tracking here](./tracking.md).
|
||||
|
||||
## Stock Location
|
||||
|
||||
@@ -49,12 +53,10 @@ If there are some icons missing in the tabler icons package, users can even inst
|
||||
A stock location type represents a specific type of location (e.g. one specific size of drawer, shelf, ... or box) which can be assigned to multiple stock locations. In the first place, it is used to specify an icon and having the icon in sync for all locations that use this location type, but it also serves as a data field to quickly see what type of location this is. It is planned to add e.g. drawer dimension information to the location type to add a "find a matching, empty stock location" tool.
|
||||
|
||||
## External Stock Location
|
||||
An external stock location can be used to indicate that items in there might not be available
|
||||
for immediate usage. Stock items in an external location are marked with an additional icon
|
||||
|
||||
It may be useful to mark certain stock locations as *external*. An external stock location can be used to indicate that items in there might not be available for immediate usage. Stock items in an external location are marked with an additional icon
|
||||
in the build order line items view where the material is allocated.
|
||||
|
||||
{{ image("stock/stock_external_icon.png", title="External stock indication") }}
|
||||
|
||||
Anyhow there is no limitation on the stock item. It can be allocated as usual.
|
||||
|
||||
The external flag does not get inherited to sublocations.
|
||||
|
||||
@@ -132,6 +132,17 @@ If a provided serial number (or group of numbers) is not considered valid, an er
|
||||
!!! tip "Serial Number Validation"
|
||||
Custom serial number validation can be implemented using an external plugin
|
||||
|
||||
#### Adjusting Serial Numbers
|
||||
|
||||
Once a stock item has been created with a serial number, it is possible to adjust that serial number value if required. This can be achieved by navigating to the stock item's detail page, and selecting the "Edit" option from the actions menu. Here, the serial number value can be modified as required:
|
||||
|
||||
{{ image("stock/serial_edit.png", title="Editing a serial number") }}
|
||||
|
||||
Note that any serial number adjustments are subject to the same validation rules as when the stock item was created. If the new serial number value is not valid, an error message will be displayed to the user:
|
||||
|
||||
{{ image("stock/serial_edit_error.png", title="Error while editing a serial number") }}
|
||||
|
||||
|
||||
#### Plugin Support
|
||||
|
||||
Custom serial number functionality, with any arbitrary requirements or level of complexity, can be implemented using the [Validation Plugin Mixin class](../plugins/mixins/validation.md#serial-numbers). Refer to the documentation for this plugin for technical details.
|
||||
|
||||
@@ -9,6 +9,12 @@ If you are struggling with an issue which is not covered in the FAQ above, pleas
|
||||
|
||||
Even if you cannot immediately resolve the issue, the information below will be very useful when reporting the issue on GitHub.
|
||||
|
||||
## Error Codes
|
||||
|
||||
InvenTree uses a system of error codes to help identify specific issues. Each error code is prefixed with `INVE-`, followed by a letter indicating the error type, and a number.
|
||||
|
||||
Refer to the [error code documentation](./settings/error_codes.md) for more information on specific error codes.
|
||||
|
||||
## Recent Update
|
||||
|
||||
If you have recently updated your InvenTree instance, please ensure that you have followed all update instructions carefully. In particular, make sure that you have run any required database migrations using the `invoke update` command.
|
||||
|
||||
@@ -78,6 +78,7 @@ nav:
|
||||
- Demo: demo.md
|
||||
- Release Notes: releases/release_notes.md
|
||||
- Development:
|
||||
- InvenTree Development: develop/index.md
|
||||
- Contributing: develop/contributing.md
|
||||
- Architecture: develop/architecture.md
|
||||
- Roadmap: develop/roadmap.md
|
||||
@@ -96,6 +97,8 @@ nav:
|
||||
- Custom States: concepts/custom_states.md
|
||||
- Pricing: concepts/pricing.md
|
||||
- Project Codes: concepts/project_codes.md
|
||||
- Attachments: concepts/attachments.md
|
||||
- Parameters: concepts/parameters.md
|
||||
- Barcodes:
|
||||
- Barcode Support: barcodes/index.md
|
||||
- Internal Barcodes: barcodes/internal.md
|
||||
@@ -125,7 +128,6 @@ nav:
|
||||
- Virtual Parts: part/virtual.md
|
||||
- Part Views: part/views.md
|
||||
- Tracking: part/trackable.md
|
||||
- Parameters: part/parameter.md
|
||||
- Revisions: part/revision.md
|
||||
- Templates: part/template.md
|
||||
- Tests: part/test.md
|
||||
@@ -134,8 +136,9 @@ nav:
|
||||
- Notifications: part/notification.md
|
||||
- Stock:
|
||||
- Stock Items: stock/index.md
|
||||
- Stock Status: stock/status.md
|
||||
- Availability: stock/availability.md
|
||||
- Stock Tracking: stock/tracking.md
|
||||
- Stock Status: stock/status.md
|
||||
- Adjusting Stock: stock/adjust.md
|
||||
- Stock Expiry: stock/expiry.md
|
||||
- Stock Ownership: stock/owner.md
|
||||
@@ -237,7 +240,7 @@ nav:
|
||||
- Export Plugins:
|
||||
- BOM Exporter: plugins/builtin/bom_exporter.md
|
||||
- InvenTree Exporter: plugins/builtin/inventree_exporter.md
|
||||
- Parameter Exporter: plugins/builtin/part_parameter_exporter.md
|
||||
- Parameter Exporter: plugins/builtin/parameter_exporter.md
|
||||
- Stocktake Exporter: plugins/builtin/stocktake_exporter.md
|
||||
- Label Printing:
|
||||
- Label Printer: plugins/builtin/inventree_label.md
|
||||
@@ -357,10 +360,10 @@ extra:
|
||||
# provider: google
|
||||
# property: UA-143467500-1
|
||||
|
||||
min_python_version: 3.9
|
||||
min_python_version: 3.11
|
||||
min_invoke_version: 2.0.0
|
||||
django_version: 4.2
|
||||
docker_postgres_version: 16
|
||||
django_version: 5.2
|
||||
docker_postgres_version: 17
|
||||
|
||||
version:
|
||||
default: stable
|
||||
|
||||
@@ -6,5 +6,5 @@ mkdocs-redirects
|
||||
mkdocs-simple-hooks>=0.1,<1.0
|
||||
mkdocs-include-markdown-plugin
|
||||
neoteroi-mkdocs
|
||||
mkdocstrings[python]>=0.25.0,<=0.30.1
|
||||
mkdocstrings[python]>=0.25.0,<=1.0.0
|
||||
mkdocs-mermaid2-plugin
|
||||
|
||||
@@ -148,14 +148,10 @@ essentials==1.1.6 \
|
||||
--hash=sha256:3fd26923f5f2ece51a219dbb17b1fb22c9190d70fa2104919be92a6419521877 \
|
||||
--hash=sha256:a470e693d83c13369ebf1f488d60236b4ea99400f38db6b7224e2808c1369256
|
||||
# via essentials-openapi
|
||||
essentials-openapi==1.2.1 \
|
||||
--hash=sha256:70b06d80a8d9cb7634b702903cfe8fcfc48e6fc054e744eab514bb7bc37f00c9 \
|
||||
--hash=sha256:d0085cde3ceaa25e6fc4983d8372ac0185909e3fa2791f498280379bb3c86c62
|
||||
essentials-openapi==1.3.0 \
|
||||
--hash=sha256:453327a0a847a431133f4472ced7e4a9180bf667437049b57381ddf88079e886 \
|
||||
--hash=sha256:9c2a88531e2c70c565d5b526d74043941e46f60c114f7a0e3ae91e9e6bef4dae
|
||||
# via neoteroi-mkdocs
|
||||
exceptiongroup==1.3.0 \
|
||||
--hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \
|
||||
--hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88
|
||||
# via anyio
|
||||
ghp-import==2.1.0 \
|
||||
--hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
|
||||
--hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343
|
||||
@@ -197,14 +193,6 @@ idna==3.10 \
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
importlib-metadata==8.7.0 \
|
||||
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
|
||||
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
|
||||
# via
|
||||
# markdown
|
||||
# mkdocs
|
||||
# mkdocs-get-deps
|
||||
# mkdocstrings
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
@@ -342,13 +330,13 @@ mkdocs-include-markdown-plugin==7.2.0 \
|
||||
--hash=sha256:4a67a91ade680dc0e15f608e5b6343bec03372ffa112c40a4254c1bfb10f42f3 \
|
||||
--hash=sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-macros-plugin==1.4.1 \
|
||||
--hash=sha256:55a9c93871e3744cdeb0736316783d60830a6d5d97b1132364e6b491607f2332 \
|
||||
--hash=sha256:5a9e483f6056fe7ad0923802affe699233ca468672e20a9640dba237165b3240
|
||||
mkdocs-macros-plugin==1.5.0 \
|
||||
--hash=sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f \
|
||||
--hash=sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material==9.6.22 \
|
||||
--hash=sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84 \
|
||||
--hash=sha256:87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c
|
||||
mkdocs-material==9.7.0 \
|
||||
--hash=sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec \
|
||||
--hash=sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material-extensions==1.3.1 \
|
||||
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
|
||||
@@ -366,9 +354,9 @@ mkdocs-simple-hooks==0.1.5 \
|
||||
--hash=sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c \
|
||||
--hash=sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a
|
||||
# via -r docs/requirements.in
|
||||
mkdocstrings[python]==0.30.1 \
|
||||
--hash=sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82 \
|
||||
--hash=sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f
|
||||
mkdocstrings[python]==1.0.0 \
|
||||
--hash=sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a \
|
||||
--hash=sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocstrings-python
|
||||
@@ -376,9 +364,9 @@ mkdocstrings-python==1.16.12 \
|
||||
--hash=sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374 \
|
||||
--hash=sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d
|
||||
# via mkdocstrings
|
||||
neoteroi-mkdocs==1.1.3 \
|
||||
--hash=sha256:3ecfb825e898d10a6d703a3ef3f0484d823b7b5660425e76af421e316ac18036 \
|
||||
--hash=sha256:772aee317c9bb10a89d67e71e322730f92cc349d5eecc8a08e8fb079398e514b
|
||||
neoteroi-mkdocs==1.2.0 \
|
||||
--hash=sha256:58e25cb1b9db093ffa8d12bdb33264bf567cac30fb964b56e0a493efa749ad6e \
|
||||
--hash=sha256:91b6aa95a4e46c9bb93e00e021d2044cb0c7d80c0b4600434ff8f440d613a0a8
|
||||
# via -r docs/requirements.in
|
||||
packaging==25.0 \
|
||||
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
|
||||
@@ -551,12 +539,9 @@ typing-extensions==4.14.1 \
|
||||
# via
|
||||
# anyio
|
||||
# beautifulsoup4
|
||||
# exceptiongroup
|
||||
# gitpython
|
||||
# mkdocstrings-python
|
||||
urllib3==2.5.0 \
|
||||
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
||||
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
||||
urllib3==2.6.0 \
|
||||
--hash=sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f \
|
||||
--hash=sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1
|
||||
# via requests
|
||||
watchdog==6.0.0 \
|
||||
--hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \
|
||||
@@ -594,7 +579,3 @@ wcmatch==10.1 \
|
||||
--hash=sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a \
|
||||
--hash=sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af
|
||||
# via mkdocs-include-markdown-plugin
|
||||
zipp==3.23.0 \
|
||||
--hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
|
||||
--hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
|
||||
# via importlib-metadata
|
||||
|
||||
@@ -97,12 +97,12 @@ skip-magic-trailing-comma = true
|
||||
line-ending = "auto"
|
||||
|
||||
[tool.uv.pip]
|
||||
python-version = "3.9.2"
|
||||
python-version = "3.11.0"
|
||||
no-strip-extras=true
|
||||
generate-hashes=true
|
||||
|
||||
[tool.ty.src]
|
||||
root = "src/backend/InvenTree"
|
||||
[tool.ty.environment]
|
||||
root = ["src/backend/InvenTree"]
|
||||
|
||||
[tool.ty.rules]
|
||||
unresolved-reference="ignore" # 21 # see https://github.com/astral-sh/ty/issues/220
|
||||
|
||||
@@ -11,7 +11,7 @@ python:
|
||||
build:
|
||||
os: "ubuntu-22.04"
|
||||
tools:
|
||||
python: "3.9"
|
||||
python: "3.11"
|
||||
jobs:
|
||||
post_install:
|
||||
- pip install -U invoke
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
"""Middleware to require 2FA for users."""
|
||||
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allauth.account.authentication import get_authentication_records
|
||||
from allauth.mfa.utils import is_mfa_enabled
|
||||
from allauth.mfa.webauthn.internal.flows import did_use_passwordless_login
|
||||
|
||||
|
||||
def is_multifactor_logged_in(request: HttpRequest) -> bool:
|
||||
"""Check if the user is logged in with multifactor authentication."""
|
||||
authns = get_authentication_records(request)
|
||||
|
||||
return is_mfa_enabled(request.user) and (
|
||||
did_use_passwordless_login(request)
|
||||
or any(record.get('method') == 'mfa' for record in authns)
|
||||
)
|
||||
|
||||
|
||||
class AllUserRequire2FAMiddleware(MiddlewareMixin):
|
||||
"""Ensure that users have two-factor authentication enabled before they have access restricted endpoints.
|
||||
|
||||
Adapted from https://github.com/pennersr/django-allauth/issues/3649
|
||||
"""
|
||||
|
||||
allowed_pages = [
|
||||
'api-user-meta',
|
||||
'api-user-me',
|
||||
'api-user-roles',
|
||||
'api-inventree-info',
|
||||
'api-token',
|
||||
# web platform urls
|
||||
'password_reset_confirm',
|
||||
'web',
|
||||
'web-wildcard',
|
||||
'web-assets',
|
||||
]
|
||||
app_names = ['headless']
|
||||
require_2fa_message = _(
|
||||
'You must enable two-factor authentication before doing anything else.'
|
||||
)
|
||||
|
||||
def on_require_2fa(self, request: HttpRequest) -> HttpResponse:
|
||||
"""Force user to mfa activation."""
|
||||
return JsonResponse({'id': 'mfa_register'}, status=401)
|
||||
|
||||
def is_allowed_page(self, request: HttpRequest) -> bool:
|
||||
"""Check if the current page can be accessed without mfa."""
|
||||
match = request.resolver_match
|
||||
return (
|
||||
None
|
||||
if match is None
|
||||
else any(ref in self.app_names for ref in match.app_names)
|
||||
or match.url_name in self.allowed_pages
|
||||
or match.route == 'favicon.ico'
|
||||
)
|
||||
|
||||
def enforce_2fa(self, request):
|
||||
"""Check if 2fa should be enforced for this request."""
|
||||
return True
|
||||
|
||||
def process_view(
|
||||
self, request: HttpRequest, view_func, view_args, view_kwargs
|
||||
) -> HttpResponse:
|
||||
"""If set up enforce 2fa registration."""
|
||||
if request.user.is_anonymous:
|
||||
return None
|
||||
if self.is_allowed_page(request):
|
||||
return None
|
||||
if is_multifactor_logged_in(request):
|
||||
return None
|
||||
|
||||
if self.enforce_2fa(request):
|
||||
return self.on_require_2fa(request)
|
||||
return None
|
||||
@@ -53,7 +53,7 @@ def read_license_file(path: Path) -> list:
|
||||
return []
|
||||
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
data = json.loads(path.read_text(encoding='utf-8'))
|
||||
except Exception as e:
|
||||
logger.exception("Failed to parse license file '%s': %s", path, e)
|
||||
return []
|
||||
@@ -600,6 +600,34 @@ class BulkUpdateMixin(BulkOperationMixin):
|
||||
return Response({'success': f'Updated {n} items'}, status=200)
|
||||
|
||||
|
||||
class ParameterListMixin:
|
||||
"""Mixin class which supports filtering against parametric fields."""
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
"""Perform filtering against parametric fields."""
|
||||
import common.filters
|
||||
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
# Filter by parametric data
|
||||
queryset = common.filters.filter_parametric_data(
|
||||
queryset, self.request.query_params
|
||||
)
|
||||
|
||||
serializer_class = (
|
||||
getattr(self, 'serializer_class', None) or self.get_serializer_class()
|
||||
)
|
||||
|
||||
model_class = serializer_class.Meta.model
|
||||
|
||||
# Apply ordering based on query parameter
|
||||
queryset = common.filters.order_by_parameter(
|
||||
queryset, model_class, self.request.query_params.get('ordering', None)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class BulkDeleteMixin(BulkOperationMixin):
|
||||
"""Mixin class for enabling 'bulk delete' operations for various models.
|
||||
|
||||
|
||||
@@ -1,11 +1,66 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 421
|
||||
INVENTREE_API_VERSION = 435
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v435 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11030
|
||||
- Adds token refresh endpoint to auth API
|
||||
|
||||
v434 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11021
|
||||
- The "tags" fields (on various API endpoints) is now optional, and disabled by default
|
||||
- To request tags information, add "tags=true" to the API request query parameters
|
||||
|
||||
v433 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11023
|
||||
- "substitutes" field on the BomItem API endpoint is now excluded by default
|
||||
- Add "?substitutes=true" query parameter to include substitute parts in BomItem API endpoint(s)
|
||||
|
||||
v432 -> 2025-12-15 : https://github.com/inventree/InvenTree/pull/11012
|
||||
- The "part_detail" field on the SupplierPart API endpoint is now optional
|
||||
- The "supplier_detail" field on the SupplierPart API endpoint is now optional
|
||||
- The "manufacturer_detail" field on the ManufacturerPart API endpoint is now optional
|
||||
- The "part_detail" field on the StockItem API is now disabled by default
|
||||
|
||||
v431 -> 2025-12-14 : https://github.com/inventree/InvenTree/pull/11006
|
||||
- Remove duplicate "address" field on the Company API endpoint
|
||||
- Make "primary_address" field optional on the Company API endpoint
|
||||
- Remove "address_count" field from the Company API endpoint
|
||||
|
||||
v430 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10699
|
||||
- Removed the "PartParameter" and "PartParameterTemplate" API endpoints
|
||||
- Removed the "ManufacturerPartParameter" API endpoint
|
||||
- Added generic "Parameter" and "ParameterTemplate" API endpoints
|
||||
|
||||
v429 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10938
|
||||
- Adjust default values for currency codes in the API schema
|
||||
- Note that this does not change any functional behavior, only the schema documentation
|
||||
|
||||
v428 -> 2025-11-28 : https://github.com/inventree/InvenTree/pull/10926
|
||||
- Various typo fixes in API - no functional changes
|
||||
|
||||
v427 -> 2025-11-24 : https://github.com/inventree/InvenTree/pull/10896
|
||||
- Fixes a spelling mistake in the API field labels
|
||||
|
||||
v426 -> 2025-11-19 : https://github.com/inventree/InvenTree/pull/10867
|
||||
- Adds optional "price_breaks" filter to the SupplierPart API endpoint
|
||||
|
||||
v425 -> 2025-11-11 : https://github.com/inventree/InvenTree/pull/10802
|
||||
- Adds "on_order" filter to the BuildLine API endpoint
|
||||
- Allow BuildLine list to be ordered by "on_order" and "in_production" fields
|
||||
|
||||
v424 -> 2025-11-11 : https://github.com/inventree/InvenTree/pull/10730
|
||||
- Adds more lower / upper bounds to integer fields in the API schema due to bump to Django 5.2- no functional changes
|
||||
|
||||
v423 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10772
|
||||
- Adds "category_detail" field to BomItem API endpoints
|
||||
- Adds "category_detail" field to BuildLine API endpoints
|
||||
|
||||
v422 -> 2025-11-03 : https://github.com/inventree/InvenTree/pull/10750
|
||||
- Adds ability to search StockItem API by supplier SKU
|
||||
- Adds ability to search StockItem API by manufacturer MPN
|
||||
|
||||
v421 -> 2025-10-31 : https://github.com/inventree/InvenTree/pull/10724
|
||||
- Allow upload of attachments against SupplierPart objects via the API
|
||||
|
||||
@@ -21,7 +76,7 @@ v418 -> 2025-10-24 : https://github.com/inventree/InvenTree/pull/10657
|
||||
|
||||
v417 -> 2025-10-22 : https://github.com/inventree/InvenTree/pull/10654
|
||||
- Adds "checked" filter to SalesOrderShipment API endpoint
|
||||
- Adds "order_status" filter to SalesOrdereShipment API endpoint
|
||||
- Adds "order_status" filter to SalesOrderShipment API endpoint
|
||||
- Adds "order_outstanding" filter to SalesOrderShipment API endpoint
|
||||
|
||||
v416 -> 2025-10-22 : https://github.com/inventree/InvenTree/pull/10651
|
||||
@@ -79,7 +134,7 @@ v401 -> 2025-10-05 : https://github.com/inventree/InvenTree/pull/10381
|
||||
- Adds machine properties to machine API endpoints
|
||||
|
||||
v400 -> 2025-10-05 : https://github.com/inventree/InvenTree/pull/10486
|
||||
- Adds return datatypes for admin/config and flags entpoints
|
||||
- Adds return datatypes for admin/config and flags endpoints
|
||||
|
||||
v399 -> 2025-10-05 : https://github.com/inventree/InvenTree/pull/10445
|
||||
- Refactors 'customer_detail' param in SalesOrder API endpoint
|
||||
|
||||
@@ -18,6 +18,7 @@ import InvenTree.conversion
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
from InvenTree.config import get_setting
|
||||
from InvenTree.ready import ignore_ready_warning
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
MIGRATIONS_CHECK_DONE = False
|
||||
@@ -70,9 +71,7 @@ class InvenTreeConfig(AppConfig):
|
||||
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
|
||||
|
||||
self.update_site_url()
|
||||
|
||||
# Ensure the unit registry is loaded
|
||||
InvenTree.conversion.get_unit_registry()
|
||||
self.load_unit_registry()
|
||||
|
||||
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
|
||||
self.add_user_on_startup()
|
||||
@@ -84,6 +83,7 @@ class InvenTreeConfig(AppConfig):
|
||||
|
||||
social_account_updated.connect(sso.ensure_sso_groups)
|
||||
|
||||
@ignore_ready_warning
|
||||
def remove_obsolete_tasks(self):
|
||||
"""Delete any obsolete scheduled tasks in the database."""
|
||||
obsolete = [
|
||||
@@ -112,6 +112,7 @@ class InvenTreeConfig(AppConfig):
|
||||
except Exception:
|
||||
logger.exception('Failed to remove obsolete tasks - database not ready')
|
||||
|
||||
@ignore_ready_warning
|
||||
def start_background_tasks(self):
|
||||
"""Start all background tests for InvenTree."""
|
||||
logger.info('Starting background tasks...')
|
||||
@@ -171,6 +172,7 @@ class InvenTreeConfig(AppConfig):
|
||||
|
||||
logger.info('Started %s scheduled background tasks...', len(tasks))
|
||||
|
||||
@ignore_ready_warning
|
||||
def add_heartbeat(self):
|
||||
"""Ensure there is at least one background task in the queue."""
|
||||
import django_q.models
|
||||
@@ -185,6 +187,7 @@ class InvenTreeConfig(AppConfig):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ignore_ready_warning
|
||||
def collect_tasks(self):
|
||||
"""Collect all background tasks."""
|
||||
for app_name, app in apps.app_configs.items():
|
||||
@@ -197,6 +200,7 @@ class InvenTreeConfig(AppConfig):
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.exception('Error loading tasks for %s: %s', app_name, e)
|
||||
|
||||
@ignore_ready_warning
|
||||
def update_site_url(self):
|
||||
"""Update the site URL setting.
|
||||
|
||||
@@ -223,6 +227,12 @@ class InvenTreeConfig(AppConfig):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ignore_ready_warning
|
||||
def load_unit_registry(self):
|
||||
"""Ensure the unit registry is loaded."""
|
||||
InvenTree.conversion.get_unit_registry()
|
||||
|
||||
@ignore_ready_warning
|
||||
def add_user_on_startup(self):
|
||||
"""Add a user on startup."""
|
||||
# stop if checks were already created
|
||||
@@ -277,10 +287,11 @@ class InvenTreeConfig(AppConfig):
|
||||
new_user = user.objects.create_superuser(
|
||||
add_user, add_email, add_password
|
||||
)
|
||||
logger.info('User %s was created!', str(new_user))
|
||||
logger.info('User %s was created!', new_user)
|
||||
except IntegrityError:
|
||||
logger.warning('The user "%s" could not be created', add_user)
|
||||
|
||||
@ignore_ready_warning
|
||||
def add_user_from_file(self):
|
||||
"""Add the superuser from a file."""
|
||||
# stop if checks were already created
|
||||
@@ -321,6 +332,18 @@ class InvenTreeConfig(AppConfig):
|
||||
return
|
||||
|
||||
if not InvenTree.tasks.check_for_migrations():
|
||||
logger.error('INVE-W8: Database Migrations required')
|
||||
sys.exit(1)
|
||||
# Detect if this an empty database - if so, start with a fresh migration
|
||||
if (
|
||||
settings.DOCKER
|
||||
and not InvenTree.ready.isInTestMode()
|
||||
and not InvenTree.ready.isRunningMigrations()
|
||||
and InvenTree.tasks.get_migration_count() == 0
|
||||
):
|
||||
logger.warning(
|
||||
'INVE-W8: Empty database detected - trying to run migrations'
|
||||
)
|
||||
InvenTree.tasks.check_for_migrations(force_run=True)
|
||||
else:
|
||||
logger.error('INVE-W8: Database Migrations required')
|
||||
sys.exit(1)
|
||||
MIGRATIONS_CHECK_DONE = True
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import datetime
|
||||
import time
|
||||
from typing import Union
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -109,7 +108,7 @@ class InvenTreeMailLoggingBackend(BaseEmailBackend):
|
||||
return self.backend.open()
|
||||
|
||||
def send_messages(
|
||||
self, email_messages: list[Union[EmailMessage, EmailMultiAlternatives]]
|
||||
self, email_messages: list[EmailMessage | EmailMultiAlternatives]
|
||||
) -> int:
|
||||
"""Send email messages and log them to the database.
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import socket
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
|
||||
import structlog
|
||||
|
||||
import InvenTree.config
|
||||
@@ -169,3 +171,22 @@ def set_session_cache(key: str, value: Any) -> None:
|
||||
|
||||
if request_cache is not None:
|
||||
request_cache[key] = value
|
||||
|
||||
|
||||
def get_cached_content_types(cache_key: str = 'all_content_types') -> list:
|
||||
"""Return a list of all ContentType objects, using session cache if possible."""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
# Attempt to retrieve a list of ContentType objects from session cache
|
||||
if content_types := get_session_cache(cache_key):
|
||||
return content_types
|
||||
|
||||
try:
|
||||
content_types = list(ContentType.objects.all())
|
||||
if len(content_types) > 0:
|
||||
set_session_cache(cache_key, content_types)
|
||||
except (OperationalError, ProgrammingError):
|
||||
# Database is likely not yet ready
|
||||
content_types = []
|
||||
|
||||
return content_types
|
||||
|
||||
@@ -7,8 +7,9 @@ import os
|
||||
import random
|
||||
import shutil
|
||||
import string
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
CONFIG_DATA = None
|
||||
@@ -177,7 +178,7 @@ def get_config_file(create=True) -> Path:
|
||||
return cfg_filename
|
||||
|
||||
|
||||
def load_config_data(set_cache: bool = False) -> Union[map, None]:
|
||||
def load_config_data(set_cache: bool = False) -> map | None:
|
||||
"""Load configuration data from the config file.
|
||||
|
||||
Arguments:
|
||||
@@ -195,7 +196,15 @@ def load_config_data(set_cache: bool = False) -> Union[map, None]:
|
||||
cfg_file = get_config_file()
|
||||
|
||||
with open(cfg_file, encoding='utf-8') as cfg:
|
||||
data = yaml.safe_load(cfg)
|
||||
try:
|
||||
data = yaml.safe_load(cfg)
|
||||
except yaml.parser.ParserError as error:
|
||||
logger.error(
|
||||
"INVE-E13: Error reading InvenTree configuration file '%s': %s",
|
||||
cfg_file,
|
||||
error,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Set the cache if requested
|
||||
if set_cache:
|
||||
@@ -223,6 +232,11 @@ def do_typecast(value, type, var_name=None):
|
||||
elif type is dict:
|
||||
value = to_dict(value)
|
||||
|
||||
# Special handling for boolean typecasting
|
||||
elif type is bool:
|
||||
val = is_true(value)
|
||||
return val
|
||||
|
||||
elif type is not None:
|
||||
# Try to typecast the value
|
||||
try:
|
||||
@@ -392,7 +406,7 @@ def get_plugin_dir():
|
||||
return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
||||
|
||||
|
||||
def get_secret_key(return_path: bool = False) -> Union[str, Path]:
|
||||
def get_secret_key(return_path: bool = False) -> str | Path:
|
||||
"""Return the secret key value which will be used by django.
|
||||
|
||||
Following options are tested, in descending order of preference:
|
||||
@@ -436,7 +450,7 @@ def get_secret_key(return_path: bool = False) -> Union[str, Path]:
|
||||
return secret_key_file.read_text().strip()
|
||||
|
||||
|
||||
def get_oidc_private_key(return_path: bool = False) -> Union[str, Path]:
|
||||
def get_oidc_private_key(return_path: bool = False) -> str | Path:
|
||||
"""Return the private key for OIDC authentication.
|
||||
|
||||
Following options are tested, in descending order of preference:
|
||||
|
||||
@@ -1,27 +1,96 @@
|
||||
"""Helper functions for converting between units."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from hashlib import md5
|
||||
from typing import Optional
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import pint
|
||||
|
||||
_unit_registry = None
|
||||
|
||||
import structlog
|
||||
|
||||
from common.settings import get_global_setting, set_global_setting
|
||||
from InvenTree.cache import get_session_cache, set_session_cache
|
||||
|
||||
_UNIT_REG_CACHE_KEY = 'unit_registry_hash'
|
||||
_unit_registry = None
|
||||
_unit_registry_hash: str = ''
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
# Disable log output for Pint library
|
||||
logging.getLogger('pint').setLevel(logging.ERROR)
|
||||
|
||||
|
||||
def can_cache_registry() -> bool:
|
||||
"""Return True if it is appropriate to cache the unit registry.
|
||||
|
||||
Prevent caching under certain conditions (such as database migration)
|
||||
to prevent database access.
|
||||
"""
|
||||
import InvenTree.ready
|
||||
|
||||
return not any([
|
||||
InvenTree.ready.isImportingData(),
|
||||
InvenTree.ready.isRunningBackup(),
|
||||
InvenTree.ready.isRunningMigrations(),
|
||||
InvenTree.ready.isInTestMode(),
|
||||
])
|
||||
|
||||
|
||||
def get_unit_registry_hash():
|
||||
"""Return a hash representing the current state of the unit registry.
|
||||
|
||||
We use this to determine if we need to reload the unit registry,
|
||||
due to changes in the database.
|
||||
"""
|
||||
# Look in the session cache first (faster, and potentially newer)
|
||||
registry_hash = get_session_cache(_UNIT_REG_CACHE_KEY)
|
||||
|
||||
if registry_hash is None:
|
||||
registry_hash = get_global_setting(
|
||||
'_UNIT_REGISTRY_HASH', create=False, backup_value=''
|
||||
)
|
||||
|
||||
if registry_hash:
|
||||
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
|
||||
|
||||
return registry_hash
|
||||
|
||||
|
||||
def set_unit_registry_hash(registry_hash: str):
|
||||
"""Save the hash representing the current state of the unit registry.
|
||||
|
||||
Because most of the registry is static, we only need to consider the
|
||||
CustomUnit entries in the database.
|
||||
"""
|
||||
global _unit_registry_hash
|
||||
_unit_registry_hash = registry_hash
|
||||
|
||||
if not can_cache_registry():
|
||||
return
|
||||
|
||||
# Save to both the global settings and the session cache
|
||||
set_global_setting('_UNIT_REGISTRY_HASH', registry_hash)
|
||||
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
|
||||
|
||||
|
||||
def get_unit_registry():
|
||||
"""Return a custom instance of the Pint UnitRegistry."""
|
||||
global _unit_registry
|
||||
global _unit_registry_hash
|
||||
|
||||
# Cache the unit registry for speedier access
|
||||
if _unit_registry is None:
|
||||
return reload_unit_registry()
|
||||
|
||||
# Check if the unit registry has changed
|
||||
if can_cache_registry() and _unit_registry_hash != get_unit_registry_hash():
|
||||
logger.info('Unit registry hash has changed, reloading unit registry')
|
||||
return reload_unit_registry()
|
||||
|
||||
return _unit_registry
|
||||
|
||||
|
||||
@@ -53,23 +122,32 @@ def reload_unit_registry():
|
||||
reg.define('thousand = 1000')
|
||||
|
||||
# Allow for custom units to be defined in the database
|
||||
# Calculate a hash of all custom units
|
||||
hash_md5 = md5()
|
||||
|
||||
try:
|
||||
from common.models import CustomUnit
|
||||
|
||||
for cu in CustomUnit.objects.all():
|
||||
try:
|
||||
reg.define(cu.fmt_string())
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
'Failed to load custom unit: %s - %s', cu.fmt_string(), e
|
||||
)
|
||||
|
||||
# Once custom units are loaded, save registry
|
||||
_unit_registry = reg
|
||||
|
||||
custom_units = list(CustomUnit.objects.all())
|
||||
except Exception:
|
||||
# Database is not ready, or CustomUnit model is not available
|
||||
pass
|
||||
# Database is likely not ready
|
||||
custom_units = []
|
||||
|
||||
for cu in custom_units:
|
||||
try:
|
||||
fmt = cu.fmt_string()
|
||||
reg.define(fmt)
|
||||
|
||||
hash_md5.update(fmt.encode('utf-8'))
|
||||
|
||||
except Exception as e:
|
||||
logger.exception('Failed to load custom unit: %s - %s', cu.fmt_string(), e)
|
||||
|
||||
# Once custom units are loaded, save registry
|
||||
_unit_registry = reg
|
||||
|
||||
# Update the unit registry hash
|
||||
set_unit_registry_hash(hash_md5.hexdigest())
|
||||
|
||||
dt = time.time() - t_start
|
||||
logger.debug('Loaded unit registry in %.3f s', dt)
|
||||
|
||||
@@ -22,7 +22,7 @@ class InvenTreeDateFilter(rest_filters.DateFilter):
|
||||
if settings.USE_TZ and value is not None:
|
||||
tz = timezone.get_current_timezone()
|
||||
value = datetime(value.year, value.month, value.day)
|
||||
value = make_aware(value, timezone=tz, is_dst=True)
|
||||
value = make_aware(value, timezone=tz)
|
||||
|
||||
return super().filter(qs, value)
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ import hashlib
|
||||
import inspect
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Optional, TypeVar, Union
|
||||
from typing import Optional, TypeVar
|
||||
from wsgiref.util import FileWrapper
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
@@ -17,6 +16,7 @@ from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db.models.fields.files import FieldFile, ImageFieldFile
|
||||
from django.http import StreamingHttpResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -28,6 +28,7 @@ import structlog
|
||||
from bleach import clean
|
||||
from djmoney.money import Money
|
||||
from PIL import Image
|
||||
from stdimage.models import StdImageField, StdImageFieldFile
|
||||
|
||||
from common.currency import currency_code_default
|
||||
|
||||
@@ -127,7 +128,7 @@ def extract_int(
|
||||
return ref_int
|
||||
|
||||
|
||||
def generateTestKey(test_name: Union[str, None]) -> str:
|
||||
def generateTestKey(test_name: str | None) -> str:
|
||||
"""Generate a test 'key' for a given test name. This must not have illegal chars as it will be used for dict lookup in a template.
|
||||
|
||||
Tests must be named such that they will have unique keys.
|
||||
@@ -175,11 +176,52 @@ def constructPathString(path: list[str], max_chars: int = 250) -> str:
|
||||
return pathstring
|
||||
|
||||
|
||||
def getMediaUrl(filename):
|
||||
def getMediaUrl(
|
||||
file: FieldFile | ImageFieldFile | StdImageFieldFile, name: str | None = None
|
||||
):
|
||||
"""Return the qualified access path for the given file, under the media directory."""
|
||||
if not isinstance(file, (FieldFile, ImageFieldFile, StdImageFieldFile)):
|
||||
raise TypeError(
|
||||
'file must be one of FileField, ImageFileField, StdImageFieldFile'
|
||||
)
|
||||
if name is not None:
|
||||
file = regenerate_imagefile(file, name)
|
||||
if settings.STORAGE_TARGET == StorageBackends.S3:
|
||||
return str(filename)
|
||||
return os.path.join(MEDIA_URL, str(filename))
|
||||
return str(file.url)
|
||||
return os.path.join(MEDIA_URL, str(file.url))
|
||||
|
||||
|
||||
def regenerate_imagefile(_file, _name: str):
|
||||
"""Regenerate a file object for a given variation name.
|
||||
|
||||
Arguments:
|
||||
_file: Original file object
|
||||
_name: Name of the variation (e.g. 'thumbnail', 'preview')
|
||||
"""
|
||||
name = _file.field.attr_class.get_variation_name(_file.name, _name)
|
||||
return ImageFieldFile(_file.instance, _file, name) # type: ignore
|
||||
|
||||
|
||||
def image2name(img_obj: StdImageField, do_preview: bool, do_thumbnail: bool):
|
||||
"""Convert an image object to a filename string.
|
||||
|
||||
Arguments:
|
||||
img_obj: Image object
|
||||
do_preview: Return preview image name
|
||||
do_thumbnail: Return thumbnail image name
|
||||
"""
|
||||
|
||||
def extract(ref: str):
|
||||
return None if not hasattr(img_obj, ref) else getattr(img_obj, ref).name
|
||||
|
||||
if not img_obj:
|
||||
return None
|
||||
elif do_preview:
|
||||
return extract('preview')
|
||||
elif do_thumbnail:
|
||||
return extract('thumbnail')
|
||||
else:
|
||||
return img_obj.name
|
||||
|
||||
|
||||
def getStaticUrl(filename):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Code for managing email functionality in InvenTree."""
|
||||
|
||||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -8,7 +8,6 @@ import structlog
|
||||
from allauth.account.models import EmailAddress
|
||||
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
from common.models import Priority, issue_mail
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
@@ -64,7 +63,7 @@ def is_email_configured() -> bool:
|
||||
def send_email(
|
||||
subject: str,
|
||||
body: str,
|
||||
recipients: Union[str, list],
|
||||
recipients: str | list,
|
||||
from_email: Optional[str] = None,
|
||||
html_message=None,
|
||||
prio: Priority = Priority.NORMAL,
|
||||
|
||||
22
src/backend/InvenTree/InvenTree/helpers_mfa.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Helper functions for allauth MFA testing."""
|
||||
|
||||
from allauth.mfa.recovery_codes.internal.auth import RecoveryCodes
|
||||
from allauth.mfa.totp.internal import auth as allauth_totp_auth
|
||||
|
||||
|
||||
def get_codes(user):
|
||||
"""Generate active TOTP and recovery codes for a user.
|
||||
|
||||
Args:
|
||||
user: User instance
|
||||
|
||||
Returns:
|
||||
Tuple of (TOTP Authenticator instance, list of recovery codes, TOTP secret)
|
||||
"""
|
||||
secret = allauth_totp_auth.generate_totp_secret()
|
||||
totp_auth = allauth_totp_auth.TOTP.activate(user, secret).instance
|
||||
rc_auth = RecoveryCodes.activate(user).instance
|
||||
|
||||
# Get usable codes
|
||||
rc_codes = rc_auth.wrap().get_unused_codes()
|
||||
return totp_auth, rc_codes, secret
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Provides helper mixins that are used throughout the InvenTree project."""
|
||||
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
@@ -24,7 +24,13 @@ from common.notifications import (
|
||||
trigger_notification,
|
||||
)
|
||||
from common.settings import get_global_setting
|
||||
from InvenTree.cache import (
|
||||
get_cached_content_types,
|
||||
get_session_cache,
|
||||
set_session_cache,
|
||||
)
|
||||
from InvenTree.format import format_money
|
||||
from InvenTree.ready import ignore_ready_warning
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
@@ -260,6 +266,7 @@ def render_currency(
|
||||
)
|
||||
|
||||
|
||||
@ignore_ready_warning
|
||||
def getModelsWithMixin(mixin_class) -> list:
|
||||
"""Return a list of database models that inherit from the given mixin class.
|
||||
|
||||
@@ -268,17 +275,23 @@ def getModelsWithMixin(mixin_class) -> list:
|
||||
Returns:
|
||||
List of models that inherit from the given mixin class
|
||||
"""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
# First, look in the session cache - to prevent repeated expensive comparisons
|
||||
cache_key = f'models_with_mixin_{mixin_class.__name__}'
|
||||
|
||||
try:
|
||||
db_models = [
|
||||
x.model_class() for x in ContentType.objects.all() if x is not None
|
||||
]
|
||||
except (OperationalError, ProgrammingError):
|
||||
# Database is likely not yet ready
|
||||
db_models = []
|
||||
if cached_models := get_session_cache(cache_key):
|
||||
return cached_models
|
||||
|
||||
return [x for x in db_models if x is not None and issubclass(x, mixin_class)]
|
||||
content_types = get_cached_content_types()
|
||||
|
||||
db_models = [x.model_class() for x in content_types if x is not None]
|
||||
|
||||
models_with_mixin = [
|
||||
x for x in db_models if x is not None and issubclass(x, mixin_class)
|
||||
]
|
||||
|
||||
# Store the result in the session cache
|
||||
set_session_cache(cache_key, models_with_mixin)
|
||||
return models_with_mixin
|
||||
|
||||
|
||||
def notify_responsible(
|
||||
|
||||
@@ -35,10 +35,6 @@ def get_type_str(type_obj):
|
||||
if type_obj.__module__ == 'builtins':
|
||||
return type_obj.__name__
|
||||
|
||||
# in python3.9, typing.Union has no __name__
|
||||
if not hasattr(type_obj, '__module__') or not hasattr(type_obj, '__name__'):
|
||||
return str(type_obj)
|
||||
|
||||
return f'{type_obj.__module__}.{type_obj.__name__}'
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Extended schema generator."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TypeVar, Union
|
||||
from typing import TypeVar
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -26,7 +26,7 @@ def prep_name(ref):
|
||||
return f'{dja_ref_prefix}.{ref}'
|
||||
|
||||
|
||||
def sub_component_name(name: T) -> Union[T, str]:
|
||||
def sub_component_name(name: T) -> T | str:
|
||||
"""Clean up component references."""
|
||||
if not isinstance(name, str):
|
||||
return name
|
||||
@@ -70,8 +70,10 @@ class Command(spectacular.Command):
|
||||
# Reformat paths
|
||||
for p_name, p_spec in spec['paths'].items():
|
||||
# strip path name
|
||||
p_name = p_name.removeprefix(dja_path_prefix).removeprefix(
|
||||
'/_allauth/browser/v1/'
|
||||
p_name = (
|
||||
p_name.removeprefix(dja_path_prefix)
|
||||
.removeprefix('/_allauth/browser/v1/')
|
||||
.removeprefix('/_allauth/app/v1/')
|
||||
)
|
||||
|
||||
# fix refs
|
||||
|
||||
@@ -23,7 +23,7 @@ logger = structlog.get_logger('inventree')
|
||||
class InvenTreeMetadata(SimpleMetadata):
|
||||
"""Custom metadata class for the DRF API.
|
||||
|
||||
This custom metadata class imits the available "actions",
|
||||
This custom metadata class limits the available "actions",
|
||||
based on the user's role permissions.
|
||||
|
||||
Thus when a client send an OPTIONS request to an API endpoint,
|
||||
@@ -50,6 +50,10 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
|
||||
for method in {'PUT', 'POST', 'GET'} & set(view.allowed_methods):
|
||||
view.request = clone_request(request, method)
|
||||
|
||||
# Mark this request, to prevent expensive prefetching
|
||||
view.request._metadata_requested = True
|
||||
|
||||
try:
|
||||
# Test global permissions
|
||||
if hasattr(view, 'check_permissions'):
|
||||
@@ -420,6 +424,13 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
if field_info['type'] == 'dependent field':
|
||||
field_info['depends_on'] = field.depends_on
|
||||
|
||||
# Extends with extra attributes from the serializer
|
||||
extra_field_attributes = ['allow_blank', 'allow_null']
|
||||
|
||||
for attr in extra_field_attributes:
|
||||
if hasattr(field, attr):
|
||||
field_info[attr] = getattr(field, attr)
|
||||
|
||||
# Extend field info if the field has a get_field_info method
|
||||
if (
|
||||
not field_info.get('read_only')
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
"""Middleware for InvenTree."""
|
||||
|
||||
import sys
|
||||
from typing import Optional
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.middleware import PersistentRemoteUserMiddleware
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import resolve, reverse_lazy
|
||||
from django.urls import resolve, reverse, reverse_lazy
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.http import is_same_domain
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import structlog
|
||||
from error_report.middleware import ExceptionProcessor
|
||||
|
||||
from common.settings import get_global_setting
|
||||
from InvenTree.AllUserRequire2FAMiddleware import AllUserRequire2FAMiddleware
|
||||
from InvenTree.cache import create_session_cache, delete_session_cache
|
||||
from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller
|
||||
from users.models import ApiToken
|
||||
@@ -40,6 +41,15 @@ def get_token_from_request(request):
|
||||
return None
|
||||
|
||||
|
||||
def ensure_slashes(path: str):
|
||||
"""Ensure that slashes are suroudning the passed path."""
|
||||
if not path.startswith('/'):
|
||||
path = f'/{path}'
|
||||
if not path.endswith('/'):
|
||||
path = f'{path}/'
|
||||
return path
|
||||
|
||||
|
||||
# List of target URL endpoints where *do not* want to redirect to
|
||||
urls = [
|
||||
reverse_lazy('account_login'),
|
||||
@@ -47,8 +57,46 @@ urls = [
|
||||
reverse_lazy('admin:logout'),
|
||||
]
|
||||
|
||||
# Do not redirect requests to any of these paths
|
||||
paths_ignore = ['/api/', '/auth/', settings.MEDIA_URL, settings.STATIC_URL]
|
||||
paths_ignore_handling = [
|
||||
'/api/',
|
||||
reverse('auth-check'),
|
||||
settings.MEDIA_URL,
|
||||
settings.STATIC_URL,
|
||||
]
|
||||
"""Paths that should not use InvenTrees own auth rejection behaviour, no host checking or redirecting. Security
|
||||
are still enforced."""
|
||||
paths_own_security = [
|
||||
'/api/', # DRF handles API
|
||||
'/o/', # oAuth2 library - has its own auth model
|
||||
'/anymail/', # Mails - wehbhooks etc
|
||||
'/accounts/', # allauth account management - has its own auth model
|
||||
'/assets/', # Web assets - only used for testing, no security model needed
|
||||
ensure_slashes(
|
||||
settings.STATIC_URL
|
||||
), # Static files - static files are considered safe to serve
|
||||
ensure_slashes(
|
||||
settings.FRONTEND_URL_BASE
|
||||
), # Frontend files - frontend paths have their own security model
|
||||
]
|
||||
"""Paths that handle their own security model."""
|
||||
pages_mfa_bypass = [
|
||||
'api-user-meta',
|
||||
'api-user-me',
|
||||
'api-user-roles',
|
||||
'api-inventree-info',
|
||||
'api-token',
|
||||
# web platform urls
|
||||
'password_reset_confirm',
|
||||
'index',
|
||||
'web',
|
||||
'web-wildcard',
|
||||
'web-assets',
|
||||
]
|
||||
"""Exact page names that bypass MFA enforcement - normal security model is still enforced."""
|
||||
apps_mfa_bypass = [
|
||||
'headless' # Headless allauth app - has its own security model
|
||||
]
|
||||
"""App namespaces that bypass MFA enforcement - normal security model is still enforced."""
|
||||
|
||||
|
||||
class AuthRequiredMiddleware:
|
||||
@@ -61,6 +109,7 @@ class AuthRequiredMiddleware:
|
||||
def check_token(self, request) -> bool:
|
||||
"""Check if the user is authenticated via token."""
|
||||
if token := get_token_from_request(request):
|
||||
request.token = token
|
||||
# Does the provided token match a valid user?
|
||||
try:
|
||||
token = ApiToken.objects.get(key=token)
|
||||
@@ -69,8 +118,10 @@ class AuthRequiredMiddleware:
|
||||
# Provide the user information to the request
|
||||
request.user = token.user
|
||||
return True
|
||||
except ApiToken.DoesNotExist:
|
||||
logger.warning('Access denied for unknown token %s', token)
|
||||
except ApiToken.DoesNotExist: # pragma: no cover
|
||||
logger.warning(
|
||||
'Access denied for unknown token %s', token
|
||||
) # pragma: no cover
|
||||
|
||||
return False
|
||||
|
||||
@@ -79,79 +130,113 @@ class AuthRequiredMiddleware:
|
||||
|
||||
Redirects to login if not authenticated.
|
||||
"""
|
||||
path: str = request.path_info
|
||||
# Code to be executed for each request before
|
||||
# the view (and later middleware) are called.
|
||||
|
||||
assert hasattr(request, 'user')
|
||||
|
||||
# API requests are handled by the DRF library
|
||||
if request.path_info.startswith('/api/'):
|
||||
return self.get_response(request)
|
||||
|
||||
# oAuth2 requests are handled by the oAuth2 library
|
||||
if request.path_info.startswith('/o/'):
|
||||
return self.get_response(request)
|
||||
|
||||
# anymail requests are handled by the anymail library
|
||||
if request.path_info.startswith('/anymail/'):
|
||||
# API requests that are handled elsewhere
|
||||
if any(path.startswith(a) for a in paths_own_security):
|
||||
return self.get_response(request)
|
||||
|
||||
# Is the function exempt from auth requirements?
|
||||
path_func = resolve(request.path).func
|
||||
|
||||
if getattr(path_func, 'auth_exempt', False) is True:
|
||||
return self.get_response(request)
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
if not request.user.is_authenticated and not (
|
||||
path == f'/{settings.FRONTEND_URL_BASE}' or self.check_token(request)
|
||||
):
|
||||
"""
|
||||
Normally, a web-based session would use csrftoken based authentication.
|
||||
|
||||
However when running an external application (e.g. the InvenTree app or Python library),
|
||||
we must validate the user token manually.
|
||||
"""
|
||||
|
||||
authorized = False
|
||||
|
||||
# Allow static files to be accessed without auth
|
||||
# Important for e.g. login page
|
||||
if (
|
||||
request.path_info.startswith('/static/')
|
||||
or request.path_info.startswith('/accounts/')
|
||||
or (
|
||||
request.path_info.startswith(f'/{settings.FRONTEND_URL_BASE}/')
|
||||
or request.path_info.startswith('/assets/')
|
||||
or request.path_info == f'/{settings.FRONTEND_URL_BASE}'
|
||||
)
|
||||
or self.check_token(request)
|
||||
if path not in urls and not any(
|
||||
path.startswith(p) for p in paths_ignore_handling
|
||||
):
|
||||
authorized = True
|
||||
# Save the 'next' parameter to pass through to the login view
|
||||
|
||||
# No authorization was found for the request
|
||||
if not authorized:
|
||||
path = request.path_info
|
||||
|
||||
if path not in urls and not any(
|
||||
path.startswith(p) for p in paths_ignore
|
||||
):
|
||||
# Save the 'next' parameter to pass through to the login view
|
||||
|
||||
return redirect(
|
||||
f'{reverse_lazy("account_login")}?next={request.path}'
|
||||
)
|
||||
# Return a 401 (Unauthorized) response code for this request
|
||||
return HttpResponse('Unauthorized', status=401)
|
||||
return redirect(f'{reverse_lazy("account_login")}?next={request.path}')
|
||||
# Return a 401 (Unauthorized) response code for this request
|
||||
return HttpResponse('Unauthorized', status=401)
|
||||
|
||||
response = self.get_response(request)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class Check2FAMiddleware(AllUserRequire2FAMiddleware):
|
||||
"""Ensure that mfa is enforced if set so."""
|
||||
class Check2FAMiddleware(MiddlewareMixin):
|
||||
"""Ensure that users have two-factor authentication enabled before they have access restricted endpoints.
|
||||
|
||||
Adapted from https://github.com/pennersr/django-allauth/issues/3649
|
||||
"""
|
||||
|
||||
require_2fa_message = _(
|
||||
'You must enable two-factor authentication before doing anything else.'
|
||||
)
|
||||
|
||||
def on_require_2fa(self, request: HttpRequest) -> HttpResponse:
|
||||
"""Force user to mfa activation."""
|
||||
return JsonResponse(
|
||||
{'id': 'mfa_register', 'error': self.require_2fa_message}, status=401
|
||||
)
|
||||
|
||||
def is_allowed_page(self, request: HttpRequest) -> bool:
|
||||
"""Check if the current page can be accessed without mfa."""
|
||||
match = request.resolver_match
|
||||
return (
|
||||
False
|
||||
if match is None
|
||||
else any(ref in apps_mfa_bypass for ref in match.app_names)
|
||||
or match.url_name in pages_mfa_bypass
|
||||
or match.route == 'favicon.ico'
|
||||
)
|
||||
|
||||
def is_multifactor_logged_in(self, request: HttpRequest) -> bool:
|
||||
"""Check if the user is logged in with multifactor authentication."""
|
||||
from allauth.account.authentication import get_authentication_records
|
||||
from allauth.mfa.utils import is_mfa_enabled
|
||||
from allauth.mfa.webauthn.internal.flows import did_use_passwordless_login
|
||||
|
||||
authns = get_authentication_records(request)
|
||||
|
||||
return is_mfa_enabled(request.user) and (
|
||||
did_use_passwordless_login(request)
|
||||
or any(record.get('method') == 'mfa' for record in authns)
|
||||
)
|
||||
|
||||
def process_view(
|
||||
self, request: HttpRequest, view_func, view_args, view_kwargs
|
||||
) -> Optional[HttpResponse]:
|
||||
"""Determine if the server is set up enforce 2fa registration."""
|
||||
from django.conf import settings
|
||||
|
||||
# Exit early if MFA is not enabled
|
||||
if not settings.MFA_ENABLED:
|
||||
return None
|
||||
|
||||
if request.user.is_anonymous:
|
||||
return None
|
||||
if self.is_allowed_page(request):
|
||||
return None
|
||||
if self.is_multifactor_logged_in(request):
|
||||
return None
|
||||
if getattr(
|
||||
request, 'token', get_token_from_request(request)
|
||||
): # Token based login can not do MFA
|
||||
return None
|
||||
|
||||
if self.enforce_2fa(request):
|
||||
return self.on_require_2fa(request)
|
||||
return None
|
||||
|
||||
def enforce_2fa(self, request):
|
||||
"""Use setting to check if MFA should be enforced."""
|
||||
return get_global_setting('LOGIN_ENFORCE_MFA')
|
||||
return get_global_setting(
|
||||
'LOGIN_ENFORCE_MFA', None, 'INVENTREE_LOGIN_ENFORCE_MFA'
|
||||
)
|
||||
|
||||
|
||||
class InvenTreeRemoteUserMiddleware(PersistentRemoteUserMiddleware):
|
||||
@@ -232,7 +317,7 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
|
||||
|
||||
# Handle commonly ignored paths that might also work without a correct setup (api, auth)
|
||||
path = request.path_info
|
||||
if path in urls or any(path.startswith(p) for p in paths_ignore):
|
||||
if path in urls or any(path.startswith(p) for p in paths_ignore_handling):
|
||||
return None
|
||||
|
||||
# treat the accessed scheme and host
|
||||
|
||||
@@ -258,6 +258,19 @@ class OutputOptionsMixin:
|
||||
|
||||
return serializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return the queryset with output options applied.
|
||||
|
||||
This automatically applies any prefetching defined against the optional fields.
|
||||
"""
|
||||
queryset = super().get_queryset()
|
||||
serializer = self.get_serializer()
|
||||
|
||||
if isinstance(serializer, FilterableSerializerMixin):
|
||||
queryset = serializer.prefetch_queryset(queryset)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class SerializerContextMixin:
|
||||
"""Mixin to add context to serializer."""
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
"""Generic models which provide extra functionality over base Django model types."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from string import Formatter
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.db.models import QuerySet
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.transaction import TransactionManagementError
|
||||
from django.dispatch import receiver
|
||||
from django.urls import resolve, reverse
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
@@ -19,6 +23,7 @@ from django_q.models import Task
|
||||
from error_report.models import Error
|
||||
from mptt.exceptions import InvalidMove
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
from stdimage.models import StdImageField
|
||||
|
||||
import common.settings
|
||||
import InvenTree.exceptions
|
||||
@@ -164,10 +169,14 @@ class MetadataMixin(models.Model):
|
||||
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||
"""Save the model instance, and perform validation on the metadata field."""
|
||||
self.validate_metadata()
|
||||
super().save(*args, **kwargs)
|
||||
if len(args) > 0:
|
||||
raise TypeError(
|
||||
'save() takes no positional arguments anymore'
|
||||
) # pragma: no cover
|
||||
super().save(force_insert=force_insert, force_update=force_update, **kwargs)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
"""Perform model validation on the metadata field."""
|
||||
@@ -443,7 +452,18 @@ class ReferenceIndexingMixin(models.Model):
|
||||
reference_int = models.BigIntegerField(default=0)
|
||||
|
||||
|
||||
class InvenTreeModel(PluginValidationMixin, models.Model):
|
||||
class ContentTypeMixin:
|
||||
"""Mixin class which supports retrieval of the ContentType for a model instance."""
|
||||
|
||||
@classmethod
|
||||
def get_content_type(cls):
|
||||
"""Return the ContentType object associated with this model."""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
return ContentType.objects.get_for_model(cls)
|
||||
|
||||
|
||||
class InvenTreeModel(ContentTypeMixin, PluginValidationMixin, models.Model):
|
||||
"""Base class for InvenTree models, which provides some common functionality.
|
||||
|
||||
Includes the following mixins by default:
|
||||
@@ -466,7 +486,167 @@ class InvenTreeMetadataModel(MetadataMixin, InvenTreeModel):
|
||||
abstract = True
|
||||
|
||||
|
||||
class InvenTreeAttachmentMixin:
|
||||
class InvenTreePermissionCheckMixin:
|
||||
"""Provides an abstracted class for managing permissions against related fields."""
|
||||
|
||||
@classmethod
|
||||
def check_related_permission(cls, permission, user) -> bool:
|
||||
"""Check if the user has permission to perform the specified action on the attachment.
|
||||
|
||||
The default implementation runs a permission check against *this* model class,
|
||||
but this can be overridden in the implementing class if required.
|
||||
|
||||
Arguments:
|
||||
permission: The permission to check (add / change / view / delete)
|
||||
user: The user to check against
|
||||
|
||||
Returns:
|
||||
bool: True if the user has permission, False otherwise
|
||||
"""
|
||||
perm = f'{cls._meta.app_label}.{permission}_{cls._meta.model_name}'
|
||||
return user.has_perm(perm)
|
||||
|
||||
|
||||
class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
|
||||
"""Provides an abstracted class for managing parameters.
|
||||
|
||||
Links the implementing model to the common.models.Parameter table,
|
||||
and provides the following methods:
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options for InvenTreeParameterMixin."""
|
||||
|
||||
abstract = True
|
||||
|
||||
# Define a reverse relation to the Parameter model
|
||||
parameters_list = GenericRelation(
|
||||
'common.Parameter', content_type_field='model_type', object_id_field='model_id'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def annotate_parameters(queryset: QuerySet) -> QuerySet:
|
||||
"""Annotate a queryset with pre-fetched parameters.
|
||||
|
||||
Args:
|
||||
queryset: Queryset to annotate
|
||||
|
||||
Returns:
|
||||
Annotated queryset
|
||||
"""
|
||||
return queryset.prefetch_related(
|
||||
'parameters_list',
|
||||
'parameters_list__model_type',
|
||||
'parameters_list__updated_by',
|
||||
'parameters_list__template',
|
||||
'parameters_list__template__model_type',
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> QuerySet:
|
||||
"""Return a QuerySet containing all the Parameter instances for this model.
|
||||
|
||||
This will return pre-fetched data if available (i.e. in a serializer context).
|
||||
"""
|
||||
# Check the query cache for pre-fetched parameters
|
||||
if cache := getattr(self, '_prefetched_objects_cache', None):
|
||||
if 'parameters_list' in cache:
|
||||
return cache['parameters_list']
|
||||
|
||||
return self.parameters_list.all()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Handle the deletion of a model instance.
|
||||
|
||||
Before deleting the model instance, delete any associated parameters.
|
||||
"""
|
||||
self.parameters_list.all().delete()
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
@transaction.atomic
|
||||
def copy_parameters_from(self, other, clear=True, **kwargs):
|
||||
"""Copy all parameters from another model instance.
|
||||
|
||||
Arguments:
|
||||
other: The other model instance to copy parameters from
|
||||
clear: If True, clear existing parameters before copying
|
||||
**kwargs: Additional keyword arguments to pass to the Parameter constructor
|
||||
"""
|
||||
import common.models
|
||||
|
||||
if clear:
|
||||
self.parameters_list.all().delete()
|
||||
|
||||
parameters = []
|
||||
|
||||
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||
|
||||
template_ids = [parameter.template.pk for parameter in other.parameters.all()]
|
||||
|
||||
# Remove all conflicting parameters first
|
||||
self.parameters_list.filter(template__pk__in=template_ids).delete()
|
||||
|
||||
for parameter in other.parameters.all():
|
||||
parameter.pk = None
|
||||
parameter.model_id = self.pk
|
||||
parameter.model_type = content_type
|
||||
|
||||
parameters.append(parameter)
|
||||
|
||||
if len(parameters) > 0:
|
||||
common.models.Parameter.objects.bulk_create(parameters)
|
||||
|
||||
def get_parameter(self, name: str):
|
||||
"""Return a Parameter instance for the given parameter name.
|
||||
|
||||
Args:
|
||||
name: Name of the parameter template
|
||||
|
||||
Returns:
|
||||
Parameter instance if found, else None
|
||||
"""
|
||||
return self.parameters_list.filter(template__name=name).first()
|
||||
|
||||
def get_parameters(self) -> QuerySet:
|
||||
"""Return all Parameter instances for this model."""
|
||||
return (
|
||||
self.parameters_list.all()
|
||||
.prefetch_related('template', 'model_type')
|
||||
.order_by('template__name')
|
||||
)
|
||||
|
||||
def parameters_map(self) -> dict:
|
||||
"""Return a map (dict) of parameter values associated with this Part instance, of the form.
|
||||
|
||||
Example:
|
||||
{
|
||||
"name_1": "value_1",
|
||||
"name_2": "value_2",
|
||||
}
|
||||
"""
|
||||
params = {}
|
||||
|
||||
for parameter in self.parameters.all().prefetch_related('template'):
|
||||
params[parameter.template.name] = parameter.data
|
||||
|
||||
return params
|
||||
|
||||
def check_parameter_delete(self, parameter):
|
||||
"""Run a check to determine if the provided parameter can be deleted.
|
||||
|
||||
The default implementation always returns True, but this can be overridden in the implementing class.
|
||||
"""
|
||||
return True
|
||||
|
||||
def check_parameter_save(self, parameter):
|
||||
"""Run a check to determine if the provided parameter can be saved.
|
||||
|
||||
The default implementation always returns True, but this can be overridden in the implementing class.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
|
||||
"""Provides an abstracted class for managing file attachments.
|
||||
|
||||
Links the implementing model to the common.models.Attachment table,
|
||||
@@ -484,33 +664,15 @@ class InvenTreeAttachmentMixin:
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def attachments(self):
|
||||
def attachments(self) -> QuerySet:
|
||||
"""Return a queryset containing all attachments for this model."""
|
||||
return self.attachments_for_model().filter(model_id=self.pk)
|
||||
|
||||
@classmethod
|
||||
def check_attachment_permission(cls, permission, user) -> bool:
|
||||
"""Check if the user has permission to perform the specified action on the attachment.
|
||||
|
||||
The default implementation runs a permission check against *this* model class,
|
||||
but this can be overridden in the implementing class if required.
|
||||
|
||||
Arguments:
|
||||
permission: The permission to check (add / change / view / delete)
|
||||
user: The user to check against
|
||||
|
||||
Returns:
|
||||
bool: True if the user has permission, False otherwise
|
||||
"""
|
||||
perm = f'{cls._meta.app_label}.{permission}_{cls._meta.model_name}'
|
||||
return user.has_perm(perm)
|
||||
|
||||
def attachments_for_model(self):
|
||||
def attachments_for_model(self) -> QuerySet:
|
||||
"""Return all attachments for this model class."""
|
||||
from common.models import Attachment
|
||||
|
||||
model_type = self.__class__.__name__.lower()
|
||||
|
||||
return Attachment.objects.filter(model_type=model_type)
|
||||
|
||||
def create_attachment(self, attachment=None, link=None, comment='', **kwargs):
|
||||
@@ -526,7 +688,7 @@ class InvenTreeAttachmentMixin:
|
||||
Attachment.objects.create(**kwargs)
|
||||
|
||||
|
||||
class InvenTreeTree(MPTTModel):
|
||||
class InvenTreeTree(ContentTypeMixin, MPTTModel):
|
||||
"""Provides an abstracted self-referencing tree model, based on the MPTTModel class.
|
||||
|
||||
Our implementation provides the following key improvements:
|
||||
@@ -757,7 +919,15 @@ class InvenTreeTree(MPTTModel):
|
||||
|
||||
if len(trees) > 0:
|
||||
# A tree update was performed, so we need to refresh the instance
|
||||
self.refresh_from_db()
|
||||
try:
|
||||
self.refresh_from_db()
|
||||
except TransactionManagementError:
|
||||
# If we are inside a transaction block, we cannot refresh from db
|
||||
pass
|
||||
except Exception as e:
|
||||
# Any other error is unexpected
|
||||
InvenTree.sentry.report_exception(e)
|
||||
InvenTree.exceptions.log_error(f'{self.__class__.__name__}.save')
|
||||
|
||||
def partial_rebuild(self, tree_id: int) -> bool:
|
||||
"""Perform a partial rebuild of the tree structure.
|
||||
@@ -1293,3 +1463,55 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
|
||||
'link': url,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvenTreeImageMixin(models.Model):
|
||||
"""A mixin class for adding image functionality to a model class.
|
||||
|
||||
The following fields are added to any model which implements this mixin:
|
||||
|
||||
- image : An image field for storing an image
|
||||
"""
|
||||
|
||||
IMAGE_RENAME: Callable | None = None
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options for this mixin.
|
||||
|
||||
Note: abstract must be true, as this is only a mixin, not a separate table
|
||||
"""
|
||||
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Custom init method for InvenTreeImageMixin to ensure IMAGE_RENAME is implemented."""
|
||||
if self.IMAGE_RENAME is None:
|
||||
raise NotImplementedError(
|
||||
'IMAGE_RENAME must be implemented in the model class'
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def rename_image(self, filename):
|
||||
"""Rename the uploaded image file using the IMAGE_RENAME function."""
|
||||
return self.IMAGE_RENAME(filename) # type: ignore
|
||||
|
||||
image = StdImageField(
|
||||
upload_to=rename_image,
|
||||
null=True,
|
||||
blank=True,
|
||||
variations={'thumbnail': (128, 128), 'preview': (256, 256)},
|
||||
delete_orphans=False,
|
||||
verbose_name=_('Image'),
|
||||
)
|
||||
|
||||
def get_image_url(self):
|
||||
"""Return the URL of the image for this object."""
|
||||
if self.image:
|
||||
return InvenTree.helpers.getMediaUrl(self.image)
|
||||
return InvenTree.helpers.getBlankImage()
|
||||
|
||||
def get_thumbnail_url(self) -> str:
|
||||
"""Return the URL of the image thumbnail for this object."""
|
||||
if self.image:
|
||||
return InvenTree.helpers.getMediaUrl(self.image, 'thumbnail')
|
||||
return InvenTree.helpers.getBlankThumbnail()
|
||||
|
||||