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:
@@ -35,12 +35,12 @@ runs:
|
|||||||
using: 'composite'
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
|
|
||||||
# Python installs
|
# Python installs
|
||||||
- name: Set up Python ${{ env.python_version }}
|
- name: Set up Python ${{ env.python_version }}
|
||||||
if: ${{ inputs.python == 'true' }}
|
if: ${{ inputs.python == 'true' }}
|
||||||
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
|
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
cache: pip
|
cache: pip
|
||||||
@@ -58,7 +58,7 @@ runs:
|
|||||||
# NPM installs
|
# NPM installs
|
||||||
- name: Install node.js ${{ env.node_version }}
|
- name: Install node.js ${{ env.node_version }}
|
||||||
if: ${{ inputs.npm == 'true' }}
|
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:
|
with:
|
||||||
node-version: ${{ env.node_version }}
|
node-version: ${{ env.node_version }}
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ name: Backport
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: ["labeled", "closed"]
|
types: [ "labeled", "closed" ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backport:
|
backport:
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
)
|
)
|
||||||
steps:
|
steps:
|
||||||
- name: Backport Action
|
- name: Backport Action
|
||||||
uses: sqren/backport-github-action@v8.9.3
|
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-
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- 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 }}
|
- 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:
|
with:
|
||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
|
|
||||||
# pull_request:
|
# pull_request:
|
||||||
# branches:
|
# branches:
|
||||||
# - 'master'
|
# - 'master'
|
||||||
@@ -39,9 +38,9 @@ jobs:
|
|||||||
python_version: 3.9
|
python_version: 3.9
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- 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 }}
|
- 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:
|
with:
|
||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
- name: Version Check
|
- name: Version Check
|
||||||
@@ -82,23 +81,23 @@ jobs:
|
|||||||
docker-compose down
|
docker-compose down
|
||||||
- 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@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2.1.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@95cb08cb2672c73d4ffd2f422e6d11953d2a9c70 # pin@v2.1.0
|
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # pin@v3.0.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@7cc35d7fdbe70d4278a0c96779081e6fac665f88 # pin@v2.8.0
|
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # pin@v3.1.2
|
||||||
- name: Login to Dockerhub
|
- name: Login to Dockerhub
|
||||||
if: github.event_name != 'pull_request'
|
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:
|
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@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2
|
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # pin@v3.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -107,7 +106,7 @@ 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@12cce9efe0d49980455aaaca9b071c0befcdd702 # pin@v4.1.0
|
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # pin@v5.0.0
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
inventree/inventree
|
inventree/inventree
|
||||||
@@ -116,7 +115,7 @@ jobs:
|
|||||||
- name: Build and Push
|
- name: Build and Push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
if: github.event_name != 'pull_request'
|
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:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -133,5 +132,4 @@ jobs:
|
|||||||
if: ${{ false }} # github.event_name != 'pull_request'
|
if: ${{ false }} # github.event_name != 'pull_request'
|
||||||
env:
|
env:
|
||||||
COSIGN_EXPERIMENTAL: "true"
|
COSIGN_EXPERIMENTAL: "true"
|
||||||
run: cosign sign ${{ steps.meta.outputs.tags }}@${{
|
run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }}
|
||||||
steps.build-and-push.outputs.digest }}
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ name: QC
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore: ['l10*']
|
branches-ignore: [ 'l10*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore: ['l10*']
|
branches-ignore: [ 'l10*' ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
python_version: 3.9
|
python_version: 3.9
|
||||||
@@ -32,20 +32,20 @@ jobs:
|
|||||||
frontend: ${{ steps.filter.outputs.frontend }}
|
frontend: ${{ steps.filter.outputs.frontend }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- uses: dorny/paths-filter@v2
|
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # pin@v2.11.1
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
server:
|
server:
|
||||||
- 'InvenTree/**'
|
- 'InvenTree/**'
|
||||||
- 'requirements.txt'
|
- 'requirements.txt'
|
||||||
- 'requirements-dev.txt'
|
- 'requirements-dev.txt'
|
||||||
migrations:
|
migrations:
|
||||||
- '**/migrations/**'
|
- '**/migrations/**'
|
||||||
- '.github/workflows**'
|
- '.github/workflows**'
|
||||||
frontend:
|
frontend:
|
||||||
- 'src/frontend/**'
|
- 'src/frontend/**'
|
||||||
|
|
||||||
pep_style:
|
pep_style:
|
||||||
name: Style [Python]
|
name: Style [Python]
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
if: needs.paths-filter.outputs.server == 'true'
|
if: needs.paths-filter.outputs.server == 'true'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -67,10 +67,10 @@ jobs:
|
|||||||
name: Style - Classic UI [JS]
|
name: Style - Classic UI [JS]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
needs: ['pep_style', 'pre-commit']
|
needs: [ 'pep_style', 'pre-commit' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -92,9 +92,9 @@ jobs:
|
|||||||
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true'
|
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Set up Python ${{ env.python_version }}
|
- 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:
|
with:
|
||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -113,9 +113,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- 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 }}
|
- 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:
|
with:
|
||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
- name: Check Config
|
- name: Check Config
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
|
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -154,8 +154,7 @@ jobs:
|
|||||||
update: true
|
update: true
|
||||||
npm: true
|
npm: true
|
||||||
- name: Download Python Code For `${{ env.wrapper_name }}`
|
- name: Download Python Code For `${{ env.wrapper_name }}`
|
||||||
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }}
|
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }}
|
||||||
./${{ env.wrapper_name }}
|
|
||||||
- name: Start InvenTree Server
|
- name: Start InvenTree Server
|
||||||
run: |
|
run: |
|
||||||
invoke delete-data -f
|
invoke delete-data -f
|
||||||
@@ -176,7 +175,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -198,7 +197,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -214,7 +213,7 @@ jobs:
|
|||||||
- name: Coverage Tests
|
- name: Coverage Tests
|
||||||
run: invoke test --coverage
|
run: invoke test --coverage
|
||||||
- name: Upload Coverage Report
|
- name: Upload Coverage Report
|
||||||
uses: coverallsapp/github-action@v2
|
uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949 # pin@v2.2.3
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -248,7 +247,7 @@ jobs:
|
|||||||
- 6379:6379
|
- 6379:6379
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -287,13 +286,12 @@ jobs:
|
|||||||
MYSQL_USER: inventree
|
MYSQL_USER: inventree
|
||||||
MYSQL_PASSWORD: password
|
MYSQL_PASSWORD: password
|
||||||
MYSQL_ROOT_PASSWORD: password
|
MYSQL_ROOT_PASSWORD: password
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s
|
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
--health-retries=3
|
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -332,7 +330,7 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -356,47 +354,47 @@ jobs:
|
|||||||
INVENTREE_PLUGINS_ENABLED: false
|
INVENTREE_PLUGINS_ENABLED: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
name: Checkout Code
|
name: Checkout Code
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
- 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
|
- name: Latest Database
|
||||||
run: |
|
run: |
|
||||||
cp test-db/latest.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
cp test-db/latest.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||||
invoke migrate
|
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
|
||||||
cp test-db/stable_0.10.0.sqlite3 /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
|
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||||
invoke migrate
|
invoke migrate
|
||||||
|
|
||||||
- name: 0.11.0 Database
|
- name: 0.11.0 Database
|
||||||
run: |
|
run: |
|
||||||
rm /home/runner/work/InvenTree/db.sqlite3
|
rm /home/runner/work/InvenTree/db.sqlite3
|
||||||
cp test-db/stable_0.11.0.sqlite3 /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
|
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||||
invoke migrate
|
invoke migrate
|
||||||
|
|
||||||
- name: 0.12.0 Database
|
- name: 0.12.0 Database
|
||||||
run: |
|
run: |
|
||||||
rm /home/runner/work/InvenTree/db.sqlite3
|
rm /home/runner/work/InvenTree/db.sqlite3
|
||||||
cp test-db/stable_0.12.0.sqlite3 /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
|
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||||
invoke migrate
|
invoke migrate
|
||||||
|
|
||||||
platform_ui:
|
platform_ui:
|
||||||
name: Tests - Platform UI
|
name: Tests - Platform UI
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: ['pre-commit', 'paths-filter']
|
needs: [ 'pre-commit', 'paths-filter' ]
|
||||||
if: needs.paths-filter.outputs.frontend == 'true'
|
if: needs.paths-filter.outputs.frontend == 'true'
|
||||||
env:
|
env:
|
||||||
INVENTREE_DB_ENGINE: sqlite3
|
INVENTREE_DB_ENGINE: sqlite3
|
||||||
@@ -405,7 +403,7 @@ jobs:
|
|||||||
INVENTREE_PLUGINS_ENABLED: false
|
INVENTREE_PLUGINS_ENABLED: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -420,7 +418,7 @@ jobs:
|
|||||||
run: 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
|
||||||
run: cd src/frontend && npx playwright test
|
run: cd src/frontend && npx playwright test
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: playwright-report
|
||||||
@@ -433,7 +431,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -446,7 +444,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd InvenTree/web/static
|
cd InvenTree/web/static
|
||||||
zip -r frontend-build.zip web/
|
zip -r frontend-build.zip web/
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: frontend-build
|
name: frontend-build
|
||||||
path: InvenTree/web/static/web
|
path: InvenTree/web/static/web
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Version Check
|
- name: Version Check
|
||||||
run: |
|
run: |
|
||||||
pip install requests
|
pip install requests
|
||||||
python3 ci/version_check.py
|
python3 ci/version_check.py
|
||||||
- name: Push to Stable Branch
|
- 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'
|
if: env.stable_release == 'true'
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
publish-build:
|
publish-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Environment Setup
|
- name: Environment Setup
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd InvenTree/web/static/web
|
cd InvenTree/web/static/web
|
||||||
zip -r ../frontend-build.zip *
|
zip -r ../frontend-build.zip *
|
||||||
- uses: svenstaro/upload-release-action@v2
|
- uses: svenstaro/upload-release-action@1beeb572c19a9242f4361f4cee78f8e0d9aec5df # pin@2.7.0
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
file: InvenTree/web/static/frontend-build.zip
|
file: InvenTree/web/static/frontend-build.zip
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # pin@v6.0.1
|
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # pin@v8.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
|
stale-issue-message: 'This issue seems stale. Please react to show this is still important.'
|
||||||
important.'
|
|
||||||
stale-pr-message: 'This PR 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-issue-label: 'inactive'
|
||||||
stale-pr-label: 'inactive'
|
stale-pr-label: 'inactive'
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- 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
|
- 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:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Set up Node 16
|
- 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:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
git add "*.po"
|
git add "*.po"
|
||||||
git commit -m "updated translation base"
|
git commit -m "updated translation base"
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
uses: ad-m/github-push-action@4dcce6dea3e3c8187237fc86b7dfdc93e5aaae58 # pin@master
|
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
branch: l10
|
branch: l10
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Setup
|
- name: Setup
|
||||||
run: pip install -r requirements-dev.txt
|
run: pip install -r requirements-dev.txt
|
||||||
- name: Update requirements.txt
|
- name: Update requirements.txt
|
||||||
run: pip-compile --output-file=requirements.txt requirements.in -U
|
run: pip-compile --output-file=requirements.txt requirements.in -U
|
||||||
- name: Update requirements-dev.txt
|
- name: Update requirements-dev.txt
|
||||||
run: pip-compile --generate-hashes --output-file=requirements-dev.txt
|
run: pip-compile --generate-hashes --output-file=requirements-dev.txt requirements-dev.in -U
|
||||||
requirements-dev.in -U
|
|
||||||
- uses: stefanzweifel/git-auto-commit-action@fd157da78fa13d9383e5580d1fd1184d89554b51 # pin@v4.15.1
|
- uses: stefanzweifel/git-auto-commit-action@fd157da78fa13d9383e5580d1fd1184d89554b51 # pin@v4.15.1
|
||||||
with:
|
with:
|
||||||
commit_message: "[Bot] Updated dependency"
|
commit_message: "[Bot] Updated dependency"
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
|
|
||||||
# InvenTree API version
|
# 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
|
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
|
v140 -> 2023-10-20 : https://github.com/inventree/InvenTree/pull/5664
|
||||||
- Expand API token functionality
|
- Expand API token functionality
|
||||||
- Multiple API tokens can be generated per user
|
- Multiple API tokens can be generated per user
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import os
|
|||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import string
|
import string
|
||||||
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
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
|
value = False
|
||||||
|
|
||||||
return value
|
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
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AuthRequiredMiddleware(object):
|
|||||||
elif request.path_info.startswith('/accounts/'):
|
elif request.path_info.startswith('/accounts/'):
|
||||||
authorized = True
|
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
|
authorized = True
|
||||||
|
|
||||||
elif 'Authorization' in request.headers.keys() or 'authorization' in request.headers.keys():
|
elif 'Authorization' in request.headers.keys() or 'authorization' in request.headers.keys():
|
||||||
|
|||||||
@@ -1044,9 +1044,9 @@ CUSTOM_SPLASH = get_custom_file('INVENTREE_CUSTOM_SPLASH', 'customize.splash', '
|
|||||||
|
|
||||||
CUSTOMIZE = get_setting('INVENTREE_CUSTOMIZE', 'customize', {})
|
CUSTOMIZE = get_setting('INVENTREE_CUSTOMIZE', 'customize', {})
|
||||||
|
|
||||||
# Frontend settings
|
# Load settings for the frontend interface
|
||||||
PUI_URL_BASE = get_setting('INVENTREE_PUI_URL_BASE', 'pui_url_base', 'platform')
|
FRONTEND_SETTINGS = config.get_frontend_settings(debug=DEBUG)
|
||||||
PUI_SETTINGS = get_setting("INVENTREE_PUI_SETTINGS", "pui_settings", {})
|
FRONTEND_URL_BASE = FRONTEND_SETTINGS.get('base_url', 'platform')
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger.info("InvenTree running with DEBUG enabled")
|
logger.info("InvenTree running with DEBUG enabled")
|
||||||
@@ -1076,5 +1076,5 @@ if CUSTOM_FLAGS:
|
|||||||
|
|
||||||
# Magic login django-sesame
|
# Magic login django-sesame
|
||||||
SESAME_MAX_AGE = 300
|
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/"
|
LOGIN_REDIRECT_URL = "/index/"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ for provider in providers.registry.get_list():
|
|||||||
social_auth_urlpatterns += provider_urlpatterns
|
social_auth_urlpatterns += provider_urlpatterns
|
||||||
|
|
||||||
|
|
||||||
class SocialProvierListView(ListAPIView):
|
class SocialProviderListView(ListAPIView):
|
||||||
"""List of available social providers."""
|
"""List of available social providers."""
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
|
|||||||
@@ -1216,6 +1216,6 @@ class MagicLoginTest(InvenTreeTestCase):
|
|||||||
self.assertEqual(resp.url, '/index/')
|
self.assertEqual(resp.url, '/index/')
|
||||||
# Note: 2023-08-08 - This test has been changed because "platform UI" is not generally available yet
|
# 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
|
# 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
|
# And we should be logged in again
|
||||||
self.assertEqual(resp.wsgi_request.user, self.user)
|
self.assertEqual(resp.wsgi_request.user, self.user)
|
||||||
|
|||||||
+25
-25
@@ -16,29 +16,29 @@ from dj_rest_auth.registration.views import (ConfirmEmailView,
|
|||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
|
||||||
from sesame.views import LoginView
|
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 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 common.urls import common_urls
|
||||||
from company.api import company_api_urls
|
|
||||||
from company.urls import (company_urls, manufacturer_part_urls,
|
from company.urls import (company_urls, manufacturer_part_urls,
|
||||||
supplier_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 order.urls import order_urls
|
||||||
from part.api import bom_api_urls, part_api_urls
|
|
||||||
from part.urls import part_urls
|
from part.urls import part_urls
|
||||||
from plugin.api import plugin_api_urls
|
|
||||||
from plugin.urls import get_plugin_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 stock.urls import stock_urls
|
||||||
from users.api import user_urls
|
|
||||||
from web.urls import urlpatterns as platform_urls
|
from web.urls import urlpatterns as platform_urls
|
||||||
|
|
||||||
from .api import APISearchView, InfoView, NotFoundView
|
from .api import APISearchView, InfoView, NotFoundView
|
||||||
from .magic_login import GetSimpleLoginView
|
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,
|
from .views import (AboutView, AppearanceSelectView, CustomConnectionsView,
|
||||||
CustomEmailView, CustomLoginView,
|
CustomEmailView, CustomLoginView,
|
||||||
CustomPasswordResetFromKeyView,
|
CustomPasswordResetFromKeyView,
|
||||||
@@ -55,23 +55,23 @@ apipatterns = [
|
|||||||
# Global search
|
# Global search
|
||||||
path('search/', APISearchView.as_view(), name='api-search'),
|
path('search/', APISearchView.as_view(), name='api-search'),
|
||||||
|
|
||||||
re_path(r'^settings/', include(settings_api_urls)),
|
re_path(r'^settings/', include(common.api.settings_api_urls)),
|
||||||
re_path(r'^part/', include(part_api_urls)),
|
re_path(r'^part/', include(part.api.part_api_urls)),
|
||||||
re_path(r'^bom/', include(bom_api_urls)),
|
re_path(r'^bom/', include(part.api.bom_api_urls)),
|
||||||
re_path(r'^company/', include(company_api_urls)),
|
re_path(r'^company/', include(company.api.company_api_urls)),
|
||||||
re_path(r'^stock/', include(stock_api_urls)),
|
re_path(r'^stock/', include(stock.api.stock_api_urls)),
|
||||||
re_path(r'^build/', include(build_api_urls)),
|
re_path(r'^build/', include(build.api.build_api_urls)),
|
||||||
re_path(r'^order/', include(order_api_urls)),
|
re_path(r'^order/', include(order.api.order_api_urls)),
|
||||||
re_path(r'^label/', include(label_api_urls)),
|
re_path(r'^label/', include(label.api.label_api_urls)),
|
||||||
re_path(r'^report/', include(report_api_urls)),
|
re_path(r'^report/', include(report.api.report_api_urls)),
|
||||||
re_path(r'^user/', include(user_urls)),
|
re_path(r'^user/', include(users.api.user_urls)),
|
||||||
re_path(r'^admin/', include(admin_api_urls)),
|
re_path(r'^admin/', include(common.api.admin_api_urls)),
|
||||||
|
|
||||||
# Plugin endpoints
|
# Plugin endpoints
|
||||||
path('', include(plugin_api_urls)),
|
path('', include(plugin.api.plugin_api_urls)),
|
||||||
|
|
||||||
# Common endpoints endpoint
|
# Common endpoints endpoint
|
||||||
path('', include(common_api_urls)),
|
path('', include(common.api.common_api_urls)),
|
||||||
|
|
||||||
# OpenAPI Schema
|
# OpenAPI Schema
|
||||||
re_path('schema/', SpectacularAPIView.as_view(custom_settings={'SCHEMA_PATH_PREFIX': '/api/'}), name='schema'),
|
re_path('schema/', SpectacularAPIView.as_view(custom_settings={'SCHEMA_PATH_PREFIX': '/api/'}), name='schema'),
|
||||||
@@ -83,7 +83,7 @@ apipatterns = [
|
|||||||
path('auth/', include([
|
path('auth/', include([
|
||||||
re_path(r'^registration/account-confirm-email/(?P<key>[-:\w]+)/$', ConfirmEmailView.as_view(), name='account_confirm_email'),
|
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('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/', include(social_auth_urlpatterns)),
|
||||||
path('social/', SocialAccountListView.as_view(), name='social_account_list'),
|
path('social/', SocialAccountListView.as_view(), name='social_account_list'),
|
||||||
path('social/<int:pk>/disconnect/', SocialAccountDisconnectView.as_view(), name='social_account_disconnect'),
|
path('social/<int:pk>/disconnect/', SocialAccountDisconnectView.as_view(), name='social_account_disconnect'),
|
||||||
|
|||||||
@@ -290,18 +290,17 @@ remote_login_header: HTTP_REMOTE_USER
|
|||||||
# logo: img/custom_logo.png
|
# logo: img/custom_logo.png
|
||||||
# splash: img/custom_splash.jpg
|
# splash: img/custom_splash.jpg
|
||||||
|
|
||||||
# Platform UI options
|
# Frontend UI settings
|
||||||
# pui_settings:
|
# frontend_settings:
|
||||||
|
# base_url: 'frontend'
|
||||||
# server_list:
|
# server_list:
|
||||||
# my_server1:
|
# my_server1:
|
||||||
# host: https://demo.inventree.org/api/
|
# host: https://demo.inventree.org/
|
||||||
# name: InvenTree Demo
|
# name: InvenTree Demo
|
||||||
# default_server: my_server1
|
# default_server: my_server1
|
||||||
# show_server_selector: false
|
# show_server_selector: false
|
||||||
# sentry_dsn: https://84f0c3ea90c64e5092e2bf5dfe325725@o1047628.ingest.sentry.io/4504160008273920
|
# sentry_dsn: https://84f0c3ea90c64e5092e2bf5dfe325725@o1047628.ingest.sentry.io/4504160008273920
|
||||||
# environment: development
|
# environment: development
|
||||||
# Base URL for serving Platform UI
|
|
||||||
# pui_url_base: 'platform'
|
|
||||||
|
|
||||||
# Custom flags
|
# Custom flags
|
||||||
# InvenTree uses django-flags; read more in their docs at https://cfpb.github.io/django-flags/conditions/
|
# 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -41,6 +41,7 @@ import InvenTree.fields
|
|||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
import InvenTree.tasks
|
import InvenTree.tasks
|
||||||
import part.settings as part_settings
|
import part.settings as part_settings
|
||||||
|
import users.models
|
||||||
from build import models as BuildModels
|
from build import models as BuildModels
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
@@ -379,7 +380,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
|
|||||||
notes: Additional notes field for this part
|
notes: Additional notes field for this part
|
||||||
creation_date: Date that this part was added to the database
|
creation_date: Date that this part was added to the database
|
||||||
creation_user: User who added this part 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
|
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')
|
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(
|
last_stocktake = models.DateField(
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import part.filters
|
|||||||
import part.stocktake
|
import part.stocktake
|
||||||
import part.tasks
|
import part.tasks
|
||||||
import stock.models
|
import stock.models
|
||||||
|
import users.models
|
||||||
from InvenTree.status_codes import BuildStatusGroups
|
from InvenTree.status_codes import BuildStatusGroups
|
||||||
from InvenTree.tasks import offload_task
|
from InvenTree.tasks import offload_task
|
||||||
|
|
||||||
@@ -695,6 +696,12 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
|
|||||||
read_only=True,
|
read_only=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
responsible = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=users.models.Owner.objects.all(),
|
||||||
|
required=False, allow_null=True,
|
||||||
|
source='responsible_owner',
|
||||||
|
)
|
||||||
|
|
||||||
# Annotated fields
|
# Annotated fields
|
||||||
allocated_to_build_orders = serializers.FloatField(read_only=True)
|
allocated_to_build_orders = serializers.FloatField(read_only=True)
|
||||||
allocated_to_sales_orders = serializers.FloatField(read_only=True)
|
allocated_to_sales_orders = serializers.FloatField(read_only=True)
|
||||||
|
|||||||
@@ -384,11 +384,11 @@
|
|||||||
<td>{% include 'clip_link.html' with link=part.link new_window=True %}</td>
|
<td>{% include 'clip_link.html' with link=part.link new_window=True %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.responsible %}
|
{% if part.responsible_owner %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-user'></span></td>
|
<td><span class='fas fa-user'></span></td>
|
||||||
<td>{% trans "Responsible" %}</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>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
import common.models
|
import common.models
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.helpers_model
|
import InvenTree.helpers_model
|
||||||
|
import plugin.models
|
||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
from InvenTree import settings, version
|
from InvenTree import settings, version
|
||||||
from plugin import registry
|
from plugin import registry
|
||||||
from plugin.models import NotificationUserSetting, PluginSetting
|
|
||||||
from plugin.plugin import InvenTreePlugin
|
from plugin.plugin import InvenTreePlugin
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
@@ -346,14 +346,14 @@ def setting_object(key, *args, **kwargs):
|
|||||||
if 'plugin' in kwargs:
|
if 'plugin' in kwargs:
|
||||||
# Note, 'plugin' is an instance of an InvenTreePlugin class
|
# Note, 'plugin' is an instance of an InvenTreePlugin class
|
||||||
|
|
||||||
plugin = kwargs['plugin']
|
plg = kwargs['plugin']
|
||||||
if issubclass(plugin.__class__, InvenTreePlugin):
|
if issubclass(plg.__class__, InvenTreePlugin):
|
||||||
plugin = plugin.plugin_config()
|
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:
|
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:
|
elif 'user' in kwargs:
|
||||||
return common.models.InvenTreeUserSetting.get_setting_object(key, user=kwargs['user'], cache=cache)
|
return common.models.InvenTreeUserSetting.get_setting_object(key, user=kwargs['user'], cache=cache)
|
||||||
|
|||||||
@@ -649,7 +649,11 @@ class PluginsRegistry:
|
|||||||
try:
|
try:
|
||||||
logger.debug("Updating plugin registry hash: %s", str(self.registry_hash))
|
logger.debug("Updating plugin registry hash: %s", str(self.registry_hash))
|
||||||
InvenTreeSetting.set_setting("_PLUGIN_REGISTRY_HASH", self.registry_hash, change_user=None)
|
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:
|
except Exception as exc:
|
||||||
|
# Some other exception, we want to know about it
|
||||||
logger.exception("Failed to update plugin registry hash: %s", str(exc))
|
logger.exception("Failed to update plugin registry hash: %s", str(exc))
|
||||||
|
|
||||||
def calculate_plugin_hash(self):
|
def calculate_plugin_hash(self):
|
||||||
|
|||||||
@@ -1673,7 +1673,11 @@ function loadPurchaseOrderTable(table, options) {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortName: 'supplier__name',
|
sortName: 'supplier__name',
|
||||||
formatter: function(value, row) {
|
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" %}',
|
title: '{% trans "Part" %}',
|
||||||
switchable: false,
|
switchable: false,
|
||||||
formatter: function(value, row, index, field) {
|
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}/`);
|
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${row.part_detail.pk}/`);
|
||||||
} else {
|
} else {
|
||||||
return '-';
|
return '-';
|
||||||
|
|||||||
@@ -1793,7 +1793,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
title: '{% trans "Part" %}',
|
title: '{% trans "Part" %}',
|
||||||
switchable: false,
|
switchable: false,
|
||||||
formatter: function(value, row, index, field) {
|
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}/`);
|
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`);
|
||||||
} else {
|
} else {
|
||||||
return '-';
|
return '-';
|
||||||
|
|||||||
@@ -3062,8 +3062,10 @@ function loadInstalledInTable(table, options) {
|
|||||||
formatter: function(value, row) {
|
formatter: function(value, row) {
|
||||||
var html = '';
|
var html = '';
|
||||||
|
|
||||||
html += imageHoverIcon(row.part_detail.thumbnail);
|
if (row.part_detail) {
|
||||||
html += renderLink(row.part_detail.full_name, `/stock/item/${row.pk}/`);
|
html += imageHoverIcon(row.part_detail.thumbnail);
|
||||||
|
html += renderLink(row.part_detail.full_name, `/stock/item/${row.pk}/`);
|
||||||
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-11
@@ -6,7 +6,6 @@ from django.contrib.admin.widgets import FilteredSelectMultiple
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from users.models import ApiToken, Owner, RuleSet
|
from users.models import ApiToken, Owner, RuleSet
|
||||||
@@ -203,20 +202,22 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover
|
|||||||
users = form.cleaned_data['users']
|
users = form.cleaned_data['users']
|
||||||
|
|
||||||
# Check for users who are members of multiple groups
|
# Check for users who are members of multiple groups
|
||||||
warning_message = ''
|
multiple_group_users = []
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
if user.groups.all().count() > 1:
|
if user.groups.all().count() > 1:
|
||||||
warning_message += f'<br>- <b>{user.username}</b> is member of: '
|
multiple_group_users.append(user.username)
|
||||||
for idx, group in enumerate(user.groups.all()):
|
|
||||||
warning_message += f'<b>{group.name}</b>'
|
|
||||||
if idx < len(user.groups.all()) - 1:
|
|
||||||
warning_message += ', '
|
|
||||||
|
|
||||||
# If any, display warning message when group is saved
|
# If any, display warning message when group is saved
|
||||||
if warning_message:
|
if len(multiple_group_users) > 0:
|
||||||
warning_message = mark_safe(_(f'The following users are members of multiple groups:'
|
|
||||||
f'{warning_message}'))
|
msg = _("The following users are members of multiple groups") + ": " + ", ".join(multiple_group_users)
|
||||||
messages.add_message(request, messages.WARNING, warning_message)
|
|
||||||
|
messages.add_message(
|
||||||
|
request,
|
||||||
|
messages.WARNING,
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
|
||||||
def save_formset(self, request, form, formset, change):
|
def save_formset(self, request, form, formset, change):
|
||||||
"""Save the inline formset"""
|
"""Save the inline formset"""
|
||||||
|
|||||||
@@ -422,11 +422,7 @@ class RuleSet(models.Model):
|
|||||||
"""Construct the correctly formatted permission string, given the app_model name, and the permission type."""
|
"""Construct the correctly formatted permission string, given the app_model name, and the permission type."""
|
||||||
model, app = split_model(model)
|
model, app = split_model(model)
|
||||||
|
|
||||||
return "{app}.{perm}_{model}".format(
|
return f"{app}.{permission}_{model}"
|
||||||
app=app,
|
|
||||||
perm=permission,
|
|
||||||
model=model
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self, debug=False): # pragma: no cover
|
def __str__(self, debug=False): # pragma: no cover
|
||||||
"""Ruleset string representation."""
|
"""Ruleset string representation."""
|
||||||
@@ -504,12 +500,7 @@ def update_group_roles(group, debug=False):
|
|||||||
# and create a simplified permission key string
|
# and create a simplified permission key string
|
||||||
for p in group.permissions.all().prefetch_related('content_type'):
|
for p in group.permissions.all().prefetch_related('content_type'):
|
||||||
(permission, app, model) = p.natural_key()
|
(permission, app, model) = p.natural_key()
|
||||||
|
permission_string = f"{app}.{permission}"
|
||||||
permission_string = '{app}.{perm}'.format(
|
|
||||||
app=app,
|
|
||||||
perm=permission
|
|
||||||
)
|
|
||||||
|
|
||||||
group_permissions.add(permission_string)
|
group_permissions.add(permission_string)
|
||||||
|
|
||||||
# List of permissions which must be added to the group
|
# 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
|
allowed: Whether or not the action is allowed
|
||||||
"""
|
"""
|
||||||
if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover
|
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)
|
permission_string = RuleSet.get_model_permission_string(model, action)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Template tag to render SPA imports."""
|
"""Template tag to render SPA imports."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -10,11 +11,7 @@ from django.utils.safestring import mark_safe
|
|||||||
logger = getLogger("InvenTree")
|
logger = getLogger("InvenTree")
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
PUI_DEFAULTS = {
|
FRONTEND_SETTINGS = json.dumps(settings.FRONTEND_SETTINGS)
|
||||||
'url_base': settings.PUI_URL_BASE,
|
|
||||||
}
|
|
||||||
PUI_DEFAULTS.update(getattr(settings, 'PUI_SETTINGS', {}))
|
|
||||||
PUI_SETTINGS = json.dumps(PUI_DEFAULTS)
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
@@ -30,11 +27,11 @@ def spa_bundle():
|
|||||||
index = manifest_data.get("index.html")
|
index = manifest_data.get("index.html")
|
||||||
css_index = manifest_data.get("index.css")
|
css_index = manifest_data.get("index.css")
|
||||||
|
|
||||||
dynmanic_files = index.get("dynamicImports", [])
|
dynamic_files = index.get("dynamicImports", [])
|
||||||
imports_files = "".join(
|
imports_files = "".join(
|
||||||
[
|
[
|
||||||
f'<script type="module" src="{settings.STATIC_URL}web/{manifest_data[file]["file"]}"></script>'
|
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
|
@register.simple_tag
|
||||||
def spa_settings():
|
def spa_settings():
|
||||||
"""Render settings for spa."""
|
"""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>""")
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ spa_view = ensure_csrf_cookie(TemplateView.as_view(template_name="web/index.html
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(f'{settings.PUI_URL_BASE}/', include([
|
path(f'{settings.FRONTEND_URL_BASE}/', include([
|
||||||
path("assets/<path:path>", RedirectAssetView.as_view()),
|
path("assets/<path:path>", RedirectAssetView.as_view()),
|
||||||
re_path(r"^(?P<path>.*)/$", spa_view),
|
re_path(r"^(?P<path>.*)/$", spa_view),
|
||||||
path("set-password?uid=<uid>&token=<token>", spa_view, name="password_reset_confirm"),
|
path("set-password?uid=<uid>&token=<token>", spa_view, name="password_reset_confirm"),
|
||||||
path("", spa_view),]
|
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()),
|
path("assets/<path:path>", RedirectAssetView.as_view()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -25,8 +25,90 @@ The following code types are known to be supported
|
|||||||
- Data Matrix
|
- Data Matrix
|
||||||
- Aztec
|
- 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.
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ The <span class='fas fa-search'></span> action opens the [Search](./search.md) s
|
|||||||
|
|
||||||
### Scan Barcode
|
### 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
|
## Context Actions
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,26 @@ The InvenTree mobile app requires some extra permissions for complete functional
|
|||||||
|
|
||||||
### User Profiles
|
### 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
|
- Server name (e.g. "InvenTree Demo")
|
||||||
- Account username
|
- Server address (e.g. "https://demo.inventree.org)
|
||||||
- Account password
|
- *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
|
### Camera Permissions
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ Configure audible app notifications:
|
|||||||
|
|
||||||
## Barcode Settings
|
## 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" %}
|
{% with id="barcode_settings", url="app/barcode_settings.png", maxheight="240px", description="Barcode Settings" %}
|
||||||
{% include 'img.html' %}
|
{% include 'img.html' %}
|
||||||
@@ -63,6 +63,7 @@ The *Barcode Settings* view allows you to configure options relating to barcode
|
|||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| Scanner Input | Select barcode capture mode |
|
||||||
| Barcode Scan Delay | Delay between successive scans |
|
| Barcode Scan Delay | Delay between successive scans |
|
||||||
|
|
||||||
## Home Screen
|
## 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 |
@@ -20,8 +20,8 @@ export const doClassicLogin = async (username: string, password: string) => {
|
|||||||
const token = await axios
|
const token = await axios
|
||||||
.get(apiUrl(ApiPaths.user_token), {
|
.get(apiUrl(ApiPaths.user_token), {
|
||||||
auth: { username, password },
|
auth: { username, password },
|
||||||
baseURL: host.toString(),
|
baseURL: host,
|
||||||
timeout: 5000,
|
timeout: 2000,
|
||||||
params: {
|
params: {
|
||||||
name: 'inventree-web-app'
|
name: 'inventree-web-app'
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,9 @@ export const doClassicLogin = async (username: string, password: string) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout the user (invalidate auth token)
|
||||||
|
*/
|
||||||
export const doClassicLogout = async () => {
|
export const doClassicLogout = async () => {
|
||||||
// TODO @matmair - logout from the server session
|
// TODO @matmair - logout from the server session
|
||||||
// Set token in context
|
// Set token in context
|
||||||
@@ -117,7 +120,7 @@ export function handleReset(navigate: any, values: { email: string }) {
|
|||||||
export function checkLoginState(navigate: any, redirect?: string) {
|
export function checkLoginState(navigate: any, redirect?: string) {
|
||||||
api
|
api
|
||||||
.get(apiUrl(ApiPaths.user_token), {
|
.get(apiUrl(ApiPaths.user_token), {
|
||||||
timeout: 5000,
|
timeout: 2000,
|
||||||
params: {
|
params: {
|
||||||
name: 'inventree-web-app'
|
name: 'inventree-web-app'
|
||||||
}
|
}
|
||||||
@@ -137,8 +140,7 @@ export function checkLoginState(navigate: any, redirect?: string) {
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(() => {
|
||||||
console.error('Error fetching login information:', error);
|
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ declare global {
|
|||||||
server_list: HostList;
|
server_list: HostList;
|
||||||
default_server: string;
|
default_server: string;
|
||||||
show_server_selector: boolean;
|
show_server_selector: boolean;
|
||||||
url_base: string;
|
base_url: string;
|
||||||
sentry_dsn?: string;
|
sentry_dsn?: string;
|
||||||
environment?: 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_DEMO = import.meta.env.VITE_DEMO === 'true';
|
||||||
export const IS_DEV_OR_DEMO = IS_DEV || IS_DEMO;
|
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 = {
|
window.INVENTREE_SETTINGS = {
|
||||||
server_list: {
|
server_list: {
|
||||||
'mantine-cqj63coxn': {
|
'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,
|
show_server_selector: IS_DEV_OR_DEMO,
|
||||||
|
|
||||||
// merge in settings that are already set via django's spa_view or for development
|
// 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) {
|
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(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
@@ -66,5 +77,5 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|||||||
|
|
||||||
// Redirect to base url if on /
|
// Redirect to base url if on /
|
||||||
if (window.location.pathname === '/') {
|
if (window.location.pathname === '/') {
|
||||||
window.location.replace(`/${url_base}`);
|
window.location.replace(`/${base_url}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ export const useServerApiState = create<ServerApiStateProps>((set, get) => ({
|
|||||||
setServer: (newServer: ServerAPIProps) => set({ server: newServer }),
|
setServer: (newServer: ServerAPIProps) => set({ server: newServer }),
|
||||||
fetchServerApiState: async () => {
|
fetchServerApiState: async () => {
|
||||||
// Fetch server data
|
// Fetch server data
|
||||||
await api.get(apiUrl(ApiPaths.api_server_info)).then((response) => {
|
await api
|
||||||
set({ server: response.data });
|
.get(apiUrl(ApiPaths.api_server_info))
|
||||||
});
|
.then((response) => {
|
||||||
|
set({ server: response.data });
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const useUserState = create<UserStateProps>((set, get) => ({
|
|||||||
// Fetch user data
|
// Fetch user data
|
||||||
await api
|
await api
|
||||||
.get(apiUrl(ApiPaths.user_me), {
|
.get(apiUrl(ApiPaths.user_me), {
|
||||||
timeout: 5000
|
timeout: 2000
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const user: UserProps = {
|
const user: UserProps = {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { BrowserRouter } from 'react-router-dom';
|
|||||||
import { queryClient, setApiDefaults } from '../App';
|
import { queryClient, setApiDefaults } from '../App';
|
||||||
import { BaseContext } from '../contexts/BaseContext';
|
import { BaseContext } from '../contexts/BaseContext';
|
||||||
import { defaultHostList } from '../defaults/defaultHostList';
|
import { defaultHostList } from '../defaults/defaultHostList';
|
||||||
import { url_base } from '../main';
|
import { base_url } from '../main';
|
||||||
import { routes } from '../router';
|
import { routes } from '../router';
|
||||||
import { useLocalState } from '../states/LocalState';
|
import { useLocalState } from '../states/LocalState';
|
||||||
import { useSessionState } from '../states/SessionState';
|
import { useSessionState } from '../states/SessionState';
|
||||||
@@ -27,10 +27,6 @@ export default function DesktopAppView() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Local state initialization
|
// Local state initialization
|
||||||
if (Object.keys(hostList).length === 0) {
|
|
||||||
console.log('Loading default host list');
|
|
||||||
useLocalState.setState({ hostList: defaultHostList });
|
|
||||||
}
|
|
||||||
setApiDefaults();
|
setApiDefaults();
|
||||||
|
|
||||||
// Server Session
|
// Server Session
|
||||||
@@ -38,6 +34,11 @@ export default function DesktopAppView() {
|
|||||||
const sessionState = useSessionState.getState();
|
const sessionState = useSessionState.getState();
|
||||||
const [token] = sessionState.token ? [sessionState.token] : [null];
|
const [token] = sessionState.token ? [sessionState.token] : [null];
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (Object.keys(hostList).length === 0) {
|
||||||
|
console.log('Loading default host list', defaultHostList);
|
||||||
|
useLocalState.setState({ hostList: defaultHostList });
|
||||||
|
}
|
||||||
|
|
||||||
if (token && !fetchedServerSession) {
|
if (token && !fetchedServerSession) {
|
||||||
setFetchedServerSession(true);
|
setFetchedServerSession(true);
|
||||||
fetchUserState();
|
fetchUserState();
|
||||||
@@ -49,7 +50,7 @@ export default function DesktopAppView() {
|
|||||||
return (
|
return (
|
||||||
<BaseContext>
|
<BaseContext>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrowserRouter basename={url_base}>{routes}</BrowserRouter>
|
<BrowserRouter basename={base_url}>{routes}</BrowserRouter>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</BaseContext>
|
</BaseContext>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,9 +11,4 @@ test('Basic Platform UI test', async ({ page }) => {
|
|||||||
await page.goto('./platform/');
|
await page.goto('./platform/');
|
||||||
|
|
||||||
await expect(page).toHaveTitle('InvenTree Demo Server');
|
await expect(page).toHaveTitle('InvenTree Demo Server');
|
||||||
await expect(
|
|
||||||
page.getByRole('heading', {
|
|
||||||
name: 'Welcome to your Dashboard, Ally Access'
|
|
||||||
})
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user