Compare commits

..

29 Commits

Author SHA1 Message Date
Matthias Mair
cc4535748e backport of https://github.com/inventree/InvenTree/pull/7620 (#7627) 2024-07-12 09:08:58 +10:00
github-actions[bot]
2329179070 Parameter value bug (#7601) (#7602)
* Handle out-of range numerical values

* Add unit test

(cherry picked from commit 84d076848a)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-07-09 21:49:15 +10:00
github-actions[bot]
50fdefa473 Fix import widget type (#7535) (#7536)
(cherry picked from commit 3b3352119f)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-06-29 20:50:02 +10:00
github-actions[bot]
1f522f47a5 Plugin load fix (#7505) (#7507)
* Cast setting to int

* Prevent single faulty plugin from killing *all* plugins

* Handle specific errors on _load_plugins

* Update unit test

(cherry picked from commit da42fdf06e)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-06-25 12:52:41 +10:00
github-actions[bot]
b17c835218 Add "showmigrations" task to invoke (#7482) (#7484)
- Helpful for debugging user installs

(cherry picked from commit 442f2594d0)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-06-20 21:22:36 +10:00
github-actions[bot]
91c5843425 Fix fields for PurchaseOrderCancelSerializer (#7481) (#7483)
- Throwing an error on an OPTIONS request

(cherry picked from commit 758871b8a9)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-06-20 20:56:13 +10:00
Oliver
b57f53c4cf Update version.py (#7458)
Bump version number to 0.15.5
2024-06-17 20:34:10 +10:00
github-actions[bot]
fa1a9da23a Improve stock item tracking API query (#7451) (#7453)
* Improve stock item tracking API query

- Cache related model lookups into single DB queries
- Significant improvements to query speed
- Ref: https://github.com/inventree/InvenTree/issues/7429

* Handle case where item does not exist in DB

(cherry picked from commit 79ea6897ea)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-06-16 18:54:11 +10:00
github-actions[bot]
fe09437214 Fix for gunicorn command (#7450) (#7452)
* Fix for gunicorn command

* Allow override of worker count

(cherry picked from commit 49f6981f46)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-06-16 16:25:06 +10:00
github-actions[bot]
f1dfced89b fix: Add installer check for python version (#7440) (#7441)
* fix: Add installer check for python version

Closes #7439
Closes #7437
Closes #7377

* fix error message

* Add color

(cherry picked from commit 960c27b142)

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-06-14 08:33:55 +10:00
github-actions[bot]
d7c76aab9d Update link for mobile app docs (#7378) (#7381)
(cherry picked from commit 74f4b85dfd)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-31 17:07:47 +10:00
github-actions[bot]
dfdaddbc7e Catch edge case for merge_stock_items: (#7373) (#7374)
* Catch edge case for merge_stock_items:

- Use current location as backup
- Handle null location

* Fix deltas

(cherry picked from commit 9fa2735f7a)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-30 20:13:28 +10:00
github-actions[bot]
1f6e52138a Plugin reload fix (#7361) (#7362)
- call registry.check_reload when registering an event
- ensure that latest versions of plugins are loaded

(cherry picked from commit 5577a086c9)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-28 01:10:36 +10:00
Oliver
e8c9ec076c Update version.py (#7353)
Bump version to 0.15.4
2024-05-27 20:16:07 +10:00
Oliver
d4d9aa9d1b Fixes for installer (#7344) (#7350)
* - move reqs file to contrib
- detect previously used python version
- safe extra requirements to INSTALLER_EXTRA

* add missing fi

* move site setting

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-05-27 19:45:38 +10:00
github-actions[bot]
54f2072e97 Fix for 'restore' command (#7348) (#7349)
- Fix typo

(cherry picked from commit bda237a13f)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-27 17:11:55 +10:00
github-actions[bot]
d1042cde0e Update docs (#7339) (#7340)
- Add note about permission denied error

(cherry picked from commit 5f9348f56d)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-26 21:50:11 +10:00
github-actions[bot]
5f4275679d PUI: Don't load stock test results for non-trackable part (#7327) (#7337)
(cherry picked from commit e8e64616da)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-26 20:54:45 +10:00
Oliver
1ba0bee1ea Update version.py (#7328)
Bump version number to 0.15.3
2024-05-26 20:50:45 +10:00
Oliver
ea7aa93a28 Merge pull request from GHSA-2crp-q9pc-457j (#7320)
* Merge pull request from GHSA-2crp-q9pc-457j

* ensure API login only works if mfa is not required

* add migration to log out users

* add migration to clear users

* Use `UV_SYSTEM_PYTHON` to allow the system Python interpreter instead of `VIRTUAL_ENV` (#7317)

* Fix docs links - pin to same branch

* Handle exception on migration

* Make migration non-atomic

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2024-05-24 23:36:00 +10:00
github-actions[bot]
9eccf69456 Add Meta subclass for build serializers (#7315) (#7316)
Ref: https://github.com/inventree/InvenTree/discussions/7314
(cherry picked from commit 0d46af7a74)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-24 09:21:40 +10:00
github-actions[bot]
9cebfa85df Add clearer error message for invalid SITE_URL (#7311) (#7312)
(cherry picked from commit 2fafb7f21c)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-23 23:43:54 +10:00
github-actions[bot]
af3cf62b8e fix: SELinux labels for Caddyfile (#7261) (#7262)
(cherry picked from commit b26640fb36)

Co-authored-by: Philipp Fruck <dev@p-fruck.de>
2024-05-20 09:14:57 +10:00
Oliver
f20a1245e7 Update version.py (#7252)
Bump version number to 0.15.2
2024-05-17 13:45:45 +10:00
github-actions[bot]
92a4989a8d Fix for email template (#7249) (#7251)
- Use `line.part` instead of `part`

(cherry picked from commit 2431fc6d58)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-17 13:44:32 +10:00
github-actions[bot]
be3b22ce36 Docker fix (#7228) (#7229)
* Copy requirements file

* Test more files when building docker image

* Refactor install task

* Raise exception

* Run install task

* Fix typos

- The tests work!

(cherry picked from commit 2265055785)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-15 09:24:22 +10:00
Oliver
258b8e4ecc Update version.py
Bump version to 0.15.1
2024-05-15 09:20:13 +10:00
github-actions[bot]
7df92aad03 Fix permissions for release.yaml (#7220) (#7221)
* Fix permissions for release.yaml

- 0.15.0 release currently borked

* Move permissions to individual job targets

(cherry picked from commit 3eae5096e3)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-05-14 22:11:39 +10:00
Oliver
2dac705779 Mark as release version (#7217) 2024-05-14 21:45:42 +10:00
4144 changed files with 1006992 additions and 819417 deletions

View File

@@ -1,43 +0,0 @@
# Dockerfile for the InvenTree devcontainer
# In contrast with the "production" image (which is based on an Alpine image)
# we use a Debian-based image for the devcontainer
FROM mcr.microsoft.com/devcontainers/python:3.11-bookworm@sha256:5140e54af7a0399a4932dd4c4653d085fcf451b093d7424867df1828ffbb9b81
# InvenTree paths
ENV INVENTREE_HOME="/home/inventree"
ENV INVENTREE_DATA_DIR="${INVENTREE_HOME}/dev"
ENV INVENTREE_STATIC_ROOT="${INVENTREE_DATA_DIR}/static"
ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DATA_DIR}/media"
ENV INVENTREE_BACKUP_DIR="${INVENTREE_DATA_DIR}/backup"
ENV INVENTREE_PLUGIN_DIR="${INVENTREE_DATA_DIR}/plugins"
ENV INVENTREE_CONFIG_FILE="${INVENTREE_DATA_DIR}/config.yaml"
ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DATA_DIR}/secret_key.txt"
ENV INVENTREE_OIDC_PRIVATE_KEY_FILE="${INVENTREE_DATA_DIR}/oidc.pem"
# Required for running playwright within devcontainer
ENV DISPLAY=:0
ENV LIBGL_ALWAYS_INDIRECT=1
COPY contrib/container/init.sh ./
RUN chmod +x init.sh
# Install required base packages
RUN apt update && apt install -y \
python3.11-dev python3.11-venv \
postgresql-client \
libldap2-dev libsasl2-dev \
libpango1.0-0 libcairo2 \
poppler-utils weasyprint
# Install packages required for frontend development
RUN apt install -y \
yarn nodejs npm
# Update to the latest stable node version
RUN npm install -g n --ignore-scripts && n lts
RUN yarn config set network-timeout 600000 -g
ENTRYPOINT ["/bin/bash", "./init.sh"]

View File

@@ -31,24 +31,17 @@
"ms-python.python", "ms-python.python",
"ms-python.vscode-pylance", "ms-python.vscode-pylance",
"batisteo.vscode-django", "batisteo.vscode-django",
"eamodio.gitlens", "eamodio.gitlens"
"biomejs.biome"
] ]
} }
}, },
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [5173, 5432, 6379, 8000, 8080], "forwardPorts": [5173, 8000, 8080],
"portsAttributes": { "portsAttributes": {
"5173": { "5173": {
"label": "Vite Server" "label": "Vite Server"
}, },
"5432": {
"label": "PostgreSQL Database"
},
"6379": {
"label": "Redis Server"
},
"8000": { "8000": {
"label": "InvenTree Server" "label": "InvenTree Server"
}, },

View File

@@ -1,46 +1,43 @@
version: "3"
services: services:
db: db:
image: postgres:15 image: postgres:13
restart: unless-stopped restart: unless-stopped
ports: expose:
- 5432/tcp - 5432/tcp
volumes: volumes:
- ../dev-db/:/var/lib/postgresql/data:z - inventreedatabase:/var/lib/postgresql/data:z
environment: environment:
POSTGRES_DB: inventree POSTGRES_DB: inventree
POSTGRES_USER: inventree_user POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password POSTGRES_PASSWORD: inventree_password
redis:
image: redis:7.0
restart: always
ports:
- 6379
inventree: inventree:
ports:
- 8000:8000
build: build:
context: .. context: ..
dockerfile: .devcontainer/Dockerfile dockerfile: ../InvenTree/contrib/container/Dockerfile
target: dev
args:
base_image: "mcr.microsoft.com/vscode/devcontainers/base:alpine-3.18"
data_dir: "dev"
volumes: volumes:
- ../:/home/inventree:z - ../:/home/inventree:z
- /tmp/.X11-unix:/tmp/.X11-unix
environment: environment:
INVENTREE_DEBUG: True
INVENTREE_DB_ENGINE: postgresql INVENTREE_DB_ENGINE: postgresql
INVENTREE_DB_NAME: inventree INVENTREE_DB_NAME: inventree
INVENTREE_DB_HOST: db INVENTREE_DB_HOST: db
INVENTREE_DB_USER: inventree_user INVENTREE_DB_USER: inventree_user
INVENTREE_DB_PASSWORD: inventree_password INVENTREE_DB_PASSWORD: inventree_password
INVENTREE_DEBUG: True
INVENTREE_CACHE_HOST: redis
INVENTREE_CACHE_PORT: 6379
INVENTREE_PLUGINS_ENABLED: True INVENTREE_PLUGINS_ENABLED: True
INVENTREE_SITE_URL: http://localhost:8000 INVENTREE_SITE_URL: http://localhost:8000
INVENTREE_CORS_ORIGIN_ALLOW_ALL: True INVENTREE_CORS_ORIGIN_ALLOW_ALL: True
INVENTREE_PY_ENV: /home/inventree/dev/venv INVENTREE_PY_ENV: /home/inventree/dev/venv
INVENTREE_DEVCONTAINER: True
depends_on: depends_on:
- db - db
volumes:
inventreedatabase:

View File

@@ -1,7 +1,4 @@
#!/bin/bash #!/bin/bash
set -e
echo "Running postCreateCommand.sh ..."
# Avoiding Dubious Ownership in Dev Containers for setup commands that use git # Avoiding Dubious Ownership in Dev Containers for setup commands that use git
git config --global --add safe.directory /home/inventree git config --global --add safe.directory /home/inventree
@@ -10,30 +7,16 @@ git config --global --add safe.directory /home/inventree
python3 -m venv /home/inventree/dev/venv --system-site-packages --upgrade-deps python3 -m venv /home/inventree/dev/venv --system-site-packages --upgrade-deps
. /home/inventree/dev/venv/bin/activate . /home/inventree/dev/venv/bin/activate
# remove existing gitconfig created by "Avoiding Dubious Ownership" step
# so that it gets copied from host to the container to have your global
# git config in container
rm -f /home/vscode/.gitconfig
# Fix issue related to CFFI version mismatch
pip uninstall cffi -y
sudo apt remove --purge -y python3-cffi
pip install --no-cache-dir --force-reinstall --ignore-installed cffi
# Upgrade pip
python3 -m pip install --upgrade pip
# Ensure the correct invoke is available
pip3 install --ignore-installed --upgrade invoke Pillow
# install base level packages
pip3 install -Ur contrib/container/requirements.txt --require-hashes
# Run initial InvenTree server setup # Run initial InvenTree server setup
invoke update -s invoke update -s
# Configure dev environment # Configure dev environment
invoke dev.setup-dev invoke setup-dev
# Install required frontend packages # Install required frontend packages
invoke int.frontend-install invoke frontend-install
# remove existing gitconfig created by "Avoiding Dubious Ownership" step
# so that it gets copied from host to the container to have your global
# git config in container
rm -f /home/vscode/.gitconfig

View File

@@ -1,79 +0,0 @@
trigger:
batch: true
branches:
include:
- master
- stable
- refs/tags/*
paths:
include:
- src/backend
pool:
vmImage: ubuntu-latest
strategy:
matrix:
Python39:
PYTHON_VERSION: '3.11'
maxParallel: 3
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
architecture: 'x64'
- task: PythonScript@0
displayName: 'Export project path'
inputs:
scriptSource: 'inline'
script: |
"""Search all subdirectories for `manage.py`."""
from glob import iglob
from os import path
# Python >= 3.5
manage_py = next(iglob(path.join('**', 'manage.py'), recursive=True), None)
if not manage_py:
raise SystemExit('Could not find a Django project')
project_location = path.dirname(path.abspath(manage_py))
print('Found Django project in', project_location)
print('##vso[task.setvariable variable=projectRoot]{}'.format(project_location))
- script: |
python -m pip install --upgrade pip setuptools wheel uv
uv pip install --require-hashes -r src/backend/requirements.txt
uv pip install --require-hashes -r src/backend/requirements-dev.txt
sudo apt-get install poppler-utils
sudo apt-get install libpoppler-dev
uv pip install unittest-xml-reporting coverage invoke
displayName: 'Install prerequisites'
env:
UV_SYSTEM_PYTHON: 1
- script: |
pushd '$(projectRoot)'
invoke update --uv
coverage run manage.py test --testrunner xmlrunner.extra.djangotestrunner.XMLTestRunner --no-input
coverage xml -i
displayName: 'Run tests'
env:
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
INVENTREE_BACKUP_DIR: ./backup
INVENTREE_SITE_URL: http://localhost:8000
INVENTREE_PLUGINS_ENABLED: true
UV_SYSTEM_PYTHON: 1
INVENTREE_DEBUG: true
INVENTREE_LOG_LEVEL: INFO
- task: PublishTestResults@2
inputs:
testResultsFiles: "**/TEST-*.xml"
testRunTitle: 'Python $(PYTHON_VERSION)'
condition: succeededOrFailed()
- task: PublishCodeCoverageResults@2
inputs:
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'

71
.devops/testing_ci.yml Normal file
View File

@@ -0,0 +1,71 @@
# Python Django
# Test a Django project on multiple versions of Python.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
trigger:
- master
pool:
vmImage: ubuntu-latest
strategy:
matrix:
Python39:
PYTHON_VERSION: '3.9'
maxParallel: 3
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
architecture: 'x64'
- task: PythonScript@0
displayName: 'Export project path'
inputs:
scriptSource: 'inline'
script: |
"""Search all subdirectories for `manage.py`."""
from glob import iglob
from os import path
# Python >= 3.5
manage_py = next(iglob(path.join('**', 'manage.py'), recursive=True), None)
if not manage_py:
raise SystemExit('Could not find a Django project')
project_location = path.dirname(path.abspath(manage_py))
print('Found Django project in', project_location)
print('##vso[task.setvariable variable=projectRoot]{}'.format(project_location))
- script: |
python -m pip install --upgrade pip setuptools wheel
pip install --require-hashes -r requirements.txt
pip install --require-hashes -r requirements-dev.txt
pip install unittest-xml-reporting coverage invoke
sudo apt-get install poppler-utils
sudo apt-get install libpoppler-dev
displayName: 'Install prerequisites'
- script: |
pushd '$(projectRoot)'
invoke update
coverage run manage.py test --testrunner xmlrunner.extra.djangotestrunner.XMLTestRunner --no-input
coverage xml -i
displayName: 'Run tests'
env:
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
INVENTREE_BACKUP_DIR: ./backup
INVENTREE_PLUGINS_ENABLED: true
- task: PublishTestResults@2
inputs:
testResultsFiles: "**/TEST-*.xml"
testRunTitle: 'Python $(PYTHON_VERSION)'
condition: succeededOrFailed()
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'

4
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,5 @@
polar: inventree
github: inventree github: inventree
ko_fi: inventree
patreon: inventree
polar: inventree
custom: [paypal.me/inventree] custom: [paypal.me/inventree]

View File

@@ -6,7 +6,7 @@ body:
id: no-duplicate-issues id: no-duplicate-issues
attributes: attributes:
label: "Please verify that this bug has NOT been raised before." label: "Please verify that this bug has NOT been raised before."
description: "Search in the issues sections by clicking [HERE](https://github.com/inventree/inventree/issues?q=) and read the [Frequently Asked Questions](https://docs.inventree.org/en/latest/sref/faq)!" description: "Search in the issues sections by clicking [HERE](https://github.com/inventree/inventree/issues?q=) and read the [Frequently Asked Questions](https://docs.inventree.org/en/latest/faq/)!"
options: options:
- label: "I checked and didn't find a similar issue" - label: "I checked and didn't find a similar issue"
required: true required: true
@@ -37,15 +37,15 @@ body:
label: "Expected behaviour" label: "Expected behaviour"
description: "A clear and concise description of what you expected to happen." description: "A clear and concise description of what you expected to happen."
placeholder: "..." placeholder: "..."
- type: dropdown - type: checkboxes
id: deployment id: deployment
attributes: attributes:
label: "Deployment Method" label: "Deployment Method"
options: options:
- Docker - label: "Docker"
- Package - label: "Package"
- Bare metal - label: "Bare metal"
- Other - added info in Steps to Reproduce - label: "Other - added info in Steps to Reproduce"
- type: textarea - type: textarea
id: version-info id: version-info
validations: validations:
@@ -54,25 +54,13 @@ body:
label: "Version Information" label: "Version Information"
description: "The version info block." description: "The version info block."
placeholder: "You can get this by going to the `About InvenTree` section in the upper right corner and clicking on the `copy version information` button" placeholder: "You can get this by going to the `About InvenTree` section in the upper right corner and clicking on the `copy version information` button"
- type: dropdown - type: checkboxes
id: tried-reproduce id: can-reproduce
attributes: attributes:
label: Try to reproduce on the demo site label: "Please verify if you can reproduce this bug on the demo site."
description: You can sign in at [InvenTree Demo](https://demo.inventree.org) with admin:inventree. Note that this instance runs on the latest dev version, so your bug may be fixed there. description: "You can sign in at [InvenTree Demo](https://demo.inventree.org) with admin:inventree. Note that this instance runs on the latest dev version, so your bug may be fixed there."
options: options:
- I did not try to reproduce - label: "I can reproduce this bug on the demo site."
- I tried to reproduce
validations:
required: true
- type: dropdown
id: result-reproduce
attributes:
label: Is the bug reproducible on the demo site?
options:
- Not reproducible
- Reproducible
validations:
required: true
- type: textarea - type: textarea
id: logs id: logs
attributes: attributes:

View File

@@ -1 +0,0 @@
blank_issues_enabled: false

View File

@@ -9,9 +9,9 @@ runs:
shell: bash shell: bash
run: | run: |
invoke migrate invoke migrate
invoke dev.import-fixtures invoke import-fixtures
invoke export-records -f data.json invoke export-records -f data.json
python3 ./src/backend/InvenTree/manage.py flush --noinput python3 ./src/backend/InvenTree/manage.py flush --noinput
invoke migrate invoke migrate
invoke import-records -c -f data.json invoke import-records -f data.json
invoke import-records -c -f data.json invoke import-records -f data.json

View File

@@ -35,9 +35,7 @@ runs:
using: 'composite' using: 'composite'
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
with:
persist-credentials: false
# Python installs # Python installs
- name: Set up Python ${{ env.python_version }} - name: Set up Python ${{ env.python_version }}
@@ -46,27 +44,20 @@ runs:
with: with:
python-version: ${{ env.python_version }} python-version: ${{ env.python_version }}
cache: pip cache: pip
cache-dependency-path: |
src/backend/requirements.txt
src/backend/requirements-dev.txt
contrib/container/requirements.txt
contrib/dev_reqs/requirements.txt
- name: Install Base Python Dependencies - name: Install Base Python Dependencies
if: ${{ inputs.python == 'true' }} if: ${{ inputs.python == 'true' }}
shell: bash shell: bash
run: | run: |
python3 -m pip install -U pip python3 -m pip install -U pip
pip3 install -U invoke wheel pip3 install -U invoke wheel
pip3 install 'uv>=0.9.6' pip3 install 'uv<0.3.0'
- name: Allow uv to use the system Python by default - name: Allow uv to use the system Python by default
run: echo "UV_SYSTEM_PYTHON=1" >> $GITHUB_ENV run: echo "UV_SYSTEM_PYTHON=1" >> $GITHUB_ENV
shell: bash shell: bash
- name: Install Specific Python Dependencies - name: Install Specific Python Dependencies
if: ${{ inputs.pip-dependency }} if: ${{ inputs.pip-dependency }}
shell: bash shell: bash
run: uv pip install ${PIP_DEPS} run: uv pip install ${{ inputs.pip-dependency }}
env:
PIP_DEPS: ${{ inputs.pip-dependency }}
# NPM installs # NPM installs
- name: Install node.js ${{ env.node_version }} - name: Install node.js ${{ env.node_version }}
@@ -74,20 +65,25 @@ runs:
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # pin to v3.8.2 uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # pin to v3.8.2
with: with:
node-version: ${{ env.node_version }} node-version: ${{ env.node_version }}
cache: 'npm'
cache-dependency-path: src/backend/package-lock.json
- name: Install npm packages
if: ${{ inputs.npm == 'true' }}
shell: bash
run: cd src/backend && npm install
# OS installs # OS installs
- name: Install OS Dependencies - name: Install OS Dependencies
if: ${{ inputs.apt-dependency }} if: ${{ inputs.apt-dependency }}
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install ${APT_DEPS} sudo apt-get install ${{ inputs.apt-dependency }}
sudo apt-get install ${APT_DEPS} sudo apt-get install ${{ inputs.apt-dependency }}
env:
APT_DEPS: ${{ inputs.apt-dependency }}
# Invoke commands # Invoke commands
- name: Install dev requirements - name: Install dev requirements
if: ${{ inputs.dev-install == 'true' || inputs.install == 'true' }} if: ${{ inputs.dev-install == 'true' ||inputs.install == 'true' }}
shell: bash shell: bash
run: uv pip install --require-hashes -r src/backend/requirements-dev.txt run: uv pip install --require-hashes -r src/backend/requirements-dev.txt
- name: Run invoke install - name: Run invoke install
@@ -97,4 +93,4 @@ runs:
- name: Run invoke update - name: Run invoke update
if: ${{ inputs.update == 'true' }} if: ${{ inputs.update == 'true' }}
shell: bash shell: bash
run: invoke update --uv --skip-backup --skip-static run: invoke update --uv

View File

@@ -4,8 +4,6 @@ updates:
directory: / directory: /
schedule: schedule:
interval: weekly interval: weekly
cooldown:
default-days: 7
groups: groups:
dependencies: dependencies:
patterns: patterns:
@@ -15,42 +13,44 @@ updates:
directory: /contrib/container directory: /contrib/container
schedule: schedule:
interval: weekly interval: weekly
cooldown:
default-days: 7
- package-ecosystem: docker
directory: /.devcontainer
schedule:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: pip - package-ecosystem: pip
directories: directory: /contrib/container
- /docs schedule:
- /contrib/dev_reqs interval: weekly
- /contrib/container
- /src/backend - package-ecosystem: pip
directory: /docs
schedule:
interval: weekly
- package-ecosystem: pip
directory: /.github
schedule:
interval: weekly
- package-ecosystem: pip
directory: /src/backend
schedule: schedule:
interval: weekly interval: weekly
day: friday
cooldown:
default-days: 7
groups: groups:
dependencies: dependencies:
patterns: patterns:
- "*" # Include all dependencies - "*" # Include all dependencies
assignees:
- "matmair"
versioning-strategy: increase
- package-ecosystem: npm - package-ecosystem: npm
directories: directory: /src/backend
- /src/frontend schedule:
interval: weekly
groups:
dependencies:
patterns:
- "*" # Include all dependencies
- package-ecosystem: npm
directory: /src/frontend
schedule: schedule:
interval: weekly interval: weekly
cooldown:
default-days: 7
groups: groups:
dependencies: dependencies:
patterns: patterns:

4
.github/release.yml vendored
View File

@@ -4,7 +4,6 @@ changelog:
exclude: exclude:
labels: labels:
- translation - translation
- translations
- documentation - documentation
categories: categories:
- title: Breaking Changes - title: Breaking Changes
@@ -14,9 +13,6 @@ changelog:
- title: Security Patches - title: Security Patches
labels: labels:
- security - security
- title: Database Changes
labels:
- migration
- title: New Features - title: New Features
labels: labels:
- Semver-Minor - Semver-Minor

103
.github/scripts/check_js_templates.py vendored Normal file
View File

@@ -0,0 +1,103 @@
"""Test that the "translated" javascript files to not contain template tags which need to be determined at "run time".
This is because the "translated" javascript files are compiled into the "static" directory.
They should only contain template tags that render static information.
"""
import os
import pathlib
import re
import sys
here = os.path.abspath(os.path.dirname(__file__))
template_dir = os.path.abspath(os.path.join(here, '..', 'InvenTree', 'templates'))
# We only care about the 'translated' files
js_i18n_dir = os.path.join(template_dir, 'js', 'translated')
js_dynamic_dir = os.path.join(template_dir, 'js', 'dynamic')
errors = 0
print('=================================')
print('Checking static javascript files:')
print('=================================')
def check_invalid_tag(data):
"""Check for invalid tags."""
pattern = r'{%(\w+)'
err_count = 0
for idx, line in enumerate(data):
results = re.findall(pattern, line)
for result in results:
err_count += 1
print(f' - Error on line {idx + 1}: %{{{result[0]}')
return err_count
def check_prohibited_tags(data):
"""Check for prohibited tags."""
allowed_tags = [
'if',
'elif',
'else',
'endif',
'for',
'endfor',
'trans',
'load',
'include',
'url',
]
pattern = r'{% (\w+)\s'
err_count = 0
for idx, line in enumerate(data):
for tag in re.findall(pattern, line):
if tag not in allowed_tags:
print(f" > Line {idx + 1} contains prohibited template tag '{tag}'")
err_count += 1
return err_count
for filename in pathlib.Path(js_i18n_dir).rglob('*.js'):
print(f"Checking file 'translated/{os.path.basename(filename)}':")
with open(filename, 'r') as js_file:
data = js_file.readlines()
errors += check_invalid_tag(data)
errors += check_prohibited_tags(data)
for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'):
print(f"Checking file 'dynamic/{os.path.basename(filename)}':")
# Check that the 'dynamic' files do not contains any translated strings
with open(filename, 'r') as js_file:
data = js_file.readlines()
invalid_tags = ['blocktrans', 'blocktranslate', 'trans', 'translate']
err_count = 0
for idx, line in enumerate(data):
for tag in invalid_tags:
tag = '{% ' + tag
if tag in line:
err_count += 1
print(f" > Error on line {idx + 1}: Prohibited tag '{tag}' found")
if errors > 0:
print(f'Found {errors} incorrect template tags')
sys.exit(errors)

View File

@@ -20,9 +20,9 @@ for line in str(out.decode()).split('\n'):
if len(migrations) == 0: if len(migrations) == 0:
sys.exit(0) sys.exit(0)
print(f'There are {len(migrations)} unstaged migration files:') print('There are {n} unstaged migration files:'.format(n=len(migrations)))
for m in migrations: for m in migrations:
print(f' - {m}') print(' - {m}'.format(m=m))
sys.exit(len(migrations)) sys.exit(len(migrations))

View File

@@ -1,100 +0,0 @@
"""Script to check source strings for translations."""
import argparse
import os
import rapidfuzz
BACKEND_SOURCE_FILE = [
'..',
'..',
'src',
'backend',
'InvenTree',
'locale',
'en',
'LC_MESSAGES',
'django.po',
]
FRONTEND_SOURCE_FILE = [
'..',
'..',
'src',
'frontend',
'src',
'locales',
'en',
'messages.po',
]
def extract_source_strings(file_path):
"""Extract source strings from the provided file."""
here = os.path.abspath(os.path.dirname(__file__))
abs_file_path = os.path.abspath(os.path.join(here, *file_path))
sources = []
with open(abs_file_path, encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith('msgid '):
msgid = line[6:].strip()
if msgid in sources:
print(f'Duplicate source string: {msgid}')
else:
sources.append(msgid)
return sources
def compare_source_strings(sources, threshold):
"""Compare source strings to find duplicates (or close matches)."""
issues = 0
for i, source in enumerate(sources):
for other in sources[i + 1 :]:
if other.lower() == source.lower():
print(f'- Duplicate: {source} ~ {other}')
issues += 1
continue
ratio = rapidfuzz.fuzz.ratio(source, other)
if ratio > threshold:
print(f'- Close match: {source} ~ {other} ({ratio:.1f}%)')
issues += 1
if issues:
print(f' - Found {issues} issues.')
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Check source strings for translations.'
)
parser.add_argument(
'--backend', action='store_true', help='Check backend source strings'
)
parser.add_argument(
'--frontend', action='store_true', help='Check frontend source strings'
)
parser.add_argument(
'--threshold',
type=int,
help='Set the threshold for string comparison',
default=99,
)
args = parser.parse_args()
if args.backend:
backend_sources = extract_source_strings(BACKEND_SOURCE_FILE)
print('Backend source strings:', len(backend_sources))
compare_source_strings(backend_sources, args.threshold)
if args.frontend:
frontend_sources = extract_source_strings(FRONTEND_SOURCE_FILE)
print('Frontend source strings:', len(frontend_sources))
compare_source_strings(frontend_sources, args.threshold)

View File

@@ -10,108 +10,16 @@ tagged branch:
""" """
import argparse
import itertools
import json import json
import os import os
import re import re
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Optional
import requests import requests
REPO = os.getenv('GITHUB_REPOSITORY', 'inventree/inventree')
GITHUB_API_URL = os.getenv('GITHUB_API_URL', 'https://api.github.com')
def get_existing_release_tags():
def get_src_dir() -> Path:
"""Return the path to the InvenTree source directory."""
here = Path(__file__).parent.absolute()
src_dir = here.joinpath('..', '..', 'src', 'backend', 'InvenTree', 'InvenTree')
if not src_dir.exists():
raise FileNotFoundError(
f"Could not find InvenTree source directory: '{src_dir}'"
)
return src_dir
def get_inventree_version() -> str:
"""Return the InvenTree version string."""
src_dir = get_src_dir()
version_file = src_dir.joinpath('version.py')
if not version_file.exists():
raise FileNotFoundError(
f"Could not find InvenTree version file: '{version_file}'"
)
with open(version_file, encoding='utf-8') as f:
text = f.read()
# Extract the InvenTree software version
results = re.findall(r"""INVENTREE_SW_VERSION = '(.*)'""", text)
if len(results) != 1:
raise ValueError(f'Could not find INVENTREE_SW_VERSION in {version_file}')
return results[0]
def get_api_version() -> str:
"""Return the InvenTree API version string."""
src_dir = get_src_dir()
api_version_file = src_dir.joinpath('api_version.py')
if not api_version_file.exists():
raise FileNotFoundError(
f"Could not find InvenTree API version file: '{api_version_file}'"
)
with open(api_version_file, encoding='utf-8') as f:
text = f.read()
# Extract the InvenTree software version
results = re.findall(r"""INVENTREE_API_VERSION = (.*)""", text)
if len(results) != 1:
raise ValueError(
f'Could not find INVENTREE_API_VERSION in {api_version_file}'
)
return results[0].strip().strip('"').strip("'")
def version_number_to_tuple(version_string: str) -> tuple[int, int, int, str]:
"""Validate a version number string, and convert to a tuple of integers.
e.g. 1.1.0
e.g. 1.1.0 dev
e.g. 1.2.3-rc2
"""
pattern = r'^(\d+)\.(\d+)\.(\d+)[\s-]?(.*)?$'
match = re.match(pattern, version_string)
if not match or len(match.groups()) < 3:
raise ValueError(
f"Version string '{version_string}' did not match required pattern"
)
result = tuple(int(x) for x in match.groups()[:3])
# Add optional prerelease tag
if len(match.groups()) > 3:
result += (match.groups()[3] or '',)
else:
result += ('',)
return result
def get_existing_release_tags(include_prerelease: bool = True):
"""Request information on existing releases via the GitHub API.""" """Request information on existing releases via the GitHub API."""
# Check for github token # Check for github token
token = os.getenv('GITHUB_TOKEN', None) token = os.getenv('GITHUB_TOKEN', None)
@@ -120,7 +28,9 @@ def get_existing_release_tags(include_prerelease: bool = True):
if token: if token:
headers = {'Authorization': f'Bearer {token}'} headers = {'Authorization': f'Bearer {token}'}
response = requests.get(f'{GITHUB_API_URL}/repos/{REPO}/releases', headers=headers) response = requests.get(
'https://api.github.com/repos/inventree/inventree/releases', headers=headers
)
if response.status_code != 200: if response.status_code != 200:
raise ValueError( raise ValueError(
@@ -134,16 +44,13 @@ def get_existing_release_tags(include_prerelease: bool = True):
for release in data: for release in data:
tag = release['tag_name'].strip() tag = release['tag_name'].strip()
match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', tag)
version_tuple = version_number_to_tuple(tag) if len(match.groups()) != 3:
print(f"Version '{tag}' did not match expected pattern")
continue
if len(version_tuple) >= 4 and version_tuple[3]: tags.append([int(x) for x in match.groups()])
# Skip prerelease tags
if not include_prerelease:
print('-- skipping prerelease tag:', tag)
continue
tags.append(tag)
return tags return tags
@@ -155,83 +62,51 @@ def check_version_number(version_string, allow_duplicate=False):
""" """
print(f"Checking version '{version_string}'") print(f"Checking version '{version_string}'")
version_tuple = version_number_to_tuple(version_string) # Check that the version string matches the required format
match = re.match(r'^(\d+)\.(\d+)\.(\d+)(?: dev)?$', version_string)
if not match or len(match.groups()) != 3:
raise ValueError(
f"Version string '{version_string}' did not match required pattern"
)
version_tuple = [int(x) for x in match.groups()]
# Look through the existing releases # Look through the existing releases
existing = get_existing_release_tags(include_prerelease=False) existing = get_existing_release_tags()
# Assume that this is the highest release, unless told otherwise # Assume that this is the highest release, unless told otherwise
highest_release = True highest_release = True
# A non-standard tag cannot be the 'highest' release
if len(version_tuple) >= 4 and version_tuple[3]:
highest_release = False
print(f"-- Version tag '{version_string}' cannot be the highest release")
for release in existing: for release in existing:
if version_string == release and not allow_duplicate: if release == version_tuple and not allow_duplicate:
raise ValueError(f"Duplicate release '{version_string}' exists!") raise ValueError(f"Duplicate release '{version_string}' exists!")
release_tuple = version_number_to_tuple(release) if release > version_tuple:
if release_tuple > version_tuple:
highest_release = False highest_release = False
print(f'Found newer release: {release!s}') print(f'Found newer release: {str(release)}')
if highest_release:
print(f"-- Version '{version_string}' is the highest release")
return highest_release return highest_release
def main() -> bool: if __name__ == '__main__':
"""Run the version check.""" if 'only_version' in sys.argv:
parser = argparse.ArgumentParser(description='InvenTree Version Check') here = Path(__file__).parent.absolute()
parser.add_argument( version_file = here.joinpath(
'--show-version', '..', '..', 'src', 'backend', 'InvenTree', 'InvenTree', 'api_version.py'
action='store_true', )
help='Print the InvenTree version and exit', text = version_file.read_text()
) results = re.findall(r"""INVENTREE_API_VERSION = (.*)""", text)
parser.add_argument( print(results[0])
'--show-api-version', exit(0)
action='store_true',
help='Print the InvenTree API version and exit',
)
parser.add_argument(
'--decrement-api',
type=str,
default='false',
help='Decrement the API version by 1 and print',
)
args = parser.parse_args()
inventree_version = get_inventree_version()
inventree_api_version = int(get_api_version())
if args.show_version:
print(inventree_version)
sys.exit(0)
if args.show_api_version:
if str(args.decrement_api).strip().lower() == 'true':
inventree_api_version -= 1
print(inventree_api_version)
sys.exit(0)
# Ensure that we are running in GH Actions
if os.environ.get('GITHUB_ACTIONS', '') != 'true':
print('This script is intended to be run within a GitHub Action!')
return False
print('Running InvenTree version check...')
# GITHUB_REF_TYPE may be either 'branch' or 'tag' # GITHUB_REF_TYPE may be either 'branch' or 'tag'
GITHUB_REF_TYPE = os.environ['GITHUB_REF_TYPE'] GITHUB_REF_TYPE = os.environ['GITHUB_REF_TYPE']
# GITHUB_REF may be either 'refs/heads/<branch>' or 'refs/heads/<tag>' # GITHUB_REF may be either 'refs/heads/<branch>' or 'refs/heads/<tag>'
GITHUB_REF = os.environ['GITHUB_REF'] GITHUB_REF = os.environ['GITHUB_REF']
GITHUB_REF_NAME = os.environ['GITHUB_REF_NAME'] GITHUB_REF_NAME = os.environ['GITHUB_REF_NAME']
GITHUB_BASE_REF = os.environ['GITHUB_BASE_REF'] GITHUB_BASE_REF = os.environ['GITHUB_BASE_REF']
# Print out version information, makes debugging actions *much* easier! # Print out version information, makes debugging actions *much* easier!
@@ -240,10 +115,26 @@ def main() -> bool:
print(f'GITHUB_REF_TYPE: {GITHUB_REF_TYPE}') print(f'GITHUB_REF_TYPE: {GITHUB_REF_TYPE}')
print(f'GITHUB_BASE_REF: {GITHUB_BASE_REF}') print(f'GITHUB_BASE_REF: {GITHUB_BASE_REF}')
print( here = Path(__file__).parent.absolute()
f"InvenTree Version: '{inventree_version}' - {version_number_to_tuple(inventree_version)}" version_file = here.joinpath(
'..', '..', 'src', 'backend', 'InvenTree', 'InvenTree', 'version.py'
) )
print(f"InvenTree API Version: '{inventree_api_version}'")
version = None
with open(version_file, 'r') as f:
text = f.read()
# Extract the InvenTree software version
results = re.findall(r"""INVENTREE_SW_VERSION = '(.*)'""", text)
if len(results) != 1:
print(f'Could not find INVENTREE_SW_VERSION in {version_file}')
sys.exit(1)
version = results[0]
print(f"InvenTree Version: '{version}'")
# Check version number and look for existing versions # Check version number and look for existing versions
# If a release is found which matches the current tag, throw an error # If a release is found which matches the current tag, throw an error
@@ -258,63 +149,50 @@ def main() -> bool:
if GITHUB_BASE_REF == 'stable': if GITHUB_BASE_REF == 'stable':
allow_duplicate = True allow_duplicate = True
highest_release = check_version_number( highest_release = check_version_number(version, allow_duplicate=allow_duplicate)
inventree_version, allow_duplicate=allow_duplicate
)
# Determine which docker tag we are going to use # Determine which docker tag we are going to use
docker_tags: Optional[list[str]] = None docker_tags = None
if GITHUB_REF_TYPE == 'tag': if GITHUB_REF_TYPE == 'tag':
# GITHUB_REF should be of the form /refs/heads/<tag> # GITHUB_REF should be of the form /refs/heads/<tag>
version_tag: str = GITHUB_REF.split('/')[-1] version_tag = GITHUB_REF.split('/')[-1]
print(f"Checking requirements for tagged release - '{version_tag}':") print(f"Checking requirements for tagged release - '{version_tag}':")
if version_tag != inventree_version: if version_tag != version:
print( print(f"Version number '{version}' does not match tag '{version_tag}'")
f"Version number '{inventree_version}' does not match tag '{version_tag}'"
)
sys.exit sys.exit
docker_tags = [version_tag, 'stable'] if highest_release else [version_tag] if highest_release:
docker_tags = [version_tag, 'stable']
else:
docker_tags = [version_tag]
elif GITHUB_REF_TYPE == 'branch': elif GITHUB_REF_TYPE == 'branch':
# Otherwise we know we are targeting the 'master' branch # Otherwise we know we are targeting the 'master' branch
docker_tags = ['latest'] docker_tags = ['latest']
highest_release = False
else: else:
print('Unsupported branch / version combination:') print('Unsupported branch / version combination:')
print(f'InvenTree Version: {inventree_version}') print(f'InvenTree Version: {version}')
print('GITHUB_REF_TYPE:', GITHUB_REF_TYPE) print('GITHUB_REF_TYPE:', GITHUB_REF_TYPE)
print('GITHUB_BASE_REF:', GITHUB_BASE_REF) print('GITHUB_BASE_REF:', GITHUB_BASE_REF)
print('GITHUB_REF:', GITHUB_REF) print('GITHUB_REF:', GITHUB_REF)
return False sys.exit(1)
if docker_tags is None: if docker_tags is None:
print('Docker tags could not be determined') print('Docker tags could not be determined')
return False sys.exit(1)
print(f"Version check passed for '{inventree_version}'!") print(f"Version check passed for '{version}'!")
print(f"Docker tags: '{docker_tags}'") print(f"Docker tags: '{docker_tags}'")
target_repos = [REPO.lower(), f'ghcr.io/{REPO.lower()}']
# Ref: https://getridbug.com/python/how-to-set-environment-variables-in-github-actions-using-python/ # Ref: https://getridbug.com/python/how-to-set-environment-variables-in-github-actions-using-python/
with open(os.getenv('GITHUB_ENV'), 'a', encoding='utf-8') as env_file: with open(os.getenv('GITHUB_ENV'), 'a') as env_file:
# Construct tag string # Construct tag string
tag_list = [[f'{r}:{t}' for t in docker_tags] for r in target_repos] tags = ','.join([f'inventree/inventree:{tag}' for tag in docker_tags])
tags = ','.join(itertools.chain(*tag_list))
env_file.write(f'docker_tags={tags}\n') env_file.write(f'docker_tags={tags}\n')
if GITHUB_REF_TYPE == 'tag' and highest_release: if GITHUB_REF_TYPE == 'tag' and highest_release:
env_file.write('stable_release=true\n') env_file.write('stable_release=true\n')
return True
if __name__ == '__main__':
rslt = main()
if rslt is not True:
print('Version check failed!')
sys.exit(1)

View File

@@ -25,7 +25,7 @@ jobs:
) )
steps: steps:
- name: Backport Action - name: Backport Action
uses: sqren/backport-github-action@ad888e978060bc1b2798690dd9d03c4036560947 # pin@v9.2.2 uses: sqren/backport-github-action@f54e19901f2a57f8b82360f2490d47ee82ec82c6 # pin@v9.2.2
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
auto_backport_label_prefix: backport-to- auto_backport_label_prefix: backport-to-

View File

@@ -9,7 +9,7 @@ on:
- l10 - l10
env: env:
python_version: 3.11 python_version: 3.9
permissions: permissions:
contents: read contents: read
@@ -22,8 +22,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_NAME: "./test_db.sqlite" INVENTREE_DB_NAME: "./test_db.sqlite"
INVENTREE_DB_ENGINE: django.db.backends.sqlite3 INVENTREE_DB_ENGINE: django.db.backends.sqlite3
INVENTREE_DEBUG: true INVENTREE_DEBUG: info
INVENTREE_LOG_LEVEL: INFO
INVENTREE_MEDIA_ROOT: ./media INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static INVENTREE_STATIC_ROOT: ./static
INVENTREE_BACKUP_DIR: ./backup INVENTREE_BACKUP_DIR: ./backup
@@ -31,19 +30,13 @@ jobs:
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
install: true install: true
apt-dependency: gettext apt-dependency: gettext
- name: Test Translations - name: Test Translations
run: invoke dev.translate run: invoke translate
- name: Check for Duplicates
run: |
python ./.github/scripts/check_source_strings.py --frontend --backend
- name: Check Migration Files - name: Check Migration Files
run: python3 .github/scripts/check_migration_files.py run: python3 .github/scripts/check_migration_files.py

View File

@@ -39,9 +39,7 @@ jobs:
docker: ${{ steps.filter.outputs.docker }} docker: ${{ steps.filter.outputs.docker }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2 - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
id: filter id: filter
with: with:
@@ -55,11 +53,12 @@ jobs:
# Build the docker image # Build the docker image
build: build:
name: Docker Build Test
needs: paths-filter needs: paths-filter
if: needs.paths-filter.outputs.docker == 'true' || github.event_name == 'release' || github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'full-run') if: needs.paths-filter.outputs.docker == 'true' || github.event_name == 'release' || github.event_name == 'push'
permissions: permissions:
contents: read contents: read
packages: write
id-token: write
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
python_version: "3.11" python_version: "3.11"
@@ -67,14 +66,21 @@ jobs:
steps: steps:
- name: Check out repo - name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # pin@v5.1.0
with: with:
persist-credentials: false python-version: ${{ env.python_version }}
- name: Version Check
run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
python3 .github/scripts/version_check.py
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Test Docker Image - name: Test Docker Image
id: test-docker id: test-docker
run: | run: |
docker build . --target production --tag inventree-test -f contrib/container/Dockerfile docker build . --target production --tag inventree-test -f contrib/container/Dockerfile
docker run --rm inventree-test invoke version
docker run --rm inventree-test invoke --version docker run --rm inventree-test invoke --version
docker run --rm inventree-test invoke --list docker run --rm inventree-test invoke --list
docker run --rm inventree-test gunicorn --version docker run --rm inventree-test gunicorn --version
@@ -90,11 +96,8 @@ jobs:
- name: Update Docker Image - name: Update Docker Image
run: | run: |
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke install docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke install
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke version
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke update docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke update
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke backup docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke setup-dev
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke restore
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke dev.setup-dev
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml up -d docker compose --project-directory . -f contrib/container/dev-docker-compose.yml up -d
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke wait docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke wait
- name: Check Data Directory - name: Check Data Directory
@@ -109,90 +112,42 @@ jobs:
test -f data/config.yaml test -f data/config.yaml
test -f data/plugins.txt test -f data/plugins.txt
test -f data/secret_key.txt test -f data/secret_key.txt
test -f data/oidc.pem
- name: Run Unit Tests - name: Run Unit Tests
run: | run: |
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> contrib/container/docker.dev.env echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> contrib/container/docker.dev.env
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke dev.test --disable-pty --translations docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke test --disable-pty
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke test --migrations --disable-pty
# Run migration test docker compose --project-directory . -f contrib/container/dev-docker-compose.yml down
migration_test: - name: Clean up test folder
name: Migration Test
needs: paths-filter
if: needs.paths-filter.outputs.docker == 'true' || github.event_name == 'release' || github.event_name == 'push'
permissions:
contents: read
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
python_version: "3.11"
runs-on: ubuntu-latest # in the future we can try to use alternative runners here
steps:
- name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- name: Run Migration Tests
run: | run: |
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke update rm -rf InvenTree/_testfolder
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke dev.setup-dev
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke dev.test --migrations --translations
# Build and publish
publish:
name: Publish Docker Image
needs: [build, migration_test]
permissions:
contents: read
packages: write
id-token: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
python_version: "3.11"
runs-on: ubuntu-latest # in the future we can try to use alternative runners here
steps:
- name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
with:
python-version: ${{ env.python_version }}
- name: Version Check
run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
python3 .github/scripts/version_check.py
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # pin@v3.7.0 uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # pin@v3.0.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # pin@v3.11.1 uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # pin@v3.3.0
- name: Set up cosign - name: Set up cosign
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # pin@v4.0.0 uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # pin@v3.5.0
- name: Check if Dockerhub login is required - name: Check if Dockerhub login is required
id: docker_login id: docker_login
run: | run: |
if [ -z "${{ secrets.DOCKER_USERNAME }}" ]; then if [ -z "${{ secrets.DOCKER_USERNAME }}" ]; then
echo "skip_dockerhub_login=true" >> $GITHUB_OUTPUT echo "skip_dockerhub_login=true" >> $GITHUB_ENV
else else
echo "skip_dockerhub_login=false" >> $GITHUB_OUTPUT echo "skip_dockerhub_login=false" >> $GITHUB_ENV
fi fi
- name: Login to Dockerhub - name: Login to Dockerhub
if: github.event_name != 'pull_request' && steps.docker_login.outputs.skip_dockerhub_login != 'true' if: github.event_name != 'pull_request' && steps.docker_login.outputs.skip_dockerhub_login != 'true'
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # pin@v3.6.0 uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # pin@v3.1.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log into registry ghcr.io - name: Log into registry ghcr.io
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # pin@v3.6.0 uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # pin@v3.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -201,18 +156,17 @@ jobs:
- name: Extract Docker metadata - name: Extract Docker metadata
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # pin@v5.10.0 uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # pin@v5.5.1
with: with:
images: | images: |
inventree/inventree inventree/inventree
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
- uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 # pin@v1
- name: Push Docker Images - name: Push Docker Images
id: push-docker id: push-docker
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: depot/build-push-action@9785b135c3c76c33db102e45be96a25ab55cd507 # pin@v1 uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # pin@v5.3.0
with: with:
project: jczzbjkk68
context: . context: .
file: ./contrib/container/Dockerfile file: ./contrib/container/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

View File

@@ -9,19 +9,20 @@ on:
branches-ignore: ["l10*"] branches-ignore: ["l10*"]
env: env:
python_version: 3.11 python_version: 3.9
node_version: 20 node_version: 18
# The OS version must be set per job # The OS version must be set per job
server_start_sleep: 60 server_start_sleep: 60
requests_version: 2.31.0
pyyaml_version: 6.0.1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_ENGINE: sqlite3 INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: /home/runner/work/InvenTree/test_inventree_media INVENTREE_MEDIA_ROOT: ../test_inventree_media
INVENTREE_STATIC_ROOT: /home/runner/work/InvenTree/test_inventree_static INVENTREE_STATIC_ROOT: ../test_inventree_static
INVENTREE_BACKUP_DIR: /home/runner/work/InvenTree/test_inventree_backup INVENTREE_BACKUP_DIR: ../test_inventree_backup
INVENTREE_SITE_URL: http://localhost:8000 INVENTREE_SITE_URL: http://localhost:8000
INVENTREE_DEBUG: true
permissions: permissions:
contents: read contents: read
@@ -37,13 +38,9 @@ jobs:
frontend: ${{ steps.filter.outputs.frontend }} frontend: ${{ steps.filter.outputs.frontend }}
api: ${{ steps.filter.outputs.api }} api: ${{ steps.filter.outputs.api }}
force: ${{ steps.force.outputs.force }} force: ${{ steps.force.outputs.force }}
cicd: ${{ steps.filter.outputs.cicd }}
requirements: ${{ steps.filter.outputs.requirements }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2 - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
id: filter id: filter
with: with:
@@ -61,13 +58,6 @@ jobs:
- 'src/backend/InvenTree/InvenTree/api_version.py' - 'src/backend/InvenTree/InvenTree/api_version.py'
frontend: frontend:
- 'src/frontend/**' - 'src/frontend/**'
cicd:
- '.github/workflows/**'
requirements:
- 'src/backend/requirements.txt'
- 'src/backend/requirements-dev.txt'
- 'docs/requirements.txt'
- 'contrib/dev_reqs/requirements.txt'
- name: Is CI being forced? - name: Is CI being forced?
run: echo "force=true" >> $GITHUB_OUTPUT run: echo "force=true" >> $GITHUB_OUTPUT
id: force id: force
@@ -75,18 +65,38 @@ jobs:
contains(github.event.pull_request.labels.*.name, 'dependency') || contains(github.event.pull_request.labels.*.name, 'dependency') ||
contains(github.event.pull_request.labels.*.name, 'full-run') contains(github.event.pull_request.labels.*.name, 'full-run')
pre-commit: javascript:
name: Style [pre-commit] name: Style - Classic UI [JS]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: paths-filter
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' needs: ["pre-commit"]
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
- name: Environment Setup
uses: ./.github/actions/setup
with: with:
persist-credentials: false npm: true
install: true
- name: Check Templated JS Files
run: |
cd .github/scripts
python3 check_js_templates.py
- name: Lint Javascript Files
run: |
python src/backend/InvenTree/manage.py prerender
cd src/backend && npx eslint InvenTree/InvenTree/static_i18n/i18n/*.js
pre-commit:
name: Style [pre-commit]
runs-on: ubuntu-20.04
needs: paths-filter
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
- name: Set up Python ${{ env.python_version }} - name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0 uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # pin@v5.1.0
with: with:
python-version: ${{ env.python_version }} python-version: ${{ env.python_version }}
cache: "pip" cache: "pip"
@@ -97,40 +107,17 @@ jobs:
pip install --require-hashes -r contrib/dev_reqs/requirements.txt pip install --require-hashes -r contrib/dev_reqs/requirements.txt
python3 .github/scripts/version_check.py python3 .github/scripts/version_check.py
typecheck:
name: Style [Typecheck]
runs-on: ubuntu-24.04
needs: [paths-filter, pre-commit]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.requirements == 'true' || needs.paths-filter.outputs.force == 'true'
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- name: Environment Setup
id: setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
- name: Check types
run: |
ty check --python ${Python_ROOT_DIR}/bin/python3
mkdocs: mkdocs:
name: Style [Documentation] name: Style [Documentation]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: paths-filter needs: paths-filter
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Set up Python ${{ env.python_version }} - name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0 uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # pin@v5.1.0
with: with:
python-version: ${{ env.python_version }} python-version: ${{ env.python_version }}
- name: Check Config - name: Check Config
@@ -139,7 +126,7 @@ jobs:
pip install --require-hashes -r docs/requirements.txt pip install --require-hashes -r docs/requirements.txt
python docs/ci/check_mkdocs_config.py python docs/ci/check_mkdocs_config.py
- name: Check Links - name: Check Links
uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # pin@v1 uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1
with: with:
folder-path: docs folder-path: docs
config-file: docs/mlc_config.json config-file: docs/mlc_config.json
@@ -148,7 +135,7 @@ jobs:
schema: schema:
name: Tests - API Schema Documentation name: Tests - API Schema Documentation
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: paths-filter needs: paths-filter
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
env: env:
@@ -164,9 +151,7 @@ jobs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -174,121 +159,74 @@ jobs:
dev-install: true dev-install: true
update: true update: true
- name: Export API Documentation - name: Export API Documentation
run: invoke dev.schema --ignore-warnings --filename src/backend/InvenTree/schema.yml run: invoke schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
- name: Upload schema - name: Upload schema
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # pin@v4.3.3
with: with:
name: schema.yml name: schema.yml
path: src/backend/InvenTree/schema.yml path: src/backend/InvenTree/schema.yml
- name: Download public schema - name: Download public schema
env: if: needs.paths-filter.outputs.api == 'false'
API: ${{ needs.paths-filter.outputs.api }}
run: | run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1 pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1
version="$(python3 .github/scripts/version_check.py --show-api-version --decrement-api=${API} 2>&1)" version="$(python3 .github/scripts/version_check.py only_version 2>&1)"
echo "API Version: $version" echo "Version: $version"
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml" url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
echo "URL: $url" echo "URL: $url"
code=$(curl -s -o api.yaml $url --write-out '%{http_code}' --silent) curl -s -o api.yaml $url
if [ "$code" != "200" ]; then
exit 1
fi
echo "Downloaded api.yaml" echo "Downloaded api.yaml"
- name: Running OpenAPI Spec diff action
id: breaking_changes
uses: oasdiff/oasdiff-action/diff@1c611ffb1253a72924624aa4fb662e302b3565d3 # pin@main
with:
base: "api.yaml"
revision: "src/backend/InvenTree/schema.yml"
format: "html"
- name: Echoing diff to step
continue-on-error: true
env:
DIFF: ${{ steps.breaking_changes.outputs.diff }}
run: echo "${DIFF}" >> $GITHUB_STEP_SUMMARY
- name: Check for differences in API Schema - name: Check for differences in API Schema
if: needs.paths-filter.outputs.api == 'false' if: needs.paths-filter.outputs.api == 'false'
run: | run: |
diff --color -u src/backend/InvenTree/schema.yml api.yaml diff --color -u src/backend/InvenTree/schema.yml api.yaml
diff -u src/backend/InvenTree/schema.yml api.yaml && echo "no difference in API schema " || exit 2 diff -u src/backend/InvenTree/schema.yml api.yaml && echo "no difference in API schema " || exit 2
- name: Check schema - including warnings - name: Check schema - including warnings
run: invoke dev.schema run: invoke schema
continue-on-error: true
- name: Extract version for publishing - name: Extract version for publishing
id: version id: version
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true' if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
run: | run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1 pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1
version="$(python3 .github/scripts/version_check.py --show-api-version 2>&1)" version="$(python3 .github/scripts/version_check.py only_version 2>&1)"
echo "API Version: $version" echo "Version: $version"
echo "version=$version" >> "$GITHUB_OUTPUT" echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Extract settings / tags
run: invoke int.export-definitions --basedir docs
- name: Upload settings
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0
with:
name: inventree_settings.json
path: docs/generated/inventree_settings.json
- name: Upload tags
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0
with:
name: inventree_tags.yml
path: docs/generated/inventree_tags.yml
- name: Upload filters
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0
with:
name: inventree_filters.yml
path: docs/generated/inventree_filters.yml
schema-push: schema-push:
name: Push new schema name: Push new schema
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: [paths-filter, schema] needs: [paths-filter, schema]
if: needs.schema.result == 'success' && github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true' && github.repository_owner == 'inventree' if: needs.schema.result == 'success' && github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true' && github.repository_owner == 'inventree'
env: env:
version: ${{ needs.schema.outputs.version }} version: ${{ needs.schema.outputs.version }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
name: Checkout Code
with: with:
repository: inventree/schema repository: inventree/schema
token: ${{ secrets.SCHEMA_PAT }} token: ${{ secrets.SCHEMA_PAT }}
persist-credentials: true
- name: Create artifact directory
run: mkdir -p artifact
- name: Download schema artifact - name: Download schema artifact
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # pin@v6.0.0 uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
path: artifact name: schema.yml
merge-multiple: true - name: Move schema to correct location
- name: Move files to correct location
run: | run: |
echo "Version: ${version}" echo "Version: $version"
echo "before move"
ls -la artifact
mkdir export/${version} mkdir export/${version}
mv artifact/schema.yml export/${version}/api.yaml mv schema.yml export/${version}/api.yaml
mv artifact/inventree_settings.json export/${version}/inventree_settings.json - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
mv artifact/inventree_tags.yml export/${version}/inventree_tags.yml
mv artifact/inventree_filters.yml export/${version}/inventree_filters.yml
echo "after move"
ls -la artifact
rm -rf artifact
- uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # pin@v7.0.0
name: Commit schema changes
with: with:
commit_message: "Update API schema for ${{ env.version }} / ${{ github.sha }}" commit_message: "Update API schema for ${version}"
python: python:
name: Tests - inventree-python name: Tests - inventree-python
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: ["pre-commit", "paths-filter"] needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
env: env:
WRAPPER_NAME: inventree-python wrapper_name: inventree-python
INVENTREE_DB_ENGINE: django.db.backends.sqlite3 INVENTREE_DB_ENGINE: django.db.backends.sqlite3
INVENTREE_DB_NAME: ../inventree_unit_test_db.sqlite3 INVENTREE_DB_NAME: ../inventree_unit_test_db.sqlite3
INVENTREE_ADMIN_USER: testuser INVENTREE_ADMIN_USER: testuser
@@ -298,57 +236,50 @@ jobs:
INVENTREE_PYTHON_TEST_USERNAME: testuser INVENTREE_PYTHON_TEST_USERNAME: testuser
INVENTREE_PYTHON_TEST_PASSWORD: testpassword INVENTREE_PYTHON_TEST_PASSWORD: testpassword
INVENTREE_SITE_URL: http://127.0.0.1:12345 INVENTREE_SITE_URL: http://127.0.0.1:12345
INVENTREE_DEBUG: true
INVENTREE_LOG_LEVEL: WARNING
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
apt-dependency: gettext poppler-utils apt-dependency: gettext poppler-utils
dev-install: true dev-install: true
update: true update: true
- name: Download Python Code For `${WRAPPER_NAME}` npm: true
run: git clone --depth 1 https://github.com/inventree/${WRAPPER_NAME} ./${WRAPPER_NAME} - name: Download Python Code For `${{ env.wrapper_name }}`
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }}
- name: Start InvenTree Server - name: Start InvenTree Server
run: | run: |
invoke dev.delete-data -f invoke delete-data -f
invoke dev.import-fixtures invoke import-fixtures
invoke dev.server -a 127.0.0.1:12345 & invoke server -a 127.0.0.1:12345 &
invoke wait invoke wait
- name: Run Tests For `${WRAPPER_NAME}` - name: Run Tests For `${{ env.wrapper_name }}`
run: | run: |
cd ${WRAPPER_NAME} cd ${{ env.wrapper_name }}
invoke check-server invoke check-server
coverage run -m unittest discover -s test/ coverage run -m unittest discover -s test/
coverage: coverage:
name: Tests - DB [SQLite] + Coverage ${{ matrix.python_version }} name: Tests - DB [SQLite] + Coverage ${{ matrix.python_version }}
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: ["pre-commit", "paths-filter"] needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
continue-on-error: true # continue if a step fails so that coverage gets pushed continue-on-error: true # continue if a step fails so that coverage gets pushed
strategy: strategy:
matrix: matrix:
python_version: [3.11] python_version: [3.9, 3.12]
# python_version: [3.11, 3.14] # Disabled due to requirement issues
env: env:
INVENTREE_DB_NAME: ./inventree.sqlite INVENTREE_DB_NAME: ./inventree.sqlite
INVENTREE_DB_ENGINE: sqlite3 INVENTREE_DB_ENGINE: sqlite3
INVENTREE_PLUGINS_ENABLED: true INVENTREE_PLUGINS_ENABLED: true
INVENTREE_CONSOLE_LOG: false
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
python_version: ${{ matrix.python_version }} python_version: ${{ matrix.python_version }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -358,61 +289,22 @@ jobs:
- name: Data Export Test - name: Data Export Test
uses: ./.github/actions/migration uses: ./.github/actions/migration
- name: Test Translations - name: Test Translations
run: invoke dev.translate run: invoke translate
- name: Check Migration Files - name: Check Migration Files
run: python3 .github/scripts/check_migration_files.py run: python3 .github/scripts/check_migration_files.py
- name: Coverage Tests - name: Coverage Tests
run: invoke dev.test --check --coverage --translations run: invoke test --coverage
- name: Upload raw coverage to artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0
with:
name: coverage
path: .coverage
retention-days: 14
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # pin@v5.5.1 uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # pin@v4.3.1
if: always() if: always()
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree slug: inventree/InvenTree
flags: backend flags: backend
performance:
name: Tests - Performance
runs-on: ubuntu-24.04
needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
permissions:
contents: read
id-token: write
env:
INVENTREE_DB_NAME: inventree_unit_test_db.sqlite
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_PLUGINS_ENABLED: true
INVENTREE_CONSOLE_LOG: false
INVENTREE_AUTO_UPDATE: true
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
- name: Performance Reporting
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # pin@v4
with:
mode: simulation
run: inv dev.test --pytest
postgres: postgres:
name: Tests - DB [PostgreSQL] name: Tests - DB [PostgreSQL]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: ["pre-commit", "paths-filter"] needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
@@ -422,15 +314,13 @@ jobs:
INVENTREE_DB_PASSWORD: password INVENTREE_DB_PASSWORD: password
INVENTREE_DB_HOST: "127.0.0.1" INVENTREE_DB_HOST: "127.0.0.1"
INVENTREE_DB_PORT: 5432 INVENTREE_DB_PORT: 5432
INVENTREE_DEBUG: true INVENTREE_DEBUG: info
INVENTREE_LOG_LEVEL: INFO
INVENTREE_CONSOLE_LOG: false
INVENTREE_CACHE_HOST: localhost INVENTREE_CACHE_HOST: localhost
INVENTREE_PLUGINS_ENABLED: true INVENTREE_PLUGINS_ENABLED: true
services: services:
postgres: postgres:
image: postgres:17 image: postgres:14
env: env:
POSTGRES_USER: inventree POSTGRES_USER: inventree
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: password
@@ -438,14 +328,12 @@ jobs:
- 5432:5432 - 5432:5432
redis: redis:
image: redis:8 image: redis
ports: ports:
- 6379:6379 - 6379:6379
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -454,13 +342,13 @@ jobs:
dev-install: true dev-install: true
update: true update: true
- name: Run Tests - name: Run Tests
run: invoke dev.test --check --translations run: invoke test
- name: Data Export Test - name: Data Export Test
uses: ./.github/actions/migration uses: ./.github/actions/migration
mysql: mysql:
name: Tests - DB [MySQL] name: Tests - DB [MySQL]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: ["pre-commit", "paths-filter"] needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
@@ -472,14 +360,12 @@ jobs:
INVENTREE_DB_PASSWORD: password INVENTREE_DB_PASSWORD: password
INVENTREE_DB_HOST: "127.0.0.1" INVENTREE_DB_HOST: "127.0.0.1"
INVENTREE_DB_PORT: 3306 INVENTREE_DB_PORT: 3306
INVENTREE_DEBUG: true INVENTREE_DEBUG: info
INVENTREE_LOG_LEVEL: WARNING
INVENTREE_CONSOLE_LOG: false
INVENTREE_PLUGINS_ENABLED: true INVENTREE_PLUGINS_ENABLED: true
services: services:
mysql: mysql:
image: mysql:9 image: mysql:latest
env: env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: ${{ env.INVENTREE_DB_NAME }} MYSQL_DATABASE: ${{ env.INVENTREE_DB_NAME }}
@@ -491,9 +377,7 @@ jobs:
- 3306:3306 - 3306:3306
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -502,7 +386,7 @@ jobs:
dev-install: true dev-install: true
update: true update: true
- name: Run Tests - name: Run Tests
run: invoke dev.test --check --translations run: invoke test
- name: Data Export Test - name: Data Export Test
uses: ./.github/actions/migration uses: ./.github/actions/migration
@@ -519,13 +403,12 @@ jobs:
INVENTREE_DB_PASSWORD: password INVENTREE_DB_PASSWORD: password
INVENTREE_DB_HOST: "127.0.0.1" INVENTREE_DB_HOST: "127.0.0.1"
INVENTREE_DB_PORT: 5432 INVENTREE_DB_PORT: 5432
INVENTREE_DEBUG: False INVENTREE_DEBUG: info
INVENTREE_LOG_LEVEL: WARNING
INVENTREE_PLUGINS_ENABLED: false INVENTREE_PLUGINS_ENABLED: false
services: services:
postgres: postgres:
image: postgres:17 image: postgres:14
env: env:
POSTGRES_USER: inventree POSTGRES_USER: inventree
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: password
@@ -533,9 +416,7 @@ jobs:
- 5432:5432 - 5432:5432
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -544,9 +425,9 @@ jobs:
dev-install: true dev-install: true
update: true update: true
- name: Run Tests - name: Run Tests
run: invoke dev.test --check --migrations --report --coverage --translations run: invoke test --migrations --report --coverage
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # pin@v5.5.1 uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # pin@v4.3.1
if: always() if: always()
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@@ -562,14 +443,11 @@ jobs:
env: env:
INVENTREE_DB_ENGINE: sqlite3 INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: /home/runner/work/InvenTree/db.sqlite3 INVENTREE_DB_NAME: /home/runner/work/InvenTree/db.sqlite3
INVENTREE_DEBUG: true INVENTREE_DEBUG: info
INVENTREE_LOG_LEVEL: WARNING
INVENTREE_PLUGINS_ENABLED: false INVENTREE_PLUGINS_ENABLED: false
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
name: Checkout Code name: Checkout Code
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
@@ -578,6 +456,12 @@ jobs:
- name: Fetch Database - name: Fetch Database
run: git clone --depth 1 https://github.com/inventree/test-db ./test-db run: git clone --depth 1 https://github.com/inventree/test-db ./test-db
- name: Latest Database
run: |
cp test-db/latest.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.10.0 Database - name: 0.10.0 Database
run: | run: |
rm /home/runner/work/InvenTree/db.sqlite3 rm /home/runner/work/InvenTree/db.sqlite3
@@ -592,6 +476,13 @@ jobs:
chmod +rw /home/runner/work/InvenTree/db.sqlite3 chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate invoke migrate
- name: 0.12.0 Database
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.12.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.13.5 Database - name: 0.13.5 Database
run: | run: |
rm /home/runner/work/InvenTree/db.sqlite3 rm /home/runner/work/InvenTree/db.sqlite3
@@ -599,106 +490,62 @@ jobs:
chmod +rw /home/runner/work/InvenTree/db.sqlite3 chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate invoke migrate
- name: 0.16.0 Database platform_ui:
run: | name: Tests - Platform UI
rm /home/runner/work/InvenTree/db.sqlite3 runs-on: ubuntu-20.04
cp test-db/stable_0.16.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.17.0 Database
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.17.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
web_ui:
name: Tests - Web UI
runs-on: ubuntu-24.04
timeout-minutes: 60 timeout-minutes: 60
needs: ["pre-commit", "paths-filter"] needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true' if: needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
services:
postgres:
image: postgres:17
env:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U testuser"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env: env:
INVENTREE_DB_ENGINE: postgresql INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree INVENTREE_DB_NAME: /home/runner/work/InvenTree/db.sqlite3
INVENTREE_DB_HOST: "127.0.0.1" INVENTREE_DEBUG: True
INVENTREE_DB_PORT: 5432
INVENTREE_DB_USER: inventree_user
INVENTREE_DB_PASSWORD: inventree_password
INVENTREE_DEBUG: true
INVENTREE_PLUGINS_ENABLED: false INVENTREE_PLUGINS_ENABLED: false
VITE_COVERAGE_BUILD: true VITE_COVERAGE: true
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
npm: true npm: true
install: true install: true
update: true update: true
apt-dependency: postgresql-client libpq-dev
pip-dependency: psycopg2
- name: Set up test data - name: Set up test data
run: | run: invoke setup-test -i
invoke dev.setup-test -iv - name: Rebuild thumbnails
invoke int.rebuild-thumbnails run: invoke rebuild-thumbnails
- name: Install dependencies - name: Install dependencies
run: | run: inv frontend-compile
invoke int.frontend-compile --extract - name: Install Playwright Browsers
cd src/frontend && npx playwright install --with-deps run: cd src/frontend && npx playwright install --with-deps
- name: Run Playwright tests - name: Run Playwright tests
id: tests id: tests
run: cd src/frontend && npx nyc playwright test run: cd src/frontend && npx nyc playwright test
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # pin@v4
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }} if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
with: with:
name: playwright-report name: playwright-report
path: src/frontend/playwright-report/ path: src/frontend/playwright-report/
retention-days: 14 retention-days: 14
- name: Report coverage - name: Report coverage
if: always()
run: cd src/frontend && npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --exclude-after-remap false run: cd src/frontend && npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --exclude-after-remap false
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # pin@v5.5.1 uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # pin@v4.3.1
if: always()
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree slug: inventree/InvenTree
flags: web flags: pui
- name: Upload bundler info
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
cd src/frontend
yarn install
yarn run build
web_ui_build: platform_ui_build:
name: Build - Web UI name: Build - UI Platform
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -707,38 +554,11 @@ jobs:
run: cd src/frontend && yarn install run: cd src/frontend && yarn install
- name: Build frontend - name: Build frontend
run: cd src/frontend && yarn run compile && yarn run build run: cd src/frontend && yarn run compile && yarn run build
- name: Write version file - SHA
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
- name: Zip frontend - name: Zip frontend
run: | run: |
cd src/backend/InvenTree/web/static cd src/backend/InvenTree/web/static
zip -r frontend-build.zip web/ web/.vite zip -r frontend-build.zip web/ web/.vite
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # pin@v5.0.0 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # pin@v4.3.3
with: with:
name: frontend-build name: frontend-build
path: src/backend/InvenTree/web/static/web path: src/backend/InvenTree/web/static/web
include-hidden-files: true
zizmor:
name: Security [Zizmor]
runs-on: ubuntu-24.04
needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.cicd == 'true' || needs.paths-filter.outputs.force == 'true'
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # pin@v2
- name: Run zizmor
run: uvx zizmor --format sarif . > results.sarif
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # pin@v3
with:
sarif_file: results.sarif
category: zizmor

View File

@@ -1,18 +1,13 @@
# Runs on releases # Runs on releases
name: Publish release name: Publish release notes
on: on:
release: release:
types: [published] types: [published]
permissions:
contents: read
env:
python_version: 3.11
jobs: jobs:
stable: stable:
runs-on: ubuntu-24.04 runs-on: ubuntu-latest
name: Write release to stable branch
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
@@ -20,32 +15,26 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Version Check - name: Version Check
run: | run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt pip install --require-hashes -r contrib/dev_reqs/requirements.txt
python3 .github/scripts/version_check.py python3 .github/scripts/version_check.py
- name: Push to Stable Branch - name: Push to Stable Branch
uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa # pin@v1.0.0 uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
if: env.stable_release == 'true' if: env.stable_release == 'true'
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
branch: stable branch: stable
force: true force: true
build: publish-build:
runs-on: ubuntu-24.04 runs-on: ubuntu-latest
name: Build and attest frontend
permissions: permissions:
id-token: write
contents: write contents: write
attestations: write pull-requests: write
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
@@ -54,83 +43,14 @@ jobs:
run: cd src/frontend && yarn install run: cd src/frontend && yarn install
- name: Build frontend - name: Build frontend
run: cd src/frontend && npm run compile && npm run build run: cd src/frontend && npm run compile && npm run build
- name: Create SBOM for frontend
uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # pin@v0
with:
artifact-name: frontend-build.spdx
path: src/frontend
- name: Write version file - SHA
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
- name: Write version file - TAG
run: cd src/backend/InvenTree/web/static/web/.vite && echo "${REF_NAME}" > tag.txt
env:
REF_NAME: ${{ github.ref_name }}
- name: Zip frontend - name: Zip frontend
run: | run: |
cd src/backend/InvenTree/web/static/web cd src/backend/InvenTree/web/static/web
zip -r ../frontend-build.zip * .vite zip -r ../frontend-build.zip *
- name: Attest Build Provenance - uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # pin@2.9.0
id: attest
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # pin@v1
with:
subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip"
- name: Upload frontend
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/backend/InvenTree/web/static/frontend-build.zip file: src/backend/InvenTree/web/static/frontend-build.zip
asset_name: frontend-build.zip asset_name: frontend-build.zip
tag: ${{ github.ref }} tag: ${{ github.ref }}
overwrite: true overwrite: true
- name: Upload Attestation
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
asset_name: frontend-build.intoto.jsonl
file: ${{ steps.attest.outputs.bundle-path}}
tag: ${{ github.ref }}
overwrite: true
docs:
runs-on: ubuntu-24.04
name: Build and publish documentation
permissions:
contents: write
env:
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: /home/runner/work/InvenTree/test_inventree_media
INVENTREE_STATIC_ROOT: /home/runner/work/InvenTree/test_inventree_static
INVENTREE_BACKUP_DIR: /home/runner/work/InvenTree/test_inventree_backup
INVENTREE_SITE_URL: http://localhost:8000
INVENTREE_DEBUG: true
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
install: true
npm: true
- name: Install dependencies
run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
pip install --require-hashes -r docs/requirements.txt
- name: Build documentation
run: |
invoke build-docs --mkdocs
- name: Zip build docs
run: |
cd docs/site
zip -r docs-html.zip *
- name: Publish documentation
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: docs/site/docs-html.zip
asset_name: docs-html.zip
tag: ${{ github.ref }}
overwrite: true

View File

@@ -32,12 +32,12 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with: with:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@@ -59,7 +59,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 uses: github/codeql-action/upload-sarif@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # pin@v10.1.1 - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # pin@v9.0.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue seems stale. Please react to show this is still important." stale-issue-message: "This issue seems stale. Please react to show this is still important."

View File

@@ -6,25 +6,23 @@ on:
- master - master
env: env:
python_version: 3.11 python_version: 3.9
node_version: 20 node_version: 18
permissions: permissions:
contents: read contents: read
jobs: jobs:
synchronize-with-crowdin: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
pull-requests: write
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_NAME: "./test_db.sqlite" INVENTREE_DB_NAME: "./test_db.sqlite"
INVENTREE_DB_ENGINE: django.db.backends.sqlite3 INVENTREE_DB_ENGINE: django.db.backends.sqlite3
INVENTREE_DEBUG: true INVENTREE_DEBUG: info
INVENTREE_LOG_LEVEL: INFO
INVENTREE_MEDIA_ROOT: ./media INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static INVENTREE_STATIC_ROOT: ./static
INVENTREE_BACKUP_DIR: ./backup INVENTREE_BACKUP_DIR: ./backup
@@ -32,42 +30,25 @@ jobs:
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # pin@v4.1.5
with:
persist-credentials: false
- name: Environment Setup - name: Environment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
install: true install: true
npm: true
apt-dependency: gettext apt-dependency: gettext
- name: Make Translations - name: Make Translations
run: invoke dev.translate run: invoke translate
- name: Remove compiled static files - name: Commit files
run: rm -rf src/backend/InvenTree/static
- name: Remove all local changes that are not *.po files
run: | run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]" git config --local user.name "github-actions[bot]"
git add src/backend/InvenTree/locale/en/LC_MESSAGES/django.po src/frontend/src/locales/en/messages.po git checkout -b l10_local
echo "Adding commit (or ignoring if no changes)" git add "*.po"
git commit -m "add translations" || true git commit -m "updated translation base"
echo "Removing all other changes" - name: Push changes
git reset --hard uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
echo "Resetting to HEAD~"
git reset HEAD~ || true
- name: crowdin action
uses: crowdin/github-action@60debf382ee245b21794321190ad0501db89d8c1 # pin@v2
with: with:
upload_sources: true github_token: ${{ secrets.GITHUB_TOKEN }}
upload_translations: false branch: l10
download_translations: true force: true
localization_branch_name: l10_crowdin
create_pull_request: true
pull_request_title: 'New Crowdin updates'
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)'
pull_request_base_branch_name: 'master'
pull_request_labels: 'translations'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -9,9 +9,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
with:
persist-credentials: false
- name: Setup - name: Setup
run: pip install --require-hashes -r requirements-dev.txt run: pip install --require-hashes -r requirements-dev.txt
- name: Update requirements.txt - name: Update requirements.txt

25
.gitignore vendored
View File

@@ -7,7 +7,6 @@ __pycache__/
.Python .Python
env/ env/
inventree-env/ inventree-env/
.venv/
./build/ ./build/
.cache/ .cache/
develop-eggs/ develop-eggs/
@@ -19,6 +18,7 @@ share/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/
lib64/ lib64/
parts/ parts/
sdist/ sdist/
@@ -31,17 +31,11 @@ var/
# Django stuff: # Django stuff:
*.log *.log
local_settings.py local_settings.py
*.sqlite
*.sqlite3 *.sqlite3
*.sqlite3-journal *.sqlite3-journal
*.backup *.backup
*.old *.old
# Files generated by profiling tools
*.prof
*.log
*.sql
# Files used for testing # Files used for testing
inventree-demo-dataset/ inventree-demo-dataset/
inventree-data/ inventree-data/
@@ -51,17 +45,18 @@ inventree_media
inventree_static inventree_static
static_i18n static_i18n
# Local config files # Local config file
config.yaml config.yaml
plugins.txt plugins.txt
secret_key.txt
oidc.pem
# Default data file # Default data file
data.json data.json
*.json.tmp *.json.tmp
*.tmp.json *.tmp.json
# Key file
secret_key.txt
# IDE / development files # IDE / development files
.idea/ .idea/
*.code-workspace *.code-workspace
@@ -85,7 +80,6 @@ js_tmp/
# Development files # Development files
dev/ dev/
dev-db/
data/ data/
env/ env/
@@ -93,10 +87,6 @@ env/
src/backend/InvenTree/InvenTree/locale_stats.json src/backend/InvenTree/InvenTree/locale_stats.json
src/backend/InvenTree/InvenTree/licenses.txt src/backend/InvenTree/InvenTree/licenses.txt
# Logs
src/backend/InvenTree/logs.json
src/backend/InvenTree/logs.log
# node.js # node.js
node_modules/ node_modules/
@@ -117,5 +107,6 @@ api.yaml
src/backend/InvenTree/web/static src/backend/InvenTree/web/static
InvenTree/web/static InvenTree/web/static
# performance test results # Generated docs files
.codspeed/ docs/docs/api/*.yml
docs/docs/api/schema/*.yml

View File

@@ -2,8 +2,10 @@ name: inventree
description: Open Source Inventory Management System description: Open Source Inventory Management System
homepage: https://inventree.org homepage: https://inventree.org
notifications: true notifications: true
buildpack: https://github.com/matmair/null-buildpack#master buildpack: https://github.com/mjmair/heroku-buildpack-python#v216-mjmair
env: env:
- STACK=heroku-20
- DISABLE_COLLECTSTATIC=1
- INVENTREE_DB_ENGINE=sqlite3 - INVENTREE_DB_ENGINE=sqlite3
- INVENTREE_DB_NAME=database.sqlite3 - INVENTREE_DB_NAME=database.sqlite3
- INVENTREE_PLUGINS_ENABLED - INVENTREE_PLUGINS_ENABLED
@@ -12,17 +14,14 @@ env:
- INVENTREE_BACKUP_DIR=/opt/inventree/backup - INVENTREE_BACKUP_DIR=/opt/inventree/backup
- INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt - INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
- INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml - INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
- APP_REPO=inventree/InvenTree
before_install: contrib/packager.io/preinstall.sh
after_install: contrib/packager.io/postinstall.sh after_install: contrib/packager.io/postinstall.sh
before_remove: contrib/packager.io/preinstall.sh
before: before:
- contrib/packager.io/before.sh - contrib/packager.io/before.sh
dependencies: dependencies:
- curl - curl
- "python3.11 | python3.12 | python3.13 | python3.14" - "python3.9 | python3.10 | python3.11"
- "python3.11-venv | python3.12-venv | python3.13-venv | python3.14-venv" - "python3.9-venv | python3.10-venv | python3.11-venv"
- "python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev" - "python3.9-dev | python3.10-dev | python3.11-dev"
- python3-pip - python3-pip
- python3-cffi - python3-cffi
- python3-brotli - python3-brotli
@@ -33,8 +32,7 @@ dependencies:
- gettext - gettext
- nginx - nginx
- jq - jq
- "libffi7 | libffi8" - libffi7
targets: targets:
ubuntu-22.04: true ubuntu-20.04: true
ubuntu-24.04: true debian-11: true
debian-12: true

View File

@@ -10,77 +10,84 @@ exclude: |
)$ )$
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0 rev: v4.6.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
exclude: mkdocs.yml
- id: mixed-line-ending - id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.8 rev: v0.4.1
hooks: hooks:
- id: ruff-format - id: ruff-format
args: [--preview] args: [--preview]
- id: ruff-check - id: ruff
args: [ args: [
--fix, --fix,
# --unsafe-fixes,
--preview --preview
] ]
- repo: https://github.com/astral-sh/uv-pre-commit - repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.16 rev: 0.1.35
hooks: hooks:
- id: pip-compile - id: pip-compile
name: pip-compile requirements-dev.in name: pip-compile requirements-dev.in
args: [src/backend/requirements-dev.in, -o, src/backend/requirements-dev.txt, --no-strip-extras, --generate-hashes] args: [src/backend/requirements-dev.in, -o, src/backend/requirements-dev.txt, --python-version=3.9, --no-strip-extras, --generate-hashes]
files: src/backend/requirements-dev\.(in|txt)$ files: src/backend/requirements-dev\.(in|txt)$
- id: pip-compile - id: pip-compile
name: pip-compile requirements.txt name: pip-compile requirements.txt
args: [src/backend/requirements.in, -o, src/backend/requirements.txt, --no-strip-extras, --generate-hashes] args: [src/backend/requirements.in, -o, src/backend/requirements.txt,--python-version=3.9, --no-strip-extras,--generate-hashes]
files: src/backend/requirements\.(in|txt)$ files: src/backend/requirements\.(in|txt)$
- id: pip-compile - id: pip-compile
name: pip-compile requirements.txt name: pip-compile requirements.txt
args: [contrib/dev_reqs/requirements.in, -o, contrib/dev_reqs/requirements.txt, --no-strip-extras, --generate-hashes, -b, src/backend/requirements.txt] args: [contrib/dev_reqs/requirements.in, -o, contrib/dev_reqs/requirements.txt,--python-version=3.9, --no-strip-extras, --generate-hashes]
files: contrib/dev_reqs/requirements\.(in|txt)$ files: contrib/dev_reqs/requirements\.(in|txt)$
- id: pip-compile - id: pip-compile
name: pip-compile requirements.txt name: pip-compile requirements.txt
args: [docs/requirements.in, -o, docs/requirements.txt, --no-strip-extras, --generate-hashes, -b, src/backend/requirements.txt] args: [docs/requirements.in, -o, docs/requirements.txt,--python-version=3.9, --no-strip-extras, --generate-hashes]
files: docs/requirements\.(in|txt)$ files: docs/requirements\.(in|txt)$
- id: pip-compile - id: pip-compile
name: pip-compile requirements.txt name: pip-compile requirements.txt
args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt, --python-version=3.11, --no-strip-extras, --generate-hashes, -b, src/backend/requirements.txt] args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt,--python-version=3.11, --no-strip-extras, --generate-hashes]
files: contrib/container/requirements\.(in|txt)$ files: contrib/container/requirements\.(in|txt)$
- repo: https://github.com/Riverside-Healthcare/djLint - repo: https://github.com/Riverside-Healthcare/djLint
rev: v1.36.4 rev: v1.34.1
hooks: hooks:
- id: djlint-django - id: djlint-django
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.4.1 rev: v2.2.6
hooks: hooks:
- id: codespell - id: codespell
additional_dependencies:
- tomli
exclude: > exclude: >
(?x)^( (?x)^(
docs/docs/stylesheets/.*| docs/docs/stylesheets/.*|
docs/docs/javascripts/.*| docs/docs/javascripts/.*|
docs/docs/webfonts/.* | docs/docs/webfonts/.* |
src/frontend/src/locales/.* | src/frontend/src/locales/.* |
pyproject.toml |
src/frontend/vite.config.ts |
)$ )$
- repo: https://github.com/biomejs/pre-commit - repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.3.8 rev: "v4.0.0-alpha.8"
hooks: hooks:
- id: biome-check - id: prettier
additional_dependencies: ["@biomejs/biome@1.9.4"] files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
files: ^src/frontend/.*\.(js|ts|tsx)$ additional_dependencies:
- "prettier@^2.4.1"
- "@trivago/prettier-plugin-sort-imports"
- repo: https://github.com/pre-commit/mirrors-eslint
rev: "v9.1.0"
hooks:
- id: eslint
additional_dependencies:
- eslint@^8.41.0
- eslint-config-google@^0.14.0
- eslint-plugin-react@6.10.3
- babel-eslint@6.1.2
- "@typescript-eslint/eslint-plugin@latest"
- "@typescript-eslint/parser"
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0 rev: v8.18.2
hooks: hooks:
- id: gitleaks - id: gitleaks
language_version: 1.25.4
#- repo: https://github.com/jumanjihouse/pre-commit-hooks #- repo: https://github.com/jumanjihouse/pre-commit-hooks
# rev: 3.0.0 # rev: 3.0.0
# hooks: # hooks:

View File

@@ -1,5 +0,0 @@
{
"recommendations": [
"biomejs.biome"
]
}

50
.vscode/launch.json vendored
View File

@@ -6,61 +6,19 @@
"configurations": [ "configurations": [
{ {
"name": "InvenTree Server", "name": "InvenTree Server",
"type": "debugpy", "type": "python",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py", "program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
"args": [ "args": ["runserver"],
"runserver",
"0.0.0.0:8000", // expose server in network (useful for testing with mobile app)
// "--noreload" // disable auto-reload
],
"django": true,
"justMyCode": true
},
{
"name": "InvenTree Server - Tests",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
"args": [
"test",
// "part.test_api.PartCategoryAPITest", // run only a specific test
],
"django": true, "django": true,
"justMyCode": true "justMyCode": true
}, },
{ {
"name": "InvenTree Server - 3rd party", "name": "InvenTree Server - 3rd party",
"type": "debugpy", "type": "python",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py", "program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
"args": [ "args": ["runserver"],
"runserver",
"0.0.0.0:8000"
],
"django": true,
"justMyCode": false
},
{
"name": "InvenTree invoke schema",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/.venv/lib/python3.11/site-packages/invoke/__main__.py",
"cwd": "${workspaceFolder}",
"args": [
"dev.schema","--ignore-warnings"
],
"justMyCode": false
},
{
"name": "schema generation",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
"args": [
"schema",
"--file","src/frontend/schema.yml"
],
"django": true, "django": true,
"justMyCode": false "justMyCode": false
}, },

View File

@@ -1,8 +0,0 @@
{
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit"
}
}

20
.vscode/tasks.json vendored
View File

@@ -9,61 +9,61 @@
{ {
"label": "worker", "label": "worker",
"type": "shell", "type": "shell",
"command": "invoke worker", "command": "inv worker",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "clean-settings", "label": "clean-settings",
"type": "shell", "type": "shell",
"command": "invoke int.clean-settings", "command": "inv clean-settings",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "delete-data", "label": "delete-data",
"type": "shell", "type": "shell",
"command": "invoke dev.delete-data", "command": "inv delete-data",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "migrate", "label": "migrate",
"type": "shell", "type": "shell",
"command": "invoke migrate", "command": "inv migrate",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "server", "label": "server",
"type": "shell", "type": "shell",
"command": "invoke dev.server", "command": "inv server",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "setup-dev", "label": "setup-dev",
"type": "shell", "type": "shell",
"command": "invoke dev.setup-dev", "command": "inv setup-dev",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "setup-test", "label": "setup-test",
"type": "shell", "type": "shell",
"command": "invoke dev.setup-test -i --path dev/inventree-demo-dataset", "command": "inv setup-test -i --path dev/inventree-demo-dataset",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "superuser", "label": "superuser",
"type": "shell", "type": "shell",
"command": "invoke superuser", "command": "inv superuser",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "test", "label": "test",
"type": "shell", "type": "shell",
"command": "invoke dev.test", "command": "inv test",
"problemMatcher": [], "problemMatcher": [],
}, },
{ {
"label": "update", "label": "update",
"type": "shell", "type": "shell",
"command": "invoke update", "command": "inv update",
"problemMatcher": [], "problemMatcher": [],
}, },
] ]

View File

@@ -1,66 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file (starting with 1.0.0).
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
### 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
- Added `order_queryset` report helper function in [#10439](https://github.com/inventree/InvenTree/pull/10439)
- Added `SupplierMixin` to import data from suppliers in [#9761](https://github.com/inventree/InvenTree/pull/9761)
- Added much more detailed status information for machines to the API endpoint (including backend and frontend changes) in [#10381](https://github.com/inventree/InvenTree/pull/10381)
- Added ability to partially complete and partially scrap build outputs in [#10499](https://github.com/inventree/InvenTree/pull/10499)
- Added support for Redis ACL user-based authentication in [#10551](https://github.com/inventree/InvenTree/pull/10551)
- Expose stock adjustment forms to the UI plugin context in [#10584](https://github.com/inventree/InvenTree/pull/10584)
- Allow stock adjustments for "in production" items in [#10600](https://github.com/inventree/InvenTree/pull/10600)
- Adds optional shipping address against individual sales order shipments in [#10650](https://github.com/inventree/InvenTree/pull/10650)
- Adds UI elements to "check" and "uncheck" sales order shipments in [#10654](https://github.com/inventree/InvenTree/pull/10654)
- Allow assigning project codes to order line items in [#10657](https://github.com/inventree/InvenTree/pull/10657)
- Added support for webauthn login for the frontend in [#9729](https://github.com/inventree/InvenTree/pull/9729)
- Added support for Debian 12, Ubuntu 22.04 and Ubuntu 24.04 in the installer and package in [#10705](https://github.com/inventree/InvenTree/pull/10705)
- Support for S3 and SFTP storage backends for media and static files ([#10140](https://github.com/inventree/InvenTree/pull/10140))
- Adds hooks for custom UI spotlight actions in [#10720](https://github.com/inventree/InvenTree/pull/10720)
- Support uploading attachments against SupplierPart in [#10724](https://github.com/inventree/InvenTree/pull/10724)
### Changed
- Changed site URL check to allow protocol mismatches if `INVENTREE_SITE_LAX_PROTOCOL` is set to `True` (default) in [#10454](https://github.com/inventree/InvenTree/pull/10454)
- Changed call signature of `get_global_setting` to use `environment_key` instead of `enviroment_key` in [#10557](https://github.com/inventree/InvenTree/pull/10557)
## 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.
An overarching theme of this release is the complete switch to a new UI framework and paradigm (PUI). The old templating based UI (CUI) is now removed. This makes major improvements in the security and portability of InvenTree possible.
Our blog holds [a few articles](https://inventree.org/blog/2024/09/23/ui-roadmap) on the topic. This journey started in [March 2022](https://github.com/inventree/InvenTree/issues/2789) and was announced [in 2023](https://inventree.org/blog/2023/08/28/react).
Specific entries to the changelog will be kept for all stable channel minor releases, for changes in 1.0 please refer to the [blog posts](https://inventree.org/blog/2025/09/15/1.0.0) and the [milestone](https://github.com/inventree/InvenTree/milestone/17)

View File

@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
support AT inventree DOR org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -39,7 +39,7 @@ InvenTree/
│ │ ├─ tsconfig.json # Settings for frontend compilation │ │ ├─ tsconfig.json # Settings for frontend compilation
├─ .pkgr.yml # Build definition for Debian/Ubuntu packages ├─ .pkgr.yml # Build definition for Debian/Ubuntu packages
├─ .pre-commit-config.yaml # Code formatter/linter configuration ├─ .pre-commit-config.yaml # Code formatter/linter configuration
├─ CONTRIBUTING.md # Contribution guidelines and overview ├─ CONTRIBUTING.md # Contirbution guidelines and overview
├─ Procfile # Process definition for Debian/Ubuntu packages ├─ Procfile # Process definition for Debian/Ubuntu packages
├─ README.md # General project information and overview ├─ README.md # General project information and overview
├─ runtime.txt # Python runtime settings for Debian/Ubuntu packages build ├─ runtime.txt # Python runtime settings for Debian/Ubuntu packages build

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017 - InvenTree Developers Copyright (c) 2017-2022 InvenTree
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -4,15 +4,13 @@
<p>Open Source Inventory Management System </p> <p>Open Source Inventory Management System </p>
<!-- Badges --> <!-- Badges -->
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/license/MIT)![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/inventree/inventree) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/inventree/inventree)
![CI](https://github.com/inventree/inventree/actions/workflows/qc_checks.yaml/badge.svg) ![CI](https://github.com/inventree/inventree/actions/workflows/qc_checks.yaml/badge.svg)
[![Documentation Status](https://readthedocs.org/projects/inventree/badge/?version=latest)](https://inventree.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/inventree/badge/?version=latest)](https://inventree.readthedocs.io/en/latest/?badge=latest)
![Docker Build](https://github.com/inventree/inventree/actions/workflows/docker.yaml/badge.svg) ![Docker Build](https://github.com/inventree/inventree/actions/workflows/docker.yaml/badge.svg)
[![Netlify Status](https://api.netlify.com/api/v1/badges/9bbb2101-0a4d-41e7-ad56-b63fb6053094/deploy-status)](https://app.netlify.com/sites/inventree/deploys)
[![Performance Testing](https://dev.azure.com/InvenTree/InvenTree%20test%20statistics/_apis/build/status%2Fmatmair.InvenTree?branchName=testing)](https://dev.azure.com/InvenTree/InvenTree%20test%20statistics/_build/latest?definitionId=3&branchName=testing)
[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7179/badge)](https://bestpractices.coreinfrastructure.org/projects/7179) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7179/badge)](https://bestpractices.coreinfrastructure.org/projects/7179)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/inventree/InvenTree/badge)](https://securityscorecards.dev/viewer/?uri=github.com/inventree/InvenTree) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/inventree/InvenTree/badge)](https://securityscorecards.dev/viewer/?uri=github.com/inventree/InvenTree)
[![Netlify Status](https://api.netlify.com/api/v1/badges/9bbb2101-0a4d-41e7-ad56-b63fb6053094/deploy-status)](https://app.netlify.com/sites/inventree/deploys)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=inventree_InvenTree&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=inventree_InvenTree) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=inventree_InvenTree&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=inventree_InvenTree)
[![codecov](https://codecov.io/gh/inventree/InvenTree/graph/badge.svg?token=9DZRGUUV7B)](https://codecov.io/gh/inventree/InvenTree) [![codecov](https://codecov.io/gh/inventree/InvenTree/graph/badge.svg?token=9DZRGUUV7B)](https://codecov.io/gh/inventree/InvenTree)
@@ -20,10 +18,10 @@
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/inventree/inventree) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/inventree/inventree)
[![Docker Pulls](https://img.shields.io/docker/pulls/inventree/inventree)](https://hub.docker.com/r/inventree/inventree) [![Docker Pulls](https://img.shields.io/docker/pulls/inventree/inventree)](https://hub.docker.com/r/inventree/inventree)
[![GitHub Org's stars](https://img.shields.io/github/stars/inventree?style=social)](https://github.com/inventree/InvenTree/) ![GitHub Org's stars](https://img.shields.io/github/stars/inventree?style=social)
[![Twitter Follow](https://img.shields.io/twitter/follow/inventreedb?style=social)](https://twitter.com/inventreedb) [![Twitter Follow](https://img.shields.io/twitter/follow/inventreedb?style=social)](https://twitter.com/inventreedb)
[![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/inventree?style=social)](https://www.reddit.com/r/InvenTree/) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/inventree?style=social)](https://www.reddit.com/r/InvenTree/)
[![Mastdon](https://img.shields.io/badge/dynamic/json?label=Mastodon&query=followers_count&url=https%3A%2F%2Fchaos.social%2Fapi%2Fv1%2Faccounts%2Flookup%3Facct=InvenTree&logo=mastodon&style=social)](https://chaos.social/@InvenTree)
<h4> <h4>
<a href="https://demo.inventree.org/">View Demo</a> <a href="https://demo.inventree.org/">View Demo</a>
@@ -53,10 +51,10 @@ Want to see what we are working on? Check out the [roadmap tag](https://github.c
InvenTree is designed to be **extensible**, and provides multiple options for **integration** with external applications or addition of custom plugins: InvenTree is designed to be **extensible**, and provides multiple options for **integration** with external applications or addition of custom plugins:
* [InvenTree API](https://docs.inventree.org/en/latest/api/) * [InvenTree API](https://docs.inventree.org/en/latest/api/api/)
* [Python module](https://docs.inventree.org/en/latest/api/python/) * [Python module](https://docs.inventree.org/en/latest/api/python/python/)
* [Plugin interface](https://docs.inventree.org/en/latest/plugins/) * [Plugin interface](https://docs.inventree.org/en/latest/extend/plugins)
* [Third party tools](https://docs.inventree.org/en/latest/plugins/integrate/) * [Third party tools](https://docs.inventree.org/en/latest/extend/integrate)
<!-- TechStack --> <!-- TechStack -->
### :space_invader: Tech Stack ### :space_invader: Tech Stack
@@ -68,7 +66,7 @@ InvenTree is designed to be **extensible**, and provides multiple options for **
<li><a href="https://www.djangoproject.com/">Django</a></li> <li><a href="https://www.djangoproject.com/">Django</a></li>
<li><a href="https://www.django-rest-framework.org/">DRF</a></li> <li><a href="https://www.django-rest-framework.org/">DRF</a></li>
<li><a href="https://django-q.readthedocs.io/">Django Q</a></li> <li><a href="https://django-q.readthedocs.io/">Django Q</a></li>
<li><a href="https://docs.allauth.org/">Django-Allauth</a></li> <li><a href="https://django-allauth.readthedocs.io/">Django-Allauth</a></li>
</ul> </ul>
</details> </details>
@@ -85,14 +83,9 @@ InvenTree is designed to be **extensible**, and provides multiple options for **
<details> <details>
<summary>Client</summary> <summary>Client</summary>
<ul> <ul>
<li><a href="https://react.dev/">React</a></li> <li><a href="https://getbootstrap.com/">Bootstrap</a></li>
<li><a href="https://lingui.dev/">Lingui</a></li> <li><a href="https://jquery.com/">jQuery</a></li>
<li><a href="https://reactrouter.com/">React Router</a></li> <li><a href="https://bootstrap-table.com/">Bootstrap-Table</a></li>
<li><a href="https://tanstack.com/query/">TanStack Query</a></li>
<li><a href="https://github.com/pmndrs/zustand">Zustand</a></li>
<li><a href="https://mantine.dev/">Mantine</a></li>
<li><a href="https://icflorescu.github.io/mantine-datatable/">Mantine Data Table</a></li>
<li><a href="https://codemirror.net/">CodeMirror</a></li>
</ul> </ul>
</details> </details>
@@ -102,7 +95,7 @@ InvenTree is designed to be **extensible**, and provides multiple options for **
<li><a href="https://hub.docker.com/r/inventree/inventree">Docker</a></li> <li><a href="https://hub.docker.com/r/inventree/inventree">Docker</a></li>
<li><a href="https://crowdin.com/project/inventree">Crowdin</a></li> <li><a href="https://crowdin.com/project/inventree">Crowdin</a></li>
<li><a href="https://app.codecov.io/gh/inventree/InvenTree">Codecov</a></li> <li><a href="https://app.codecov.io/gh/inventree/InvenTree">Codecov</a></li>
<li><a href="https://sonarcloud.io/project/overview?id=inventree_InvenTree">SonarCloud</a></li> <li><a href="https://app.deepsource.com/gh/inventree/InvenTree">DeepSource</a></li>
<li><a href="https://packager.io/gh/inventree/InvenTree">Packager.io</a></li> <li><a href="https://packager.io/gh/inventree/InvenTree">Packager.io</a></li>
</ul> </ul>
</details> </details>
@@ -130,7 +123,7 @@ Refer to the [getting started guide](https://docs.inventree.org/en/latest/start/
<!-- Mobile App --> <!-- Mobile App -->
## :iphone: Mobile App ## :iphone: Mobile App
InvenTree is supported by a [companion mobile app](https://docs.inventree.org/en/latest/app/) which allows users access to stock control information and functionality. InvenTree is supported by a [companion mobile app](https://docs.inventree.org/app/) which allows users access to stock control information and functionality.
<div align="center"><h4> <div align="center"><h4>
<a href="https://play.google.com/store/apps/details?id=inventree.inventree_app">Android Play Store</a> <a href="https://play.google.com/store/apps/details?id=inventree.inventree_app">Android Play Store</a>
@@ -138,17 +131,10 @@ InvenTree is supported by a [companion mobile app](https://docs.inventree.org/en
<a href="https://apps.apple.com/au/app/inventree/id1581731101#?platform=iphone">Apple App Store</a> <a href="https://apps.apple.com/au/app/inventree/id1581731101#?platform=iphone">Apple App Store</a>
</h4></div> </h4></div>
<!-- Security -->
## :lock: Code of Conduct & Security Policy
The InvenTree project team is committed to providing a safe and welcoming environment for all users. Please read our [Code of Conduct](CODE_OF_CONDUCT.md) for more information.
InvenTree is following industry best practices for security. Our security policy is included [in this repo](SECURITY.md). We provide dedicated security pages on [our documentation site](https://docs.inventree.org/en/latest/security/).
<!-- Contributing --> <!-- Contributing -->
## :wave: Contributing ## :wave: Contributing
Contributions are welcomed and encouraged. Please help to make this project even better! Refer to the [contribution page](https://docs.inventree.org/en/latest/develop/contributing/). Contributions are welcomed and encouraged. Please help to make this project even better! Refer to the [contribution page](CONTRIBUTING.md).
<!-- Translation --> <!-- Translation -->
## :scroll: Translation ## :scroll: Translation
@@ -163,8 +149,11 @@ If you use InvenTree and find it to be useful, please consider [sponsoring the p
<!-- Acknowledgments --> <!-- Acknowledgments -->
## :gem: Acknowledgements ## :gem: Acknowledgements
We want to acknowledge [PartKeepr](https://github.com/partkeepr/PartKeepr) as a valuable predecessor and inspiration. We would like to acknowledge a few special projects:
Find a full list of used third-party libraries in the license information dialog of your instance. - [PartKeepr](https://github.com/partkeepr/PartKeepr) as a valuable predecessor and inspiration
- [Readme Template](https://github.com/Louis3797/awesome-readme-template) for the template of this page
Find a full list of used third-party libraries in [our documentation](https://docs.inventree.org/en/latest/credits/).
## :heart: Support ## :heart: Support
@@ -180,26 +169,17 @@ Find a full list of used third-party libraries in the license information dialog
<a href="https://github.com/PricelessToolkit"><img src="https://github.com/PricelessToolkit.png" width="60px" alt="" /></a> <a href="https://github.com/PricelessToolkit"><img src="https://github.com/PricelessToolkit.png" width="60px" alt="" /></a>
<a href="https://github.com/cabottech"><img src="https://github.com/cabottech.png" width="60px" alt="Cabot Technologies" /></a> <a href="https://github.com/cabottech"><img src="https://github.com/cabottech.png" width="60px" alt="Cabot Technologies" /></a>
<a href="https://github.com/markus-k"><img src="https://github.com/markus-k.png" width="60px" alt="Markus Kasten" /></a> <a href="https://github.com/markus-k"><img src="https://github.com/markus-k.png" width="60px" alt="Markus Kasten" /></a>
<a href="https://github.com/jefffhaynes"><img src="https://github.com/jefffhaynes.png" width="60px" alt="Jeff Haynes" /></a> <a href="https://github.com/jefffhaynes"><img src="https://github.com/jefffhaynes.png" width="60px" alt="Jess Haynes" /></a>
<a href="https://github.com/dnviti"><img src="https://github.com/dnviti.png" width="60px" alt="Daniele Viti" /></a>
<a href="https://github.com/Islendur"><img src="https://github.com/Islendur.png" width="60px" alt="Islendur" /></a>
<a href="https://github.com/Gibeon-NL"><img src="https://github.com/Gibeon-NL.png" width="60px" alt="Gibeon-NL" /></a>
<a href="https://github.com/Motrac-Research-Engineering"><img src="https://github.com/Motrac-Research-Engineering.png" width="60px" alt="Motrac Research" /></a>
<a href="https://github.com/trytuna"><img src="https://github.com/trytuna.png" width="60px" alt="Timo Scrappe" /></a>
<a href="https://github.com/ATLAS2246"><img src="https://github.com/ATLAS2246.png" width="60px" alt="ATLAS2246" /></a>
<a href="https://github.com/Kedarius"><img src="https://github.com/Kedarius.png" width="60px" alt="Radek Hladik" /></a>
</p> </p>
<p>With ongoing resources provided by:</p> <p>With ongoing resources provided by:</p>
<p align="center"> <p align="center">
<a href="https://depot.dev?utm_source=inventree"><img src="https://depot.dev/badges/built-with-depot.svg" alt="Built with Depot" /></a>
<a href="https://inventree.org/digitalocean"> <a href="https://inventree.org/digitalocean">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px" alt="Servers by Digital Ocean"> <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px" alt="Servers by Digital Ocean">
</a> </a>
<a href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" /> </a> <a href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" /> </a>
<a href="https://crowdin.com"> <img src="https://crowdin.com/images/crowdin-logo.svg" alt="Translation by Crowdin" /> </a> <br> <a href="https://crowdin.com"> <img src="https://crowdin.com/images/crowdin-logo.svg" alt="Translation by Crowdin" /> </a>
</p> </p>

23
RELEASE.md Normal file
View File

@@ -0,0 +1,23 @@
## Release Checklist
Checklist of steps to perform at each code release
### Update Version String
Update `INVENTREE_SW_VERSION` in [version.py](https://github.com/inventree/InvenTree/blob/master/src/backend/InvenTree/InvenTree/version.py)
### Increment API Version
If the API has changed, ensure that the API version number is incremented.
### Translation Files
Merge the crowdin translation updates into master branch
### Python Library Release
Create new release for the [InvenTree python library](https://github.com/inventree/inventree-python)
## App Release
Create new versioned release for the InvenTree mobile app.

View File

@@ -1,9 +1,7 @@
# Security Policy # Security Policy
The InvenTree team take all security vulnerabilities seriously. Thank you for improving the security of our open source software. The InvenTree team take all security vulnerabilities seriously. Thank you for improving the security of our open source software.
We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
The general project security policies and processes are documented in [our documentation](https://docs.inventree.org/en/stable/security/).
## Reporting a Vulnerability ## Reporting a Vulnerability
@@ -13,13 +11,7 @@ Please report security vulnerabilities by emailing the InvenTree team at:
security@inventree.org security@inventree.org
``` ```
Someone from the InvenTree development team will acknowledge your email as soon as possible (normally within a week), and indicate the next steps in handling your security report. Someone from the InvenTree development team will acknowledge your email as soon as possible, and indicate the next steps in handling your security report.
The team will endeavour to keep you informed of the progress towards a fix for the issue, and subsequent release to the stable and development code branches. Where possible, the issue will be resolved within 90 days of reporting. The team will endeavour to keep you informed of the progress towards a fix for the issue, and subsequent release to the stable and development code branches. Where possible, the issue will be resolved within 90 days of reporting.
### Public Disclosure
Using GitHub's security advisory system, we will publish a public disclosure of the issue once it has been acknowledged, reproduced and resolved.
We support assigning CVEs to security issues where appropriate.
The project can be identified by the CPE code ``cpe:2.3:a:inventree_project:inventree:``.

View File

@@ -1,40 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "single",
"trailingCommas": "none",
"indentStyle": "space"
}
},
"linter": {
"rules": {
"suspicious" : {
"noExplicitAny": "off",
"noDoubleEquals": "off",
"noArrayIndexKey": "off",
"useDefaultSwitchClauseLast": "off"
},
"style": {
"noUselessElse": "off",
"noNonNullAssertion": "off",
"noParameterAssign": "off"
}, "correctness":{
"useExhaustiveDependencies": "off",
"useJsxKeyInIterable": "off",
"noUnsafeOptionalChaining": "off",
"noSwitchDeclarations": "off",
"noUnusedImports":"error"
}, "complexity": {
"noBannedTypes": "off",
"noExtraBooleanCast": "off",
"noForEach": "off",
"noUselessSwitchCase": "off",
"useLiteralKeys":"off"
}, "performance": {
"noDelete":"off"
}
}
}
}

View File

@@ -2,8 +2,7 @@ coverage:
status: status:
project: project:
default: default:
target: 85% target: 82%
patch: off
github_checks: github_checks:
annotations: true annotations: true
@@ -22,62 +21,8 @@ flag_management:
statuses: statuses:
- type: project - type: project
target: 40% target: 40%
- name: web - name: pui
carryforward: true carryforward: true
statuses: statuses:
- type: project - type: project
target: 45% target: 45%
component_management:
default_rules:
statuses:
- type: project
target: auto
branches:
- "!main"
individual_components:
- component_id: backend-apps
name: Backend Apps
paths:
- src/backend/InvenTree/build/**
- src/backend/InvenTree/company/**
- src/backend/InvenTree/data_exporter/**
- src/backend/InvenTree/importer/**
- src/backend/InvenTree/machine/**
- src/backend/InvenTree/order/**
- src/backend/InvenTree/part/**
- src/backend/InvenTree/plugin/**
- src/backend/InvenTree/report/**
- src/backend/InvenTree/stock/**
- src/backend/InvenTree/users/**
- src/backend/InvenTree/web/**
statuses:
- type: project
target: 90%
- component_id: backend-general
name: Backend General
paths:
- src/backend/InvenTree/generic/**
- src/backend/InvenTree/common/**
statuses:
- type: project
target: 92% # 95%
- type: patch
target: 95%
- component_id: web
name: Frontend
paths:
- src/frontend/**
statuses:
- type: project
target: 68% # 90%
- type: patch
target: 80%
comment:
require_bundle_changes: True
bundle_change_threshold: "1Kb"
layout: "header, diff, flags, components"
bundle_analysis:
warning_threshold: "5%"
status: "informational"

1
config/.gitignore vendored
View File

@@ -1 +0,0 @@
*

View File

@@ -1 +0,0 @@
This folder is recommended for configuration values and excluded from change tracking in git

View File

@@ -1,61 +1,49 @@
# InvenTree environment variables for docker compose deployment # InvenTree environment variables for docker compose deployment
# For a full list of the available configuration options, refer to the InvenTree documentation:
# https://docs.inventree.org/en/stable/start/config/
# Specify the name of the docker-compose project
COMPOSE_PROJECT_NAME=inventree
# InvenTree version tag (e.g. 'stable' / 'latest' / 'x.x.x')
INVENTREE_TAG=stable
# InvenTree server URL - update this to match your server URL
INVENTREE_SITE_URL="http://inventree.localhost"
#INVENTREE_SITE_URL="http://192.168.1.2" # You can specify a local IP address here
#INVENTREE_SITE_URL="https://inventree.my-domain.com" # Or a public domain name (which you control)
# InvenTree proxy forwarding settings
INVENTREE_USE_X_FORWARDED_HOST=True
INVENTREE_USE_X_FORWARDED_PORT=True
INVENTREE_USE_X_FORWARDED_PROTO=True
# Specify the location of the external data volume # Specify the location of the external data volume
# By default, placed in local directory 'inventree-data' # By default, placed in local directory 'inventree-data'
INVENTREE_EXT_VOLUME=./inventree-data INVENTREE_EXT_VOLUME=./inventree-data
# Ensure debug is false for a production setup # Ensure debug is false for a production setup
INVENTREE_DEBUG=False
INVENTREE_LOG_LEVEL=WARNING INVENTREE_LOG_LEVEL=WARNING
# Enable custom plugins? # InvenTree admin account details
INVENTREE_PLUGINS_ENABLED=True # Un-comment (and complete) these lines to auto-create an admin acount
# Run database migrations automatically?
# Note: This does not negate the need to run "invoke update"
INVENTREE_AUTO_UPDATE=True
# InvenTree superuser account details
# Un-comment (and complete) these lines to auto-create an admin account
#INVENTREE_ADMIN_USER= #INVENTREE_ADMIN_USER=
#INVENTREE_ADMIN_PASSWORD= #INVENTREE_ADMIN_PASSWORD=
#INVENTREE_ADMIN_EMAIL= #INVENTREE_ADMIN_EMAIL=
# Database configuration options # Database configuration options
# DO NOT CHANGE THESE SETTINGS (unless you really know what you are doing)
INVENTREE_DB_ENGINE=postgresql INVENTREE_DB_ENGINE=postgresql
INVENTREE_DB_NAME=inventree INVENTREE_DB_NAME=inventree
INVENTREE_DB_HOST=inventree-db INVENTREE_DB_HOST=inventree-db
INVENTREE_DB_PORT=5432 INVENTREE_DB_PORT=5432
# Database credentials - These should be changed from the default values! # Database credentials - These should be changed from the default values!
# Note: These are *NOT* the InvenTree server login credentials,
# they are the credentials for the PostgreSQL database
INVENTREE_DB_USER=pguser INVENTREE_DB_USER=pguser
INVENTREE_DB_PASSWORD=pgpassword INVENTREE_DB_PASSWORD=pgpassword
# Redis cache setup # Redis cache setup (disabled by default)
# Refer to the documentation for other cache options # Un-comment the following lines to enable Redis cache
INVENTREE_CACHE_ENABLED=True # Note that you will also have to run docker-compose with the --profile redis command
INVENTREE_CACHE_HOST=inventree-cache # Refer to settings.py for other cache options
INVENTREE_CACHE_PORT=6379 #INVENTREE_CACHE_HOST=inventree-cache
#INVENTREE_CACHE_PORT=6379
# Options for gunicorn server # Options for gunicorn server
INVENTREE_GUNICORN_TIMEOUT=90 INVENTREE_GUNICORN_TIMEOUT=90
# Enable custom plugins?
INVENTREE_PLUGINS_ENABLED=True
# Run migrations automatically?
INVENTREE_AUTO_UPDATE=True
# Image tag that should be used
INVENTREE_TAG=stable
# Site URL - update this to match your host
INVENTREE_SITE_URL="http://inventree.localhost"
COMPOSE_PROJECT_NAME=inventree

View File

@@ -1,21 +1,17 @@
# Example Caddyfile for InvenTree # Example Caddyfile for Inventree
# The following environment variables may be used: # The following environment variables may be used:
# - INVENTREE_SITE_URL: The upstream URL of the InvenTree site (default: inventree.localhost) # - INVENTREE_SITE_URL: The upstream URL of the Inventree site (default: inventree.localhost)
# - INVENTREE_SERVER: The internal URL of the InvenTree container (default: http://inventree-server:8000) # - INVENTREE_SERVER: The internal URL of the Inventree container (default: http://inventree-server:8000)
# #
# Note that while this file is a good starting point, it may need to be modified to suit your specific requirements # Note that while this file is a good starting point, it may need to be modified to suit your specific requirements
#
# Ref to the Caddyfile documentation: https://caddyserver.com/docs/caddyfile
# Logging configuration for Caddy
(log_common) { (log_common) {
log { log {
output file /var/log/caddy/{args[0]}.access.log output file /var/log/caddy/{args[0]}.access.log
} }
} }
# CORS headers control (used for static and media files)
(cors-headers) { (cors-headers) {
header Allow GET,HEAD,OPTIONS header Allow GET,HEAD,OPTIONS
header Access-Control-Allow-Origin * header Access-Control-Allow-Origin *
@@ -29,10 +25,8 @@
} }
} }
# The default server address is configured in the .env file # Change the host to your domain (this will serve at inventree.localhost)
# If not specified, the proxy listens for all http/https traffic {$INVENTREE_SITE_URL:inventree.localhost} {
# If you need to listen on multiple addresses, or use a different port, you can modify this section directly
{$INVENTREE_SITE_URL:"http://, https://"} {
import log_common inventree import log_common inventree
encode gzip encode gzip
@@ -41,7 +35,6 @@
max_size 100MB max_size 100MB
} }
# Handle static request files
handle_path /static/* { handle_path /static/* {
import cors-headers static import cors-headers static
@@ -49,29 +42,18 @@
file_server file_server
} }
# Handle media request files
handle_path /media/* { handle_path /media/* {
import cors-headers media import cors-headers media
root * /var/www/media root * /var/www/media
file_server file_server
# Force download of media files (for security)
# Comment out this line if you do not want to force download
header Content-Disposition attachment header Content-Disposition attachment
# Authentication is handled by the forward_auth directive
# This is required to ensure that media files are only accessible to authenticated users
forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} { forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} {
uri /auth/ uri /auth/
} }
} }
# All other requests are proxied to the InvenTree server reverse_proxy {$INVENTREE_SERVER:"http://inventree-server:8000"}
reverse_proxy {$INVENTREE_SERVER:"http://inventree-server:8000"} {
# If you are running behind another proxy, you may need to specify 'trusted_proxies'
# Ref: https://caddyserver.com/docs/json/apps/http/servers/trusted_proxies/
# trusted_proxies ...
}
} }

View File

@@ -9,7 +9,8 @@
# - Runs InvenTree web server under django development server # - Runs InvenTree web server under django development server
# - Monitors source files for any changes, and live-reloads server # - Monitors source files for any changes, and live-reloads server
FROM python:3.11-slim-trixie@sha256:1d6131b5d479888b43200645e03a78443c7157efbdb730e6b48129740727c312 AS inventree_base ARG base_image=python:3.11-alpine3.18
FROM ${base_image} AS inventree_base
# Build arguments for this image # Build arguments for this image
ARG commit_tag="" ARG commit_tag=""
@@ -18,9 +19,9 @@ ARG commit_date=""
ARG data_dir="data" ARG data_dir="data"
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 ENV PIP_DISABLE_PIP_VERSION_CHECK 1
ENV INVOKE_RUN_SHELL="/bin/bash" ENV INVOKE_RUN_SHELL="/bin/ash"
ENV INVENTREE_DOCKER="true" ENV INVENTREE_DOCKER="true"
@@ -37,7 +38,6 @@ ENV INVENTREE_BACKEND_DIR="${INVENTREE_HOME}/src/backend"
# InvenTree configuration files # InvenTree configuration files
ENV INVENTREE_CONFIG_FILE="${INVENTREE_DATA_DIR}/config.yaml" ENV INVENTREE_CONFIG_FILE="${INVENTREE_DATA_DIR}/config.yaml"
ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DATA_DIR}/secret_key.txt" ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DATA_DIR}/secret_key.txt"
ENV INVENTREE_OIDC_PRIVATE_KEY_FILE="${INVENTREE_DATA_DIR}/oidc.pem"
ENV INVENTREE_PLUGIN_FILE="${INVENTREE_DATA_DIR}/plugins.txt" ENV INVENTREE_PLUGIN_FILE="${INVENTREE_DATA_DIR}/plugins.txt"
# Worker configuration (can be altered by user) # Worker configuration (can be altered by user)
@@ -48,84 +48,67 @@ ENV INVENTREE_BACKGROUND_WORKERS="4"
ENV INVENTREE_WEB_ADDR=0.0.0.0 ENV INVENTREE_WEB_ADDR=0.0.0.0
ENV INVENTREE_WEB_PORT=8000 ENV INVENTREE_WEB_PORT=8000
LABEL org.opencontainers.image.vendor="inventree" \ LABEL org.label-schema.schema-version="1.0" \
org.opencontainers.image.title="InvenTree backend server" \ org.label-schema.build-date=${DATE} \
org.opencontainers.image.description="InvenTree is the open-source inventory management system" \ org.label-schema.vendor="inventree" \
org.opencontainers.image.url="https://inventree.org" \ org.label-schema.name="inventree/inventree" \
org.opencontainers.image.documentation="https://docs.inventree.org" \ org.label-schema.url="https://hub.docker.com/r/inventree/inventree" \
org.opencontainers.image.source="https://github.com/inventree/InvenTree" \ org.label-schema.vcs-url="https://github.com/inventree/InvenTree.git" \
org.opencontainers.image.revision=${commit_hash} \ org.label-schema.vcs-ref=${commit_tag}
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.version=${commit_tag}
# Install basic system level packages # Install required system level packages
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apk add --no-cache \
git gettext libldap2 wget curl ssh \ git gettext py-cryptography \
# Image format support
libjpeg libwebp zlib \
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12 # Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12
weasyprint libpango-1.0-0 libcairo2 poppler-utils \ py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils openldap \
# Database client libraries # Postgres client
postgresql-client mariadb-client \ postgresql13-client \
# font support # MySQL / MariaDB client
fontconfig fonts-freefont-ttf fonts-terminus fonts-noto-core fonts-noto-cjk \ mariadb-client mariadb-connector-c \
# Cleanup && \
&& apt-get clean \ # fonts
&& rm -rf /var/lib/apt/lists/* apk --update --upgrade --no-cache add fontconfig ttf-freefont font-noto terminus-font && fc-cache -f
# Remove heavy python packages installed by weasyprint (that we don't need)
RUN rm -rf /usr/lib/python3/dist-packages/numpy \
&& rm -rf /usr/lib/python3/dist-packages/scipy \
&& rm -rf /usr/lib/python3/dist-packages/sympy
EXPOSE 8000 EXPOSE 8000
# Fix invoke command path for InvenTree environment check
RUN python -m pip install --no-cache-dir -U invoke
RUN mkdir -p ${INVENTREE_HOME} RUN mkdir -p ${INVENTREE_HOME}
WORKDIR ${INVENTREE_HOME} WORKDIR ${INVENTREE_HOME}
COPY contrib/container/requirements.txt base_requirements.txt COPY contrib/container/requirements.txt base_requirements.txt
COPY src/backend/requirements.txt ./
COPY contrib/container/install_build_packages.sh .
RUN chmod +x install_build_packages.sh
COPY tasks.py \ # For ARMv7 architecture, add the piwheels repo (for cryptography library)
src/backend/requirements.txt \ # Otherwise, we have to build from source, which is difficult
contrib/container/gunicorn.conf.py \ # Ref: https://github.com/inventree/InvenTree/pull/4598
contrib/container/init.sh \ RUN if [ `apk --print-arch` = "armv7" ]; then \
./ printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
COPY tasks.py contrib/container/gunicorn.conf.py contrib/container/init.sh ./
RUN chmod +x init.sh RUN chmod +x init.sh
ENTRYPOINT ["/bin/bash", "./init.sh"] ENTRYPOINT ["/bin/ash", "./init.sh"]
# Multi-stage build to compile project requirements FROM inventree_base AS prebuild
FROM inventree_base AS builder_stage
# Copy source files ENV PATH=/root/.local/bin:$PATH
RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
pip install --user --require-hashes -r base_requirements.txt --no-cache && \
pip install --user --require-hashes -r requirements.txt --no-cache && \
apk --purge del .build-deps
# Frontend builder image:
FROM prebuild AS frontend
RUN apk add --no-cache --update nodejs npm yarn
RUN yarn config set network-timeout 600000 -g
COPY src ${INVENTREE_HOME}/src COPY src ${INVENTREE_HOME}/src
COPY tasks.py ${INVENTREE_HOME}/tasks.py COPY tasks.py ${INVENTREE_HOME}/tasks.py
RUN cd ${INVENTREE_HOME} && inv frontend-compile
# Install backend build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config build-essential \
libldap2-dev libsasl2-dev libssl-dev \
libmariadb-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Build and install python dependencies
RUN pip install --user --require-hashes -r base_requirements.txt --no-cache-dir && \
pip install --user --require-hashes -r requirements.txt --no-cache-dir && \
pip cache purge && \
rm -rf /root/.cache/pip
# Install frontend build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
nodejs npm \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g n yarn --ignore-scripts && \
yarn config set network-timeout 600000 -g
RUN bash -c "n lts"
RUN cd "${INVENTREE_HOME}" && invoke int.frontend-compile --extract
# InvenTree production image: # InvenTree production image:
# - Copies required files from local directory # - Copies required files from local directory
@@ -138,26 +121,34 @@ ENV INVENTREE_DEBUG=False
ENV INVENTREE_COMMIT_HASH="${commit_hash}" ENV INVENTREE_COMMIT_HASH="${commit_hash}"
ENV INVENTREE_COMMIT_DATE="${commit_date}" ENV INVENTREE_COMMIT_DATE="${commit_date}"
# use dependencies and compiled wheels from the prebuild image
ENV PATH=/root/.local/bin:$PATH
COPY --from=prebuild /root/.local /root/.local
# Copy source code # Copy source code
COPY src/backend/InvenTree ${INVENTREE_BACKEND_DIR}/InvenTree COPY src/backend/InvenTree ${INVENTREE_BACKEND_DIR}/InvenTree
COPY src/backend/requirements.txt ${INVENTREE_BACKEND_DIR}/requirements.txt COPY src/backend/requirements.txt ${INVENTREE_BACKEND_DIR}/requirements.txt
COPY --from=frontend ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web
# Copy compiled dependencies from prebuild image
ENV PATH=/root/.local/bin:$PATH
COPY --from=builder_stage ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web
COPY --from=builder_stage /root/.local /root/.local
# Launch the production server # Launch the production server
CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"] CMD gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ${INVENTREE_BACKEND_DIR}/InvenTree
FROM builder_stage AS dev FROM inventree_base AS dev
ENV PATH=/root/.local/bin:$PATH
# Vite server (for local frontend development) # Vite server (for local frontend development)
EXPOSE 5173 EXPOSE 5173
# Install packages required for building python packages
RUN ./install_build_packages.sh
RUN pip install --require-hashes -r base_requirements.txt --no-cache
# Install nodejs / npm / yarn
RUN apk add --no-cache --update nodejs npm yarn
RUN yarn config set network-timeout 600000 -g
# The development image requires the source code to be mounted to /home/inventree/ # The development image requires the source code to be mounted to /home/inventree/
# So from here, we don't actually "do" anything, apart from some file management # So from here, we don't actually "do" anything, apart from some file management
@@ -170,7 +161,7 @@ ENV INVENTREE_PY_ENV="${INVENTREE_DATA_DIR}/env"
WORKDIR ${INVENTREE_HOME} WORKDIR ${INVENTREE_HOME}
# Entrypoint ensures that we are running in the python virtual environment # Entrypoint ensures that we are running in the python virtual environment
ENTRYPOINT ["/bin/bash", "./contrib/container/init.sh"] ENTRYPOINT ["/bin/ash", "./contrib/container/init.sh"]
# Launch the development server # Launch the development server
CMD ["invoke", "dev.server", "-a", "${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}"] CMD ["invoke", "server", "-a", "${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}"]

View File

@@ -1,3 +1,5 @@
version: "3.8"
# Docker compose recipe for InvenTree development server # Docker compose recipe for InvenTree development server
# - Runs PostgreSQL as the database backend # - Runs PostgreSQL as the database backend
# - Uses built-in django webserver # - Uses built-in django webserver
@@ -18,7 +20,7 @@ services:
# Use PostgreSQL as the database backend # Use PostgreSQL as the database backend
# Note: This can be changed to a different backend if required # Note: This can be changed to a different backend if required
inventree-dev-db: inventree-dev-db:
image: postgres:17 image: postgres:13
expose: expose:
- 5432/tcp - 5432/tcp
environment: environment:

View File

@@ -1,3 +1,5 @@
version: "3.8"
# Docker compose recipe for a production-ready InvenTree setup, with the following containers: # Docker compose recipe for a production-ready InvenTree setup, with the following containers:
# - PostgreSQL as the database backend # - PostgreSQL as the database backend
# - gunicorn as the InvenTree web server # - gunicorn as the InvenTree web server
@@ -32,17 +34,11 @@
# INVENTREE_TAG=0.7.5 # INVENTREE_TAG=0.7.5
# #
# ----------------------------
# Docker compose customization
# ----------------------------
# If you wish to customize the docker-compose script, you should only do so if you understand the stack!
# Do not expect support for customizations that are not part of the standard InvenTree setup!
services: services:
# Database service # Database service
# Use PostgreSQL as the database backend # Use PostgreSQL as the database backend
inventree-db: inventree-db:
image: postgres:17 image: postgres:13
container_name: inventree-db container_name: inventree-db
expose: expose:
- ${INVENTREE_DB_PORT:-5432}/tcp - ${INVENTREE_DB_PORT:-5432}/tcp
@@ -57,9 +53,14 @@ services:
restart: unless-stopped restart: unless-stopped
# redis acts as database cache manager # redis acts as database cache manager
# only runs under the "redis" profile : https://docs.docker.com/compose/profiles/
inventree-cache: inventree-cache:
image: redis:7-alpine image: redis:7.0
container_name: inventree-cache container_name: inventree-cache
depends_on:
- inventree-db
profiles:
- redis
env_file: env_file:
- .env - .env
expose: expose:
@@ -77,7 +78,6 @@ services:
- 8000 - 8000
depends_on: depends_on:
- inventree-db - inventree-db
- inventree-cache
env_file: env_file:
- .env - .env
volumes: volumes:
@@ -101,7 +101,6 @@ services:
restart: unless-stopped restart: unless-stopped
# caddy acts as reverse proxy and static file server # caddy acts as reverse proxy and static file server
# You can adjust the ports that the proxy listens on via the .env file
# https://hub.docker.com/_/caddy # https://hub.docker.com/_/caddy
inventree-proxy: inventree-proxy:
container_name: inventree-proxy container_name: inventree-proxy
@@ -110,8 +109,8 @@ services:
depends_on: depends_on:
- inventree-server - inventree-server
ports: ports:
- ${INVENTREE_HTTP_PORT:-80}:80 - ${INVENTREE_WEB_PORT:-80}:80
- ${INVENTREE_HTTPS_PORT:-443}:443 - 443:443
env_file: env_file:
- .env - .env
volumes: volumes:
@@ -121,3 +120,17 @@ services:
- ${INVENTREE_EXT_VOLUME}:/var/log:z - ${INVENTREE_EXT_VOLUME}:/var/log:z
- ${INVENTREE_EXT_VOLUME}:/data:z - ${INVENTREE_EXT_VOLUME}:/data:z
- ${INVENTREE_EXT_VOLUME}:/config:z - ${INVENTREE_EXT_VOLUME}:/config:z
# alternative: run nginx as reverse proxy
# inventree-proxy:
# container_name: inventree-proxy
# image: nginx:stable
# restart: always
# depends_on:
# - inventree-server
# ports:
# - ${INVENTREE_WEB_PORT:-80}:80
# - 443:443
# volumes:
# - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro,z
# - ${INVENTREE_EXT_VOLUME}:/var/www:z

View File

@@ -1,11 +1,9 @@
# InvenTree environment variables for a development setup # InvenTree environment variables for a development setup
# These variables will be used by the docker-compose.yml file # These variables will be used by the docker-compose.yml file
INVENTREE_SITE_URL=http://localhost:8000
# Set DEBUG to True for a development setup # Set DEBUG to True for a development setup
INVENTREE_DEBUG=True INVENTREE_DEBUG=True
INVENTREE_LOG_LEVEL=WARNING INVENTREE_LOG_LEVEL=INFO
INVENTREE_DB_LOGGING=False INVENTREE_DB_LOGGING=False
# Database configuration options # Database configuration options

View File

@@ -19,7 +19,7 @@ threads = 4
# Worker timeout (default = 90 seconds) # Worker timeout (default = 90 seconds)
timeout = os.environ.get('INVENTREE_GUNICORN_TIMEOUT', '90') timeout = os.environ.get('INVENTREE_GUNICORN_TIMEOUT', 90)
# Number of worker processes # Number of worker processes
workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None) workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None)
@@ -40,18 +40,3 @@ max_requests_jitter = 50
# preload app so that the ready functions are only executed once # preload app so that the ready functions are only executed once
preload_app = True preload_app = True
def post_fork(server, worker):
"""Post-fork hook to set up logging for each worker."""
from django.conf import settings
if not settings.TRACING_ENABLED:
return
# Instrument gunicorm
from InvenTree.tracing import setup_instruments, setup_tracing
# Run tracing/logging instrumentation
setup_tracing(**settings.TRACING_DETAILS)
setup_instruments(settings.DB_ENGINE)

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/bin/ash
# exit when any command fails # exit when any command fails
set -e set -e
@@ -40,17 +40,14 @@ if [[ -n "$INVENTREE_PY_ENV" ]]; then
if test -d "$INVENTREE_PY_ENV"; then if test -d "$INVENTREE_PY_ENV"; then
# venv already exists # venv already exists
echo "Using Python virtual environment: ${INVENTREE_PY_ENV}" echo "Using Python virtual environment: ${INVENTREE_PY_ENV}"
source ${INVENTREE_PY_ENV}/bin/activate
else else
# Setup a virtual environment (within the provided directory) # Setup a virtual environment (within the "data/env" directory)
echo "Running first time setup for python environment" echo "Running first time setup for python environment"
python3 -m venv ${INVENTREE_PY_ENV} --system-site-packages --upgrade-deps python3 -m venv ${INVENTREE_PY_ENV} --system-site-packages --upgrade-deps
# Ensure invoke tool is installed locally
source ${INVENTREE_PY_ENV}/bin/activate
python3 -m pip install --ignore-installed --upgrade invoke
fi fi
# Now activate the venv
source ${INVENTREE_PY_ENV}/bin/activate
fi fi
cd ${INVENTREE_HOME} cd ${INVENTREE_HOME}

View File

@@ -0,0 +1,12 @@
#!/bin/ash
# Install system packages required for building InvenTree python libraries
# Note that for postgreslql, we use the 13 version, which matches the version used in the InvenTree docker image
apk add gcc g++ musl-dev openssl-dev libffi-dev cargo python3-dev openldap-dev \
libstdc++ build-base linux-headers py3-grpcio \
jpeg-dev openjpeg-dev libwebp-dev zlib-dev \
sqlite sqlite-dev \
mariadb-connector-c-dev mariadb-client mariadb-dev \
postgresql13-dev postgresql-libs \
$@

View File

@@ -17,7 +17,6 @@ gunicorn>=22.0.0
# LDAP required packages # LDAP required packages
django-auth-ldap # Django integration for ldap auth django-auth-ldap # Django integration for ldap auth
python-ldap # LDAP auth support python-ldap # LDAP auth support
django<6.0 # Force lower to match main project
# Upgraded python package installer # Upgraded python package installer
uv uv

View File

@@ -1,256 +1,220 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv pip compile contrib/container/requirements.in -o contrib/container/requirements.txt --python-version=3.11 --no-strip-extras --generate-hashes -b src/backend/requirements.txt # uv pip compile contrib/container/requirements.in -o contrib/container/requirements.txt --python-version=3.11 --no-strip-extras --generate-hashes
asgiref==3.10.0 \ asgiref==3.8.1 \
--hash=sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734 \ --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \
--hash=sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590
# via django # via django
django==5.2.9 \ django==4.2.14 \
--hash=sha256:16b5ccfc5e8c27e6c0561af551d2ea32852d7352c67d452ae3e76b4f6b2ca495 \ --hash=sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240 \
--hash=sha256:3a4ea88a70370557ab1930b332fd2887a9f48654261cdffda663fef5976bb00a --hash=sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96
# via # via django-auth-ldap
# -r contrib/container/requirements.in django-auth-ldap==4.8.0 \
# django-auth-ldap --hash=sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce \
django-auth-ldap==5.2.0 \ --hash=sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738
--hash=sha256:08ba6efc0340d9874725a962311b14991e29a33593eb150a8fb640709dbfa80f \ gunicorn==22.0.0 \
--hash=sha256:7dc6eb576ba36051850b580e4bdf4464e04bbe7367c3827a3121b4d7c51fb175 --hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \
# via -r contrib/container/requirements.in --hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63
gunicorn==23.0.0 \ invoke==2.2.0 \
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \ --hash=sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820 \
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec --hash=sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5
# via -r contrib/container/requirements.in mariadb==1.1.10 \
invoke==2.2.1 \ --hash=sha256:03d6284ef713d1cad40146576a4cc2d6cbc1662060f2a0e59b174e1694521698 \
--hash=sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8 \ --hash=sha256:1ce87971c02375236ff8933e6c593c748e7b2f2950b86eabfab4289fd250ea63 \
--hash=sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707 --hash=sha256:1d81b22efbaaf4c5bc5e4cc4e2ef3c459538c1a939371089d8c5591d6f26a62e \
# via -r contrib/container/requirements.in --hash=sha256:29040e426f877ddc45f337c6eb381b6bbab63cc6bf8431a28effe30162142513 \
mariadb==1.1.14 \ --hash=sha256:4521aa721f926946bd71491f872e8babc78fa97755ed2114f5684b77363107cb \
--hash=sha256:0f5fc74023f2e479be159542633f8b5865fee18a36e5a6d4e262387b39a692ee \ --hash=sha256:49200378c614984f5ec875481662a49d7c97c2be27970b01b32fa4b7520d4e22 \
--hash=sha256:1a50b4612c0dd5b69690cebb34cef552a7f64dcadeb5aa91d70cd99bf01bc5b3 \ --hash=sha256:5d652117e2fdf12b9723e7452a05fce9e6ccbae6ea48871b21a3a8fde259dc48 \
--hash=sha256:3d2c795cde606f4e12c0d73282b062433f414cae035675b0d81f2d65c9b79ac5 \ --hash=sha256:8c8c6b27486b0e1772a23002c702b5fd244eecf9f05633dd6cb345fc26755a20 \
--hash=sha256:3f6fdc4ded5e0500a6a29bf0c8bf1be94189dcef5a8d5e0e154a4b3456f86bcc \ --hash=sha256:a332893e3ef7ceb7970ab4bd7c844bcb4bd68a051ca51313566f9808d7411f2d \
--hash=sha256:4c7f33578da163a1b79929aae241f5f981d7b9d5a94d89e589aad7ec58e313ea \ --hash=sha256:d7b09ec4abd02ed235257feb769f90cd4066e8f536b55b92f5166103d5b66a63 \
--hash=sha256:55ddbe5272c292cbcb2968d87681b5d2b327e65646a015e324b8eeb804d14531 \ --hash=sha256:dff8b28ce4044574870d7bdd2d9f9f5da8e5f95a7ff6d226185db733060d1a93
--hash=sha256:5b514362ba3ad3ef7ada91bc8a8b3b4c0e5144efce96b5bffa3dbc46b8af7d7a \ mysqlclient==2.2.4 \
--hash=sha256:6659725837e48fa6af05e20128fb525029f706f1921d5dbf639a25b2f80b9f93 \ --hash=sha256:329e4eec086a2336fe3541f1ce095d87a6f169d1cc8ba7b04ac68bcb234c9711 \
--hash=sha256:685a1ad2a24fd0aae1c4416fe0ac794adc84ab9209c8d0c57078f770d39731db \ --hash=sha256:33bc9fb3464e7d7c10b1eaf7336c5ff8f2a3d3b88bab432116ad2490beb3bf41 \
--hash=sha256:7fd603c5cf23c47ef0d28fdc2b4b79919ee7f75d00ed070d3cd1054dcf816aeb \ --hash=sha256:3c318755e06df599338dad7625f884b8a71fcf322a9939ef78c9b3db93e1de7a \
--hash=sha256:932a95016b7e9b8d78893aa5ee608e74199e3c6dd607dbe5e4da2010a4f67b88 \ --hash=sha256:4e80dcad884dd6e14949ac6daf769123223a52a6805345608bf49cdaf7bc8b3a \
--hash=sha256:98d552a8bb599eceaa88f65002ad00bd88aeed160592c273a7e5c1d79ab733dd \ --hash=sha256:9d3310295cb682232cadc28abd172f406c718b9ada41d2371259098ae37779d3 \
--hash=sha256:e6d702a53eccf20922e47f2f45cfb5c7a0c2c6c0a46e4ee2d8a80d0ff4a52f34 --hash=sha256:9d4c015480c4a6b2b1602eccd9846103fc70606244788d04aa14b31c4bd1f0e2 \
# via -r contrib/container/requirements.in --hash=sha256:ac44777eab0a66c14cb0d38965572f762e193ec2e5c0723bcd11319cc5b693c5 \
mysqlclient==2.2.7 \ --hash=sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54 \
--hash=sha256:199dab53a224357dd0cb4d78ca0e54018f9cee9bf9ec68d72db50e0a23569076 \ --hash=sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab
--hash=sha256:201a6faa301011dd07bca6b651fe5aaa546d7c9a5426835a06c3172e1056a3c5 \ packaging==24.0 \
--hash=sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845 \ --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:2e3c11f7625029d7276ca506f8960a7fd3c5a0a0122c9e7404e6a8fe961b3d22 \ --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
--hash=sha256:4b4c0200890837fc64014cc938ef2273252ab544c1b12a6c1d674c23943f3f2e \
--hash=sha256:92af368ed9c9144737af569c86d3b6c74a012a6f6b792eb868384787b52bb585 \
--hash=sha256:977e35244fe6ef44124e9a1c2d1554728a7b76695598e4b92b37dc2130503069 \
--hash=sha256:a22d99d26baf4af68ebef430e3131bb5a9b722b79a9fcfac6d9bbf8a88800687
# via -r contrib/container/requirements.in
packaging==25.0 \
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
# via # via
# gunicorn # gunicorn
# mariadb # mariadb
psycopg[binary, pool]==3.2.12 \ psycopg[binary, pool]==3.1.18 \
--hash=sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b \ --hash=sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b \
--hash=sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee --hash=sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e
# via -r contrib/container/requirements.in psycopg-binary==3.1.18 \
psycopg-binary==3.2.12 \ --hash=sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7 \
--hash=sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d \ --hash=sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c \
--hash=sha256:0afb71a99871a41dd677d207c6a988d978edde5d6a018bafaed4f9da45357055 \ --hash=sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31 \
--hash=sha256:100fdfee763d701f6da694bde711e264aca4c2bc84fb81e1669fb491ce11d219 \ --hash=sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5 \
--hash=sha256:13cd057f406d2c8063ae8b489395b089a7f23c39aff223b5ea39f0c4dd640550 \ --hash=sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e \
--hash=sha256:15e226f0d8af85cc8b2435b2e9bc6f0d40febc79eef76cf20fceac4d902a6a7b \ --hash=sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4 \
--hash=sha256:16db2549a31ccd4887bef05570d95036813ce25fd9810b523ba1c16b0f6cfd90 \ --hash=sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404 \
--hash=sha256:1c1dbeb8e97d00a33dfa9987776ce3d1c1e4cc251dfbd663b8f9e173f5c89d17 \ --hash=sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7 \
--hash=sha256:1d7cedecbe0bb60a2e72b1613fba4072a184a6472d6cc9aa99e540217f544e3e \ --hash=sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46 \
--hash=sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc \ --hash=sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57 \
--hash=sha256:26b5927b5880b396231ab6190ee5c8fb47ed3f459b53504ed5419faaf16d3bfb \ --hash=sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee \
--hash=sha256:294f08b014f08dfd3c9b72408f5e1a0fd187bd86d7a85ead651e32dbd47aa038 \ --hash=sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834 \
--hash=sha256:2aa80ca8d17266507bef853cecefa7d632ffd087883ee7ca92b8a7ea14a1e581 \ --hash=sha256:3c7afcd6f1d55992f26d9ff7b0bd4ee6b475eb43aa3f054d67d32e09f18b0065 \
--hash=sha256:2d55009eeddbef54c711093c986daaf361d2c4210aaa1ee905075a3b97a62441 \ --hash=sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686 \
--hash=sha256:310c95a68a9b948b89d6d187622757d57b6c26cece3c3f7c2cbb645ee36531b2 \ --hash=sha256:3e7ce4d988112ca6c75765c7f24c83bdc476a6a5ce00878df6c140ca32c3e16d \
--hash=sha256:32b3e12d9441508f9c4e1424f4478b1a518a90a087cd54be3754e74954934194 \ --hash=sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804 \
--hash=sha256:356b4266e5cde7b5bbcf232f549dedf7fbed4983daa556042bdec397780e044d \ --hash=sha256:4575da95fc441244a0e2ebaf33a2b2f74164603341d2046b5cde0a9aa86aa7e2 \
--hash=sha256:385c7b5cfffac115f413b8e32c941c85ea0960e0b94a6ef43bb260f774c54893 \ --hash=sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143 \
--hash=sha256:3c1e38b1eda54910628f68448598139a9818973755abf77950057372c1fe89a6 \ --hash=sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722 \
--hash=sha256:3e9c9e64fb7cda688e9488402611c0be2c81083664117edcc709d15f37faa30f \ --hash=sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82 \
--hash=sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b \ --hash=sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad \
--hash=sha256:489b154891f1c995355adeb1077ee3479e9c9bada721b93270c20243bbad6542 \ --hash=sha256:59701118c7d8842e451f1e562d08e8708b3f5d14974eefbce9374badd723c4ae \
--hash=sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39 \ --hash=sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a \
--hash=sha256:49582c3b6d578bdaab2932b59f70b1bd93351ed4d594b2c97cea1611633c9de1 \ --hash=sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f \
--hash=sha256:58ed30d33c25d7dc8d2f06285e88493147c2a660cc94713e4b563a99efb80a1f \ --hash=sha256:639dd78ac09b144b0119076783cb64e1128cc8612243e9701d1503c816750b2e \
--hash=sha256:5b6e505618cb376a7a7d6af86833a8f289833fe4cc97541d7100745081dc31bd \ --hash=sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83 \
--hash=sha256:66a031f22e4418016990446d3e38143826f03ad811b9f78f58e2afbc1d343f7a \ --hash=sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd \
--hash=sha256:6a898717ab560db393355c6ecf39b8c534f252afc3131480db1251e061090d3a \ --hash=sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09 \
--hash=sha256:7130effd0517881f3a852eff98729d51034128f0737f64f0d1c7ea8343d77bd7 \ --hash=sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73 \
--hash=sha256:72fd979e410ba7805462817ef8ed6f37dd75f9f4ae109bdb8503e013ccecb80b \ --hash=sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d \
--hash=sha256:77690f0bf08356ca00fc357f50a5980c7a25f076c2c1f37d9d775a278234fefd \ --hash=sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68 \
--hash=sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5 \ --hash=sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773 \
--hash=sha256:7b9a99ded7d19b24d3b6fa632b58e52bbdecde7e1f866c3b23d0c27b092af4e3 \ --hash=sha256:812726266ab96de681f2c7dbd6b734d327f493a78357fcc16b2ac86ff4f4e080 \
--hash=sha256:802bd01fb18a0acb0dea491f69a9a2da6034f33329a62876ab5b558a1fb66b45 \ --hash=sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299 \
--hash=sha256:8335d989a4e94df2ccd8a1acbba9d03c4157ea8d73b65b79d447c6dc10b001d8 \ --hash=sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1 \
--hash=sha256:89b3c5201ca616d69ca0c3c0003ca18f7170a679c445c7e386ebfb4f29aa738e \ --hash=sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c \
--hash=sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e \ --hash=sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d \
--hash=sha256:909de94de7dd4d6086098a5755562207114c9638ec42c52d84c8a440c45fe084 \ --hash=sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5 \
--hash=sha256:940ac69ef6e89c17b3d30f3297a2ad03efdd06a4b1857f81bc533a9108a90eb9 \ --hash=sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c \
--hash=sha256:95f2806097a49bfd57e0c6a178f77b99487c53c157d9d507aee9c40dd58efdb4 \ --hash=sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5 \
--hash=sha256:9c674887d1e0d4384c06c822bc7fcfede4952742e232ec1e76b5a6ae39a3ddd4 \ --hash=sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe \
--hash=sha256:9fdf3a0c24822401c60c93640da69b3dfd4d9f29c3a8d797244fe22bfe592823 \ --hash=sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921 \
--hash=sha256:ab02b7d138768fd6ac4230e45b073f7b9fd688d88c04f24c34df4a250a94d066 \ --hash=sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1 \
--hash=sha256:acb1811219a4144539f0baee224a11a2aa323a739c349799cf52f191eb87bc52 \ --hash=sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a \
--hash=sha256:bfd632f7038c76b0921f6d5621f5ba9ecabfad3042fa40e5875db11771d2a5de \ --hash=sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d \
--hash=sha256:ce68839da386f137bc8d814fdbeede8f89916b8605e3593a85b504a859243af9 \ --hash=sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d \
--hash=sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3 \ --hash=sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741 \
--hash=sha256:dc68094e00a5a7e8c20de1d3a0d5e404a27f522e18f8eb62bbbc9f865c3c81ef \ --hash=sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c \
--hash=sha256:deeb06b7141f3a577c3aa8562307e2747580ae43d705a0482603a2c1f110d046 \ --hash=sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69 \
--hash=sha256:e0b5ccd03ca4749b8f66f38608ccbcb415cbd130d02de5eda80d042b83bee90e \ --hash=sha256:c76659ae29a84f2c14f56aad305dd00eb685bd88f8c0a3281a9a4bc6bd7d2aa7 \
--hash=sha256:ea049c8d33c4f4e6b030d5a68123c0ccd2ffb77d4035f073db97187b49b6422f \ --hash=sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38 \
--hash=sha256:ea9751310b840186379c949ede5a5129b31439acdb929f3003a8685372117ed8 \ --hash=sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c \
--hash=sha256:ec82fa5134517af44e28a30c38f34384773a0422ffd545fd298433ea9f2cc5a9 \ --hash=sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970 \
--hash=sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675 \ --hash=sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679 \
--hash=sha256:ef40601b959cc1440deaf4d53472ab54fa51036c37189cf3fe5500559ac25347 \ --hash=sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85 \
--hash=sha256:ef92d5ba6213de060d1390b1f71f5c3b2fbb00b4d55edee39f3b07234538b64a \ --hash=sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30 \
--hash=sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3 \ --hash=sha256:dea4a59da7850192fdead9da888e6b96166e90608cf39e17b503f45826b16f84 \
--hash=sha256:f33c9e12ed05e579b7fb3c8fdb10a165f41459394b8eb113e7c377b2bd027f61 \ --hash=sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008 \
--hash=sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a \ --hash=sha256:e1cf59e0bb12e031a48bb628aae32df3d0c98fd6c759cb89f464b1047f0ca9c8 \
--hash=sha256:f6ba1fe35fd215813dac4544a5ffc90f13713b29dd26e9e5be97ba53482bf6d6 \ --hash=sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e \
--hash=sha256:f7c81bc60560be9eb3c23601237765069ebfa9881097ce19ca6b5ea17c5faa8f \ --hash=sha256:e262398e5d51563093edf30612cd1e20fedd932ad0994697d7781ca4880cdc3d \
--hash=sha256:f8107968a9eadb451cfa6cf86036006fdde32a83cd39c26c9ca46765e653b547 \ --hash=sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5 \
--hash=sha256:f821e0c8a8fdfddfa71acb4f462d7a4c5aae1655f3f5e078970dbe9f19027386 --hash=sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414 \
--hash=sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c \
--hash=sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54
# via psycopg # via psycopg
psycopg-pool==3.2.7 \ psycopg-pool==3.2.1 \
--hash=sha256:4b47bb59d887ef5da522eb63746b9f70e2faf967d34aac4f56ffc65e9606728f \ --hash=sha256:060b551d1b97a8d358c668be58b637780b884de14d861f4f5ecc48b7563aafb7 \
--hash=sha256:a77d531bfca238e49e5fb5832d65b98e69f2c62bfda3d2d4d833696bdc9ca54b --hash=sha256:6509a75c073590952915eddbba7ce8b8332a440a31e77bba69561483492829ad
# via psycopg # via psycopg
pyasn1==0.6.1 \ pyasn1==0.6.0 \
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ --hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \
--hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 --hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473
# via # via
# pyasn1-modules # pyasn1-modules
# python-ldap # python-ldap
pyasn1-modules==0.4.2 \ pyasn1-modules==0.4.0 \
--hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a \ --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \
--hash=sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6 --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b
# via python-ldap # via python-ldap
python-ldap==3.4.5 \ python-ldap==3.4.4 \
--hash=sha256:b2f6ef1c37fe2c6a5a85212efe71311ee21847766a7d45fcb711f3b270a5f79a --hash=sha256:7edb0accec4e037797705f3a05cbf36a9fde50d08c8f67f2aef99a2628fab828
# via # via django-auth-ldap
# -r contrib/container/requirements.in pyyaml==6.0.1 \
# django-auth-ldap --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
pyyaml==6.0.3 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ --hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
--hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ setuptools==69.5.1 \
--hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ --hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ --hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ sqlparse==0.5.0 \
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ --hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
--hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ --hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \
--hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
--hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
--hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \
--hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
--hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
# via -r contrib/container/requirements.in
setuptools==80.9.0 \
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
# via -r contrib/container/requirements.in
sqlparse==0.5.3 \
--hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \
--hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca
# via django # via django
typing-extensions==4.15.0 \ typing-extensions==4.11.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a
# via # via
# psycopg # psycopg
# psycopg-pool # psycopg-pool
uv==0.9.8 \ uv==0.1.38 \
--hash=sha256:0f03bc413c933dbf850ad0dc2dba3df6b80c860a5c65cd767add49da19dadef0 \ --hash=sha256:03242a734a572733f2b9a5dbb94517e918fe26fc01114b7c51d12296dfbb8f8b \
--hash=sha256:14670bf55ecb5cfd0f3654fbf51c58a21dec3ad8ab531079b3ed8599271dc77b \ --hash=sha256:067af2d986329db4fa3c7373017d49f0e16ddff23e483b7e5bc3a5a18ce08ea6 \
--hash=sha256:1b8b5bdcda3e10ea70b618d0609acddc5c725cb58d4caf933030ddedd7c2e98f \ --hash=sha256:0937ad16ae0e0b6bb6dd3c386f8fb33141ad08d1762eaacffb4d2b27fb466a17 \
--hash=sha256:40253d00c1e900a0a61b132b1e0dd4aa83575cfd5302d3671899b6de29b1ef67 \ --hash=sha256:0e1d64ac437b0a14fbcec55b1c3f162fa24860711e0d855fcd9c672b149a122a \
--hash=sha256:50d130c46d97d7f10675ebea8608b7b4722c84b5745cd1bb0c8ae6d7984c05d5 \ --hash=sha256:1be7aa46936c0351ccb1400ea95e5381b3f05fef772fa3b9f23af728cc175dea \
--hash=sha256:543693def38fa41b9706aba391111fe8d9dd6be86899d76f9581faf045ac1cb6 \ --hash=sha256:309e73a3ec3a5a536a3efaf434270fc94b483069f1425765165c1c9d786c27fd \
--hash=sha256:5af28f1645eb3c50fd34a78508792db2d0799816f4eb5f55e1e6e2c724dfb125 \ --hash=sha256:4251f9771d392d7badc1e5fb934b397b12ca00fef9d955207ade169cc1f7e872 \
--hash=sha256:6a01d7cd41953ffac583139b10ad1df004a67c0246a6b694eb5bcdbc8c99deaf \ --hash=sha256:43772e7589f70e954b1ae29230e575ef9e4d8d769138a94dfa5ae7eaf1e26ac5 \
--hash=sha256:6df2e16f6df32018047c60bab2c0284868ad5c309addba9183ea2eeb71746bf0 \ --hash=sha256:4a6024256d38b77151e32876be9fcb99cf75df7a86b26e0161cc202bed558adf \
--hash=sha256:7038a552159f2291dd0d1f4f66a36261b5f3ed5fcd92e2869186f8e910b2c935 \ --hash=sha256:5a98d6aacd4b57b7e00daf154919e7c9206fefdf40bd28cfb13efe0e0324d491 \
--hash=sha256:75671150d6eb9d5ee829e1fdb8cf86b8e44a66d27cbb996fe807e986c4107b5d \ --hash=sha256:8de6dbd8f348ee90af044f4cc7b6650521d25ba2d20a813c1e157a3f90069dd9 \
--hash=sha256:87c3b65b6d5fcbdeab199d54c74fbf75de19cb534a690c936c5616478a038576 \ --hash=sha256:9133e24db9bdd4f412eab69586d03294419825432a9a27ee1b510a4c01eb7b0b \
--hash=sha256:99b18bfe92c33c3862b65d74677697e799763e669e0064685f405e7e27517f25 \ --hash=sha256:92f65b6e4e5c8126501785af3629dc537d7c82caa56ac9336a86929c73d0e138 \
--hash=sha256:9f2f3576c4518ff4f15e48dbca70585a513523c4738bc8cc2e48b20fd1190ce3 \ --hash=sha256:afd85029923e712b6b2c45ddc1680c785392220876c766521e45778db3f71f8e \
--hash=sha256:a4010b3fdabbb3c4f2cf2f7aa3bf6002d00049dcbc54ce0ee5ada32a933b2290 \ --hash=sha256:b0b15e51a0f8240969bc412ed0dd60cfe3f664b30173139ef263d71c596d631f \
--hash=sha256:bb0f8e83c2a2fc5a802e930cc8a7b71ab068180300a3f27ba38037f9fcb3d430 \ --hash=sha256:ea44c07605d1359a7d82bf42706dd86d341f15f4ca2e1f36e51626a7111c2ad5 \
--hash=sha256:cdbfadca9522422ab9820f5ada071c9c5c869bcd6fee719d20d91d5ec85b2a7d \ --hash=sha256:f87c9711493c53d32012a96b49c4d53aabdf7ed666cbf2c3fb55dd402a6b31a8
--hash=sha256:d93a2227d23e81ab3a16c30363559afc483e8aca40ea9343b3f326a9a41718c9 \ wheel==0.43.0 \
--hash=sha256:f52c6a99197028a314d4c1825f7ccb696eb9a88b822d2e2f17046266c75e543e --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \
# via -r contrib/container/requirements.in --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81
wheel==0.45.1 \
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \
--hash=sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248
# via -r contrib/container/requirements.in

View File

@@ -23,7 +23,7 @@ port = 127.0.0.1:9001
; InvenTree Web Server Process ; InvenTree Web Server Process
[program:inventree-server] [program:inventree-server]
user=inventree user=inventree
directory=/home/inventree/src/src/backend/InvenTree directory=/home/inventree/src/InvenTree
command=/home/inventree/env/bin/gunicorn -c gunicorn.conf.py InvenTree.wsgi command=/home/inventree/env/bin/gunicorn -c gunicorn.conf.py InvenTree.wsgi
startsecs=10 startsecs=10
autostart=true autostart=true
@@ -36,7 +36,7 @@ stdout_logfile=/home/inventree/log/server.out.log
; InvenTree Background Worker Process ; InvenTree Background Worker Process
[program:inventree-cluster] [program:inventree-cluster]
user=inventree user=inventree
directory=/home/inventree/src/src/backend/InvenTree directory=/home/inventree/src/InvenTree
command=/home/inventree/env/bin/python manage.py qcluster command=/home/inventree/env/bin/python manage.py qcluster
startsecs=10 startsecs=10
autostart=true autostart=true

View File

@@ -1,4 +1,4 @@
# Packages needed for CI/packages # Packages needed for CI/packages
requests==2.32.5 requests==2.31.0
pyyaml==6.0.3 pyyaml==6.0.1
jc==1.25.6 jc==1.25.2

View File

@@ -1,287 +1,228 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt --no-strip-extras --generate-hashes -b src/backend/requirements.txt # uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt --python-version=3.9 --no-strip-extras --generate-hashes
certifi==2025.10.5 \ certifi==2024.2.2 \
--hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \
--hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43 --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1
# via requests # via requests
charset-normalizer==3.4.4 \ charset-normalizer==3.3.2 \
--hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \ --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \
--hash=sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93 \ --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \
--hash=sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394 \ --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \
--hash=sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89 \ --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \
--hash=sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc \ --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \
--hash=sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 \ --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \
--hash=sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63 \ --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \
--hash=sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d \ --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \
--hash=sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f \ --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \
--hash=sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8 \ --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \
--hash=sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0 \ --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \
--hash=sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505 \ --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \
--hash=sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161 \ --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \
--hash=sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af \ --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \
--hash=sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152 \ --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \
--hash=sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318 \ --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \
--hash=sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72 \ --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \
--hash=sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4 \ --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \
--hash=sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e \ --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \
--hash=sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3 \ --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \
--hash=sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576 \ --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \
--hash=sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c \ --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \
--hash=sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1 \ --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \
--hash=sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8 \ --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \
--hash=sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1 \ --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \
--hash=sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2 \ --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \
--hash=sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44 \ --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \
--hash=sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26 \ --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \
--hash=sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88 \ --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \
--hash=sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 \ --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \
--hash=sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede \ --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \
--hash=sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf \ --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \
--hash=sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a \ --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \
--hash=sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc \ --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \
--hash=sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0 \ --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \
--hash=sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84 \ --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \
--hash=sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db \ --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \
--hash=sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1 \ --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \
--hash=sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7 \ --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \
--hash=sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed \ --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \
--hash=sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 \ --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \
--hash=sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133 \ --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \
--hash=sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e \ --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \
--hash=sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef \ --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \
--hash=sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14 \ --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \
--hash=sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2 \ --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \
--hash=sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0 \ --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \
--hash=sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d \ --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \
--hash=sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828 \ --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \
--hash=sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f \ --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \
--hash=sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf \ --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \
--hash=sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6 \ --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \
--hash=sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328 \ --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \
--hash=sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090 \ --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \
--hash=sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa \ --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \
--hash=sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 \ --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \
--hash=sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c \ --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \
--hash=sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb \ --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \
--hash=sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc \ --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \
--hash=sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a \ --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \
--hash=sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec \ --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \
--hash=sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc \ --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \
--hash=sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac \ --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \
--hash=sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e \ --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \
--hash=sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313 \ --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \
--hash=sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569 \ --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \
--hash=sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3 \ --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \
--hash=sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d \ --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \
--hash=sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525 \ --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \
--hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 \ --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \
--hash=sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3 \ --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \
--hash=sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9 \ --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \
--hash=sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a \ --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \
--hash=sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9 \ --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \
--hash=sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 \ --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \
--hash=sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25 \ --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \
--hash=sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50 \ --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \
--hash=sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf \ --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \
--hash=sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1 \ --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \
--hash=sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3 \ --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \
--hash=sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac \ --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \
--hash=sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e \ --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \
--hash=sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815 \ --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \
--hash=sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c \ --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \
--hash=sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6 \ --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \
--hash=sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6 \ --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \
--hash=sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e \ --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \
--hash=sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4 \ --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \
--hash=sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84 \ --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \
--hash=sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69 \ --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561
--hash=sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15 \
--hash=sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191 \
--hash=sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0 \
--hash=sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897 \
--hash=sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd \
--hash=sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2 \
--hash=sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 \
--hash=sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d \
--hash=sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074 \
--hash=sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3 \
--hash=sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224 \
--hash=sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838 \
--hash=sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a \
--hash=sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d \
--hash=sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d \
--hash=sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f \
--hash=sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8 \
--hash=sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490 \
--hash=sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966 \
--hash=sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9 \
--hash=sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3 \
--hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \
--hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608
# via requests # via requests
idna==3.11 \ idna==3.7 \
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902 --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests # via requests
jc==1.25.6 \ jc==1.25.2 \
--hash=sha256:27f58befc7ae0a4c63322926c5f1ec892e3eac4a065eff3b07cfe420a6924a07 \ --hash=sha256:26e412a65a478f9da3097653db6277f915cfae5c0f0a3f42026b405936abd358 \
--hash=sha256:7367b59e6e0da8babeede1e5b0da083f3c5aa6b6e585b4aed28dd7c4b2d76162 --hash=sha256:97ada193495f79550f06fe0cbfb119ff470bcca57c1cc593a5cdb0008720e0b3
# via -r contrib/dev_reqs/requirements.in pygments==2.17.2 \
pygments==2.19.2 \ --hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
# via jc # via jc
pyyaml==6.0.3 \ pyyaml==6.0.1 \
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ --hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ requests==2.31.0 \
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
--hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ ruamel-yaml==0.18.6 \
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ --hash=sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636 \
--hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ --hash=sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
--hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
--hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \
--hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
--hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
# via -r contrib/dev_reqs/requirements.in
requests==2.32.5 \
--hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
--hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
# via -r contrib/dev_reqs/requirements.in
ruamel-yaml==0.18.15 \
--hash=sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701 \
--hash=sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700
# via jc # via jc
ruamel-yaml-clib==0.2.15 \ ruamel-yaml-clib==0.2.8 \
--hash=sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490 \ --hash=sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d \
--hash=sha256:04d21dc9c57d9608225da28285900762befbb0165ae48482c15d8d4989d4af14 \ --hash=sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001 \
--hash=sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6 \ --hash=sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462 \
--hash=sha256:0ba6604bbc3dfcef844631932d06a1a4dcac3fee904efccf582261948431628a \ --hash=sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9 \
--hash=sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9 \ --hash=sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe \
--hash=sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d \ --hash=sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b \
--hash=sha256:1bb7b728fd9f405aa00b4a0b17ba3f3b810d0ccc5f77f7373162e9b5f0ff75d5 \ --hash=sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b \
--hash=sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3 \ --hash=sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615 \
--hash=sha256:27dc656e84396e6d687f97c6e65fb284d100483628f02d95464fd731743a4afe \ --hash=sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62 \
--hash=sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c \ --hash=sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15 \
--hash=sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc \ --hash=sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b \
--hash=sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf \ --hash=sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1 \
--hash=sha256:3cb75a3c14f1d6c3c2a94631e362802f70e83e20d1f2b2ef3026c05b415c4900 \ --hash=sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9 \
--hash=sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a \ --hash=sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675 \
--hash=sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa \ --hash=sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899 \
--hash=sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6 \ --hash=sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7 \
--hash=sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd \ --hash=sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7 \
--hash=sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25 \ --hash=sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312 \
--hash=sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600 \ --hash=sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa \
--hash=sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf \ --hash=sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91 \
--hash=sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642 \ --hash=sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b \
--hash=sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614 \ --hash=sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6 \
--hash=sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf \ --hash=sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3 \
--hash=sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000 \ --hash=sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334 \
--hash=sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb \ --hash=sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5 \
--hash=sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690 \ --hash=sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3 \
--hash=sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e \ --hash=sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe \
--hash=sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137 \ --hash=sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c \
--hash=sha256:5d3c9210219cbc0f22706f19b154c9a798ff65a6beeafbf77fc9c057ec806f7d \ --hash=sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed \
--hash=sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401 \ --hash=sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337 \
--hash=sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f \ --hash=sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880 \
--hash=sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2 \ --hash=sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f \
--hash=sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471 \ --hash=sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d \
--hash=sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed \ --hash=sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248 \
--hash=sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524 \ --hash=sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d \
--hash=sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60 \ --hash=sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf \
--hash=sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef \ --hash=sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512 \
--hash=sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043 \ --hash=sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069 \
--hash=sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03 \ --hash=sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb \
--hash=sha256:923816815974425fbb1f1bf57e85eca6e14d8adc313c66db21c094927ad01815 \ --hash=sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942 \
--hash=sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77 \ --hash=sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d \
--hash=sha256:a8220fd4c6f98485e97aea65e1df76d4fed1678ede1fe1d0eed2957230d287c4 \ --hash=sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31 \
--hash=sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d \ --hash=sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92 \
--hash=sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467 \ --hash=sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5 \
--hash=sha256:badd1d7283f3e5894779a6ea8944cc765138b96804496c91812b2829f70e18a7 \ --hash=sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28 \
--hash=sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e \ --hash=sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d \
--hash=sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec \ --hash=sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1 \
--hash=sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4 \ --hash=sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2 \
--hash=sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd \ --hash=sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875 \
--hash=sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff \ --hash=sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412
--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 # via ruamel-yaml
urllib3==2.6.0 \ urllib3==2.2.1 \
--hash=sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f \ --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1 --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
# via requests # via requests
xmltodict==1.0.2 \ xmltodict==0.13.0 \
--hash=sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649 \ --hash=sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56 \
--hash=sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d --hash=sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852
# via jc # via jc

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# This script was generated by bashly 1.3.3 (https://bashly.dev) # This script was generated by bashly 1.1.1 (https://bashly.dannyb.co)
# Modifying it manually is not recommended # Modifying it manually is not recommended
if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 2))); then if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
printf "bash version 4.2 or higher is required\n" >&2 printf "bash version 4 or higher is required\n" >&2
exit 1 exit 1
fi fi
@@ -15,7 +15,7 @@ root_command() {
no_call=${args[--no-call]} no_call=${args[--no-call]}
dry_run=${args[--dry-run]} dry_run=${args[--dry-run]}
REQS="wget apt-transport-https curl gpg" REQS="wget apt-transport-https"
function do_call() { function do_call() {
if [[ $dry_run ]]; then if [[ $dry_run ]]; then
@@ -56,16 +56,17 @@ root_command() {
get_distribution get_distribution
echo "### Detected distribution: $OS $VER" echo "### Detected distribution: $OS $VER"
SUPPORTED=true # is this OS supported? SUPPORTED=true # is this OS supported?
NEEDS_LIBSSL1_1=false # does this OS need libssl1.1?
DIST_OS=${OS,,} DIST_OS=${OS,,}
DIST_VER=$VER DIST_VER=$VER
case "$OS" in case "$OS" in
Ubuntu) Ubuntu)
if [[ $VER == "24.04" ]]; then if [[ $VER == "22.04" ]]; then
SUPPORTED=true
elif [[ $VER == "22.04" ]]; then
SUPPORTED=true SUPPORTED=true
NEEDS_LIBSSL1_1=true
DIST_VER="20.04"
elif [[ $VER == "20.04" ]]; then elif [[ $VER == "20.04" ]]; then
SUPPORTED=true SUPPORTED=true
else else
@@ -109,6 +110,15 @@ root_command() {
fi fi
done done
if [[ $NEEDS_LIBSSL1_1 == "true" ]]; then
echo "### Installing libssl1.1"
echo "deb http://security.ubuntu.com/ubuntu focal-security main" | sudo tee /etc/apt/sources.list.d/focal-security.list
do_call "sudo apt-get update"
do_call "sudo apt-get install libssl1.1"
sudo rm /etc/apt/sources.list.d/focal-security.list
fi
echo "### Getting and adding key" echo "### Getting and adding key"
curl -fsSL https://dl.packager.io/srv/$publisher/InvenTree/key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/pkgr-inventree.gpg > /dev/null curl -fsSL https://dl.packager.io/srv/$publisher/InvenTree/key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/pkgr-inventree.gpg > /dev/null
echo "### Adding package source" echo "### Adding package source"
@@ -135,7 +145,15 @@ version_command() {
} }
install.sh_usage() { install.sh_usage() {
printf "install.sh - Interactive installer for InvenTree\n\n" if [[ -n $long_usage ]]; then
printf "install.sh - Interactive installer for InvenTree\n"
echo
else
printf "install.sh - Interactive installer for InvenTree\n"
echo
fi
printf "%s\n" "Usage:" printf "%s\n" "Usage:"
printf " install.sh [SOURCE] [PUBLISHER] [OPTIONS]\n" printf " install.sh [SOURCE] [PUBLISHER] [OPTIONS]\n"
@@ -143,7 +161,7 @@ install.sh_usage() {
printf " install.sh --version | -v\n" printf " install.sh --version | -v\n"
echo echo
if [[ -n "$long_usage" ]]; then if [[ -n $long_usage ]]; then
printf "%s\n" "Options:" printf "%s\n" "Options:"
printf " %s\n" "--no-call, -n" printf " %s\n" "--no-call, -n"
@@ -165,13 +183,13 @@ install.sh_usage() {
printf " %s\n" "SOURCE" printf " %s\n" "SOURCE"
printf " Package source that should be used\n" printf " Package source that should be used\n"
printf " %s\n" "Allowed: stable, master, main" printf " Allowed: stable, master, main\n"
printf " %s\n" "Default: stable" printf " Default: stable\n"
echo echo
printf " %s\n" "PUBLISHER" printf " %s\n" "PUBLISHER"
printf " Publisher that should be used\n" printf " Publisher that should be used\n"
printf " %s\n" "Default: inventree" printf " Default: inventree\n"
echo echo
printf "%s\n" "Examples:" printf "%s\n" "Examples:"
@@ -184,14 +202,11 @@ install.sh_usage() {
} }
normalize_input() { normalize_input() {
local arg passthru flags local arg flags
passthru=false
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
arg="$1" arg="$1"
if [[ $passthru == true ]]; then if [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
input+=("$arg")
elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
input+=("${BASH_REMATCH[1]}") input+=("${BASH_REMATCH[1]}")
input+=("${BASH_REMATCH[2]}") input+=("${BASH_REMATCH[2]}")
elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then
@@ -202,9 +217,6 @@ normalize_input() {
for ((i = 0; i < ${#flags}; i++)); do for ((i = 0; i < ${#flags}; i++)); do
input+=("-${flags:i:1}") input+=("-${flags:i:1}")
done done
elif [[ "$arg" == "--" ]]; then
passthru=true
input+=("$arg")
else else
input+=("$arg") input+=("$arg")
fi fi
@@ -213,11 +225,37 @@ normalize_input() {
done done
} }
inspect_args() {
if ((${#args[@]})); then
readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort)
echo args:
for k in "${sorted_keys[@]}"; do echo "- \${args[$k]} = ${args[$k]}"; done
else
echo args: none
fi
if ((${#other_args[@]})); then
echo
echo other_args:
echo "- \${other_args[*]} = ${other_args[*]}"
for i in "${!other_args[@]}"; do
echo "- \${other_args[$i]} = ${other_args[$i]}"
done
fi
if ((${#deps[@]})); then
readarray -t sorted_keys < <(printf '%s\n' "${!deps[@]}" | sort)
echo
echo deps:
for k in "${sorted_keys[@]}"; do echo "- \${deps[$k]} = ${deps[$k]}"; done
fi
}
parse_requirements() { parse_requirements() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
key="$1" case "${1:-}" in
case "$key" in
--version | -v) --version | -v)
version_command version_command
exit exit
@@ -262,10 +300,11 @@ parse_requirements() {
*) *)
if [[ -z ${args['source']+x} ]]; then if [[ -z ${args['source']+x} ]]; then
args['source']=$1 args['source']=$1
shift shift
elif [[ -z ${args['publisher']+x} ]]; then elif [[ -z ${args['publisher']+x} ]]; then
args['publisher']=$1 args['publisher']=$1
shift shift
else else
@@ -281,7 +320,7 @@ parse_requirements() {
[[ -n ${args['source']:-} ]] || args['source']="stable" [[ -n ${args['source']:-} ]] || args['source']="stable"
[[ -n ${args['publisher']:-} ]] || args['publisher']="inventree" [[ -n ${args['publisher']:-} ]] || args['publisher']="inventree"
if [[ -n ${args['source']:-} ]] && [[ ! ${args['source']:-} =~ ^(stable|master|main)$ ]]; then if [[ -n ${args['source']} ]] && [[ ! ${args['source']} =~ ^(stable|master|main)$ ]]; then
printf "%s\n" "source must be one of: stable, master, main" >&2 printf "%s\n" "source must be one of: stable, master, main" >&2
exit 1 exit 1
fi fi
@@ -289,19 +328,18 @@ parse_requirements() {
} }
initialize() { initialize() {
declare -g version="2.0" version="2.0"
long_usage=''
set -e set -e
} }
run() { run() {
declare -A args=()
declare -g long_usage='' declare -A deps=()
declare -g -A args=() declare -a other_args=()
declare -g -A deps=() declare -a input=()
declare -g -a env_var_names=()
declare -g -a input=()
normalize_input "$@" normalize_input "$@"
parse_requirements "${input[@]}" parse_requirements "${input[@]}"
@@ -310,6 +348,5 @@ run() {
esac esac
} }
command_line_args=("$@")
initialize initialize
run "${command_line_args[@]}" run "$@"

View File

@@ -5,7 +5,7 @@ publisher=${args[publisher]}
no_call=${args[--no-call]} no_call=${args[--no-call]}
dry_run=${args[--dry-run]} dry_run=${args[--dry-run]}
REQS="wget apt-transport-https curl gpg" REQS="wget apt-transport-https"
function do_call() { function do_call() {
if [[ $dry_run ]]; then if [[ $dry_run ]]; then
@@ -46,16 +46,17 @@ echo "### Installer for InvenTree - source: $publisher/$source_url"
get_distribution get_distribution
echo "### Detected distribution: $OS $VER" echo "### Detected distribution: $OS $VER"
SUPPORTED=true # is this OS supported? SUPPORTED=true # is this OS supported?
NEEDS_LIBSSL1_1=false # does this OS need libssl1.1?
DIST_OS=${OS,,} DIST_OS=${OS,,}
DIST_VER=$VER DIST_VER=$VER
case "$OS" in case "$OS" in
Ubuntu) Ubuntu)
if [[ $VER == "24.04" ]]; then if [[ $VER == "22.04" ]]; then
SUPPORTED=true
elif [[ $VER == "22.04" ]]; then
SUPPORTED=true SUPPORTED=true
NEEDS_LIBSSL1_1=true
DIST_VER="20.04"
elif [[ $VER == "20.04" ]]; then elif [[ $VER == "20.04" ]]; then
SUPPORTED=true SUPPORTED=true
else else
@@ -99,6 +100,15 @@ for pkg in $REQS; do
fi fi
done done
if [[ $NEEDS_LIBSSL1_1 == "true" ]]; then
echo "### Installing libssl1.1"
echo "deb http://security.ubuntu.com/ubuntu focal-security main" | sudo tee /etc/apt/sources.list.d/focal-security.list
do_call "sudo apt-get update"
do_call "sudo apt-get install libssl1.1"
sudo rm /etc/apt/sources.list.d/focal-security.list
fi
echo "### Getting and adding key" echo "### Getting and adding key"
curl -fsSL https://dl.packager.io/srv/$publisher/InvenTree/key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/pkgr-inventree.gpg > /dev/null curl -fsSL https://dl.packager.io/srv/$publisher/InvenTree/key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/pkgr-inventree.gpg > /dev/null
echo "### Adding package source" echo "### Adding package source"

View File

@@ -5,40 +5,33 @@
set -eu set -eu
# The sha is the second element in APP_PKG_ITERATION
VERSION="$APP_PKG_VERSION-$APP_PKG_ITERATION" VERSION="$APP_PKG_VERSION-$APP_PKG_ITERATION"
echo "Setting VERSION information to $VERSION"
echo "$VERSION" > VERSION
# The sha is the second element in APP_PKG_ITERATION
SHA=$(echo $APP_PKG_ITERATION | cut -d'.' -f2) SHA=$(echo $APP_PKG_ITERATION | cut -d'.' -f2)
# Download info # Download info
echo "INFO collection | Getting info from github for commit $SHA" echo "Getting info from github for commit $SHA"
curl -L -s -f \ curl -L \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$APP_REPO/commits/$SHA > commit.json https://api.github.com/repos/InvenTree/InvenTree/commits/$SHA > commit.json
echo "INFO collection | Got commit.json with size $(wc -c commit.json)" curl -L \
curl -L -s -f \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$APP_REPO/commits/$SHA/branches-where-head > branches.json https://api.github.com/repos/InvenTree/InvenTree/commits/$SHA/branches-where-head > branches.json
echo "INFO collection | Got branches.json with size $(wc -c branches.json)"
curl -L -s -f \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$APP_REPO/commits/$APP_PKG_VERSION > tag.json
echo "INFO collection | Got tag.json with size $(wc -c tag.json)"
# Extract info # Extract info
echo "INFO extract | Extracting info from github" echo "Extracting info from github"
DATE=$(jq -r '.commit.committer.date' commit.json) DATE=$(jq -r '.commit.committer.date' commit.json)
BRANCH=$(jq -r '.[].name' branches.json) BRANCH=$(jq -r '.[].name' branches.json)
NODE_ID=$(jq -r '.node_id' commit.json) NODE_ID=$(jq -r '.node_id' commit.json)
SIGNATURE=$(jq -r '.commit.verification.signature' commit.json) SIGNATURE=$(jq -r '.commit.verification.signature' commit.json)
FULL_SHA=$(jq -r '.sha' commit.json)
echo "INFO write | Write VERSION information" echo "Write VERSION information"
echo "$VERSION" > VERSION
echo "INVENTREE_COMMIT_HASH='$SHA'" >> VERSION echo "INVENTREE_COMMIT_HASH='$SHA'" >> VERSION
echo "INVENTREE_COMMIT_SHA='$FULL_SHA'" >> VERSION
echo "INVENTREE_COMMIT_DATE='$DATE'" >> VERSION echo "INVENTREE_COMMIT_DATE='$DATE'" >> VERSION
echo "INVENTREE_PKG_INSTALLER='PKG'" >> VERSION echo "INVENTREE_PKG_INSTALLER='PKG'" >> VERSION
echo "INVENTREE_PKG_BRANCH='$BRANCH'" >> VERSION echo "INVENTREE_PKG_BRANCH='$BRANCH'" >> VERSION
@@ -46,22 +39,5 @@ echo "INVENTREE_PKG_TARGET='$TARGET'" >> VERSION
echo "NODE_ID='$NODE_ID'" >> VERSION echo "NODE_ID='$NODE_ID'" >> VERSION
echo "SIGNATURE='$SIGNATURE'" >> VERSION echo "SIGNATURE='$SIGNATURE'" >> VERSION
echo "INFO write | Written VERSION information" echo "Written VERSION information"
echo "### VERSION ###"
cat VERSION cat VERSION
echo "### VERSION ###"
# Try to get frontend
echo "INFO frontend | Trying to get frontend"
# Check if tag sha is the same as the commit sha
TAG_SHA=$(jq -r '.sha' tag.json)
if [ "$TAG_SHA" != "$FULL_SHA" ]; then
echo "INFO frontend | Tag sha '$TAG_SHA' is not the same as commit sha $FULL_SHA, can not download frontend"
else
echo "INFO frontend | Getting frontend from github via tag"
curl https://github.com/$APP_REPO/releases/download/$APP_PKG_VERSION/frontend-build.zip -L -O -f
mkdir -p src/backend/InvenTree/web/static
echo "INFO frontend | Unzipping frontend"
unzip -qq frontend-build.zip -d src/backend/InvenTree/web/static/web
echo "INFO frontend | Unzipped frontend"
fi

View File

@@ -4,8 +4,6 @@
# #
Color_Off='\033[0m' Color_Off='\033[0m'
On_Red='\033[41m' On_Red='\033[41m'
PYTHON_FROM=11
PYTHON_TO=15
function detect_docker() { function detect_docker() {
if [ -n "$(grep docker </proc/1/cgroup)" ]; then if [ -n "$(grep docker </proc/1/cgroup)" ]; then
@@ -13,7 +11,6 @@ function detect_docker() {
else else
DOCKER="no" DOCKER="no"
fi fi
echo "# POI04| Running in docker: ${DOCKER}"
} }
function detect_initcmd() { function detect_initcmd() {
@@ -31,7 +28,6 @@ function detect_initcmd() {
if [ "${DOCKER}" == "yes" ]; then if [ "${DOCKER}" == "yes" ]; then
INIT_CMD="initctl" INIT_CMD="initctl"
fi fi
echo "# POI05| Using init command: ${INIT_CMD}"
} }
function detect_ip() { function detect_ip() {
@@ -39,52 +35,37 @@ function detect_ip() {
if [ "${SETUP_NO_CALLS}" == "true" ]; then if [ "${SETUP_NO_CALLS}" == "true" ]; then
# Use local IP address # Use local IP address
echo "# POI06| Getting the IP address of the first local IP address" echo "# Getting the IP address of the first local IP address"
export INVENTREE_IP=$(hostname -I | awk '{print $1}') export INVENTREE_IP=$(hostname -I | awk '{print $1}')
else else
# Use web service to get the IP address # Use web service to get the IP address
echo "# POI06| Getting the IP address of the server via web service" echo "# Getting the IP address of the server via web service"
export INVENTREE_IP=$(curl -s https://checkip.amazonaws.com) export INVENTREE_IP=$(curl -s https://checkip.amazonaws.com)
fi fi
echo "# POI06| IP address is ${INVENTREE_IP}" echo "IP address is ${INVENTREE_IP}"
} }
function detect_python() { function detect_python() {
# Detect if there is already a python version installed in /opt/inventree/env/lib # Detect if there is already a python version installed in /opt/inventree/env/lib
if test -f "${APP_HOME}/env/bin/python"; then if test -f "${APP_HOME}/env/bin/python"; then
echo "# POI07| Python environment already present" echo "# Python environment already present"
# Extract earliest python version initialised from /opt/inventree/env/lib # Extract earliest python version initialised from /opt/inventree/env/lib
SETUP_PYTHON=$(ls -1 ${APP_HOME}/env/bin/python* | sort | head -n 1) SETUP_PYTHON=$(ls -1 ${APP_HOME}/env/bin/python* | sort | head -n 1)
echo "# POI07| Found earlier used version: ${SETUP_PYTHON}" echo "# Found earlier used version: ${SETUP_PYTHON}"
else else
echo "# POI07| No python environment found - using environment variable: ${SETUP_PYTHON}" echo "# No python environment found - using environment variable: ${SETUP_PYTHON}"
fi
# 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
echo "# POI07| Checking for python3.${i}"
if [ -n "$(which python3.${i})" ]; then
SETUP_PYTHON="python3.${i}"
echo "# POI07| Found python3.${i} installed - using for setup ${SETUP_PYTHON}"
break
fi
done
fi fi
# Ensure python can be executed - abort if not # Ensure python can be executed - abort if not
if [ -z "$(which ${SETUP_PYTHON})" ]; then if [ -z "$(which ${SETUP_PYTHON})" ]; then
echo "${On_Red}" echo "${On_Red}"
echo "# POI07| Python ${SETUP_PYTHON} not found - aborting!" echo "# 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 "# 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.11'." echo "# If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.10'."
echo "${Color_Off}" echo "${Color_Off}"
exit 1 exit 1
fi fi
echo "# POI07| Using python command: ${SETUP_PYTHON}"
} }
function get_env() { function get_env() {
@@ -99,7 +80,7 @@ function get_env() {
done done
if [ -n "${SETUP_DEBUG}" ]; then if [ -n "${SETUP_DEBUG}" ]; then
echo "# POI02| Done getting env $envname: ${!envname}" echo "Done getting env $envname: ${!envname}"
fi fi
} }
@@ -107,7 +88,7 @@ function detect_local_env() {
# Get all possible envs for the install # Get all possible envs for the install
if [ -n "${SETUP_DEBUG}" ]; then if [ -n "${SETUP_DEBUG}" ]; then
echo "# POI02| Printing local envs - before #++#" echo "# Printing local envs - before #++#"
printenv printenv
fi fi
@@ -117,72 +98,46 @@ function detect_local_env() {
done done
if [ -n "${SETUP_DEBUG}" ]; then if [ -n "${SETUP_DEBUG}" ]; then
echo "# POI02| Printing local envs - after #++#" echo "# Printing local envs - after #++#"
printenv printenv
fi fi
# Print branch and dir from VERSION file
if [ -f "${APP_HOME}/VERSION" ]; then
echo "# POI02| Loading environment variables from VERSION file"
content=$(cat "${APP_HOME}/VERSION")
# use grep to get the branch and target
INVENTREE_PKG_BRANCH=($(echo $content | grep -oP 'INVENTREE_PKG_BRANCH=\K[^ ]+'))
INVENTREE_PKG_TARGET=($(echo $content | grep -oP 'INVENTREE_PKG_TARGET=\K[^ ]+'))
echo "Running in a package environment build on branch $INVENTREE_PKG_BRANCH for target $INVENTREE_PKG_TARGET"
else
echo "# POI02| VERSION file not found"
fi
} }
function detect_envs() { function detect_envs() {
# Detect all envs that should be passed to setup commands # Detect all envs that should be passed to setup commands
echo "# POI03| Setting base environment variables" echo "# Setting base environment variables"
export INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE:-${CONF_DIR}/config.yaml} export INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE:-${CONF_DIR}/config.yaml}
if test -f "${INVENTREE_CONFIG_FILE}"; then if test -f "${INVENTREE_CONFIG_FILE}"; then
echo "# POI03| Using existing config file: ${INVENTREE_CONFIG_FILE}" echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
# Install parser # Install parser
echo "# POI03| Installing requirements"
pip install --require-hashes -r ${APP_HOME}/contrib/dev_reqs/requirements.txt -q pip install --require-hashes -r ${APP_HOME}/contrib/dev_reqs/requirements.txt -q
echo "# POI03| Installed requirements"
# Load config # Load config
export INVENTREE_CONF_DATA=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml) local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
# Parse the config file # Parse the config file
export INVENTREE_MEDIA_ROOT=$(jq -r '.[].media_root' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_MEDIA_ROOT=$(jq -r '.[].media_root' <<< ${CONF})
export INVENTREE_STATIC_ROOT=$(jq -r '.[].static_root' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_STATIC_ROOT=$(jq -r '.[].static_root' <<< ${CONF})
export INVENTREE_BACKUP_DIR=$(jq -r '.[].backup_dir' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_BACKUP_DIR=$(jq -r '.[].backup_dir' <<< ${CONF})
export INVENTREE_PLUGINS_ENABLED=$(jq -r '.[].plugins_enabled' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_PLUGINS_ENABLED=$(jq -r '.[].plugins_enabled' <<< ${CONF})
export INVENTREE_PLUGIN_FILE=$(jq -r '.[].plugin_file' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_PLUGIN_FILE=$(jq -r '.[].plugin_file' <<< ${CONF})
export INVENTREE_SECRET_KEY_FILE=$(jq -r '.[].secret_key_file' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_SECRET_KEY_FILE=$(jq -r '.[].secret_key_file' <<< ${CONF})
export INVENTREE_DB_ENGINE=$(jq -r '.[].database.ENGINE' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_DB_ENGINE=$(jq -r '.[].database.ENGINE' <<< ${CONF})
export INVENTREE_DB_NAME=$(jq -r '.[].database.NAME' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_DB_NAME=$(jq -r '.[].database.NAME' <<< ${CONF})
export INVENTREE_DB_USER=$(jq -r '.[].database.USER' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_DB_USER=$(jq -r '.[].database.USER' <<< ${CONF})
export INVENTREE_DB_PASSWORD=$(jq -r '.[].database.PASSWORD' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_DB_PASSWORD=$(jq -r '.[].database.PASSWORD' <<< ${CONF})
export INVENTREE_DB_HOST=$(jq -r '.[].database.HOST' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_DB_HOST=$(jq -r '.[].database.HOST' <<< ${CONF})
export INVENTREE_DB_PORT=$(jq -r '.[].database.PORT' <<< ${INVENTREE_CONF_DATA}) export INVENTREE_DB_PORT=$(jq -r '.[].database.PORT' <<< ${CONF})
# Parse site URL if not already set
if [ -z "${INVENTREE_SITE_URL}" ]; then
# Try to read out the app config
if [ -n "$(inventree config:get INVENTREE_SITE_URL)" ]; then
echo "# POI03| Getting site URL from app config"
export INVENTREE_SITE_URL=$(inventree config:get INVENTREE_SITE_URL)
else
echo "# POI03| Getting site URL from config file"
export INVENTREE_SITE_URL=$(jq -r '.[].site_url' <<< ${INVENTREE_CONF_DATA})
fi
fi
else else
echo "# POI03| No config file found: ${INVENTREE_CONFIG_FILE}, using envs or defaults" echo "# No config file found: ${INVENTREE_CONFIG_FILE}, using envs or defaults"
if [ -n "${SETUP_DEBUG}" ]; then if [ -n "${SETUP_DEBUG}" ]; then
echo "# POI03| Print current envs" echo "# Print current envs"
printenv | grep INVENTREE_ printenv | grep INVENTREE_
printenv | grep SETUP_ printenv | grep SETUP_
fi fi
@@ -193,7 +148,6 @@ function detect_envs() {
export INVENTREE_PLUGINS_ENABLED=true export INVENTREE_PLUGINS_ENABLED=true
export INVENTREE_PLUGIN_FILE=${CONF_DIR}/plugins.txt export INVENTREE_PLUGIN_FILE=${CONF_DIR}/plugins.txt
export INVENTREE_SECRET_KEY_FILE=${CONF_DIR}/secret_key.txt export INVENTREE_SECRET_KEY_FILE=${CONF_DIR}/secret_key.txt
export INVENTREE_OIDC_PRIVATE_KEY_FILE=${CONF_DIR}/oidc.pem
export INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE:-sqlite3} export INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE:-sqlite3}
export INVENTREE_DB_NAME=${INVENTREE_DB_NAME:-${DATA_DIR}/database.sqlite3} export INVENTREE_DB_NAME=${INVENTREE_DB_NAME:-${DATA_DIR}/database.sqlite3}
@@ -202,50 +156,47 @@ function detect_envs() {
export INVENTREE_DB_HOST=${INVENTREE_DB_HOST:-samplehost} export INVENTREE_DB_HOST=${INVENTREE_DB_HOST:-samplehost}
export INVENTREE_DB_PORT=${INVENTREE_DB_PORT:-123456} export INVENTREE_DB_PORT=${INVENTREE_DB_PORT:-123456}
export INVENTREE_SITE_URL=${INVENTREE_SITE_URL:-http://${INVENTREE_IP}}
export SETUP_CONF_LOADED=true export SETUP_CONF_LOADED=true
fi fi
# For debugging pass out the envs # For debugging pass out the envs
echo "# POI03| Collected environment variables:" echo "# Collected environment variables:"
echo "# POI03| INVENTREE_MEDIA_ROOT=${INVENTREE_MEDIA_ROOT}" echo "# INVENTREE_MEDIA_ROOT=${INVENTREE_MEDIA_ROOT}"
echo "# POI03| INVENTREE_STATIC_ROOT=${INVENTREE_STATIC_ROOT}" echo "# INVENTREE_STATIC_ROOT=${INVENTREE_STATIC_ROOT}"
echo "# POI03| INVENTREE_BACKUP_DIR=${INVENTREE_BACKUP_DIR}" echo "# INVENTREE_BACKUP_DIR=${INVENTREE_BACKUP_DIR}"
echo "# POI03| INVENTREE_PLUGINS_ENABLED=${INVENTREE_PLUGINS_ENABLED}" echo "# INVENTREE_PLUGINS_ENABLED=${INVENTREE_PLUGINS_ENABLED}"
echo "# POI03| INVENTREE_PLUGIN_FILE=${INVENTREE_PLUGIN_FILE}" echo "# INVENTREE_PLUGIN_FILE=${INVENTREE_PLUGIN_FILE}"
echo "# POI03| INVENTREE_SECRET_KEY_FILE=${INVENTREE_SECRET_KEY_FILE}" echo "# INVENTREE_SECRET_KEY_FILE=${INVENTREE_SECRET_KEY_FILE}"
echo "# POI03| INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE}" echo "# INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE}"
echo "# POI03| INVENTREE_DB_NAME=${INVENTREE_DB_NAME}" echo "# INVENTREE_DB_NAME=${INVENTREE_DB_NAME}"
echo "# POI03| INVENTREE_DB_USER=${INVENTREE_DB_USER}" echo "# INVENTREE_DB_USER=${INVENTREE_DB_USER}"
if [ -n "${SETUP_DEBUG}" ]; then if [ -n "${SETUP_DEBUG}" ]; then
echo "# POI03| INVENTREE_DB_PASSWORD=${INVENTREE_DB_PASSWORD}" echo "# INVENTREE_DB_PASSWORD=${INVENTREE_DB_PASSWORD}"
fi fi
echo "# POI03| INVENTREE_DB_HOST=${INVENTREE_DB_HOST}" echo "# INVENTREE_DB_HOST=${INVENTREE_DB_HOST}"
echo "# POI03| INVENTREE_DB_PORT=${INVENTREE_DB_PORT}" echo "# INVENTREE_DB_PORT=${INVENTREE_DB_PORT}"
echo "# POI03| INVENTREE_SITE_URL=${INVENTREE_SITE_URL}"
} }
function create_initscripts() { function create_initscripts() {
# Make sure python env exists # Make sure python env exists
if test -f "${APP_HOME}/env"; then if test -f "${APP_HOME}/env"; then
echo "# POI09| python environment already present - skipping" echo "# python environment already present - skipping"
else else
echo "# POI09| Setting up python environment" echo "# Setting up python environment"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && ${SETUP_PYTHON} -m venv env" sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && ${SETUP_PYTHON} -m venv env"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install invoke wheel" sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install invoke wheel"
# Check INSTALLER_EXTRA exists and load it # Check INSTALLER_EXTRA exists and load it
if test -f "${APP_HOME}/INSTALLER_EXTRA"; then if test -f "${APP_HOME}/INSTALLER_EXTRA"; then
echo "# POI09| Loading extra packages from INSTALLER_EXTRA" echo "# Loading extra packages from INSTALLER_EXTRA"
source ${APP_HOME}/INSTALLER_EXTRA source ${APP_HOME}/INSTALLER_EXTRA
fi fi
if [ -n "${SETUP_EXTRA_PIP}" ]; then if [ -n "${SETUP_EXTRA_PIP}" ]; then
echo "# POI09| Installing extra pip packages" echo "# Installing extra pip packages"
if [ -n "${SETUP_DEBUG}" ]; then if [ -n "${SETUP_DEBUG}" ]; then
echo "# POI09| Extra pip packages: ${SETUP_EXTRA_PIP}" echo "# Extra pip packages: ${SETUP_EXTRA_PIP}"
fi fi
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install ${SETUP_EXTRA_PIP}" sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install ${SETUP_EXTRA_PIP}"
# Write extra packages to INSTALLER_EXTRA # Write extra packages to INSTALLER_EXTRA
@@ -255,45 +206,37 @@ function create_initscripts() {
# Unlink default config if it exists # Unlink default config if it exists
if test -f "/etc/nginx/sites-enabled/default"; then if test -f "/etc/nginx/sites-enabled/default"; then
echo "# POI09| Unlinking default nginx config\n# POI09| Old file still in /etc/nginx/sites-available/default" echo "# Unlinking default nginx config\n# Old file still in /etc/nginx/sites-available/default"
sudo unlink /etc/nginx/sites-enabled/default sudo unlink /etc/nginx/sites-enabled/default
echo "# POI09| Unlinked default nginx config"
fi fi
# Create InvenTree specific nginx config # Create InvenTree specific nginx config
echo "# POI09| Stopping nginx" echo "# Stopping nginx"
${INIT_CMD} stop nginx ${INIT_CMD} stop nginx
echo "# POI09| Stopped nginx" echo "# Setting up nginx to ${SETUP_NGINX_FILE}"
echo "# POI09| Setting up nginx to ${SETUP_NGINX_FILE}"
# Always use the latest nginx config; important if new headers are added / needed for security # Always use the latest nginx config; important if new headers are added / needed for security
cp ${APP_HOME}/contrib/packager.io/nginx.prod.conf ${SETUP_NGINX_FILE} cp ${APP_HOME}/contrib/packager.io/nginx.prod.conf ${SETUP_NGINX_FILE}
sed -i s/inventree-server:8000/localhost:6000/g ${SETUP_NGINX_FILE} sed -i s/inventree-server:8000/localhost:6000/g ${SETUP_NGINX_FILE}
sed -i s=var/www=opt/inventree/data=g ${SETUP_NGINX_FILE} sed -i s=var/www=opt/inventree/data=g ${SETUP_NGINX_FILE}
# Start nginx # Start nginx
echo "# POI09| Starting nginx" echo "# Starting nginx"
${INIT_CMD} start nginx ${INIT_CMD} start nginx
echo "# POI09| Started nginx"
echo "# POI09| (Re)creating init scripts" echo "# (Re)creating init scripts"
# This resets scale parameters to a known state # This resets scale parameters to a known state
inventree scale web="1" worker="1" inventree scale web="1" worker="1"
echo "# POI09| Enabling InvenTree on boot" echo "# Enabling InvenTree on boot"
${INIT_CMD} enable inventree ${INIT_CMD} enable inventree
echo "# POI09| Enabled InvenTree on boot"
} }
function create_admin() { function create_admin() {
# Create data for admin users - stop with setting SETUP_ADMIN_NOCREATION to true # Create data for admin user
if [ "${SETUP_ADMIN_NOCREATION}" == "true" ]; then
echo "# POI10| Admin creation is disabled - skipping"
return
fi
if test -f "${SETUP_ADMIN_PASSWORD_FILE}"; then if test -f "${SETUP_ADMIN_PASSWORD_FILE}"; then
echo "# POI10| Admin data already exists - skipping" echo "# Admin data already exists - skipping"
else else
echo "# POI10| Creating admin user data" echo "# Creating admin user data"
# Static admin data # Static admin data
export INVENTREE_ADMIN_USER=${INVENTREE_ADMIN_USER:-admin} export INVENTREE_ADMIN_USER=${INVENTREE_ADMIN_USER:-admin}
@@ -308,41 +251,38 @@ function create_admin() {
} }
function start_inventree() { function start_inventree() {
echo "# POI15| Starting InvenTree" echo "# Starting InvenTree"
${INIT_CMD} start inventree ${INIT_CMD} start inventree
echo "# POI15| Started InvenTree"
} }
function stop_inventree() { function stop_inventree() {
echo "# POI11| Stopping InvenTree" echo "# Stopping InvenTree"
${INIT_CMD} stop inventree ${INIT_CMD} stop inventree
echo "# POI11| Stopped InvenTree"
} }
function update_or_install() { function update_or_install() {
set -e
# Set permissions so app user can write there # Set permissions so app user can write there
chown ${APP_USER}:${APP_GROUP} ${APP_HOME} -R chown ${APP_USER}:${APP_GROUP} ${APP_HOME} -R
# Run update as app user # Run update as app user
echo "# POI12| Updating InvenTree" echo "# Updating InvenTree"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && pip install wheel python-dotenv" sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && pip install wheel"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && set -e && invoke update | sed -e 's/^/# POI12| u | /;'" sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke update | sed -e 's/^/# inv update| /;'"
# Make sure permissions are correct again # Make sure permissions are correct again
echo "# POI12| Set permissions for data dir and media: ${DATA_DIR}" echo "# Set permissions for data dir and media: ${DATA_DIR}"
chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} -R chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} -R
chown ${APP_USER}:${APP_GROUP} ${CONF_DIR} -R chown ${APP_USER}:${APP_GROUP} ${CONF_DIR} -R
} }
function set_env() { function set_env() {
echo "# POI13| Setting up InvenTree config values" echo "# Setting up InvenTree config values"
inventree config:set INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE} inventree config:set INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE}
# Changing the config file # Changing the config file
echo "# POI13| Writing the settings to the config file ${INVENTREE_CONFIG_FILE}" echo "# Writing the settings to the config file ${INVENTREE_CONFIG_FILE}"
# Media Root # Media Root
sed -i s=#media_root:\ \'/home/inventree/data/media\'=media_root:\ \'${INVENTREE_MEDIA_ROOT}\'=g ${INVENTREE_CONFIG_FILE} sed -i s=#media_root:\ \'/home/inventree/data/media\'=media_root:\ \'${INVENTREE_MEDIA_ROOT}\'=g ${INVENTREE_CONFIG_FILE}
# Static Root # Static Root
@@ -355,108 +295,48 @@ function set_env() {
sed -i s=#plugin_file:\ \'/path/to/plugins.txt\'=plugin_file:\ \'${INVENTREE_PLUGIN_FILE}\'=g ${INVENTREE_CONFIG_FILE} sed -i s=#plugin_file:\ \'/path/to/plugins.txt\'=plugin_file:\ \'${INVENTREE_PLUGIN_FILE}\'=g ${INVENTREE_CONFIG_FILE}
# Secret key file # Secret key file
sed -i s=#secret_key_file:\ \'/etc/inventree/secret_key.txt\'=secret_key_file:\ \'${INVENTREE_SECRET_KEY_FILE}\'=g ${INVENTREE_CONFIG_FILE} sed -i s=#secret_key_file:\ \'/etc/inventree/secret_key.txt\'=secret_key_file:\ \'${INVENTREE_SECRET_KEY_FILE}\'=g ${INVENTREE_CONFIG_FILE}
# OIDC private key file
sed -i s=#oidc_private_key_file:\ \'/etc/inventree/oidc.pem\'=oidc_private_key_file:\ \'${INVENTREE_OIDC_PRIVATE_KEY_FILE}\'=g ${INVENTREE_CONFIG_FILE}
# Debug mode # Debug mode
sed -i s=debug:\ True=debug:\ False=g ${INVENTREE_CONFIG_FILE} sed -i s=debug:\ True=debug:\ False=g ${INVENTREE_CONFIG_FILE}
# Database engine # Database engine
sed -i s=#\ ENGINE:\ Database\ engine.\ Selection\ from:=ENGINE:\ ${INVENTREE_DB_ENGINE}=g ${INVENTREE_CONFIG_FILE} sed -i s=#ENGINE:\ sampleengine=ENGINE:\ ${INVENTREE_DB_ENGINE}=g ${INVENTREE_CONFIG_FILE}
# Database name # Database name
sed -i s=#\ NAME:\ Database\ name=NAME:\ \'${INVENTREE_DB_NAME}\'=g ${INVENTREE_CONFIG_FILE} sed -i s=#NAME:\ \'/path/to/database\'=NAME:\ \'${INVENTREE_DB_NAME}\'=g ${INVENTREE_CONFIG_FILE}
# Database user # Database user
sed -i s=#\ USER:\ Database\ username\ \(if\ required\)=USER:\ ${INVENTREE_DB_USER}=g ${INVENTREE_CONFIG_FILE} sed -i s=#USER:\ sampleuser=USER:\ ${INVENTREE_DB_USER}=g ${INVENTREE_CONFIG_FILE}
# Database password # Database password
sed -i s=#\ PASSWORD:\ Database\ password\ \(if\ required\)=PASSWORD:\ ${INVENTREE_DB_PASSWORD}=g ${INVENTREE_CONFIG_FILE} sed -i s=#PASSWORD:\ samplepassword=PASSWORD:\ ${INVENTREE_DB_PASSWORD}=g ${INVENTREE_CONFIG_FILE}
# Database host # Database host
sed -i s=#\ HOST:\ Database\ host\ address\ \(if\ required\)=HOST:\ ${INVENTREE_DB_HOST}=g ${INVENTREE_CONFIG_FILE} sed -i s=#HOST:\ samplehost=HOST:\ ${INVENTREE_DB_HOST}=g ${INVENTREE_CONFIG_FILE}
# Database port # Database port
sed -i s=#\ PORT:\ Database\ host\ port\ \(if\ required\)=PORT:\ ${INVENTREE_DB_PORT}=g ${INVENTREE_CONFIG_FILE} sed -i s=#PORT:\ 123456=PORT:\ ${INVENTREE_DB_PORT}=g ${INVENTREE_CONFIG_FILE}
# Fixing the permissions # Fixing the permissions
chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} ${INVENTREE_CONFIG_FILE} chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} ${INVENTREE_CONFIG_FILE}
echo "# POI13| Done setting up InvenTree config values"
} }
function set_site() { function set_site() {
# Ensure IP is known # Ensure IP is known
if [ -z "${INVENTREE_IP}" ]; then if [ -z "${INVENTREE_IP}" ]; then
echo "# POI14| No IP address found - skipping" echo "# No IP address found - skipping"
return return
fi fi
# Check if INVENTREE_SITE_URL in inventree config # Check if INVENTREE_SITE_URL in inventree config
if [ -z "$(inventree config:get INVENTREE_SITE_URL)" ]; then if [ -z "$(inventree config:get INVENTREE_SITE_URL)" ]; then
# Prefer current INVENTREE_SITE_URL if set echo "# Setting up InvenTree site URL"
if [ -n "${INVENTREE_SITE_URL}" ]; then inventree config:set INVENTREE_SITE_URL=http://${INVENTREE_IP}
inventree config:set INVENTREE_SITE_URL=${INVENTREE_SITE_URL}
else
echo "# POI14| Setting up InvenTree site URL"
inventree config:set INVENTREE_SITE_URL=http://${INVENTREE_IP}
fi
else
echo "# POI14| Site URL already set to '$INVENTREE_SITE_URL' - skipping"
fi fi
} }
function final_message() { function final_message() {
echo "# POI16| Printing Final message"
echo -e "####################################################################################" echo -e "####################################################################################"
echo -e "This InvenTree install uses nginx, the settings for the webserver can be found in" echo -e "This InvenTree install uses nginx, the settings for the webserver can be found in"
echo -e "${SETUP_NGINX_FILE}" echo -e "${SETUP_NGINX_FILE}"
echo -e "Try opening InvenTree with any of \n${INVENTREE_SITE_URL} , http://localhost/ or http://${INVENTREE_IP}/ \n" echo -e "Try opening InvenTree with either\nhttp://localhost/ or http://${INVENTREE_IP}/\n"
# Print admin user data only if set echo -e "Admin user data:"
if [ -n "${INVENTREE_ADMIN_USER}" ]; then echo -e " Email: ${INVENTREE_ADMIN_EMAIL}"
echo -e "Admin user data:" echo -e " Username: ${INVENTREE_ADMIN_USER}"
echo -e " Email: ${INVENTREE_ADMIN_EMAIL}" echo -e " Password: ${INVENTREE_ADMIN_PASSWORD}"
echo -e " Username: ${INVENTREE_ADMIN_USER}"
echo -e " Password: ${INVENTREE_ADMIN_PASSWORD}"
else
echo -e "No admin set during this operation - depending on the deployment method a admin user might have been created with an initial password saved in `$SETUP_ADMIN_PASSWORD_FILE`"
fi
echo -e "####################################################################################" echo -e "####################################################################################"
} }
function update_checks() {
echo "# POI08| Running upgrade"
local old_version=$1
local old_version_rev=$(echo ${old_version} | cut -d'-' -f1 | cut -d'.' -f2)
local new_version=$(dpkg-query --show --showformat='${Version}' inventree)
local new_version_rev=$(echo ${new_version} | cut -d'-' -f1 | cut -d'.' -f2)
echo "# POI08| Old version is: ${old_version} | ${old_version_rev} - updating to ${new_version} | ${old_version_rev}"
local ABORT=false
function check_config_value() {
local env_key=$1
local config_key=$2
local name=$3
local value=$(inventree config:get ${env_key})
if [ -z "${value}" ] || [ "$value" == "null" ]; then
value=$(jq -r ".[].${config_key}" <<< ${INVENTREE_CONF_DATA})
fi
if [ -z "${value}" ] || [ "$value" == "null" ]; then
echo "# POI08| No setting for ${name} found - please set it manually either in ${INVENTREE_CONFIG_FILE} under '${config_key}' or with 'inventree config:set ${env_key}=value'"
ABORT=true
else
echo "# POI08| Found setting for ${name} - ${value}"
fi
}
# Custom checks if old version is below 0.8.0
if [ "${old_version_rev}" -lt "9" ]; then
echo "# POI08| Old version is below 0.9.0 - You might be missing some configs"
# Check for BACKUP_DIR and SITE_URL in INVENTREE_CONF_DATA and config
check_config_value "INVENTREE_SITE_URL" "site_url" "site URL"
check_config_value "INVENTREE_BACKUP_DIR" "backup_dir" "backup dir"
if [ "${ABORT}" = true ]; then
echo "# POI08| Aborting - please set the missing values and run the update again"
exit 1
fi
echo "# POI08| All checks passed - continuing with the update"
fi
}

View File

@@ -3,18 +3,15 @@
# packager.io postinstall script # packager.io postinstall script
# #
echo "# POI01| Running postinstall script - start - $(date)"
exec > >(tee ${APP_HOME}/log/setup_$(date +"%F_%H_%M_%S").log) 2>&1 exec > >(tee ${APP_HOME}/log/setup_$(date +"%F_%H_%M_%S").log) 2>&1
PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin: PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
# import functions # import functions
echo "# POI01| Importing functions"
. ${APP_HOME}/contrib/packager.io/functions.sh . ${APP_HOME}/contrib/packager.io/functions.sh
echo "# POI01| Functions imported"
# Envs that should be passed to setup commands # Envs that should be passed to setup commands
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,INVENTREE_SITE_URL,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON,SETUP_ADMIN_NOCREATION export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON
# Get the envs # Get the envs
detect_local_env detect_local_env
@@ -26,27 +23,18 @@ export DATA_DIR=${APP_HOME}/data
export SETUP_NGINX_FILE=${SETUP_NGINX_FILE:-/etc/nginx/sites-enabled/inventree.conf} 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_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false} export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
export SETUP_PYTHON=${SETUP_PYTHON:-python3.11} export SETUP_PYTHON=${SETUP_PYTHON:-python3.9}
export SETUP_ADMIN_NOCREATION=${SETUP_ADMIN_NOCREATION:-false}
# SETUP_DEBUG can be set to get debug info # SETUP_DEBUG can be set to get debug info
# SETUP_EXTRA_PIP can be set to install extra pip packages # SETUP_EXTRA_PIP can be set to install extra pip packages
# SETUP_PYTHON can be set to use a different python version # SETUP_PYTHON can be set to use a different python version
# get base info # get base info
detect_ip
detect_envs detect_envs
detect_docker detect_docker
detect_initcmd detect_initcmd
detect_ip
detect_python detect_python
# Check if we are updating and need to alert
echo "# POI08| Checking if update checks are needed"
if [ -z "$2" ]; then
echo "# POI08| Normal install - no need for checks"
else
update_checks $2
fi
# create processes # create processes
create_initscripts create_initscripts
create_admin create_admin
@@ -63,4 +51,3 @@ start_inventree
# show info # show info
final_message final_message
echo "# POI17| Running postinstall script - done - $(date)"

View File

@@ -1,23 +0,0 @@
#!/bin/bash
#
# packager.io preinstall/preremove script
#
echo "# PRI01| Running preinstall script - start - $(date)"
PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
# Envs that should be passed to setup commands
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,INVENTREE_SITE_URL,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON
if test -f "${APP_HOME}/env/bin/pip"; then
# Check if clear-generated is available
if sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke int.clear-generated --help" > /dev/null 2>&1; then
echo "# PRI02| Clearing precompiled files"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke int.clear-generated"
else
echo "# PRI02| Clearing precompiled files - skipping"
fi
else
echo "# PRI02| No python environment found - skipping"
fi
echo "# PRI03| Running preinstall script - done - $(date)"

View File

@@ -1,14 +1,8 @@
# Configuration file for Crowdin project integration
# See: https://crowdin.com/project/inventree
"commit_message": "Fix: New translations %original_file_name% from Crowdin" "commit_message": "Fix: New translations %original_file_name% from Crowdin"
"append_commit_message": false "append_commit_message": false
"preserve_hierarchy": true
files: files:
- source: /src/backend/InvenTree/locale/en/LC_MESSAGES/django.po - source: /src/backend/InvenTree/locale/en/LC_MESSAGES/django.po
dest: /%original_path%/%original_file_name%
translation: /src/backend/InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name% translation: /src/backend/InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%
- source: /src/frontend/src/locales/en/messages.po - source: /src/frontend/src/locales/en/messages.po
dest: /%original_path%/%original_file_name%
translation: /src/frontend/src/locales/%two_letters_code%/%original_file_name% translation: /src/frontend/src/locales/%two_letters_code%/%original_file_name%

9
docs/.gitignore vendored
View File

@@ -13,17 +13,8 @@ site/
# Generated API schema files # Generated API schema files
docs/api/schema/*.yml docs/api/schema/*.yml
# Temporary cache files
url_cache.txt
invoke-commands.txt
# Temp files # Temp files
releases.json releases.json
versions.json versions.json
inventree_filters.yml
inventree_settings.json
observed_settings.json
inventree_tags.yml
.vscode/ .vscode/
generated/

View File

@@ -4,92 +4,32 @@
This repository hosts the [official documentation](https://inventree.readthedocs.io/) for [InvenTree](https://github.com/inventree/inventree), an open source inventory management system. This repository hosts the [official documentation](https://inventree.readthedocs.io/) for [InvenTree](https://github.com/inventree/inventree), an open source inventory management system.
## Prerequisites To serve this documentation locally (e.g. for development), you will need to have Python 3 installed on your system.
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. ## Setup
!!! info "Prerequisites" Run the following commands from the top-level project directory:
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:
``` ```
$ cd /home/inventree $ git clone https://github.com/inventree/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 $ pip install --require-hashes -r docs/requirements.txt
``` ```
## Schema generation ## Serve Locally
Building the documentation requires extracting the API schema from the source code. To serve the pages locally, run the following command (from the top-level project directory):
!!! tip
This command is only required when building the documentation for the first time, or when changes have been made to the API schema.
``` ```
$ invoke build-docs $ mkdocs serve -f docs/mkdocs.yml -a localhost:8080
``` ```
You will see output similar to this (truncated for brevity): ## Edit Documentation Files
```
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 Once the server is running, it will monitor the documentation files for any changes, and update the served pages.
Documentation build complete, but mkdocs not requested
```
## Viewing the documentation
Generate the HTML files from the markdown source files, and start the MkDocs webpage server:
```
$ mkdocs serve -f docs/mkdocs.yml
```
You can then point your web browser at http://localhost:8080/
Alternatively, you can use the `invoke` command:
```
$ invoke dev.docs-server
```
If you need to, use the `-a` option after `mkdocs` or `invoke` to set the address and port. Run `invoke dev.docs-server --help` for details.
## Editing the Documentation Files
Once the server is running, it will monitor the documentation files for changes, and regenerate the HTML pages as required. Refresh your web browser to see the changes.
### Admonitions ### Admonitions
"Admonition" blocks can be added to the documentation source as follows: "Admonition" blocks can be added as follow:
``` ```
!!! info "This is the admonition block title" !!! info "This is the admonition block title"
This is the admonition block content This is the admonition block content
@@ -119,24 +59,18 @@ Click [here](/part/views)
### Images ### Images
Images are served from the `./docs/assets/images` folder and can be added as follows: Images are served from the `./docs/assets/images` folder and can be added as follow:
``` ```
{{ image("image_name.png", base="subfolder", title="Image title") }} {% with id="image_id", url="folder/image_name.png", description="Text shown if image is not loaded properly" %}
{% include 'img.html' %}
{% endwith %}
``` ```
See the `image` macro in `./docs/main.py` for more information. Replace:
* `image_id` with a short unique identifier for the image (most commonly, `image_id` is same as `image_name`)
### Icons * `folder` with the folder in `docs/assets/images` in which the image is stored
* `image_name` with the name of the image
Icons can be rendered (using the [tabler icon set](https://tabler.io/icons)) as follows: * `.png` with the image extension (PNG or JPEG are preferred formats)
```
{{ icon("brand-github", color="red")}}
```
See the `icon` macro in `./docs/main.py` for more information.
### Global variables ### Global variables

View File

@@ -0,0 +1,5 @@
{% set url = 'app/' + url %}
{% with id=id, url=url, maxheight="240px", description="" %}
{% include "img.html" %}
{% endwith %}

30
docs/_includes/img.html Normal file
View File

@@ -0,0 +1,30 @@
{% if 'http' in url %}
{% set img_url = url %}
{% else %}
{% set img_url = config.assets_dir + '/images/' + url %}
{% endif %}
<figure class='image image-inventree'>
{% if id %}
<!-- The link that, when clicked, will display the image in full screen -->
<a href="#{{ id }}">
{% elif img_url %}
<a href="{{ img_url }}">
{% endif %}
<img class='img-inline' src='{{ img_url }}' alt='{{ description }}' title='{{ description }}'
{% if maxwidth or maxheight %}style='
{% if maxwidth %} max-width:{{ maxwidth }};{% endif %}
{% if maxheight %} max-height: {{ maxheight }};{% endif %}
'{% endif %}
>
{% if id or img_url %}
</a>
{% endif %}
{% if id %}
<!-- The full screen image, hidden by default -->
<a href="#_" class="overlay" id="{{ id }}">
<img src="{{ img_url }}" alt="{{ description }}" />
</a>
{% endif %}
</figure>

View File

@@ -20,7 +20,8 @@
<div class="md-grid md-typeset"> <div class="md-grid md-typeset">
<div class="mdx-hero"></div> <div class="mdx-hero"></div>
<h1> <h1>
<i class="ti ti-search"></i> Page not found <span class='fas fa-search'></span>
Page not found
</h1> </h1>
</div> </div>
</section> </section>

View File

@@ -7,7 +7,7 @@
{% block content %} {% block content %}
{{ page.content }}
<style> <style>
h1 { h1 {
@@ -15,8 +15,6 @@
} }
</style> </style>
<h2 id="intuitive-inventory-management">InvenTree - Intuitive Inventory Management</h2>
<!-- Hero for landing page --> <!-- Hero for landing page -->
<section class="mdx-container"> <section class="mdx-container">
<div class="md-grid md-typeset"> <div class="md-grid md-typeset">
@@ -26,24 +24,24 @@
<div class="mdx-hero__content"> <div class="mdx-hero__content">
<a href="https://inventree.org" title="InvenTree Website" class="md-button"> <a href="https://inventree.org" title="InvenTree Website" class="md-button">
<i class='ti ti-world'></i> Website <span class='fas fa-globe'></span> Website
</a> </a>
<a href="start" title="Install InvenTree" class="md-button"> <a href="start/intro" title="Install InvenTree" class="md-button">
<i class='ti ti-server-bolt'></i> Install <span class='fas fa-server'></span> Install
</a> </a>
<a href="app" title="InvenTree mobile app" class="md-button"> <a href="app/app" title="InvenTree mobile app" class="md-button">
<i class='ti ti-device-mobile'></i> Mobile App <span class='fas fa-mobile-alt'></span> Mobile App
</a> </a>
<a href="https://crowdin.com/project/inventree" title="Help translate InvenTree" class="md-button"> <a href="https://crowdin.com/project/inventree" title="Help translate InvenTree" class="md-button">
<i class='ti ti-language'></i> Translate <span class='fas fa-language'></span> Translate
</a> </a>
<a href="https://github.com/inventree/inventree" title="Explore InvenTree source code" class="md-button md-button"> <a href="https://github.com/inventree/inventree" title="Explore InvenTree source code" class="md-button md-button">
<i class='ti ti-code-circle'></i> Source Code <span class='fab fa-github'></span> Source Code
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
{{ page.content }}
{% endblock content %} {% endblock content %}

View File

@@ -4,10 +4,10 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th>{{ icon("clipboard") }}</span> Release</th> <th><span class='fas fa-clipboard-list'></span> Release</th>
<th>{{ icon("calendar") }} Date</th> <th><span class='fas fa-calendar-alt'></span> Date</th>
<th>{{ icon("brand-github") }} GitHub</th> <th><span class='fab fa-github'></span> GitHub</th>
<th>{{ icon("brand-docker") }} Docker</th> <th><span class='fab fa-docker'></span> Docker</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -10,7 +10,7 @@ tld = os.path.abspath(os.path.join(here, '..'))
config_file = os.path.join(tld, 'mkdocs.yml') config_file = os.path.join(tld, 'mkdocs.yml')
with open(config_file, encoding='utf-8') as f: with open(config_file, 'r') as f:
data = yaml.load(f, yaml.BaseLoader) data = yaml.load(f, yaml.BaseLoader)
assert data['strict'] == 'true' assert data['strict'] == 'true'

121
docs/docs/api/api.md Normal file
View File

@@ -0,0 +1,121 @@
---
title: InvenTree API
---
## InvenTree API
InvenTree provides a powerful REST API for interacting with inventory data on the server. Low-level data access and manipulation is available, with integrated user authentication and data validation.
!!! info "Django REST Framework"
The InvenTree API is based on the powerful and flexible [Django REST Framework](https://www.django-rest-framework.org/).
## Documentation
The API is self-documenting, and the documentation is provided alongside any InvenTree installation instance. If (for example) you have an InvenTree instance running at `http://127.0.0.1:8000` then the API documentation is available at `http://127.0.0.1:8000/api-doc/`
{% with id="api_doc", url="api/api_doc.png", description="API documentation" %}
{% include 'img.html' %}
{% endwith %}
### Schema Description
The API schema is also documented in the [API Schema](./schema.md) page.
### Generating Schema File
If you want to generate the API schema file yourself (for example to use with an external client, use the `invoke schema` command. Run with the `-help` command to see available options.
```
invoke schema -help
```
## Authentication
Users must be authenticated to gain access to the InvenTree API. The API accepts either basic username:password authentication, or token authentication. Token authentication is recommended as it provides much faster API access.
!!! warning "Permissions"
API access is restricted based on the permissions assigned to the user.
### Basic Auth
Users can authenticate against the API using basic authentication - specifically a valid combination of `username` and `password` credentials.
### Tokens
Each user is assigned an authentication token which can be used to access the API. This token is persistent for that user (unless invalidated by an administrator) and can be used across multiple sessions.
!!! info "Token Administration"
User tokens can be created and/or invalidated via the Admin interface.
### Requesting a Token
If a user does not know their access token, it can be requested via the API interface itself, using a basic authentication request.
To obtain a valid token, perform a GET request to `/api/user/token/`. No data are required, but a valid username / password combination must be supplied in the authentication headers.
!!! info "Credentials"
Ensure that a valid username:password combination are supplied as basic authorization headers.
Once a valid token is received from the server, subsequent API requests should be performed using that token.
If the supplied user credentials are validated, the server will respond with:
```
HTTP_200_OK
{
token: "usertokendatastring",
}
```
### Using a Token
After reception of a valid authentication token, it can be subsequently used to perform token-based authentication.
The token value sent to the server must be of the format `Token <TOKEN-VALUE>` (without the `<` and `>` characters).
**Example: Javascript**
```javascript
var token = "MY-TOKEN-VALUE-HERE";
$.ajax({
url: "http://localhost:8080/api/part/",
type: 'GET',
headers: {"Authorization": `Token ${token}`}
});
```
**Example: Python (Requests)**
```python
import requests
token = 'MY-TOKEN-VALUE-HERE'
data = { ... }
headers = {
'AUTHORIZATION': f'Token {token}'
}
response = request.get('http://localhost:8080/api/part/', data=data, headers=headers)
```
## Authorization
### User Roles
Users can only perform REST API actions which align with their assigned [role permissions](../settings/permissions.md#roles).
Once a user has *authenticated* via the API, a list of the available roles can be retrieved from:
`/api/user/roles/`
For example, when accessing the API from a *superuser* account:
{% with id="api_roles", url="api/api_roles.png", description="API superuser roles" %}
{% include 'img.html' %}
{% endwith %}
Or, when accessing the API from an account which has read-only permissions:
{% with id="api_roles_2", url="api/api_roles_2.png", description="API user roles" %}
{% include 'img.html' %}
{% endwith %}
### Permission Denied
If an API action outside of the user's role(s) is attempted, the server will respond with a 403 permission error message.

View File

@@ -4,7 +4,7 @@ title: Interactive API
## Interactive API ## Interactive API
If the server is running in [Debug Mode](../start/index.md#debug-mode) then an interactive version of the API is available using a browser. If the server is running in [Debug Mode](../start/intro.md#debug-mode) then an interactive version of the API is available using a browser.
!!! info "Debug Mode" !!! info "Debug Mode"
This interactive API is only available when running the server in debug mode This interactive API is only available when running the server in debug mode
@@ -16,16 +16,22 @@ If the server is running in [Debug Mode](../start/index.md#debug-mode) then an i
Various list endpoints can be displayed as shown below: Various list endpoints can be displayed as shown below:
{{ image("api/api_browse.png", "List API") }} {% with id="api_browse", url="api/api_browse.png", description="List API" %}
{% include 'img.html' %}
{% endwith %}
### Filtering ### Filtering
List views can be filtered interactively: List views can be filtered interactively:
{{ image("api/api_filters.png", "Filter API") }} {% with id="api_filter", url="api/api_filters.png", description="Filter API" %}
{% include 'img.html' %}
{% endwith %}
### Detail View ### Detail View
Detail view endpoints can also be displayed: Detail view endpoints can also be displayed:
{{ image("api/api_detail.png", "Detail API") }} {% with id="api_detail", url="api/api_detail.png", description="Detail API" %}
{% include 'img.html' %}
{% endwith %}

View File

@@ -6,7 +6,7 @@ title: Bulk Deletion
While deleting items individually via the API is supported, it can prove inefficient (time consuming) when multiple items are to be deleted sequentially. While deleting items individually via the API is supported, it can prove inefficient (time consuming) when multiple items are to be deleted sequentially.
For example, if the user wishes to delete a large number items (such as lines from a [Bill of Materials](../manufacturing/bom.md)), these items are deleted sequentially, with each `DELETE` separate request requiring network transfer, database access, cleanup, etc. For example, if the user wishes to delete a large number items (such as lines from a [Bill of Materials](../build/bom.md)), these items are deleted sequentially, with each `DELETE` separate request requiring network transfer, database access, cleanup, etc.
A much more efficient approach is to allow for "bulk deletion" of multiple database items in a single transaction. This means that only one network request is required, and only a single database access request. A much more efficient approach is to allow for "bulk deletion" of multiple database items in a single transaction. This means that only one network request is required, and only a single database access request.

View File

@@ -1,161 +0,0 @@
---
title: InvenTree API
---
## InvenTree API
InvenTree provides a powerful REST API for interacting with inventory data on the server. Low-level data access and manipulation is available, with integrated user authentication and data validation.
!!! info "Django REST Framework"
The InvenTree API is based on the powerful and flexible [Django REST Framework](https://www.django-rest-framework.org/).
## Documentation
The API is self-documenting, and the documentation is provided alongside any InvenTree installation instance. If (for example) you have an InvenTree instance running at `http://127.0.0.1:8000` then the API documentation is available at `http://127.0.0.1:8000/api-doc/`
{{ image("api/api_doc.png", "API documentation") }}
### Browseble API
If [debug mode](../start/index.md#debug-mode) is enabled, the API can be browsed directly from the web interface. This provides a simple way to explore the API and test out different endpoints. Simply navigate your web browser to any API endpoint, and the API will be displayed in a human-readable format.
### Schema Description
The API schema is also documented in the [API Schema](./schema.md) page.
### Generating Schema File
If you want to generate the API schema file yourself. For example, to use with an external client, use the `invoke dev.schema` command. Run with the `-help` command to see available options.
```
invoke dev.schema -help
```
## Authentication
Users must be authenticated to gain access to the InvenTree API. The API accepts either basic username:password authentication, or token authentication. Token authentication is recommended as it provides much faster API access.
!!! warning "Permissions"
API access is restricted based on the permissions assigned to the user or scope of the application.
### Basic Auth
Users can authenticate against the API using basic authentication - specifically a valid combination of `username` and `password` credentials.
### Tokens
Each user is assigned an authentication token which can be used to access the API. This token is persistent for that user (unless invalidated by an administrator) and can be used across multiple sessions.
!!! info "Token Administration"
User tokens can be created and/or invalidated via the user settings, [Admin Center](../settings/admin.md#admin-center) or admin interface.
#### Requesting a Token
If a user does not know their access token, it can be requested via the API interface itself, using a basic authentication request.
To obtain a valid token, perform a GET request to `/api/user/token/`. No data are required, but a valid username / password combination must be supplied in the authentication headers.
!!! info "Credentials"
Ensure that a valid username:password combination are supplied as basic authorization headers.
Once a valid token is received from the server, subsequent API requests should be performed using that token.
If the supplied user credentials are validated, the server will respond with:
```
HTTP_200_OK
{
token: "usertokendatastring",
}
```
#### Using a Token
After reception of a valid authentication token, it can be subsequently used to perform token-based authentication.
The token value sent to the server must be of the format `Token <TOKEN-VALUE>` (without the `<` and `>` characters).
**Example: Javascript**
```javascript
var token = "MY-TOKEN-VALUE-HERE";
$.ajax({
url: "http://localhost:8080/api/part/",
type: 'GET',
headers: {"Authorization": `Token ${token}`}
});
```
**Example: Python (Requests)**
```python
import requests
token = 'MY-TOKEN-VALUE-HERE'
data = { ... }
headers = {
'AUTHORIZATION': f'Token {token}'
}
response = request.get('http://localhost:8080/api/part/', data=data, headers=headers)
```
### oAuth2 and OIDC
!!! warning "Experimental"
This is an experimental feature that needs to be specifically enabled. See [Experimental features](../settings/experimental.md) for more information.
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 client_id is `zDFnsiRheJIOKNx6aCQ0quBxECg1QBHtVFDPloJ6`.
#### Managing applications
Superusers can register new applications and manage existing ones using a small application under the subpath `/o/applications/`.
It is recommended to:
- read the spec (RFC 6749 / 6750) and/or best practices (RFC 9700) before choosing client types
- chose scopes as narrow as possible
- configure redirection URIs as exact as possible
#### Scopes
InvenTree's oAuth scopes are strongly related to the [user roles](#user-roles).
Names consist of 1. type, 2. kind and 3. (opt) role, separated by colons.
There are 3 types:
- a: administrative scopes - used for administrating the server - these can be staff or superuser scopes
- g: general scopes - give wide access to the basic building blocks of InvenTree
- r: role scopes - map to specific actions (2) and roles (3)
Examples:
```bash
a:superuser
g:read
r:change:part
r:delete:stock
```
!!! info "Read the API docs"
The API [documentation](#documentation) and [schema](./schema.md) list the required scopes for every API endpoint / interaction in the security sections.
## Authorization
### User Roles
Users can only perform REST API actions which align with their assigned [role permissions](../settings/permissions.md#roles).
Once a user has *authenticated* via the API, a list of the available roles can be retrieved from:
`/api/user/roles/`
For example, when accessing the API from a *superuser* account:
{{ image("api/api_roles.png", "API superuser roles") }}
Or, when accessing the API from an account which has read-only permissions:
{{ image("api/api_roles_2.png", "API user roles") }}
### Permission Denied
If an API action outside of the user's role(s) is attempted, the server will respond with a 403 permission error message.

View File

@@ -4,7 +4,7 @@ title: Model Metadata
## Model Metadata ## Model Metadata
The API is *self describing* in that it provides metadata about the various fields available at any given endpoint. External applications (such as the [python interface](../api/python/index.md)) can introspect the API to determine information about the model fields. The API is *self describing* in that it provides metadata about the various fields available at any given endpoint. External applications (such as the [python interface](../api/python/python.md)) can introspect the API to determine information about the model fields.
!!! tip "API Forms" !!! tip "API Forms"
The various forms implemented in the InvenTree web interface make heavy use of this metadata feature The various forms implemented in the InvenTree web interface make heavy use of this metadata feature
@@ -13,9 +13,11 @@ The API is *self describing* in that it provides metadata about the various fiel
To request metadata about a particular API endpoint, simply perform an `OPTIONS` method request against the API URL. To request metadata about a particular API endpoint, simply perform an `OPTIONS` method request against the API URL.
For example, to view the metadata available for creating a new [Part Category](../part/index.md#part-category), an `OPTIONS` request to `/api/part/category/` yields: For example, to view the metadata available for creating a new [Part Category](../part/part.md#part-category), an `OPTIONS` request to `/api/part/category/` yields:
{{ image("api/api_category_options.png", "Part category options") }} {% with id="api_cat_options", url="api/api_category_options.png", description="Part category options" %}
{% include 'img.html' %}
{% endwith %}
You can see here a detailed list of the various fields which are available for this API endpoint. You can see here a detailed list of the various fields which are available for this API endpoint.
@@ -31,7 +33,9 @@ The `OPTIONS` endpoint provides the following information:
Specific details are provided on the available attributes of each field: Specific details are provided on the available attributes of each field:
{{ image("api/api_metadata_fields.png", "Metadata fields") }} {% with id="api_fields", url="api/api_metadata_fields.png", description="Metadata fields" %}
{% include 'img.html' %}
{% endwith %}
### Field Types ### Field Types
@@ -78,5 +82,10 @@ Field *label* and *help text* values are localized using the [community contribu
For example, the same forms (in the web interface) are served via identical API requests, with the locale information determined "on the fly": For example, the same forms (in the web interface) are served via identical API requests, with the locale information determined "on the fly":
{{ image("api/api_english.png", "API forms (english)") }} {% with id="api_english", url="api/api_english.png", description="API forms (english)" %}
{{ image("api/api_german.png", "API forms (german)") }} {% include 'img.html' %}
{% endwith %}
{% with id="api_german", url="api/api_german.png", description="API forms (german)" %}
{% include 'img.html' %}
{% endwith %}

View File

@@ -6,7 +6,7 @@ title: Python Currency Support
InvenTree provides native support for multiple currencies, which can mean that data require conversion between these currencies, at defined exchange rates. InvenTree provides native support for multiple currencies, which can mean that data require conversion between these currencies, at defined exchange rates.
The InvenTree server maintains a set of exchange rates, which are updated periodically. These exchange rates are available via the [InvenTree API](../index.md), and can be used by the Python bindings. The InvenTree server maintains a set of exchange rates, which are updated periodically. These exchange rates are available via the [InvenTree API](../api.md), and can be used by the Python bindings.
### CurrencyManager Class ### CurrencyManager Class

View File

@@ -67,7 +67,7 @@ print("Minimum stock:", part.minimum_stock)
### Adding Parameters ### Adding Parameters
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. Each [part](../../part/part.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.
```python ```python
from inventree.part import Parameter from inventree.part import Parameter
@@ -190,7 +190,7 @@ item.transferStock(loc, quantity=50)
### Delete a Part ### Delete a Part
To delete a [Part instance](../../part/index.md), first in needs to be marked as *inactive* (otherwise it will throw an error): To delete a [Part instance](../../part/part.md), first in needs to be marked as *inactive* (otherwise it will throw an error):
```python ```python
from inventree.part import Part from inventree.part import Part

View File

@@ -1,167 +0,0 @@
---
title: Python Interface
---
## Python Module
A [Python module](https://github.com/inventree/inventree-python) is provided for rapid development of third party scripts or applications using the REST API. The python module handles authentication and API transactions, providing an extremely clean interface for interacting with and manipulating database data.
### Features
- Automatic authentication management using token-based authentication
- Pythonic data access
- Native file uploads
- Powerful functions for accessing related model data
### Installation
The inventree python interface can be easily installed via the [PIP package manager](https://pypi.org/project/inventree/):
```
pip3 install inventree
```
!!! tip "Upgrading"
To upgrade to the latest version, run `pip install --upgrade inventree`
Alternatively, it can downloaded and installed from source, from [GitHub](https://github.com/inventree/inventree-python).
### Authentication
Authentication against an InvenTree server is simple:
#### Basic Auth
Connect using your username/password as follows:
```python
from inventree.api import InvenTreeAPI
SERVER_ADDRESS = 'http://127.0.0.1:8000'
MY_USERNAME = 'not_my_real_username'
MY_PASSWORD = 'not_my_real_password'
api = InvenTreeAPI(SERVER_ADDRESS, username=MY_USERNAME, password=MY_PASSWORD)
```
#### Token Auth
Alternatively, if you already have an access token:
```python
api = InvenTreeAPI(SERVER_ADDRESS, token=MY_TOKEN)
```
#### Environment Variables
Authentication variables can also be set using environment variables:
- `INVENTREE_API_HOST`
- `INVENTREE_API_USERNAME`
- `INVENTREE_API_PASSWORD`
- `INVENTREE_API_TOKEN`
And simply connect as follows:
```python
api = InvenTreeAPI()
```
### Retrieving Data
Once a connection is established to the InvenTree server, querying individual items is simple.
#### Single Item
If the primary-key of an object is already known, retrieving it from the database is performed as follows:
```python
from inventree.part import PartCategory
category = PartCategory(api, 10)
```
#### Multiple Items
Database items can be queried by using the `list` method for the given class. Note that arbitrary filter parameters can be applied (as specified by the [InvenTree API](../index.md)) to filter the returned results.
```python
from inventree.part import Part
from inventree.stock import StockItem
parts = Part.list(api, category=10, assembly=True)
items = StockItem.list(api, location=4, part=24)
```
The `items` variable above provides a list of `StockItem` objects.
#### Filtering by parent
In tree based models the child items could be filtered by using the parent keyword:
```python
from inventree.part import PartCategory
child_categories = PartCategory.list(api, parent=10)
```
The top level items can can be queried by passing empty string as a parent filter:
```python
from inventree.part import PartCategory
parent_categories = PartCategory.list(api, parent='')
```
### Item Attributes
The available model attributes are determined by introspecting [API metadata](../metadata.md). To view the fields (attributes) available for a given database model type within the python interface, use the `fieldNames` and `fieldInfo` methods, as below:
```python
from inventree.api import InvenTreeAPI
from inventree.part import Part
api = InvenTreeAPI("http://localhost:8000", username="admin", password="inventree")
fields = Part.fieldNames(api)
for field in Part.fieldNames(api):
print(field, '->', Part.fieldInfo(field, api))
```
```
active -> {'type': 'boolean', 'required': True, 'read_only': False, 'label': 'Active', 'help_text': 'Is this part active?', 'default': True, 'max_length': None}
allocated_to_build_orders -> {'type': 'float', 'required': True, 'read_only': True, 'label': 'Allocated to build orders'}
allocated_to_sales_orders -> {'type': 'float', 'required': True, 'read_only': True, 'label': 'Allocated to sales orders'}
assembly -> {'type': 'boolean', 'required': True, 'read_only': False, 'label': 'Assembly', 'help_text': 'Can this part be built from other parts?', 'default': False, 'max_length': None}
category -> {'type': 'related field', 'required': True, 'read_only': False, 'label': 'Category', 'model': 'partcategory', 'api_url': '/api/part/category/', 'filters': {}, 'help_text': 'Part category', 'max_length': None}
component -> {'type': 'boolean', 'required': True, 'read_only': False, 'label': 'Component', 'help_text': 'Can this part be used to build other parts?', 'default': True, 'max_length': None}
default_expiry -> {'type': 'integer', 'required': True, 'read_only': False, 'label': 'Default Expiry', 'help_text': 'Expiry time (in days) for stock items of this part', 'min_value': 0, 'max_value': 2147483647, 'default': 0, 'max_length': None}
...
variant_stock -> {'type': 'float', 'required': True, 'read_only': True, 'label': 'Variant stock'}
```
### Item Methods
Once an object has been retrieved from the database, its related objects can be returned with the provided helper methods:
```python
part = Part(api, 25)
stock_items = part.getStockItems()
```
Some classes also have helper functions for performing certain actions, such as uploading file attachments or test results:
```python
stock_item = StockItem(api, 1001)
stock_item.uploadTestResult("Firmware", True, value="0x12345678", attachment="device_firmware.bin")
```
#### Discovering Methods
You can determine the available methods by either [reading the source code](https://github.com/inventree/inventree-python) or using the `dir()` function in an interactive terminal.
### Further Reading
The [InvenTree Python Interface](https://github.com/inventree/inventree-python) is open source, and well documented. The best way to learn is to read through the source code and try for yourself!

View File

@@ -0,0 +1,167 @@
---
title: Python Interface
---
## Python Module
A [Python module](https://github.com/inventree/inventree-python) is provided for rapid development of third party scripts or applications using the REST API. The python module handles authentication and API transactions, providing an extremely clean interface for interacting with and manipulating database data.
### Features
- Automatic authentication management using token-based authentication
- Pythonic data access
- Native file uploads
- Powerful functions for accessing related model data
### Installation
The inventree python interface can be easily installed via the [PIP package manager](https://pypi.org/project/inventree/):
```
pip3 install inventree
```
!!! tip "Upgrading"
To upgrade to the latest version, run `pip install --upgrade inventree`
Alternatively, it can downloaded and installed from source, from [GitHub](https://github.com/inventree/inventree-python).
### Authentication
Authentication against an InvenTree server is simple:
#### Basic Auth
Connect using your username/password as follows:
```python
from inventree.api import InvenTreeAPI
SERVER_ADDRESS = 'http://127.0.0.1:8000'
MY_USERNAME = 'not_my_real_username'
MY_PASSWORD = 'not_my_real_password'
api = InvenTreeAPI(SERVER_ADDRESS, username=MY_USERNAME, password=MY_PASSWORD)
```
#### Token Auth
Alternatively, if you already have an access token:
```python
api = InvenTreeAPI(SERVER_ADDRESS, token=MY_TOKEN)
```
#### Environment Variables
Authentication variables can also be set using environment variables:
- `INVENTREE_API_HOST`
- `INVENTREE_API_USERNAME`
- `INVENTREE_API_PASSWORD`
- `INVENTREE_API_TOKEN`
And simply connect as follows:
```python
api = InvenTreeAPI()
```
### Retrieving Data
Once a connection is established to the InvenTree server, querying individual items is simple.
#### Single Item
If the primary-key of an object is already known, retrieving it from the database is performed as follows:
```python
from inventree.part import PartCategory
category = PartCategory(api, 10)
```
#### Multiple Items
Database items can be queried by using the `list` method for the given class. Note that arbitrary filter parameters can be applied (as specified by the [InvenTree API](../api.md)) to filter the returned results.
```python
from inventree.part import Part
from inventree.stock import StockItem
parts = Part.list(api, category=10, assembly=True)
items = StockItem.list(api, location=4, part=24)
```
The `items` variable above provides a list of `StockItem` objects.
#### Filtering by parent
In tree based models the child items could be filtered by using the parent keyword:
```python
from inventree.part import PartCategory
child_categories = PartCategory.list(api, parent=10)
```
The top level items can can be queried by passing empty string as a parent filter:
```python
from inventree.part import PartCategory
parent_categories = PartCategory.list(api, parent='')
```
### Item Attributes
The available model attributes are determined by introspecting [API metadata](../metadata.md). To view the fields (attributes) available for a given database model type within the python interface, use the `fieldNames` and `fieldInfo` methods, as below:
```python
from inventree.api import InvenTreeAPI
from inventree.part import Part
api = InvenTreeAPI("http://localhost:8000", username="admin", password="inventree")
fields = Part.fieldNames(api)
for field in Part.fieldNames(api):
print(field, '->', Part.fieldInfo(field, api))
```
```
active -> {'type': 'boolean', 'required': True, 'read_only': False, 'label': 'Active', 'help_text': 'Is this part active?', 'default': True, 'max_length': None}
allocated_to_build_orders -> {'type': 'float', 'required': True, 'read_only': True, 'label': 'Allocated to build orders'}
allocated_to_sales_orders -> {'type': 'float', 'required': True, 'read_only': True, 'label': 'Allocated to sales orders'}
assembly -> {'type': 'boolean', 'required': True, 'read_only': False, 'label': 'Assembly', 'help_text': 'Can this part be built from other parts?', 'default': False, 'max_length': None}
category -> {'type': 'related field', 'required': True, 'read_only': False, 'label': 'Category', 'model': 'partcategory', 'api_url': '/api/part/category/', 'filters': {}, 'help_text': 'Part category', 'max_length': None}
component -> {'type': 'boolean', 'required': True, 'read_only': False, 'label': 'Component', 'help_text': 'Can this part be used to build other parts?', 'default': True, 'max_length': None}
default_expiry -> {'type': 'integer', 'required': True, 'read_only': False, 'label': 'Default Expiry', 'help_text': 'Expiry time (in days) for stock items of this part', 'min_value': 0, 'max_value': 2147483647, 'default': 0, 'max_length': None}
...
variant_stock -> {'type': 'float', 'required': True, 'read_only': True, 'label': 'Variant stock'}
```
### Item Methods
Once an object has been retrieved from the database, its related objects can be returned with the provided helper methods:
```python
part = Part(api, 25)
stock_items = part.getStockItems()
```
Some classes also have helper functions for performing certain actions, such as uploading file attachments or test results:
```python
stock_item = StockItem(api, 1001)
stock_item.uploadTestResult("Firmware", True, value="0x12345678", attachment="device_firmware.bin")
```
#### Discovering Methods
You can determine the available methods by either [reading the source code](https://github.com/inventree/inventree-python) or using the `dir()` function in an interactive terminal.
### Further Reading
The [InvenTree Python Interface](https://github.com/inventree/inventree-python) is open source, and well documented. The best way to learn is to read through the source code and try for yourself!

View File

@@ -7,7 +7,7 @@ The API schema as documented below is generated using the [drf-spectactular](htt
## API Version ## API Version
This documentation is for API version: `352` This documentation is for API version: `171`
!!! tip "API Schema History" !!! tip "API Schema History"
We track API schema changes, and provide a snapshot of each API schema version in the [API schema repository](https://github.com/inventree/schema/). We track API schema changes, and provide a snapshot of each API schema version in the [API schema repository](https://github.com/inventree/schema/).

33
docs/docs/app/app.md Normal file
View File

@@ -0,0 +1,33 @@
---
title: InvenTree Mobile App
---
{% with directory="appgallery", per_page=2 %}
{% include "carousel.html" %}
{% endwith %}
-----
The InvenTree Mobile App brings stock control to your pocket. Integrating seamlessly with the [InvenTree API](../api/api.md), the app provides immediate access to inventory data without requiring physical access to a computer.
Native barcode support provides a multitude of context-sensitive stock control actions, allowing streamlined inventory management at your fingertips. The app has been optimized for speed, providing instant access to stock knowledge and handy on-site functionality.
## Features
- View and edit part and stock information with a blazingly fast interface
- Perform stock control actions on the go
- Barcode integrations simply stock operations
- Receive purchase orders and check in stock items
- And many more!
## Download
The InvenTree app can be downloaded from either the Android or Apple app stores, or accessed via the links below:
### Android
<span class='fab fa-android'></span> [Android Play Store](https://play.google.com/store/apps/details?id=inventree.inventree_app).
### iOS
<span class='fab fa-apple'></span> [Apple App Store](https://apps.apple.com/au/app/inventree/id1581731101#?platform=iphone)

View File

@@ -53,7 +53,9 @@ If a match is found, the app will navigate to the relevant page.
From the [Stock Location detail page](./stock.md#stock-location-view), multiple barcode actions may be available: From the [Stock Location detail page](./stock.md#stock-location-view), multiple barcode actions may be available:
{{ image("app/barcode_stock_location_actions.png", "Stock location barcode actions") }} {% with id="location-actions", url="app/barcode_stock_location_actions.png", maxheight="240px", description="Stock location barcode actions" %}
{% include 'img.html' %}
{% endwith %}
#### Assign Barcode #### Assign Barcode
@@ -73,9 +75,11 @@ the *Scan Items Into Location* action allows you to scan items into the selected
### Stock Item Actions ### Stock Item Actions
From the [Stock Item detail page](./stock.md#details-tab), the following barcode actions may be available: From the [Stock Item detail page](./stock.md#stock-item-detail-view), the following barcode actions may be available:
{{ image("app/barcode_stock_item_actions.png", "Stock item barcode actions") }} {% with id="item-actions", url="app/barcode_stock_item_actions.png", maxheight="240px", description="Stock item barcode actions" %}
{% include 'img.html' %}
{% endwith %}
#### Assign Barcode #### Assign Barcode
@@ -89,7 +93,9 @@ Scan the selected stock item into a stock location. Scanning a valid barcode ass
From the [Part detail page](./part.md#part-detail-view), the following barcode actions are available: From the [Part detail page](./part.md#part-detail-view), the following barcode actions are available:
{{ image("app/barcode_part_actions.png", "Part barcode actions") }} {% with id="part-actions", url="app/barcode_part_actions.png", maxheight="240px", description="Part barcode actions" %}
{% include 'img.html' %}
{% endwith %}
#### Assign Barcode #### Assign Barcode
@@ -99,7 +105,9 @@ Assign a custom barcode to the selected part. Scanning a barcode (which is not a
From the [Purchase Order detail page](./po.md#purchase-order-detail) page, the following barcode actions are available: From the [Purchase Order detail page](./po.md#purchase-order-detail) page, the following barcode actions are available:
{{ image("app/barcode_po_actions.png", "Purchase order barcode actions") }} {% with id="po-actions", url="app/barcode_po_actions.png", maxheight="240px", description="Purchase order barcode actions" %}
{% include 'img.html' %}
{% endwith %}
#### Scan Received Parts #### Scan Received Parts

View File

@@ -8,20 +8,27 @@ Use of the InvenTree app assumes that you (the user) have access to an InvenTree
When first running the app, no profile has been configured. The *server* icon in the top-right corner of the home screen is <span style='color: red'>red</span>, indicating that there is no connection to an InvenTree server: When first running the app, no profile has been configured. The *server* icon in the top-right corner of the home screen is <span style='color: red'>red</span>, indicating that there is no connection to an InvenTree server:
{{ image("app/initial.png", "No server configured") }} {% with id="no_server", url="app/initial.png", maxheight="240px", description="No server configured" %}
{% include "img.html" %}
{% endwith %}
Press on the server icon to navigate to the server selection view: Press on the server icon to navigate to the server selection view:
{{ image("app/no_profiles.png", "No server configured") }} {% with id="no_profiles", url="app/no_profiles.png", maxheight="240px", description="No server configured" %}
{% include "img.html" %}
{% endwith %}
### Create Server ### Create Server
!!! success "Server Profiles" !!! success "Server Profiles"
The app supports multiple server profiles, providing simple switching between different InvenTree servers and/or account profiles. The app supports multiple server profiles, providing simple switching between different InvenTree servers and/or account profiles.
Press the {{ icon("circle-plus", color="blue") }} button in the bottom-right corner of the screen to create a new server profile. Press the <span class='fas fa-plus-circle blue'></span> button in the bottom-right corner of the screen to create a new server profile.
{{ image("app/add_server_profile.png", "Add server profile") }} {% with id="add_profile", url="app/add_server_profile.png", maxheight="240px", description="Add server" %}
{% include 'img.html' %}
{% endwith %}
Enter the required server details: Enter the required server details:
@@ -38,18 +45,24 @@ Once the server profile is created, you need to connect to the server. Simply sh
Alternatively, long press on the server profile to activate the context menu, then select *Connect to Server*. Alternatively, long press on the server profile to activate the context menu, then select *Connect to Server*.
When the app successfully connects to the server, a success message is briefly displayed at the bottom of the screen. A green {{ icon("circle-check", color="green") }} icon next to the server profile indicate that the profile is currently *selected* and also the connection was successful. When the app successfully connects to the server, a success message is briefly displayed at the bottom of the screen. A green <span class='fas fa-check-circle green'></span> icon next to the server profile indicate that the profile is currently *selected* and also the connection was successful.
{{ image("app/connected.png", "Connected to server") }} {% with id="connected", url="app/connected.png", maxheight="240px", description="Connected to server" %}
{% include 'img.html' %}
{% endwith %}
### Connection Failure ### Connection Failure
If (for whatever reason) the app does not successfully connect to the InvenTree server, a failure message is displayed, and a red {{ icon("circle-x", color="red") }} icon is displayed next to the server profile. If (for whatever reason) the app does not successfully connect to the InvenTree server, a failure message is displayed, and a red <span class='fas fa-times-circle red'></span> icon is displayed next to the server profile.
{{ image("app/unauthorized.png", "Connection failure") }} {% with id="failed", url="app/unauthorized.png", maxheight="240px", description="Connection failure" %}
{% include 'img.html' %}
{% endwith %}
In this case, the error message displayed at the bottom of the screen provides context as to why the app could not successfully connect to the server. In this case, the error message displayed at the bottom of the screen provides context as to why the app could not successfully connect to the server.
To edit the server profile details, long press on the server profile, and select *Edit Server Profile*: To edit the server profile details, long press on the server profile, and select *Edit Server Profile*:
{{ image("app/edit_server.png", "Edit server profile") }} {% with id="edit", url="app/edit_server.png", maxheight="240px", description="Edit server profile" %}
{% include 'img.html' %}
{% endwith %}

View File

@@ -1,33 +0,0 @@
---
title: InvenTree Mobile App
---
{% with directory="appgallery", per_page=2 %}
{% include "carousel.html" %}
{% endwith %}
-----
The InvenTree Mobile App brings stock control to your pocket. Integrating seamlessly with the [InvenTree API](../api/index.md), the app provides immediate access to inventory data without requiring physical access to a computer.
Native barcode support provides a multitude of context-sensitive stock control actions, allowing streamlined inventory management at your fingertips. The app has been optimized for speed, providing instant access to stock knowledge and handy on-site functionality.
## Features
- View and edit part and stock information with a blazingly fast interface
- Perform stock control actions on the go
- Barcode integrations simply stock operations
- Receive purchase orders and check in stock items
- And many more!
## Download
The InvenTree app can be downloaded from either the Android or Apple app stores, or accessed via the links below:
### Android
<span class='fab fa-android'></span> [Android Play Store](https://play.google.com/store/apps/details?id=inventree.inventree_app).
### iOS
<span class='fab fa-apple'></span> [Apple App Store](https://apps.apple.com/au/app/inventree/id1581731101#?platform=iphone)

View File

@@ -7,13 +7,17 @@ title: App Navigation
The app *home screen* provides quick-access buttons for stock view and actions: The app *home screen* provides quick-access buttons for stock view and actions:
{{ image("app/home.png", "Home screen") }} {% with id="home", url="app/home.png", maxheight="240px", description="Home screen" %}
{% include 'img.html' %}
{% endwith %}
## Tab Display ## Tab Display
Some screens provide multiple tabbed views, which are displayed at the top of the screen: Some screens provide multiple tabbed views, which are displayed at the top of the screen:
{{ image("app/app_tabs.png", "App tabs") }} {% with id="global_nav", url="app/app_tabs.png", maxheight="240px", description="App tabs" %}
{% include 'img.html' %}
{% endwith %}
Tabs can be navigated by pressing on the text of each tab, or by scrolling the screen left or right. Tabs can be navigated by pressing on the text of each tab, or by scrolling the screen left or right.
@@ -21,13 +25,17 @@ Tabs can be navigated by pressing on the text of each tab, or by scrolling the s
The *Global Action* buttons are visible on most screens, displayed in the bottom left corner of the screen: The *Global Action* buttons are visible on most screens, displayed in the bottom left corner of the screen:
{{ image("app/app_global_navigation.png", "Global navigation actions") }} {% with id="global_nav", url="app/app_global_navigation.png", maxheight="240px", description="Global navigation actions" %}
{% include 'img.html' %}
{% endwith %}
### Open Drawer Menu ### Open Drawer Menu
The {{ icon("list") }} action opens the *Drawer Menu*, which is a quick-access menu for global navigation: The <span class='fas fa-list'></span> action opens the *Drawer Menu*, which is a quick-access menu for global navigation:
{{ image("app/drawer.png", "Open drawer menu") }} {% with id="drawer", url="app/drawer.png", maxheight="240px", description="Open drawer menu" %}
{% include 'img.html' %}
{% endwith %}
The *Drawer Menu* can be accessed in the following ways: The *Drawer Menu* can be accessed in the following ways:
@@ -36,17 +44,19 @@ The *Drawer Menu* can be accessed in the following ways:
### Search ### Search
The {{ icon("search", title="Search") }} action opens the [Search](./search.md) screen The <span class='fas fa-search'></span> action opens the [Search](./search.md) screen
### Scan Barcode ### Scan Barcode
The {{ icon("barcode", title="Scan") }} action opens the [barcode scan](./barcode.md#global-scan) window, which allows quick access to the barcode scanning functionality. The <span class='fas fa-qrcode'></span> action opens the [barcode scan](./barcode.md#global-scan) window, which allows quick access to the barcode scanning functionality.
## Context Actions ## Context Actions
Within a given view, certain context actions may be available. If there are contextual actions which can be performed, they are displayed in the bottom right corner: Within a given view, certain context actions may be available. If there are contextual actions which can be performed, they are displayed in the bottom right corner:
{{ image("app/context_actions.png", "Context actions") }} {% with id="drawer", url="app/context_actions.png", maxheight="240px", description="Context actions" %}
{% include 'img.html' %}
{% endwith %}
!!! tip "Barcode Actions" !!! tip "Barcode Actions"
Available barcode actions are displayed in a separate context action menu Available barcode actions are displayed in a separate context action menu

View File

@@ -10,7 +10,9 @@ From the *home screen*, select *Parts* to open the top-level part category view.
The *Details* tab shows information about the selected part category. In particular, it shows the name and description of the category, a link to the parent category (if available) and a list of subcategories. The *Details* tab shows information about the selected part category. In particular, it shows the name and description of the category, a link to the parent category (if available) and a list of subcategories.
{{ image("app/part_category_detail.png", "Part Category") }} {% with id="part-category", url="part_category_detail.png" %}
{% include "app_img.html" %}
{% endwith %}
#### Parent Category #### Parent Category
@@ -24,35 +26,47 @@ If the current category has any subcategories, these are listed here. Select any
The *Parts* tab displays all the parts available in this category. Tap a displayed part to navigate to the part detail view. The *Parts* tab displays all the parts available in this category. Tap a displayed part to navigate to the part detail view.
{{ image("app/category_parts_tab.png", "Category Parts") }} {% with id="cat-parts", url="category_parts_tab.png" %}
{% include "app_img.html" %}
{% endwith %}
The list of available parts can be filtered using the input box at the top of the screen: The list of available parts can be filtered using the input box at the top of the screen:
{{ image("app/category_parts_filter.png", "Category Parts Filter") }} {% with id="cat-parts-filter", url="category_parts_filter.png" %}
{% include "app_img.html" %}
{% endwith %}
### Context Actions ### Context Actions
The following *Context Actions* are available for the selected category: The following *Context Actions* are available for the selected category:
{{ image("app/category_actions_tab.png", "Category Actions") }} {% with id="cat-actions", url="category_actions_tab.png" %}
{% include "app_img.html" %}
{% endwith %}
#### New Category #### New Category
Create a new subcategory under the current category: Create a new subcategory under the current category:
{{ image("app/new_category.jpg", "New Category") }} {% with id="cat-new-cat", url="new_category.jpg" %}
{% include "app_img.html" %}
{% endwith %}
#### New Part #### New Part
Create a new part within the current category: Create a new part within the current category:
{{ image("app/new_part.jpg", "New Part") }} {% with id="cat-new-part", url="new_part.jpg" %}
{% include "app_img.html" %}
{% endwith %}
### Edit Category ### Edit Category
Select the *Edit* button in the top right corner of the screen to edit the details for the selected part category: Select the *Edit* button in the top right corner of the screen to edit the details for the selected part category:
{{ image("app/part_category_edit.jpg", "Edit Category") }} {% with id="cat-edit", url="part_category_edit.jpg" %}
{% include "app_img.html" %}
{% endwith %}
!!! info "Permission Required" !!! info "Permission Required"
If the user does not have permission to edit part details, this button will be hidden If the user does not have permission to edit part details, this button will be hidden
@@ -63,7 +77,9 @@ In the part category display screen, there are three tabs of information availab
The *Part Detail* view displays information about a single part: The *Part Detail* view displays information about a single part:
{{ image("app/part_details.png", "Part Detail") }} {% with id="part-details", url="part_details.png" %}
{% include "app_img.html" %}
{% endwith %}
### Details Tab ### Details Tab
@@ -81,13 +97,17 @@ The *stock* tile shows the total quantity of stock available for the part. Tap o
Tap on the *notes* tile to view (and edit) the notes for this part: Tap on the *notes* tile to view (and edit) the notes for this part:
{{ image("app/part_notes.jpg", "Part Notes") }} {% with id="part-notes", url="part_notes.jpg" %}
{% include "app_img.html" %}
{% endwith %}
#### Attachments #### Attachments
Tap on the *attachments* tile to view the file attachments for this part: Tap on the *attachments* tile to view the file attachments for this part:
{{ image("app/part_attachments.jpg", "Part Attachments") }} {% with id="part-attachments", url="part_attachments.jpg" %}
{% include "app_img.html" %}
{% endwith %}
New attachments can be uploaded by tapping on the icons in the top right of the screen. New attachments can be uploaded by tapping on the icons in the top right of the screen.
@@ -97,7 +117,9 @@ Select a particular attachment file to downloaded it to the local device.
The *Stock* tab displays all the stock items available for this part. Tap on a particular stock item to navigate to a detail view for that item. The *Stock* tab displays all the stock items available for this part. Tap on a particular stock item to navigate to a detail view for that item.
{{ image("app/part_stock.png", "Part Stock") }} {% with id="part-stock", url="part_stock.png" %}
{% include "app_img.html" %}
{% endwith %}
The list of available stock items can be filtered using the input box at the top of the screen. The list of available stock items can be filtered using the input box at the top of the screen.
@@ -109,13 +131,17 @@ The *Actions* tab displays the available actions for the selected part:
Create a new stock item for this part: Create a new stock item for this part:
{{ image("app/new_stock_item.jpg", "New Stock Item") }} {% with id="part-stock-new", url="new_stock_item.jpg" %}
{% include "app_img.html" %}
{% endwith %}
### Edit Part ### Edit Part
To edit the part details, select the *Edit* button in the top right corner of the screen: To edit the part details, select the *Edit* button in the top right corner of the screen:
{{ image("app/part_edit.jpg", "Edit Part") }} {% with id="part-edit", url="part_edit.jpg" %}
{% include "app_img.html" %}
{% endwith %}
!!! info "Permission Required" !!! info "Permission Required"
If the user does not have permission to edit part details, this button will be hidden If the user does not have permission to edit part details, this button will be hidden
@@ -124,6 +150,8 @@ To edit the part details, select the *Edit* button in the top right corner of th
Tap the image of the part (displayed at the top left of the screen) to launch the part image view: Tap the image of the part (displayed at the top left of the screen) to launch the part image view:
{{ image("app/part_image.jpg", "Part Image") }} {% with id="part-image", url="part_image.jpg" %}
{% include "app_img.html" %}
{% endwith %}
A full-screen view of the image is displayed. The user can also upload a new image for the part, either selecting an image from the device, or taking a new picture with the device's camera. A full-screen view of the image is displayed. The user can also upload a new image for the part, either selecting an image from the device, or taking a new picture with the device's camera.

View File

@@ -6,7 +6,9 @@ title: Purchase Orders
The purchase order list display lists all purchase orders: The purchase order list display lists all purchase orders:
{{ image("app/po_list.png", "Purchase order list") }} {% with id="po_list", url="app/po_list.png", maxheight="240px", description="Purchase order list" %}
{% include "img.html" %}
{% endwith %}
Select an individual purchase order to display the detail view for that order. Select an individual purchase order to display the detail view for that order.
@@ -16,19 +18,25 @@ Displayed purchase orders can be subsequently filtered using the search input at
## Purchase Order Detail ## Purchase Order Detail
{{ image("app/po_detail.png", "Purchase order detail") }} {% with id="po_detail", url="app/po_detail.png", maxheight="240px", description="Purchase order details" %}
{% include "img.html" %}
{% endwith %}
### Edit Order Details ### Edit Order Details
From the detail view, select the *Edit* button in the top-right of the screen. This opens the purchase order editing display: From the detail view, select the *Edit* button in the top-right of the screen. This opens the purchase order editing display:
{{ image("app/po_edit.png", "Edit purchase order") }} {% with id="edit_po", url="app/po_edit.png", maxheight="240px", description="Edit purchase order" %}
{% include "img.html" %}
{% endwith %}
### Line Items ### Line Items
The *Line Items* tab shows the line items associated with this purchase order: The *Line Items* tab shows the line items associated with this purchase order:
{{ image("app/po_lines.png", "Purchase order line items") }} {% with id="po_lines", url="app/po_lines.png", maxheight="240px", description="Purchase order line items" %}
{% include "img.html" %}
{% endwith %}
Long press on a particular line item to receive the item into stock. Long press on a particular line item to receive the item into stock.
@@ -36,4 +44,6 @@ Long press on a particular line item to receive the item into stock.
Once items have been received into stock against a particular purchase order, they are displayed in the *Stock Items* tab: Once items have been received into stock against a particular purchase order, they are displayed in the *Stock Items* tab:
{{ image("app/po_stock.png", "Purchase order stock items") }} {% with id="po_stock", url="app/po_stock.png", maxheight="240px", description="Purchase order stock items" %}
{% include "img.html" %}
{% endwith %}

View File

@@ -6,5 +6,10 @@ title: App Search
The global search screen provides quick search functionality across the connected InvenTree database. Entering a search term will return multiple search results, as shown in the examples below: The global search screen provides quick search functionality across the connected InvenTree database. Entering a search term will return multiple search results, as shown in the examples below:
{{ image("app/search_1.png", "Search results") }} {% with id="search_1", url="app/search_1.png", maxheight="240px", description="Search results" %}
{{ image("app/search_2.png", "Search results") }} {% include 'img.html' %}
{% endwith %}
{% with id="search_2", url="app/search_2.png", maxheight="240px", description="Search results" %}
{% include 'img.html' %}
{% endwith %}

View File

@@ -17,13 +17,18 @@ The main settings view is shown below, and provides the following options:
| [Part](#part-settings) | Configure part management options | | [Part](#part-settings) | Configure part management options |
| About | Display app version information | | About | Display app version information |
{{ image("app/settings.png", "Settings view") }}
{% with id="settings_view", url="app/settings.png", maxheight="240px", description="Settings view" %}
{% include 'img.html' %}
{% endwith %}
## App Settings ## App Settings
The *App Settings* view provides configuration options for the InvenTree app: The *App Settings* view provides configuration options for the InvenTree app:
{{ image("app/app_settings.png", "App settings") }} {% with id="app_settings", url="app/app_settings.png", maxheight="240px", description="App Settings" %}
{% include 'img.html' %}
{% endwith %}
### App Settings ### App Settings
@@ -52,7 +57,9 @@ Configure audible app notifications:
The *Barcode Settings* view allows you to configure options relating to [barcode scanning](./barcode.md): The *Barcode Settings* view allows you to configure options relating to [barcode scanning](./barcode.md):
{{ image("app/barcode_settings.png", "Barcode settings") }} {% with id="barcode_settings", url="app/barcode_settings.png", maxheight="240px", description="Barcode Settings" %}
{% include 'img.html' %}
{% endwith %}
| Option | Description | | Option | Description |
| --- | --- | | --- | --- |
@@ -63,7 +70,9 @@ The *Barcode Settings* view allows you to configure options relating to [barcode
The *Home Screen* view allows you to configure display options for the app 'home screen': The *Home Screen* view allows you to configure display options for the app 'home screen':
{{ image("app/home_settings.png", "Home screen settings") }} {% with id="home_settings", url="app/home_settings.png", maxheight="240px", description="Home Screen Settings" %}
{% include 'img.html' %}
{% endwith %}
| Option | Description | | Option | Description |
| --- | --- | | --- | --- |

View File

@@ -6,7 +6,9 @@ title: Sales Orders
The sales order list display shows all sales orders: The sales order list display shows all sales orders:
{{ image("app/so_list.png", "Sales order list") }} {% with id="so_list", url="app/so_list.png", maxheight="240px", description="Sales order list" %}
{% include "img.html" %}
{% endwith %}
Select an individual sales order to display the detail view for that order. Select an individual sales order to display the detail view for that order.
@@ -18,7 +20,9 @@ Displayed sales orders can be subsequently filtered using the search input at th
Select an individual order to show the detailed view for that order: Select an individual order to show the detailed view for that order:
{{ image("app/so_detail.png", "Sales order detail") }} {% with id="so_detail", url="app/so_detail.png", maxheight="240px", description="Sales order details" %}
{% include "img.html" %}
{% endwith %}
### Edit Order Details ### Edit Order Details
@@ -28,4 +32,6 @@ From the detail view, select the *Edit* button in the top-right of the screen. T
View the line items associated with the selected order: View the line items associated with the selected order:
{{ image("app/so_lines.png", "Sales order line items") }} {% with id="so_lines", url="app/so_lines.png", maxheight="240px", description="Sales order lines" %}
{% include "img.html" %}
{% endwith %}

View File

@@ -10,7 +10,9 @@ From the *home screen*, select *Stock* to open the top-level stock location view
The *Details* tab shows information about the selected stock location. The *Details* tab shows information about the selected stock location.
{{ image("app/location_detail.png", "Stock Location") }} {% with id="loc-detail", url="location_detail.png" %}
{% include "app_img.html" %}
{% endwith %}
#### Parent Location #### Parent Location
@@ -24,29 +26,44 @@ If the current stock location has any sublocations, they are listed here. Select
The *Stock* tab displays all the stock items available in this location. Tap a displayed stock item to navigate to the stock item detail view. The *Stock* tab displays all the stock items available in this location. Tap a displayed stock item to navigate to the stock item detail view.
{{ image("app/location_stock.png", "Location Stock") }} {% with id="loc-stock", url="location_stock.png" %}
{% include "app_img.html" %}
{% endwith %}
The list of available stock items can be filtered using the input box at the top of the screen: The list of available stock items can be filtered using the input box at the top of the screen:
{{ image("app/location_stock_filter.jpg", "Location Stock Filter") }} {% with id="loc-filter", url="location_stock_filter.jpg" %}
{% include "app_img.html" %}
{% endwith %}
### Context Actions ### Context Actions
The following *Context Actions* are available for the selected location: The following *Context Actions* are available for the selected location:
{{ image("app/location_actions.png", "Location Actions") }} {% with id="loc-actions", url="location_actions.png" %}
{% include "app_img.html" %}
{% endwith %}
#### New Location #### New Location
Create a new location under the current location: Create a new location under the current location:
{{ image("app/new_location.jpg", "New Location") }} {% with id="loc-new", url="new_location.jpg" %}
{% include "app_img.html" %}
{% endwith %}
#### New Stock Item #### New Stock Item
Create a new stock item in the current location: Create a new stock item in the current location:
{{ image("app/new_stock_item_from_location.jpg", "New Stock Item") }} {% with id="loc-new-stock", url="new_stock_item_from_location.jpg" %}
{% include "app_img.html" %}
{% endwith %}
#### Scan Stock Items Into Location #### Scan Stock Items Into Location
@@ -57,7 +74,10 @@ Use the barcode scanner to scan a stock item into the current location.
The *Stock Item Detail* view displays information about a single stock item: The *Stock Item Detail* view displays information about a single stock item:
{{ image("app/stock_detail.png", "Stock Item") }} {% with id="stock-detail", url="stock_detail.png" %}
{% include "app_img.html" %}
{% endwith %}
### Details Tab ### Details Tab
@@ -79,34 +99,44 @@ Tap on the notes tile to display and edit the notes for this stock item
The *actions* tab displays the available actions for the selected stock item: The *actions* tab displays the available actions for the selected stock item:
{{ image("app/stock_actions.png", "Stock Actions") }} {% with id="stock-actions", url="stock_actions.png" %}
{% include "app_img.html" %}
{% endwith %}
#### Count Stock #### Count Stock
Select the *Count Stock* action to validate the current number of items in stock. Use this option to perform a quick stocktake! Select the *Count Stock* action to validate the current number of items in stock. Use this option to perform a quick stocktake!
{{ image("app/stock_count.png", "Count Stock") }} {% with id="stock-count", url="stock_count.png" %}
{% include "app_img.html" %}
{% endwith %}
!!! info "Serialized Stock" !!! info "Serialized Stock"
The *count stock* action is not available for serialized stock items, as they have a fixed quantity of 1 The *count stock* action is not available for serialized stock items, as they have a fixed quantity of 1
#### Remove Stock #### Remove Stock
Select this action to remove a certain quantity from the selected stock item. For example, if there are 12 items available, and you take 3 items, the listed quantity will be reduced to 9 items. Select this action to remove a certain quantity from the selected stock item. For example, if there are 12 items available, and you take 3 items, the listed quantity will be reduced to 9 itemes.
{{ image("app/stock_remove.png", "Remove Stock") }} {% with id="stock-remove", url="stock_remove.png" %}
{% include "app_img.html" %}
{% endwith %}
#### Add Stock #### Add Stock
Select this action to add a certain quantity to the selected stock item. For example, if there are 12 items available, and you add 3 items, the listed quantity will be increased to 15 items. Select this action to add a certain quantity to the selected stock item. For example, if there are 12 items available, and you add 3 items, the listed quantity will be increased to 15 items.
{{ image("app/stock_add.png", "Add Stock") }} {% with id="stock-add", url="stock_add.png" %}
{% include "app_img.html" %}
{% endwith %}
#### Transfer Stock #### Transfer Stock
Transfer (move) the stock item to a new location: Transfer (move) the stock item to a new location:
{{ image("app/stock_transfer.png", "Transfer Stock") }} {% with id="stock-transfer", url="stock_transfer.png" %}
{% include "app_img.html" %}
{% endwith %}
#### Scan Into Location #### Scan Into Location
@@ -122,13 +152,20 @@ This barcode can then be used to track the stock item.
#### Print Label #### Print Label
If the server supports [label printing plugins](../plugins/mixins/label.md), then an option to print a label for the selected stock item: If the server supports [label printing plugins](../extend/plugins/label.md), then an option to print a label for the selected stock item:
{{ image("app/stock_print_label_1.png", "Print Label") }} {% with id="label_print_1", url="stock_print_label_1.png", description="Print label via plugin" %}
{{ image("app/stock_print_label_2.png", "Print Label") }} {% include 'app_img.html' %}
{% endwith %}
{% with id="label_print_2", url="stock_print_label_2.png", description="Print label via plugin" %}
{% include 'app_img.html' %}
{% endwith %}
### Edit Stock Item ### Edit Stock Item
To edit the stock item details, select the *Edit* button in the top right corner of the screen: To edit the stock item details, select the *Edit* button in the top right corner of the screen:
{{ image("app/stock_edit.jpg", "Edit Stock Item") }} {% with id="stock-edit", url="stock_edit.jpg" %}
{% include "app_img.html" %}
{% endwith %}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Some files were not shown because too many files have changed in this diff Show More