2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-05-21 16:56:47 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue5729

This commit is contained in:
Matthias Mair
2023-10-25 23:15:00 +02:00
47 changed files with 510 additions and 224 deletions
+3 -3
View File
@@ -35,12 +35,12 @@ runs:
using: 'composite'
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
# Python installs
- name: Set up Python ${{ env.python_version }}
if: ${{ inputs.python == 'true' }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
cache: pip
@@ -58,7 +58,7 @@ runs:
# NPM installs
- name: Install node.js ${{ env.node_version }}
if: ${{ inputs.npm == 'true' }}
uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b # pin to v3.5.0
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # pin to v3.8.2
with:
node-version: ${{ env.node_version }}
cache: 'npm'
+2 -2
View File
@@ -7,7 +7,7 @@ name: Backport
on:
pull_request_target:
types: ["labeled", "closed"]
types: [ "labeled", "closed" ]
jobs:
backport:
@@ -22,7 +22,7 @@ jobs:
)
steps:
- name: Backport Action
uses: sqren/backport-github-action@v8.9.3
uses: sqren/backport-github-action@f54e19901f2a57f8b82360f2490d47ee82ec82c6 # pin@v9.2.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
auto_backport_label_prefix: backport-to-
+2 -2
View File
@@ -25,9 +25,9 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
cache: 'pip'
+10 -12
View File
@@ -20,7 +20,6 @@ on:
push:
branches:
- 'master'
# pull_request:
# branches:
# - 'master'
@@ -39,9 +38,9 @@ jobs:
python_version: 3.9
steps:
- name: Check out repo
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
- name: Version Check
@@ -82,23 +81,23 @@ jobs:
docker-compose down
- name: Set up QEMU
if: github.event_name != 'pull_request'
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2.1.0
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # pin@v3.0.0
- name: Set up Docker Buildx
if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@95cb08cb2672c73d4ffd2f422e6d11953d2a9c70 # pin@v2.1.0
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # pin@v3.0.0
- name: Set up cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@7cc35d7fdbe70d4278a0c96779081e6fac665f88 # pin@v2.8.0
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # pin@v3.1.2
- name: Login to Dockerhub
if: github.event_name != 'pull_request'
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # pin@v3.0.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log into registry ghcr.io
if: github.event_name != 'pull_request'
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # pin@v3.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -107,7 +106,7 @@ jobs:
- name: Extract Docker metadata
if: github.event_name != 'pull_request'
id: meta
uses: docker/metadata-action@12cce9efe0d49980455aaaca9b071c0befcdd702 # pin@v4.1.0
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # pin@v5.0.0
with:
images: |
inventree/inventree
@@ -116,7 +115,7 @@ jobs:
- name: Build and Push
id: build-and-push
if: github.event_name != 'pull_request'
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # pin@v3.2.0
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # pin@v5.0.0
with:
context: .
platforms: linux/amd64,linux/arm64
@@ -133,5 +132,4 @@ jobs:
if: ${{ false }} # github.event_name != 'pull_request'
env:
COSIGN_EXPERIMENTAL: "true"
run: cosign sign ${{ steps.meta.outputs.tags }}@${{
steps.build-and-push.outputs.digest }}
run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }}
+68 -70
View File
@@ -4,9 +4,9 @@ name: QC
on:
push:
branches-ignore: ['l10*']
branches-ignore: [ 'l10*' ]
pull_request:
branches-ignore: ['l10*']
branches-ignore: [ 'l10*' ]
env:
python_version: 3.9
@@ -32,20 +32,20 @@ jobs:
frontend: ${{ steps.filter.outputs.frontend }}
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
server:
- 'InvenTree/**'
- 'requirements.txt'
- 'requirements-dev.txt'
migrations:
- '**/migrations/**'
- '.github/workflows**'
frontend:
- 'src/frontend/**'
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # pin@v2.11.1
id: filter
with:
filters: |
server:
- 'InvenTree/**'
- 'requirements.txt'
- 'requirements-dev.txt'
migrations:
- '**/migrations/**'
- '.github/workflows**'
frontend:
- 'src/frontend/**'
pep_style:
name: Style [Python]
@@ -55,7 +55,7 @@ jobs:
if: needs.paths-filter.outputs.server == 'true'
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -67,10 +67,10 @@ jobs:
name: Style - Classic UI [JS]
runs-on: ubuntu-20.04
needs: ['pep_style', 'pre-commit']
needs: [ 'pep_style', 'pre-commit' ]
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -92,9 +92,9 @@ jobs:
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true'
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
cache: 'pip'
@@ -113,9 +113,9 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
- name: Check Config
@@ -145,7 +145,7 @@ jobs:
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -154,8 +154,7 @@ jobs:
update: true
npm: true
- name: Download Python Code For `${{ env.wrapper_name }}`
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }}
./${{ env.wrapper_name }}
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }}
- name: Start InvenTree Server
run: |
invoke delete-data -f
@@ -176,7 +175,7 @@ jobs:
continue-on-error: true
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -198,7 +197,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -214,7 +213,7 @@ jobs:
- name: Coverage Tests
run: invoke test --coverage
- name: Upload Coverage Report
uses: coverallsapp/github-action@v2
uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949 # pin@v2.2.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -248,7 +247,7 @@ jobs:
- 6379:6379
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -287,13 +286,12 @@ jobs:
MYSQL_USER: inventree
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s
--health-retries=3
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
ports:
- 3306:3306
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -332,7 +330,7 @@ jobs:
- 5432:5432
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -356,47 +354,47 @@ jobs:
INVENTREE_PLUGINS_ENABLED: false
steps:
- uses: actions/checkout@v3
name: Checkout Code
- name: Environment Setup
uses: ./.github/actions/setup
with:
install: true
- name: Fetch Database
run: git clone --depth 1 https://github.com/inventree/test-db ./test-db
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
name: Checkout Code
- name: Environment Setup
uses: ./.github/actions/setup
with:
install: true
- name: Fetch Database
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: 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
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.10.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.10.0 Database
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.10.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.11.0 Database
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.11.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.11.0 Database
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.11.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
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.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
platform_ui:
name: Tests - Platform UI
runs-on: ubuntu-20.04
timeout-minutes: 60
needs: ['pre-commit', 'paths-filter']
needs: [ 'pre-commit', 'paths-filter' ]
if: needs.paths-filter.outputs.frontend == 'true'
env:
INVENTREE_DB_ENGINE: sqlite3
@@ -405,7 +403,7 @@ jobs:
INVENTREE_PLUGINS_ENABLED: false
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -420,7 +418,7 @@ jobs:
run: cd src/frontend && npx playwright install --with-deps
- name: Run Playwright tests
run: cd src/frontend && npx playwright test
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
if: always()
with:
name: playwright-report
@@ -433,7 +431,7 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -446,7 +444,7 @@ jobs:
run: |
cd InvenTree/web/static
zip -r frontend-build.zip web/
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
with:
name: frontend-build
path: InvenTree/web/static/web
+4 -4
View File
@@ -13,13 +13,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Version Check
run: |
pip install requests
python3 ci/version_check.py
- name: Push to Stable Branch
uses: ad-m/github-push-action@4dcce6dea3e3c8187237fc86b7dfdc93e5aaae58 # pin@master
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
if: env.stable_release == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -29,7 +29,7 @@ jobs:
publish-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -42,7 +42,7 @@ jobs:
run: |
cd InvenTree/web/static/web
zip -r ../frontend-build.zip *
- uses: svenstaro/upload-release-action@v2
- uses: svenstaro/upload-release-action@1beeb572c19a9242f4361f4cee78f8e0d9aec5df # pin@2.7.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: InvenTree/web/static/frontend-build.zip
+2 -3
View File
@@ -14,11 +14,10 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # pin@v6.0.1
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # pin@v8.0.0
with:
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.'
stale-pr-message: 'This PR seems stale. Please react to show this is still important.'
stale-issue-label: 'inactive'
stale-pr-label: 'inactive'
+4 -4
View File
@@ -21,13 +21,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set up Python 3.9
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: 3.9
- name: Set up Node 16
uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b # pin to v3.5.0
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # pin to v3.8.2
with:
node-version: 16
- name: Install Dependencies
@@ -46,7 +46,7 @@ jobs:
git add "*.po"
git commit -m "updated translation base"
- name: Push changes
uses: ad-m/github-push-action@4dcce6dea3e3c8187237fc86b7dfdc93e5aaae58 # pin@master
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: l10
+2 -3
View File
@@ -9,14 +9,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Setup
run: pip install -r requirements-dev.txt
- name: Update requirements.txt
run: pip-compile --output-file=requirements.txt requirements.in -U
- name: Update requirements-dev.txt
run: pip-compile --generate-hashes --output-file=requirements-dev.txt
requirements-dev.in -U
run: pip-compile --generate-hashes --output-file=requirements-dev.txt requirements-dev.in -U
- uses: stefanzweifel/git-auto-commit-action@fd157da78fa13d9383e5580d1fd1184d89554b51 # pin@v4.15.1
with:
commit_message: "[Bot] Updated dependency"
+4 -1
View File
@@ -2,11 +2,14 @@
# InvenTree API version
INVENTREE_API_VERSION = 140
INVENTREE_API_VERSION = 141
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v141 -> 2023-10-23 : https://github.com/inventree/InvenTree/pull/5774
- Changed 'part.responsible' from User to Owner
v140 -> 2023-10-20 : https://github.com/inventree/InvenTree/pull/5664
- Expand API token functionality
- Multiple API tokens can be generated per user
+56
View File
@@ -7,6 +7,7 @@ import os
import random
import shutil
import string
import warnings
from pathlib import Path
logger = logging.getLogger('inventree')
@@ -341,3 +342,58 @@ def get_custom_file(env_ref: str, conf_ref: str, log_ref: str, lookup_media: boo
value = False
return value
def get_frontend_settings(debug=True):
"""Return a dictionary of settings for the frontend interface.
Note that the new config settings use the 'FRONTEND' key,
whereas the legacy key was 'PUI' (platform UI) which is now deprecated
"""
# Legacy settings
pui_settings = get_setting('INVENTREE_PUI_SETTINGS', 'pui_settings', {}, typecast=dict)
if len(pui_settings) > 0:
warnings.warn(
"The 'INVENTREE_PUI_SETTINGS' key is deprecated. Please use 'INVENTREE_FRONTEND_SETTINGS' instead",
DeprecationWarning, stacklevel=2
)
# New settings
frontend_settings = get_setting('INVENTREE_FRONTEND_SETTINGS', 'frontend_settings', {}, typecast=dict)
# Merge settings
settings = {**pui_settings, **frontend_settings}
# Set the base URL
if 'base_url' not in settings:
base_url = get_setting('INVENTREE_PUI_URL_BASE', 'pui_url_base', '')
if base_url:
warnings.warn(
"The 'INVENTREE_PUI_URL_BASE' key is deprecated. Please use 'INVENTREE_FRONTEND_URL_BASE' instead",
DeprecationWarning, stacklevel=2
)
else:
base_url = get_setting('INVENTREE_FRONTEND_URL_BASE', 'frontend_url_base', 'platform')
settings['base_url'] = base_url
# Set the server list
settings['server_list'] = settings.get('server_list', [])
# Set the debug flag
settings['debug'] = debug
if 'environment' not in settings:
settings['environment'] = 'development' if debug else 'production'
if debug and 'show_server_selector' not in settings:
# In debug mode, show server selector by default
settings['show_server_selector'] = True
elif len(settings['server_list']) == 0:
# If no servers are specified, show server selector
settings['show_server_selector'] = True
return settings
+1 -1
View File
@@ -64,7 +64,7 @@ class AuthRequiredMiddleware(object):
elif request.path_info.startswith('/accounts/'):
authorized = True
elif request.path_info.startswith(f'/{settings.PUI_URL_BASE}/') or request.path_info.startswith('/assets/') or request.path_info == f'/{settings.PUI_URL_BASE}':
elif request.path_info.startswith(f'/{settings.FRONTEND_URL_BASE}/') or request.path_info.startswith('/assets/') or request.path_info == f'/{settings.FRONTEND_URL_BASE}':
authorized = True
elif 'Authorization' in request.headers.keys() or 'authorization' in request.headers.keys():
+4 -4
View File
@@ -1044,9 +1044,9 @@ CUSTOM_SPLASH = get_custom_file('INVENTREE_CUSTOM_SPLASH', 'customize.splash', '
CUSTOMIZE = get_setting('INVENTREE_CUSTOMIZE', 'customize', {})
# Frontend settings
PUI_URL_BASE = get_setting('INVENTREE_PUI_URL_BASE', 'pui_url_base', 'platform')
PUI_SETTINGS = get_setting("INVENTREE_PUI_SETTINGS", "pui_settings", {})
# Load settings for the frontend interface
FRONTEND_SETTINGS = config.get_frontend_settings(debug=DEBUG)
FRONTEND_URL_BASE = FRONTEND_SETTINGS.get('base_url', 'platform')
if DEBUG:
logger.info("InvenTree running with DEBUG enabled")
@@ -1076,5 +1076,5 @@ if CUSTOM_FLAGS:
# Magic login django-sesame
SESAME_MAX_AGE = 300
# LOGIN_REDIRECT_URL = f"/{PUI_URL_BASE}/logged-in/"
# LOGIN_REDIRECT_URL = f"/{FRONTEND_URL_BASE}/logged-in/"
LOGIN_REDIRECT_URL = "/index/"
+1 -1
View File
@@ -96,7 +96,7 @@ for provider in providers.registry.get_list():
social_auth_urlpatterns += provider_urlpatterns
class SocialProvierListView(ListAPIView):
class SocialProviderListView(ListAPIView):
"""List of available social providers."""
permission_classes = (AllowAny,)
+1 -1
View File
@@ -1216,6 +1216,6 @@ class MagicLoginTest(InvenTreeTestCase):
self.assertEqual(resp.url, '/index/')
# Note: 2023-08-08 - This test has been changed because "platform UI" is not generally available yet
# TODO: In the future, the URL comparison will need to be reverted
# self.assertEqual(resp.url, f'/{settings.PUI_URL_BASE}/logged-in/')
# self.assertEqual(resp.url, f'/{settings.FRONTEND_URL_BASE}/logged-in/')
# And we should be logged in again
self.assertEqual(resp.wsgi_request.user, self.user)
+25 -25
View File
@@ -16,29 +16,29 @@ from dj_rest_auth.registration.views import (ConfirmEmailView,
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
from sesame.views import LoginView
from build.api import build_api_urls
import build.api
import common.api
import company.api
import label.api
import order.api
import part.api
import plugin.api
import report.api
import stock.api
import users.api
from build.urls import build_urls
from common.api import admin_api_urls, common_api_urls, settings_api_urls
from common.urls import common_urls
from company.api import company_api_urls
from company.urls import (company_urls, manufacturer_part_urls,
supplier_part_urls)
from label.api import label_api_urls
from order.api import order_api_urls
from order.urls import order_urls
from part.api import bom_api_urls, part_api_urls
from part.urls import part_urls
from plugin.api import plugin_api_urls
from plugin.urls import get_plugin_urls
from report.api import report_api_urls
from stock.api import stock_api_urls
from stock.urls import stock_urls
from users.api import user_urls
from web.urls import urlpatterns as platform_urls
from .api import APISearchView, InfoView, NotFoundView
from .magic_login import GetSimpleLoginView
from .social_auth_urls import SocialProvierListView, social_auth_urlpatterns
from .social_auth_urls import SocialProviderListView, social_auth_urlpatterns
from .views import (AboutView, AppearanceSelectView, CustomConnectionsView,
CustomEmailView, CustomLoginView,
CustomPasswordResetFromKeyView,
@@ -55,23 +55,23 @@ apipatterns = [
# Global search
path('search/', APISearchView.as_view(), name='api-search'),
re_path(r'^settings/', include(settings_api_urls)),
re_path(r'^part/', include(part_api_urls)),
re_path(r'^bom/', include(bom_api_urls)),
re_path(r'^company/', include(company_api_urls)),
re_path(r'^stock/', include(stock_api_urls)),
re_path(r'^build/', include(build_api_urls)),
re_path(r'^order/', include(order_api_urls)),
re_path(r'^label/', include(label_api_urls)),
re_path(r'^report/', include(report_api_urls)),
re_path(r'^user/', include(user_urls)),
re_path(r'^admin/', include(admin_api_urls)),
re_path(r'^settings/', include(common.api.settings_api_urls)),
re_path(r'^part/', include(part.api.part_api_urls)),
re_path(r'^bom/', include(part.api.bom_api_urls)),
re_path(r'^company/', include(company.api.company_api_urls)),
re_path(r'^stock/', include(stock.api.stock_api_urls)),
re_path(r'^build/', include(build.api.build_api_urls)),
re_path(r'^order/', include(order.api.order_api_urls)),
re_path(r'^label/', include(label.api.label_api_urls)),
re_path(r'^report/', include(report.api.report_api_urls)),
re_path(r'^user/', include(users.api.user_urls)),
re_path(r'^admin/', include(common.api.admin_api_urls)),
# Plugin endpoints
path('', include(plugin_api_urls)),
path('', include(plugin.api.plugin_api_urls)),
# Common endpoints endpoint
path('', include(common_api_urls)),
path('', include(common.api.common_api_urls)),
# OpenAPI Schema
re_path('schema/', SpectacularAPIView.as_view(custom_settings={'SCHEMA_PATH_PREFIX': '/api/'}), name='schema'),
@@ -83,7 +83,7 @@ apipatterns = [
path('auth/', include([
re_path(r'^registration/account-confirm-email/(?P<key>[-:\w]+)/$', ConfirmEmailView.as_view(), name='account_confirm_email'),
path('registration/', include('dj_rest_auth.registration.urls')),
path('providers/', SocialProvierListView.as_view(), name='social_providers'),
path('providers/', SocialProviderListView.as_view(), name='social_providers'),
path('social/', include(social_auth_urlpatterns)),
path('social/', SocialAccountListView.as_view(), name='social_account_list'),
path('social/<int:pk>/disconnect/', SocialAccountDisconnectView.as_view(), name='social_account_disconnect'),
+4 -5
View File
@@ -290,18 +290,17 @@ remote_login_header: HTTP_REMOTE_USER
# logo: img/custom_logo.png
# splash: img/custom_splash.jpg
# Platform UI options
# pui_settings:
# Frontend UI settings
# frontend_settings:
# base_url: 'frontend'
# server_list:
# my_server1:
# host: https://demo.inventree.org/api/
# host: https://demo.inventree.org/
# name: InvenTree Demo
# default_server: my_server1
# show_server_selector: false
# sentry_dsn: https://84f0c3ea90c64e5092e2bf5dfe325725@o1047628.ingest.sentry.io/4504160008273920
# environment: development
# Base URL for serving Platform UI
# pui_url_base: 'platform'
# Custom flags
# InvenTree uses django-flags; read more in their docs at https://cfpb.github.io/django-flags/conditions/
@@ -0,0 +1,20 @@
# Generated by Django 3.2.22 on 2023-10-23 01:59
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0009_auto_20231020_2356'),
('part', '0114_alter_part_minimum_stock'),
]
operations = [
migrations.AddField(
model_name='part',
name='responsible_owner',
field=models.ForeignKey(blank=True, help_text='Owner responsible for this part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parts_responsible', to='users.owner', verbose_name='Responsible'),
),
]
@@ -0,0 +1,76 @@
# Generated by Django 3.2.22 on 2023-10-23 03:32
from django.db import migrations
def migrate_part_responsible_owner(apps, schema_editor):
"""Copy existing part.responsible field to part.responsible_owner"""
Owner = apps.get_model('users', 'Owner')
Part = apps.get_model('part', 'Part')
User = apps.get_model('auth', 'user')
ContentType = apps.get_model('contenttypes', 'contenttype')
user_type = ContentType.objects.get_for_model(User)
parts = Part.objects.exclude(responsible=None)
for part in parts:
# Find a corresponding Owner object, or create one if it does not exist
owner, _created = Owner.objects.get_or_create(
owner_type=user_type,
owner_id=part.responsible.id,
)
part.responsible_owner = owner
part.save()
if parts.count() > 0:
print(f"Added 'responsible_owner' for {parts.count()} parts")
def reverse_owner_migration(apps, schema_editor):
"""Reverse the owner migration:
- Set the 'responsible' field to a selected user
- Only where 'responsible_owner' is set
- Only where 'responsible_owner' is a User object
"""
Part = apps.get_model('part', 'Part')
User = apps.get_model('auth', 'user')
ContentType = apps.get_model('contenttypes', 'contenttype')
user_type = ContentType.objects.get_for_model(User)
parts = Part.objects.exclude(responsible_owner=None)
for part in parts:
if part.responsible_owner.owner_type == user_type:
# Attempt to find matching user
try:
user = User.objects.get(pk=part.responsible_owner.owner_id)
part.responsible = user
part.save()
except User.DoesNotExist:
print("User does not exist:", part.responsible_owner.owner_id)
if parts.count() > 0:
print(f"Added 'responsible' for {parts.count()} parts")
class Migration(migrations.Migration):
dependencies = [
('part', '0115_part_responsible_owner'),
('users', '0005_owner_model'),
]
operations = [
migrations.RunPython(
migrate_part_responsible_owner,
reverse_code=reverse_owner_migration,
)
]
@@ -0,0 +1,17 @@
# Generated by Django 3.2.22 on 2023-10-23 05:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('part', '0116_auto_20231023_0332'),
]
operations = [
migrations.RemoveField(
model_name='part',
name='responsible',
),
]
+9 -2
View File
@@ -41,6 +41,7 @@ import InvenTree.fields
import InvenTree.ready
import InvenTree.tasks
import part.settings as part_settings
import users.models
from build import models as BuildModels
from common.models import InvenTreeSetting
from common.settings import currency_code_default
@@ -379,7 +380,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
notes: Additional notes field for this part
creation_date: Date that this part was added to the database
creation_user: User who added this part to the database
responsible: User who is responsible for this part (optional)
responsible_owner: Owner (either user or group) which is responsible for this part (optional)
last_stocktake: Date at which last stocktake was performed for this Part
"""
@@ -1036,7 +1037,13 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
creation_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Creation User'), related_name='parts_created')
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Responsible'), help_text=_('User responsible for this part'), related_name='parts_responible')
responsible_owner = models.ForeignKey(
users.models.Owner, on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('Responsible'),
help_text=_('Owner responsible for this part'),
related_name='parts_responsible'
)
last_stocktake = models.DateField(
blank=True, null=True,
+7
View File
@@ -26,6 +26,7 @@ import part.filters
import part.stocktake
import part.tasks
import stock.models
import users.models
from InvenTree.status_codes import BuildStatusGroups
from InvenTree.tasks import offload_task
@@ -695,6 +696,12 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
read_only=True,
)
responsible = serializers.PrimaryKeyRelatedField(
queryset=users.models.Owner.objects.all(),
required=False, allow_null=True,
source='responsible_owner',
)
# Annotated fields
allocated_to_build_orders = serializers.FloatField(read_only=True)
allocated_to_sales_orders = serializers.FloatField(read_only=True)
+2 -2
View File
@@ -384,11 +384,11 @@
<td>{% include 'clip_link.html' with link=part.link new_window=True %}</td>
</tr>
{% endif %}
{% if part.responsible %}
{% if part.responsible_owner %}
<tr>
<td><span class='fas fa-user'></span></td>
<td>{% trans "Responsible" %}</td>
<td> <span class='badge badge-right rounded-pill bg-dark'>{{ part.responsible }}</span></td>
<td> <span class='badge badge-right rounded-pill bg-dark'>{{ part.responsible_owner }}</span></td>
</tr>
{% endif %}
</table>
@@ -16,10 +16,10 @@ from django.utils.translation import gettext_lazy as _
import common.models
import InvenTree.helpers
import InvenTree.helpers_model
import plugin.models
from common.settings import currency_code_default
from InvenTree import settings, version
from plugin import registry
from plugin.models import NotificationUserSetting, PluginSetting
from plugin.plugin import InvenTreePlugin
register = template.Library()
@@ -346,14 +346,14 @@ def setting_object(key, *args, **kwargs):
if 'plugin' in kwargs:
# Note, 'plugin' is an instance of an InvenTreePlugin class
plugin = kwargs['plugin']
if issubclass(plugin.__class__, InvenTreePlugin):
plugin = plugin.plugin_config()
plg = kwargs['plugin']
if issubclass(plg.__class__, InvenTreePlugin):
plg = plg.plugin_config()
return PluginSetting.get_setting_object(key, plugin=plugin, cache=cache)
return plugin.models.PluginSetting.get_setting_object(key, plugin=plg, cache=cache)
elif 'method' in kwargs:
return NotificationUserSetting.get_setting_object(key, user=kwargs['user'], method=kwargs['method'], cache=cache)
return plugin.models.NotificationUserSetting.get_setting_object(key, user=kwargs['user'], method=kwargs['method'], cache=cache)
elif 'user' in kwargs:
return common.models.InvenTreeUserSetting.get_setting_object(key, user=kwargs['user'], cache=cache)
+4
View File
@@ -649,7 +649,11 @@ class PluginsRegistry:
try:
logger.debug("Updating plugin registry hash: %s", str(self.registry_hash))
InvenTreeSetting.set_setting("_PLUGIN_REGISTRY_HASH", self.registry_hash, change_user=None)
except (OperationalError, ProgrammingError):
# Exception if the database has not been migrated yet, or is not ready
pass
except Exception as exc:
# Some other exception, we want to know about it
logger.exception("Failed to update plugin registry hash: %s", str(exc))
def calculate_plugin_hash(self):
@@ -1673,7 +1673,11 @@ function loadPurchaseOrderTable(table, options) {
sortable: true,
sortName: 'supplier__name',
formatter: function(value, row) {
return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/?display=purchase-orders`);
if (row.supplier_detail) {
return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/?display=purchase-orders`);
} else {
return '-';
}
}
},
{
@@ -1986,7 +1990,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
title: '{% trans "Part" %}',
switchable: false,
formatter: function(value, row, index, field) {
if (row.part) {
if (row.part_detail) {
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${row.part_detail.pk}/`);
} else {
return '-';
@@ -1793,7 +1793,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
title: '{% trans "Part" %}',
switchable: false,
formatter: function(value, row, index, field) {
if (row.part) {
if (row.part_detail) {
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`);
} else {
return '-';
+4 -2
View File
@@ -3062,8 +3062,10 @@ function loadInstalledInTable(table, options) {
formatter: function(value, row) {
var html = '';
html += imageHoverIcon(row.part_detail.thumbnail);
html += renderLink(row.part_detail.full_name, `/stock/item/${row.pk}/`);
if (row.part_detail) {
html += imageHoverIcon(row.part_detail.thumbnail);
html += renderLink(row.part_detail.full_name, `/stock/item/${row.pk}/`);
}
return html;
}
+12 -11
View File
@@ -6,7 +6,6 @@ from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from users.models import ApiToken, Owner, RuleSet
@@ -203,20 +202,22 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover
users = form.cleaned_data['users']
# Check for users who are members of multiple groups
warning_message = ''
multiple_group_users = []
for user in users:
if user.groups.all().count() > 1:
warning_message += f'<br>- <b>{user.username}</b> is member of: '
for idx, group in enumerate(user.groups.all()):
warning_message += f'<b>{group.name}</b>'
if idx < len(user.groups.all()) - 1:
warning_message += ', '
multiple_group_users.append(user.username)
# If any, display warning message when group is saved
if warning_message:
warning_message = mark_safe(_(f'The following users are members of multiple groups:'
f'{warning_message}'))
messages.add_message(request, messages.WARNING, warning_message)
if len(multiple_group_users) > 0:
msg = _("The following users are members of multiple groups") + ": " + ", ".join(multiple_group_users)
messages.add_message(
request,
messages.WARNING,
msg
)
def save_formset(self, request, form, formset, change):
"""Save the inline formset"""
+3 -12
View File
@@ -422,11 +422,7 @@ class RuleSet(models.Model):
"""Construct the correctly formatted permission string, given the app_model name, and the permission type."""
model, app = split_model(model)
return "{app}.{perm}_{model}".format(
app=app,
perm=permission,
model=model
)
return f"{app}.{permission}_{model}"
def __str__(self, debug=False): # pragma: no cover
"""Ruleset string representation."""
@@ -504,12 +500,7 @@ def update_group_roles(group, debug=False):
# and create a simplified permission key string
for p in group.permissions.all().prefetch_related('content_type'):
(permission, app, model) = p.natural_key()
permission_string = '{app}.{perm}'.format(
app=app,
perm=permission
)
permission_string = f"{app}.{permission}"
group_permissions.add(permission_string)
# List of permissions which must be added to the group
@@ -527,7 +518,7 @@ def update_group_roles(group, debug=False):
allowed: Whether or not the action is allowed
"""
if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover
raise ValueError("Action {a} is invalid".format(a=action))
raise ValueError(f"Action {action} is invalid")
permission_string = RuleSet.get_model_permission_string(model, action)
+5 -8
View File
@@ -1,4 +1,5 @@
"""Template tag to render SPA imports."""
import json
from logging import getLogger
from pathlib import Path
@@ -10,11 +11,7 @@ from django.utils.safestring import mark_safe
logger = getLogger("InvenTree")
register = template.Library()
PUI_DEFAULTS = {
'url_base': settings.PUI_URL_BASE,
}
PUI_DEFAULTS.update(getattr(settings, 'PUI_SETTINGS', {}))
PUI_SETTINGS = json.dumps(PUI_DEFAULTS)
FRONTEND_SETTINGS = json.dumps(settings.FRONTEND_SETTINGS)
@register.simple_tag
@@ -30,11 +27,11 @@ def spa_bundle():
index = manifest_data.get("index.html")
css_index = manifest_data.get("index.css")
dynmanic_files = index.get("dynamicImports", [])
dynamic_files = index.get("dynamicImports", [])
imports_files = "".join(
[
f'<script type="module" src="{settings.STATIC_URL}web/{manifest_data[file]["file"]}"></script>'
for file in dynmanic_files
for file in dynamic_files
]
)
@@ -47,4 +44,4 @@ def spa_bundle():
@register.simple_tag
def spa_settings():
"""Render settings for spa."""
return mark_safe(f"""<script>window.INVENTREE_SETTINGS={PUI_SETTINGS}</script>""")
return mark_safe(f"""<script>window.INVENTREE_SETTINGS={FRONTEND_SETTINGS}</script>""")
+2 -2
View File
@@ -20,12 +20,12 @@ spa_view = ensure_csrf_cookie(TemplateView.as_view(template_name="web/index.html
urlpatterns = [
path(f'{settings.PUI_URL_BASE}/', include([
path(f'{settings.FRONTEND_URL_BASE}/', include([
path("assets/<path:path>", RedirectAssetView.as_view()),
re_path(r"^(?P<path>.*)/$", spa_view),
path("set-password?uid=<uid>&token=<token>", spa_view, name="password_reset_confirm"),
path("", spa_view),]
)),
path(settings.PUI_URL_BASE, spa_view, name='platform'),
path(settings.FRONTEND_URL_BASE, spa_view, name='platform'),
path("assets/<path:path>", RedirectAssetView.as_view()),
]
+85 -3
View File
@@ -25,8 +25,90 @@ The following code types are known to be supported
- Data Matrix
- Aztec
## Actions
## Barcode Input Methods
The InvenTree app uses barcodes where possible to provide efficient stock control operations.
Barcodes can be scanned using the following methods:
If there is a new barcode feature you would like to see, [let us know on GitHub](https://github.com/inventree/InvenTree/issues?q=is%3Aopen+is%3Aissue+label%3Aapp)!
### Camera Input
The camera input method allows you to scan barcodes using the device's internal camera. Both the forward and rear-facing cameras are supported.
### Keyboard Input
The keyboard wedge input method allows you to scan barcodes using any scanner which presents barcode data as keyboard input. This works with external bluetooth scanners, and also provides support for integrated barcode scanner devices which run Android natively.
Note that if using keyboard wedge input mode, the scanner must be configured to append an enter (`\n`) character to the end of the barcode data.
## Barcode Actions
The InvenTree app uses barcodes where possible to provide efficient stock control operations. Some pages in the app will provide context-sensitive barcode actions. These actions are available from the *Barcode Actions* menu, which is displayed in the bottom right corner of the screen.
### Global Scan
Available from the global bottom menu, the *Scan Barcode* provides quick access for scanning a barcode already associated with an InvenTree database item (such as a stock item or location).
If a match is found, the app will navigate to the relevant page.
### Stock Location Actions
From the [Stock Location detail page](./stock.md#stock-location-view), multiple barcode actions may be available:
{% 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 a custom barcode to the selected location. Scanning a barcode (which is not already associated with an item in the database) will result in that barcode being assigned to the selected location.
#### Transfer Stock Location
Transfer the currently selected stock location into another location. Scanning a valid barcode associated with a stock location will result in the current location being *moved* to the scanned location.
#### Scan Received Parts
Receive incoming purchase order items into the selected location. Scanning a *new* barcode which is associated with an item in an incoming purchase order will receive the item into the selected location.
#### Scan Items Into Location
the *Scan Items Into Location* action allows you to scan items into the selected location. Scanning a valid barcode associated with a stock item (already in the database) will result in that item being transferred to the selected location.
### Stock Item Actions
From the [Stock Item detail page](./stock.md#stock-item-detail-view), the following barcode actions may be available:
{% 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 a custom barcode to the selected stock item. Scanning a barcode (which is not already associated with an item in the database) will result in that barcode being assigned to the selected stock item.
#### Scan Into Location
Scan the selected stock item into a stock location. Scanning a valid barcode associated with a stock location will result in the selected stock item being transferred to the scanned location.
### Part Actions
From the [Part detail page](./part.md#part-detail-view), the following barcode actions are available:
{% with id="part-actions", url="app/barcode_part_actions.png", maxheight="240px", description="Part barcode actions" %}
{% include 'img.html' %}
{% endwith %}
#### Assign Barcode
Assign a custom barcode to the selected part. Scanning a barcode (which is not already associated with an item in the database) will result in that barcode being assigned to the selected part.
### Purchase Order Actions
From the [Purchase Order detail page](./po.md#purchase-order-detail) page, the following barcode actions are available:
{% with id="po-actions", url="app/barcode_po_actions.png", maxheight="240px", description="Purchase order barcode actions" %}
{% include 'img.html' %}
{% endwith %}
#### Scan Received Parts
Receive incoming purchase order items against the selected purchase order. Scanning a *new* barcode which is associated with an item in an incoming purchase order will receive the item into stock.
+1 -1
View File
@@ -48,7 +48,7 @@ The <span class='fas fa-search'></span> action opens the [Search](./search.md) s
### Scan Barcode
The <span class='fas fa-qrcode'></span> action opens the [barcode scan](./barcode.md) 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
+18 -5
View File
@@ -10,13 +10,26 @@ The InvenTree mobile app requires some extra permissions for complete functional
### User Profiles
The InvenTree app requires the user to enter profile data to connect with an InvenTree server:
For each *profile* configured in the app, the following information is stored locally on the device:
- Server address
- Account username
- Account password
- Server name (e.g. "InvenTree Demo")
- Server address (e.g. "https://demo.inventree.org)
- *API token*
Profile data is stored locally on the device, and can be deleted by the user if they wish. Profile data is only used for connection with an InvenTree server.
#### User Authentication
The InvenTree app uses an API token for user authentication. This token is requested once from the server, and then stored locally on the device.
To initially request the token, the user will be required to enter their username and password.
!!! info "Password Storage"
The user's username and password are not stored locally, or used for any purpose other than requesting an API token
#### Token Handling
A separate API token is stored locally for each profile. This token can be deleted at any time from within the app settings - this will force the user to enter their login credentials again to request a new token.
Additionally, the stored token may be revoked by the server, or expire. Either situation will again require the user to re-enter their username and password.
### Camera Permissions
+2 -1
View File
@@ -55,7 +55,7 @@ Configure audible app notifications:
## Barcode Settings
The *Barcode Settings* view allows you to configure options relating to barcode scanning:
The *Barcode Settings* view allows you to configure options relating to [barcode scanning](./barcode.md):
{% with id="barcode_settings", url="app/barcode_settings.png", maxheight="240px", description="Barcode Settings" %}
{% include 'img.html' %}
@@ -63,6 +63,7 @@ The *Barcode Settings* view allows you to configure options relating to barcode
| Option | Description |
| --- | --- |
| Scanner Input | Select barcode capture mode |
| Barcode Scan Delay | Delay between successive scans |
## Home Screen
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

+7 -5
View File
@@ -20,8 +20,8 @@ export const doClassicLogin = async (username: string, password: string) => {
const token = await axios
.get(apiUrl(ApiPaths.user_token), {
auth: { username, password },
baseURL: host.toString(),
timeout: 5000,
baseURL: host,
timeout: 2000,
params: {
name: 'inventree-web-app'
}
@@ -43,6 +43,9 @@ export const doClassicLogin = async (username: string, password: string) => {
return true;
};
/**
* Logout the user (invalidate auth token)
*/
export const doClassicLogout = async () => {
// TODO @matmair - logout from the server session
// Set token in context
@@ -117,7 +120,7 @@ export function handleReset(navigate: any, values: { email: string }) {
export function checkLoginState(navigate: any, redirect?: string) {
api
.get(apiUrl(ApiPaths.user_token), {
timeout: 5000,
timeout: 2000,
params: {
name: 'inventree-web-app'
}
@@ -137,8 +140,7 @@ export function checkLoginState(navigate: any, redirect?: string) {
navigate('/login');
}
})
.catch((error) => {
console.error('Error fetching login information:', error);
.catch(() => {
navigate('/login');
});
}
+16 -5
View File
@@ -14,7 +14,7 @@ declare global {
server_list: HostList;
default_server: string;
show_server_selector: boolean;
url_base: string;
base_url: string;
sentry_dsn?: string;
environment?: string;
};
@@ -25,6 +25,17 @@ export const IS_DEV = import.meta.env.DEV;
export const IS_DEMO = import.meta.env.VITE_DEMO === 'true';
export const IS_DEV_OR_DEMO = IS_DEV || IS_DEMO;
// Filter out any settings that are not defined
let loaded_vals = (window.INVENTREE_SETTINGS || {}) as any;
Object.keys(loaded_vals).forEach((key) => {
if (loaded_vals[key] === undefined) {
delete loaded_vals[key];
// check for empty server list
} else if (key === 'server_list' && loaded_vals[key].length === 0) {
delete loaded_vals[key];
}
});
window.INVENTREE_SETTINGS = {
server_list: {
'mantine-cqj63coxn': {
@@ -40,11 +51,11 @@ window.INVENTREE_SETTINGS = {
}
: {})
},
default_server: IS_DEMO ? 'mantine-u56l5jt85' : 'mantine-cqj63coxn', // use demo server for demo mode
default_server: IS_DEMO ? 'mantine-u56l5jt85' : 'mantine-cqj63coxn',
show_server_selector: IS_DEV_OR_DEMO,
// merge in settings that are already set via django's spa_view or for development
...((window.INVENTREE_SETTINGS || {}) as any)
...loaded_vals
};
if (window.INVENTREE_SETTINGS.sentry_dsn) {
@@ -56,7 +67,7 @@ if (window.INVENTREE_SETTINGS.sentry_dsn) {
});
}
export const url_base = window.INVENTREE_SETTINGS.url_base || 'platform';
export const base_url = window.INVENTREE_SETTINGS.base_url || 'platform';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
@@ -66,5 +77,5 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
// Redirect to base url if on /
if (window.location.pathname === '/') {
window.location.replace(`/${url_base}`);
window.location.replace(`/${base_url}`);
}
+6 -3
View File
@@ -15,9 +15,12 @@ export const useServerApiState = create<ServerApiStateProps>((set, get) => ({
setServer: (newServer: ServerAPIProps) => set({ server: newServer }),
fetchServerApiState: async () => {
// Fetch server data
await api.get(apiUrl(ApiPaths.api_server_info)).then((response) => {
set({ server: response.data });
});
await api
.get(apiUrl(ApiPaths.api_server_info))
.then((response) => {
set({ server: response.data });
})
.catch(() => {});
}
}));
+1 -1
View File
@@ -32,7 +32,7 @@ export const useUserState = create<UserStateProps>((set, get) => ({
// Fetch user data
await api
.get(apiUrl(ApiPaths.user_me), {
timeout: 5000
timeout: 2000
})
.then((response) => {
const user: UserProps = {
+7 -6
View File
@@ -5,7 +5,7 @@ import { BrowserRouter } from 'react-router-dom';
import { queryClient, setApiDefaults } from '../App';
import { BaseContext } from '../contexts/BaseContext';
import { defaultHostList } from '../defaults/defaultHostList';
import { url_base } from '../main';
import { base_url } from '../main';
import { routes } from '../router';
import { useLocalState } from '../states/LocalState';
import { useSessionState } from '../states/SessionState';
@@ -27,10 +27,6 @@ export default function DesktopAppView() {
]);
// Local state initialization
if (Object.keys(hostList).length === 0) {
console.log('Loading default host list');
useLocalState.setState({ hostList: defaultHostList });
}
setApiDefaults();
// Server Session
@@ -38,6 +34,11 @@ export default function DesktopAppView() {
const sessionState = useSessionState.getState();
const [token] = sessionState.token ? [sessionState.token] : [null];
useEffect(() => {
if (Object.keys(hostList).length === 0) {
console.log('Loading default host list', defaultHostList);
useLocalState.setState({ hostList: defaultHostList });
}
if (token && !fetchedServerSession) {
setFetchedServerSession(true);
fetchUserState();
@@ -49,7 +50,7 @@ export default function DesktopAppView() {
return (
<BaseContext>
<QueryClientProvider client={queryClient}>
<BrowserRouter basename={url_base}>{routes}</BrowserRouter>
<BrowserRouter basename={base_url}>{routes}</BrowserRouter>
</QueryClientProvider>
</BaseContext>
);
-5
View File
@@ -11,9 +11,4 @@ test('Basic Platform UI test', async ({ page }) => {
await page.goto('./platform/');
await expect(page).toHaveTitle('InvenTree Demo Server');
await expect(
page.getByRole('heading', {
name: 'Welcome to your Dashboard, Ally Access'
})
).toBeVisible();
});