Merge branch 'master' into pr/ChristianSchindler/6305

This commit is contained in:
Matthias Mair 2024-08-19 21:54:59 +02:00 committed by GitHub
commit 7b88d0caa7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
256 changed files with 105642 additions and 118128 deletions

4
.github/FUNDING.yml vendored
View File

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

View File

@ -10,6 +10,7 @@ tagged branch:
"""
import itertools
import json
import os
import re
@ -198,10 +199,13 @@ if __name__ == '__main__':
print(f"Version check passed for '{version}'!")
print(f"Docker tags: '{docker_tags}'")
target_repos = [REPO.lower(), f'ghcr.io/{REPO.lower()}']
# Ref: https://getridbug.com/python/how-to-set-environment-variables-in-github-actions-using-python/
with open(os.getenv('GITHUB_ENV'), 'a') as env_file:
# Construct tag string
tags = ','.join([f'{REPO.lower()}:{tag}' for tag in docker_tags])
tag_list = [[f'{r}:{t}' for t in docker_tags] for r in target_repos]
tags = ','.join(itertools.chain(*tag_list))
env_file.write(f'docker_tags={tags}\n')

View File

@ -127,17 +127,17 @@ jobs:
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # pin@v3.2.0
- name: Set up Docker Buildx
if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # pin@v3.5.0
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # pin@v3.6.1
- name: Set up cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # pin@v3.5.0
uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # pin@v3.6.0
- name: Check if Dockerhub login is required
id: docker_login
run: |
if [ -z "${{ secrets.DOCKER_USERNAME }}" ]; then
echo "skip_dockerhub_login=true" >> $GITHUB_ENV
echo "skip_dockerhub_login=true" >> $GITHUB_OUTPUT
else
echo "skip_dockerhub_login=false" >> $GITHUB_ENV
echo "skip_dockerhub_login=false" >> $GITHUB_OUTPUT
fi
- name: Login to Dockerhub
if: github.event_name != 'pull_request' && steps.docker_login.outputs.skip_dockerhub_login != 'true'
@ -166,7 +166,7 @@ jobs:
- name: Push Docker Images
id: push-docker
if: github.event_name != 'pull_request'
uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # pin@v6.5.0
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # pin@v6.6.1
with:
context: .
file: ./contrib/container/Dockerfile

View File

@ -159,7 +159,7 @@ jobs:
- name: Export API Documentation
run: invoke schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
- name: Upload schema
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # pin@v4.3.4
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4.3.6
with:
name: schema.yml
path: src/backend/InvenTree/schema.yml
@ -177,7 +177,7 @@ jobs:
echo "Downloaded api.yaml"
- name: Running OpenAPI Spec diff action
id: breaking_changes
uses: oasdiff/oasdiff-action/diff@205ce7e2c5ae1511e720cbd307cae79fd7d4a909 # pin@main
uses: oasdiff/oasdiff-action/diff@a2ff6682b27d175162a74c09ace8771bd3d512f8 # pin@main
with:
base: 'api.yaml'
revision: 'src/backend/InvenTree/schema.yml'
@ -535,7 +535,7 @@ jobs:
- name: Run Playwright tests
id: tests
run: cd src/frontend && npx nyc playwright test
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # pin@v4
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
with:
name: playwright-report
@ -573,7 +573,7 @@ jobs:
run: |
cd src/backend/InvenTree/web/static
zip -r frontend-build.zip web/ web/.vite
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # pin@v4.3.4
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4.3.6
with:
name: frontend-build
path: src/backend/InvenTree/web/static/web

View File

@ -37,7 +37,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
@ -59,7 +59,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: SARIF file
path: results.sarif
@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13
uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0
with:
sarif_file: results.sarif

1
.gitignore vendored
View File

@ -111,3 +111,4 @@ InvenTree/web/static
docs/schema.yml
docs/docs/api/*.yml
docs/docs/api/schema/*.yml
inventree_settings.json

View File

@ -11,6 +11,7 @@
ARG base_image=python:3.11-alpine3.18
FROM ${base_image} AS inventree_base
ARG base_image
# Build arguments for this image
ARG commit_tag=""
@ -48,13 +49,18 @@ ENV INVENTREE_BACKGROUND_WORKERS="4"
ENV INVENTREE_WEB_ADDR=0.0.0.0
ENV INVENTREE_WEB_PORT=8000
LABEL org.label-schema.schema-version="1.0" \
org.label-schema.build-date=${DATE} \
org.label-schema.vendor="inventree" \
org.label-schema.name="inventree/inventree" \
org.label-schema.url="https://hub.docker.com/r/inventree/inventree" \
org.label-schema.vcs-url="https://github.com/inventree/InvenTree.git" \
org.label-schema.vcs-ref=${commit_tag}
LABEL org.opencontainers.image.created=${DATE} \
org.opencontainers.image.vendor="inventree" \
org.opencontainers.image.title="InvenTree backend server" \
org.opencontainers.image.description="InvenTree is the open-source inventory management system" \
org.opencontainers.image.url="https://inventree.org" \
org.opencontainers.image.documentation="https://docs.inventree.org" \
org.opencontainers.image.source="https://github.com/inventree/InvenTree" \
org.opencontainers.image.revision=${commit_hash} \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.base.name="docker.io/library/${base_image}" \
org.opencontainers.image.version=${commit_tag}
# Install required system level packages
RUN apk add --no-cache \
@ -68,8 +74,9 @@ RUN apk add --no-cache \
# MySQL / MariaDB client
mariadb-client mariadb-connector-c \
&& \
# fonts
apk --update --upgrade --no-cache add fontconfig ttf-freefont font-noto terminus-font && fc-cache -f
# font support
apk --update --upgrade --no-cache add fontconfig ttf-freefont font-terminus font-noto font-noto-cjk font-noto-extra \
&& fc-cache -f
EXPOSE 8000

View File

@ -1,4 +1,4 @@
# Packages needed for CI/packages
requests==2.32.3
pyyaml==6.0.1
pyyaml==6.0.2
jc==1.25.3

View File

@ -108,58 +108,60 @@ pygments==2.17.2 \
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
# via jc
pyyaml==6.0.1 \
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
pyyaml==6.0.2 \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
# via -r contrib/dev_reqs/requirements.in
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@ -265,18 +265,16 @@ Build orders may (optionally) have a target complete date specified. If this dat
- Builds can be filtered by overdue status in the build list
- Overdue builds will be displayed on the home page
## Build Order Restrictions
## Build Order Settings
There are a number of optional restrictions which can be applied to build orders, which may be enabled or disabled in the system settings:
The following [global settings](../settings/global.md) are available for adjusting the behavior of build orders:
### Require Active Part
If this option is enabled, build orders can only be created for parts which are marked as [Active](../part/part.md#active-parts).
### Require Locked Part
If this option is enabled, build orders can only be created for parts which are marked as [Locked](../part/part.md#locked-parts).
### Require Valid BOM
If this option is enabled, build orders can only be created for parts which have a valid [Bill of Materials](./bom.md) defined.
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("BUILDORDER_REFERENCE_PATTERN") }}
{{ globalsetting("BUILDORDER_REQUIRE_RESPONSIBLE") }}
{{ globalsetting("BUILDORDER_REQUIRE_ACTIVE_PART") }}
{{ globalsetting("BUILDORDER_REQUIRE_LOCKED_PART") }}
{{ globalsetting("BUILDORDER_REQUIRE_VALID_BOM") }}
{{ globalsetting("BUILDORDER_REQUIRE_CLOSED_CHILDS") }}
{{ globalsetting("PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS") }}

View File

@ -139,3 +139,14 @@ This view can be accessed externally as an ICS calendar using a URL like the fol
`http://inventree.example.org/api/order/calendar/purchase-order/calendar.ics`
by default, completed orders are not exported. These can be included by appending `?include_completed=True` to the URL.
## Purchase Order Settings
The following [global settings](../settings/global.md) are available for purchase orders:
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("PURCHASEORDER_REFERENCE_PATTERN") }}
{{ globalsetting("PURCHASEORDER_REQUIRE_RESPONSIBLE") }}
{{ globalsetting("PURCHASEORDER_EDIT_COMPLETED_ORDERS") }}
{{ globalsetting("PURCHASEORDER_AUTO_COMPLETE") }}

View File

@ -121,3 +121,14 @@ This view can be accessed externally as an ICS calendar using a URL like the fol
`http://inventree.example.org/api/order/calendar/return-order/calendar.ics`
by default, completed orders are not exported. These can be included by appending `?include_completed=True` to the URL.
## Return Order Settings
The following [global settings](../settings/global.md) are available for return orders:
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("RETURNORDER_ENABLED") }}
{{ globalsetting("RETURNORDER_REFERENCE_PATTERN") }}
{{ globalsetting("RETURNORDER_REQUIRE_RESPONSIBLE") }}
{{ globalsetting("RETURNORDER_EDIT_COMPLETED_ORDERS") }}

View File

@ -183,3 +183,15 @@ All these fields can be edited by the user:
{% with id="edit-shipment", url="order/edit_shipment.png", description="Edit shipment" %}
{% include "img.html" %}
{% endwith %}
## Sales Order Settings
The following [global settings](../settings/global.md) are available for sales orders:
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("SALESORDER_REFERENCE_PATTERN") }}
{{ globalsetting("SALESORDER_REQUIRE_RESPONSIBLE") }}
{{ globalsetting("SALESORDER_DEFAULT_SHIPMENT") }}
{{ globalsetting("SALESORDER_EDIT_COMPLETED_ORDERS") }}
{{ globalsetting("SALESORDER_SHIP_COMPLETE") }}

View File

@ -47,6 +47,10 @@ If a part is designated as an *Assembly* it can be created (or built) from other
If a part is designated as a *Component* it can be used as a sub-component of an *Assembly*. [Read further information about BOM management here](../build/bom.md)
### Testable
Testable parts can have test templates defined against the part, allowing test results to be recorded against any stock items for that part. For more information on testing, refer to the [testing documentation](./test.md).
### Trackable
Trackable parts can be assigned batch numbers or serial numbers which uniquely identify a particular stock item. Trackable parts also provide other features (and restrictions) in the InvenTree ecosystem.

View File

@ -4,7 +4,7 @@ title: Part Test Templates
## Part Test Templates
Parts which are designated as *trackable* (meaning they can be uniquely serialized) can define templates for tests which are to be performed against individual stock items corresponding to the part.
Parts which are designated as [testable](./part.md#testable) can define templates for tests which are to be performed against individual stock items corresponding to the part.
A test template defines the parameters of the test; the individual stock items can then have associated test results which correspond to a test template.

View File

@ -131,9 +131,9 @@ The *Scheduling* tab provides an overview of the *predicted* future availability
The *Stocktake* tab provide historical stock level information, based on user-provided stocktake data. Refer to the [stocktake documentation](./stocktake.md) for further information.
### Tests
### Test Templates
If a part is marked as *trackable*, the user can define tests which must be performed on any stock items which are instances of this part. [Read more about testing](./test.md).
If a part is marked as *testable*, the user can define tests which must be performed on any stock items which are instances of this part. [Read more about testing](./test.md).
### Related Parts

View File

@ -24,12 +24,7 @@ If a different currency exchange backend is needed, or a custom implementation i
### Currency Settings
In the [settings screen](./global.md), under the *Pricing* section, the following currency settings are available:
| Setting | Description | Default Value |
| --- | --- |
| Default Currency | The selected *default* currency for the system. | USD |
| Supported Currencies | The list of supported currencies for the system. | AUD, CAD, CNY, EUR, GBP, JPY, NZD, USD |
Refer to the [global settings](./global.md#pricing-and-currency) documentation for more information on available currency settings.
#### Supported Currencies

View File

@ -17,120 +17,139 @@ Global settings are arranged in the following categories:
### Server Settings
Configuration of basic server settings.
Configuration of basic server settings:
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("INVENTREE_BASE_URL") }}
{{ globalsetting("INVENTREE_COMPANY_NAME") }}
{{ globalsetting("INVENTREE_INSTANCE") }}
{{ globalsetting("INVENTREE_INSTANCE_TITLE") }}
{{ globalsetting("INVENTREE_RESTRICT_ABOUT") }}
{{ globalsetting("DISPLAY_FULL_NAMES") }}
{{ globalsetting("INVENTREE_UPDATE_CHECK_INTERVAL") }}
{{ globalsetting("INVENTREE_DOWNLOAD_FROM_URL") }}
{{ globalsetting("INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE") }}
{{ globalsetting("INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT") }}
{{ globalsetting("INVENTREE_REQUIRE_CONFIRM") }}
{{ globalsetting("INVENTREE_STRICT_URLS") }}
{{ globalsetting("INVENTREE_TREE_DEPTH") }}
{{ globalsetting("INVENTREE_BACKUP_ENABLE") }}
{{ globalsetting("INVENTREE_BACKUP_DAYS") }}
{{ globalsetting("INVENTREE_DELETE_TASKS_DAYS") }}
{{ globalsetting("INVENTREE_DELETE_ERRORS_DAYS") }}
{{ globalsetting("INVENTREE_DELETE_NOTIFICATIONS_DAYS") }}
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| InvenTree Instance Name | String | String descriptor for the InvenTree server instance | InvenTree Server |
| Use Instance Name | Boolean | Use instance name in title bars | False |
| Restrict showing `about` | Boolean | Show the `about` modal only to superusers | False |
| Base URL | String | Base URL for server instance | *blank* |
| Company Name | String | Company name | My company name |
| Download from URL | Boolean | Allow downloading of images from remote URLs | False |
### Login Settings
Change how logins, password-forgot, signups are handled.
Change how logins, password-forgot, signups are handled:
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Enable registration | Boolean | Enable self-registration for users on the login-pages | False |
| Enable SSO | Boolean | Enable SSO on the login-pages | False |
| Enable SSO registration | Boolean | Enable self-registration for users via SSO on the login-pages | False |
| Enable SSO group sync | Boolean | Enable synchronizing InvenTree groups directly from the IdP | False |
| SSO group key | String | The name of the groups claim attribute provided by the IdP | |
| SSO group map | String (JSON) | A mapping from SSO groups to local InvenTree groups | {} |
| Remove groups outside of SSO | Boolean | Whether groups assigned to the user should be removed if they are not backend by the IdP. Disabling this setting might cause security issues | True |
| Enable password forgot | Boolean | Enable password forgot function on the login-pages.<br><br>This will let users reset their passwords on their own. For this feature to work you need to configure E-mail | True |
| E-Mail required | Boolean | Require user to supply e-mail on signup.<br><br>Without a way (e-mail) to contact the user notifications and security features might not work! | False |
| Enforce MFA | Boolean | Users must use multifactor security.<br><br>This forces each user to setup MFA and use it on each authentication | False |
| Mail twice | Boolean | On signup ask users twice for their mail | False |
| Password twice | Boolean | On signup ask users twice for their password | True |
| Auto-fill SSO users | Boolean | Automatically fill out user-details from SSO account-data.<br><br>If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over. | True |
| Allowed domains | String | Restrict signup to certain domains (comma-separated, starting with @) | |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("LOGIN_ENABLE_PWD_FORGOT") }}
{{ globalsetting("LOGIN_MAIL_REQUIRED") }}
{{ globalsetting("LOGIN_ENFORCE_MFA") }}
{{ globalsetting("LOGIN_ENABLE_REG") }}
{{ globalsetting("LOGIN_SIGNUP_MAIL_TWICE") }}
{{ globalsetting("LOGIN_SIGNUP_PWD_TWICE") }}
{{ globalsetting("SIGNUP_GROUP") }}
{{ globalsetting("LOGIN_SIGNUP_MAIL_RESTRICTION") }}
{{ globalsetting("LOGIN_ENABLE_SSO") }}
{{ globalsetting("LOGIN_ENABLE_SSO_REG") }}
{{ globalsetting("LOGIN_SIGNUP_SSO_AUTO") }}
{{ globalsetting("LOGIN_ENABLE_SSO_GROUP_SYNC") }}
{{ globalsetting("SSO_GROUP_MAP") }}
{{ globalsetting("SSO_GROUP_KEY") }}
{{ globalsetting("SSO_REMOVE_GROUPS") }}
#### Require User Email
If this setting is enabled, users must provide an email address when signing up. Note that some notification and security features require a valid email address.
#### Forgot Password
If this setting is enabled, users can reset their password via email. This requires a valid email address to be associated with the user account.
#### Enforce Multi-Factor Authentication
If this setting is enabled, users must have multi-factor authentication enabled to log in.
#### Auto Fil SSO Users
Automatically fill out user-details from SSO account-data. If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over.
### Barcodes
Configuration of barcode functionality
Configuration of barcode functionality:
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Barcode Support | Boolean | Enable barcode functionality in web interface | True |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("BARCODE_ENABLE") }}
{{ globalsetting("BARCODE_INPUT_DELAY") }}
{{ globalsetting("BARCODE_WEBCAM_SUPPORT") }}
{{ globalsetting("BARCODE_SHOW_TEXT") }}
{{ globalsetting("BARCODE_GENERATION_PLUGIN") }}
### Currencies
### Pricing and Currency
Configuration of currency support
Configuration of pricing data and currency support:
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Default Currency | Currency | Default currency | USD |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("INVENTREE_DEFAULT_CURRENCY") }}
{{ globalsetting("CURRENCY_CODES") }}
{{ globalsetting("PART_INTERNAL_PRICE") }}
{{ globalsetting("PART_BOM_USE_INTERNAL_PRICE") }}
{{ globalsetting("PRICING_DECIMAL_PLACES_MIN") }}
{{ globalsetting("PRICING_DECIMAL_PLACES") }}
{{ globalsetting("PRICING_UPDATE_DAYS") }}
{{ globalsetting("PRICING_USE_SUPPLIER_PRICING") }}
{{ globalsetting("PRICING_PURCHASE_HISTORY_OVERRIDES_SUPPLIER") }}
{{ globalsetting("PRICING_USE_STOCK_PRICING") }}
{{ globalsetting("PRICING_STOCK_ITEM_AGE_DAYS") }}
{{ globalsetting("PRICING_USE_VARIANT_PRICING") }}
{{ globalsetting("PRICING_ACTIVE_VARIANTS") }}
### Reporting
Configuration of report generation
Configuration of report generation:
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Enable Reports | Boolean | Enable report generation | False |
| Page Size | String | Default page size | A4 |
| Debug Mode | Boolean | Generate reports in debug mode (HTML output) | False |
| Test Reports | Boolean | Enable generation of test reports | False |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("REPORT_ENABLE") }}
{{ globalsetting("REPORT_DEFAULT_PAGE_SIZE") }}
{{ globalsetting("REPORT_DEBUG_MODE") }}
{{ globalsetting("REPORT_LOG_ERRORS") }}
{{ globalsetting("REPORT_ENABLE_TEST_REPORT") }}
{{ globalsetting("REPORT_ATTACH_TEST_REPORT") }}
### Parts
#### Main Settings
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| IPN Regex | String | Regular expression pattern for matching Part IPN | *blank* |
| Allow Duplicate IPN | Boolean | Allow multiple parts to share the same IPN | True |
| Allow Editing IPN | Boolean | Allow changing the IPN value while editing a part | True |
| Part Name Display Format | String | Format to display the part name | {% raw %}`{{ part.id if part.id }}{{ ' | ' if part.id }}{{ part.name }}{{ ' | ' if part.revision }}{{ part.revision if part.revision }}`{% endraw %} |
| Show Price History | Boolean | Display historical pricing for Part | False |
| Show Price in Forms | Boolean | Display part price in some forms | True |
| Show Price in BOM | Boolean | Include pricing information in BOM tables | True |
| Show related parts | Boolean | Display related parts for a part | True |
| Create initial stock | Boolean | Create initial stock on part creation | True |
#### Creation Settings
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Template | Boolean | Parts are templates by default | False |
| Assembly | Boolean | Parts can be assembled from other components by default | False |
| Component | Boolean | Parts can be used as sub-components by default | True |
| Trackable | Boolean | Parts are trackable by default | False |
| Purchaseable | Boolean | Parts are purchaseable by default | True |
| Salable | Boolean | Parts are salable by default | False |
| Virtual | Boolean | Parts are virtual by default | False |
#### Copy Settings
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Copy Part BOM Data | Boolean | Copy BOM data by default when duplicating a part | True |
| Copy Part Parameter Data | Boolean | Copy parameter data by default when duplicating a part | True |
| Copy Part Test Data | Boolean | Copy test data by default when duplicating a part | True |
| Copy Category Parameter Templates | Boolean | Copy category parameter templates when creating a part | True |
#### Internal Price Settings
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Internal Prices | Boolean | Enable internal prices for parts | False |
| Internal Price as BOM-Price | Boolean | Use the internal price (if set) in BOM-price calculations | False |
#### Part Import Setting
This section of the part settings allows staff users to:
- import parts to InvenTree clicking the <span class="badge inventree add"><span class='fas fa-plus-circle'></span> Import Part</span> button
- enable the ["Import Parts" tab in the part category view](../part/part.md#part-import).
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Show Import in Views | Boolean | Display the import wizard in some part views | True |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("PART_IPN_REGEX") }}
{{ globalsetting("PART_ALLOW_DUPLICATE_IPN") }}
{{ globalsetting("PART_ALLOW_EDIT_IPN") }}
{{ globalsetting("PART_ALLOW_DELETE_FROM_ASSEMBLY") }}
{{ globalsetting("PART_ENABLE_REVISION") }}
{{ globalsetting("PART_REVISION_ASSEMBLY_ONLY") }}
{{ globalsetting("PART_NAME_FORMAT") }}
{{ globalsetting("PART_SHOW_RELATED") }}
{{ globalsetting("PART_CREATE_INITIAL") }}
{{ globalsetting("PART_CREATE_SUPPLIER") }}
{{ globalsetting("PART_TEMPLATE") }}
{{ globalsetting("PART_ASSEMBLY") }}
{{ globalsetting("PART_COMPONENT") }}
{{ globalsetting("PART_TRACKABLE") }}
{{ globalsetting("PART_PURCHASEABLE") }}
{{ globalsetting("PART_SALABLE") }}
{{ globalsetting("PART_VIRTUAL") }}
{{ globalsetting("PART_COPY_BOM") }}
{{ globalsetting("PART_COPY_PARAMETERS") }}
{{ globalsetting("PART_COPY_TESTS") }}
{{ globalsetting("PART_CATEGORY_PARAMETERS") }}
{{ globalsetting("PART_CATEGORY_DEFAULT_ICON") }}
#### Part Parameter Templates
@ -153,45 +172,48 @@ After a list of parameters is added to a part category and upon creation of a ne
Configuration of stock item options
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Stock Expiry | Boolean | Enable stock expiry functionality | False |
| Stock Stale Time | Days | Number of days stock items are considered stale before expiring | 90 |
| Sell Expired Stock | Boolean | Allow sale of expired stock | False |
| Build Expired Stock | Boolean | Allow building with expired stock | False |
| Stock Ownership Control | Boolean | Enable ownership control functionality | False |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("SERIAL_NUMBER_GLOBALLY_UNIQUE") }}
{{ globalsetting("SERIAL_NUMBER_AUTOFILL") }}
{{ globalsetting("STOCK_DELETE_DEPLETED_DEFAULT") }}
{{ globalsetting("STOCK_BATCH_CODE_TEMPLATE") }}
{{ globalsetting("STOCK_ENABLE_EXPIRY") }}
{{ globalsetting("STOCK_STALE_DAYS") }}
{{ globalsetting("STOCK_ALLOW_EXPIRED_SALE") }}
{{ globalsetting("STOCK_ALLOW_EXPIRED_BUILD") }}
{{ globalsetting("STOCK_OWNERSHIP_CONTROL") }}
{{ globalsetting("STOCK_LOCATION_DEFAULT_ICON") }}
{{ globalsetting("STOCK_SHOW_INSTALLED_ITEMS") }}
{{ globalsetting("STOCK_ENFORCE_BOM_INSTALLATION") }}
{{ globalsetting("STOCK_ALLOW_OUT_OF_STOCK_TRANSFER") }}
{{ globalsetting("TEST_STATION_DATA") }}
### Build Orders
Options for build orders
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Reference Pattern | String | Pattern for defining Build Order reference values | {% raw %}BO-{ref:04d}{% endraw %} |
Refer to the [build order settings](../build/build.md#build-order-settings).
### Purchase Orders
Options for purchase orders
Refer to the [purchase order settings](../order/purchase_order.md#purchase-order-settings).
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Reference Pattern | String | Pattern for defining Purchase Order reference values | {% raw %}PO-{ref:04d}{% endraw %} |
### Sales Orders
### Sales orders
Refer to the [sales order settings](../order/sales_order.md#sales-order-settings).
Options for sales orders
### Return Orders
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Reference Pattern | String | Pattern for defining Sales Order reference values | {% raw %}SO-{ref:04d}{% endraw %} |
Refer to the [return order settings](../order/return_order.md#return-order-settings).
### Plugin Settings
Change into what parts plugins can integrate into.
| Setting | Type | Description | Default |
| --- | --- | --- | --- |
| Enable URL integration | Boolean | Enable plugins to add URL routes | False |
| Enable navigation integration | Boolean | Enable plugins to integrate into navigation | False |
| Enable setting integration | Boolean | Enable plugins to integrate into inventree settings | False |
| Enable app integration | Boolean | Enable plugins to add apps | False |
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("PLUGIN_ON_STARTUP") }}
{{ globalsetting("PLUGIN_UPDATE_CHECK") }}
{{ globalsetting("ENABLE_PLUGINS_URL") }}
{{ globalsetting("ENABLE_PLUGINS_NAVIGATION") }}
{{ globalsetting("ENABLE_PLUGINS_APP") }}
{{ globalsetting("ENABLE_PLUGINS_SCHEDULE") }}
{{ globalsetting("ENABLE_PLUGINS_EVENTS") }}

View File

@ -32,24 +32,44 @@ This screen allows the user to customize display of items on the InvenTree home
### Search Settings
Customize settings for search results
Customize settings for search results:
{% with id="user-search", url="settings/user_search.png", description="User Search Settings" %}
{% include 'img.html' %}
{% endwith %}
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ usersetting("SEARCH_WHOLE") }}
{{ usersetting("SEARCH_REGEX") }}
{{ usersetting("SEARCH_PREVIEW_RESULTS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_PARTS") }}
{{ usersetting("SEARCH_HIDE_INACTIVE_PARTS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_CATEGORIES") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_STOCK") }}
{{ usersetting("SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_LOCATIONS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_COMPANIES") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_BUILD_ORDERS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS") }}
{{ usersetting("SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_SALES_ORDERS") }}
{{ usersetting("SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS") }}
{{ usersetting("SEARCH_PREVIEW_SHOW_RETURN_ORDERS") }}
{{ usersetting("SEARCH_PREVIEW_EXCLUDE_INACTIVE_RETURN_ORDERS") }}
### Notifications
Settings related to notification messages
Settings related to notification messages:
{% with id="user-notification", url="settings/user_notifications.png", description="User Notification Settings" %}
{% include 'img.html' %}
{% endwith %}
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ usersetting("NOTIFICATION_ERROR_REPORT") }}
### Reporting
Settings for label printing and report generation
Settings for label printing and report generation:
{% with id="user-reporting", url="settings/user_reporting.png", description="User Reporting Settings" %}
{% include 'img.html' %}
{% endwith %}
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ usersetting("REPORT_INLINE") }}
{{ usersetting("LABEL_INLINE") }}
{{ usersetting("LABEL_DEFAULT_PRINTER") }}

View File

@ -4,7 +4,7 @@ title: Stock Test Result
## Stock Test Result
Stock items which are associated with a *trackable* part can have associated test data - this is particularly useful for tracking unit testing / commissioning / acceptance data against a serialized stock item.
Stock items which are associated with a [testable part](../part/part.md#testable) can have associated test data - this is particularly useful for tracking unit testing / commissioning / acceptance data against a serialized stock item.
The master "Part" record for the stock item can define multiple [test templates](../part/test.md), against which test data can be uploaded. Additionally, arbitrary test information can be assigned to the stock item.

View File

@ -1,5 +1,6 @@
"""Main entry point for the documentation build process."""
import json
import os
import subprocess
import textwrap
@ -7,6 +8,20 @@ import textwrap
import requests
import yaml
# Cached settings dict values
global GLOBAL_SETTINGS
global USER_SETTINGS
# Read in the InvenTree settings file
here = os.path.dirname(__file__)
settings_file = os.path.join(here, 'inventree_settings.json')
with open(settings_file, 'r') as sf:
settings = json.load(sf)
GLOBAL_SETTINGS = settings['global']
USER_SETTINGS = settings['user']
def get_repo_url(raw=False):
"""Return the repository URL for the current project."""
@ -219,3 +234,37 @@ def define_env(env):
)
return includefile(fn, f'Template: {base}', format='html')
@env.macro
def rendersetting(setting: dict):
"""Render a provided setting object into a table row."""
name = setting['name']
description = setting['description']
default = setting.get('default', None)
units = setting.get('units', None)
return f'| {name} | {description} | {default if default is not None else ""} | {units if units is not None else ""} |'
@env.macro
def globalsetting(key: str):
"""Extract information on a particular global setting.
Arguments:
- key: The name of the global setting to extract information for.
"""
global GLOBAL_SETTINGS
setting = GLOBAL_SETTINGS[key]
return rendersetting(setting)
@env.macro
def usersetting(key: str):
"""Extract information on a particular user setting.
Arguments:
- key: The name of the user setting to extract information for.
"""
global USER_SETTINGS
setting = USER_SETTINGS[key]
return rendersetting(setting)

View File

@ -305,9 +305,9 @@ mkdocs-git-revision-date-localized-plugin==1.2.6 \
--hash=sha256:e432942ce4ee8aa9b9f4493e993dee9d2cc08b3ea2b40a3d6b03ca0f2a4bcaa2 \
--hash=sha256:f015cb0f3894a39b33447b18e270ae391c4e25275cac5a626e80b243784e2692
# via -r docs/requirements.in
mkdocs-include-markdown-plugin==6.2.1 \
--hash=sha256:46fc372886d48eec541d36138d1fe1db42afd08b976ef7c8d8d4ea6ee4d5d1e8 \
--hash=sha256:8dfc3aee9435679b094cbdff023239e91d86cf357c40b0e99c28036449661830
mkdocs-include-markdown-plugin==6.2.2 \
--hash=sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7 \
--hash=sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d
# via -r docs/requirements.in
mkdocs-macros-plugin==1.0.5 \
--hash=sha256:f60e26f711f5a830ddf1e7980865bf5c0f1180db56109803cdd280073c1a050a \
@ -335,9 +335,9 @@ mkdocstrings-python==1.10.2 \
--hash=sha256:38a4fd41953defb458a107033440c229c7e9f98f35a24e84d888789c97da5a63 \
--hash=sha256:e8e596b37f45c09b67bec253e035fe18988af5bbbbf44e0ccd711742eed750e5
# via mkdocstrings
neoteroi-mkdocs==1.0.5 \
--hash=sha256:1f3b372dee79269157361733c0f45b3a89189077078e0e3224d829a144ef3579 \
--hash=sha256:29875ef444b08aec5619a384142e16f1b4e851465cab4e380fb2b8ae730fe046
neoteroi-mkdocs==1.1.0 \
--hash=sha256:609aae655e781c7aec517ab14759c34ce896b8132d1df4b9c2e504779c2e48ef \
--hash=sha256:9c59aebf83ca09d1d486bf8c0351e6ddfa912f09413d153ecabc5cd268a3155a
# via -r docs/requirements.in
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
@ -378,58 +378,60 @@ pytz==2024.1 \
--hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \
--hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319
# via mkdocs-git-revision-date-localized-plugin
pyyaml==6.0.1 \
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
pyyaml==6.0.2 \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
# via
# essentials-openapi
# mkdocs
@ -593,7 +595,7 @@ wcmatch==8.5.2 \
--hash=sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478 \
--hash=sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2
# via mkdocs-include-markdown-plugin
zipp==3.19.2 \
--hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \
--hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c
zipp==3.20.0 \
--hash=sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31 \
--hash=sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d
# via importlib-metadata

View File

@ -20,7 +20,7 @@ src = ["src/backend/InvenTree"]
"__init__.py" = ["D104"]
[tool.ruff.lint]
select = ["A", "B", "C4", "D", "I", "N"]
select = ["A", "B", "C4", "D", "I", "N", "F"]
# Things that should be enabled in the future:
# - LOG
# - DJ # for Django stuff

View File

@ -17,5 +17,6 @@ build:
- echo "Generating API schema file"
- pip install -U invoke
- invoke migrate
- invoke export-settings-definitions --filename docs/inventree_settings.json --overwrite
- invoke schema --filename docs/schema.yml --ignore-warnings
- python docs/extract_schema.py docs/schema.yml

View File

@ -8,7 +8,6 @@ from pathlib import Path
from django.conf import settings
from django.db import transaction
from django.http import JsonResponse
from django.urls import include, path
from django.utils.translation import gettext_lazy as _
from django_q.models import OrmQ
@ -21,9 +20,7 @@ from rest_framework.views import APIView
import InvenTree.version
import users.models
from InvenTree.filters import SEARCH_ORDER_FILTER
from InvenTree.mixins import ListCreateAPI
from InvenTree.permissions import RolePermission
from InvenTree.templatetags.inventree_extras import plugins_info
from part.models import Part
from plugin.serializers import MetadataSerializer

View File

@ -1,13 +1,34 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 235
INVENTREE_API_VERSION = 241
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v241 - 2024-09-18 : https://github.com/inventree/InvenTree/pull/7906
- Adjusts required fields for the MeUserDetail endpoint
v240 - 2024-08-16 : https://github.com/inventree/InvenTree/pull/7900
- Adjust "issued_by" filter for the BuildOrder list endpoint
- Adjust "assigned_to" filter for the BuildOrder list endpoint
v239 - 2024-08-15 : https://github.com/inventree/InvenTree/pull/7888
- Adds "testable" field to the Part model
- Adds associated filters to various API endpoints
v238 - 2024-08-14 : https://github.com/inventree/InvenTree/pull/7874
- Add "assembly" filter to BuildLine API endpoint
v237 - 2024-08-13 : https://github.com/inventree/InvenTree/pull/7863
- Reimplement "bulk delete" operation for Attachment model
- Fix permission checks for Attachment API endpoints
v236 - 2024-08-10 : https://github.com/inventree/InvenTree/pull/7844
- Adds "supplier_name" to the PurchaseOrder API serializer
v235 - 2024-08-08 : https://github.com/inventree/InvenTree/pull/7837
- Adds "on_order" quantity to SalesOrderLineItem serializer
- Adds "building" quantity to SalesOrderLineItem serializer

View File

@ -11,7 +11,7 @@ from django.core.exceptions import AppRegistryNotReady
from django.db import transaction
from django.db.utils import IntegrityError, OperationalError
from allauth.socialaccount.signals import social_account_added, social_account_updated
from allauth.socialaccount.signals import social_account_updated
import InvenTree.conversion
import InvenTree.ready

View File

@ -153,7 +153,7 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
if unit:
try:
valid = unit in ureg
except Exception as exc:
except Exception:
valid = False
if not valid:
@ -196,7 +196,7 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
try:
value = convert_value(attempt, unit)
break
except Exception as exc:
except Exception:
value = None
if value is None:

View File

@ -9,7 +9,6 @@ import traceback
from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db.utils import IntegrityError, OperationalError
from django.utils.translation import gettext_lazy as _
import rest_framework.views as drfviews

View File

@ -3,7 +3,6 @@
import datetime
import hashlib
import io
import json
import logging
import os
import os.path
@ -27,7 +26,6 @@ from bleach import clean
from djmoney.money import Money
from PIL import Image
import InvenTree.version
from common.currency import currency_code_default
from .settings import MEDIA_URL, STATIC_URL

View File

@ -36,8 +36,6 @@ def get_base_url(request=None):
3. If settings.SITE_URL is set (e.g. in the Django settings), use that
4. If the InvenTree setting INVENTREE_BASE_URL is set, use that
"""
import common.models
# Check if a request is provided
if request:
return request.build_absolute_uri('/')
@ -104,8 +102,6 @@ def download_image_from_url(remote_url, timeout=2.5):
ValueError: Server responded with invalid 'Content-Length' value
TypeError: Response is not a valid image
"""
import common.models
# Check that the provided URL at least looks valid
validator = URLValidator()
validator(remote_url)
@ -203,8 +199,6 @@ def render_currency(
max_decimal_places: The maximum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
include_symbol: If True, include the currency symbol in the output
"""
import common.models
if money in [None, '']:
return '-'

View File

@ -0,0 +1,53 @@
"""Custom management command to export settings definitions.
This is used to generate a JSON file which contains all of the settings,
so that they can be introspected by the InvenTree documentation system.
This in turn allows settings to be documented in the InvenTree documentation,
without having to manually duplicate the information in multiple places.
"""
import json
from django.core.management.base import BaseCommand
class Command(BaseCommand):
"""Extract settings information, and export to a JSON file."""
def add_arguments(self, parser):
"""Add custom arguments for this command."""
parser.add_argument(
'filename', type=str, help='Output filename for settings definitions'
)
def handle(self, *args, **kwargs):
"""Export settings information to a JSON file."""
from common.models import InvenTreeSetting, InvenTreeUserSetting
settings = {'global': {}, 'user': {}}
# Global settings
for key, setting in InvenTreeSetting.SETTINGS.items():
settings['global'][key] = {
'name': str(setting['name']),
'description': str(setting['description']),
'default': str(InvenTreeSetting.get_setting_default(key)),
'units': str(setting.get('units', '')),
}
# User settings
for key, setting in InvenTreeUserSetting.SETTINGS.items():
settings['user'][key] = {
'name': str(setting['name']),
'description': str(setting['description']),
'default': str(InvenTreeUserSetting.get_setting_default(key)),
'units': str(setting.get('units', '')),
}
filename = kwargs.get('filename', 'inventree_settings.json')
with open(filename, 'w') as f:
json.dump(settings, f, indent=4)
print(f"Exported InvenTree settings definitions to '{filename}'")

View File

@ -93,7 +93,7 @@ class PluginValidationMixin(DiffMixin):
return
except ValidationError as exc:
raise exc
except Exception as exc:
except Exception:
# Log the exception to the database
import InvenTree.exceptions

View File

@ -402,17 +402,17 @@ class UserSerializer(InvenTreeModelSerializer):
model = User
fields = ['pk', 'username', 'first_name', 'last_name', 'email']
read_only_fields = ['username']
read_only_fields = ['username', 'email']
username = serializers.CharField(label=_('Username'), help_text=_('Username'))
first_name = serializers.CharField(
label=_('First Name'), help_text=_('First name of the user')
label=_('First Name'), help_text=_('First name of the user'), allow_blank=True
)
last_name = serializers.CharField(
label=_('Last Name'), help_text=_('Last name of the user')
label=_('Last Name'), help_text=_('Last name of the user'), allow_blank=True
)
email = serializers.EmailField(
label=_('Email'), help_text=_('Email address of the user')
label=_('Email'), help_text=_('Email address of the user'), allow_blank=True
)

View File

@ -18,7 +18,6 @@ import django.conf.locale
import django.core.exceptions
from django.core.validators import URLValidator
from django.http import Http404
from django.utils.translation import gettext_lazy as _
import pytz
from dotenv import load_dotenv
@ -301,7 +300,7 @@ if (
and get_boolean_setting('INVENTREE_DEBUG_SHELL', 'debug_shell', False)
): # noqa
try:
import django_admin_shell
import django_admin_shell # noqa: F401
INSTALLED_APPS.append('django_admin_shell')
ADMIN_SHELL_ENABLE = True

View File

@ -4,6 +4,6 @@ This file remains here for backwards compatibility,
as external plugins may import status codes from this file.
"""
from build.status_codes import *
from order.status_codes import *
from stock.status_codes import *
from build.status_codes import * # noqa: F403
from order.status_codes import * # noqa: F403
from stock.status_codes import * # noqa: F403

View File

@ -9,7 +9,6 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin
from common.models import InvenTreeSetting
from InvenTree import sso
from InvenTree.forms import RegistratonMixin
from InvenTree.unit_test import InvenTreeTestCase
class Dummy:

View File

@ -1,6 +1,5 @@
"""Test general functions and helpers."""
import json
import os
import time
from datetime import datetime, timedelta

View File

@ -117,7 +117,7 @@ def inventreeDocUrl():
def inventreeAppUrl():
"""Return URL for InvenTree app site."""
return f'https://docs.inventree.org/app/'
return 'https://docs.inventree.org/app/'
def inventreeCreditsUrl():

View File

@ -37,7 +37,6 @@ class BuildFilter(rest_filters.FilterSet):
'parent',
'sales_order',
'part',
'issued_by',
]
status = rest_filters.NumberFilter(label='Status')
@ -58,7 +57,10 @@ class BuildFilter(rest_filters.FilterSet):
return queryset.filter(Build.OVERDUE_FILTER)
return queryset.exclude(Build.OVERDUE_FILTER)
assigned_to_me = rest_filters.BooleanFilter(label='assigned_to_me', method='filter_assigned_to_me')
assigned_to_me = rest_filters.BooleanFilter(
label=_('Assigned to me'),
method='filter_assigned_to_me'
)
def filter_assigned_to_me(self, queryset, name, value):
"""Filter by orders which are assigned to the current user."""
@ -71,10 +73,33 @@ class BuildFilter(rest_filters.FilterSet):
return queryset.filter(responsible__in=owners)
return queryset.exclude(responsible__in=owners)
assigned_to = rest_filters.NumberFilter(label='responsible', method='filter_responsible')
issued_by = rest_filters.ModelChoiceFilter(
queryset=Owner.objects.all(),
label=_('Issued By'),
method='filter_issued_by'
)
def filter_responsible(self, queryset, name, value):
def filter_issued_by(self, queryset, name, owner):
"""Filter by 'owner' which issued the order."""
if owner.label() == 'user':
user = User.objects.get(pk=owner.owner_id)
return queryset.filter(issued_by=user)
elif owner.label() == 'group':
group = User.objects.filter(groups__pk=owner.owner_id)
return queryset.filter(issued_by__in=group)
else:
return queryset.none()
assigned_to = rest_filters.ModelChoiceFilter(
queryset=Owner.objects.all(),
field_name='responsible',
label=_('Assigned To')
)
def filter_responsible(self, queryset, name, owner):
"""Filter by orders which are assigned to the specified owner."""
owners = list(Owner.objects.filter(pk=value))
# if we query by a user, also find all ownerships through group memberships
@ -290,7 +315,9 @@ class BuildLineFilter(rest_filters.FilterSet):
# Fields on related models
consumable = rest_filters.BooleanFilter(label=_('Consumable'), field_name='bom_item__consumable')
optional = rest_filters.BooleanFilter(label=_('Optional'), field_name='bom_item__optional')
assembly = rest_filters.BooleanFilter(label=_('Assembly'), field_name='bom_item__sub_part__assembly')
tracked = rest_filters.BooleanFilter(label=_('Tracked'), field_name='bom_item__sub_part__trackable')
testable = rest_filters.BooleanFilter(label=_('Testable'), field_name='bom_item__sub_part__testable')
allocated = rest_filters.BooleanFilter(label=_('Allocated'), method='filter_allocated')

View File

@ -395,9 +395,9 @@ class Build(
def sub_builds(self, cascade=True):
"""Return all Build Order objects under this one."""
if cascade:
return Build.objects.filter(parent=self.pk)
descendants = self.get_descendants(include_self=True)
Build.objects.filter(parent__pk__in=[d.pk for d in descendants])
return self.get_descendants(include_self=False)
else:
return self.get_children()
def sub_build_count(self, cascade=True):
"""Return the number of sub builds under this one.
@ -407,6 +407,11 @@ class Build(
"""
return self.sub_builds(cascade=cascade).count()
@property
def has_open_child_builds(self):
"""Return True if this build order has any open child builds."""
return self.sub_builds().filter(status__in=BuildStatusGroups.ACTIVE_CODES).exists()
@property
def is_overdue(self):
"""Returns true if this build is "overdue".
@ -576,6 +581,9 @@ class Build(
- Untracked parts must be allocated
"""
if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and self.has_open_child_builds:
return False
if self.status != BuildStatus.PRODUCTION.value:
return False
@ -619,6 +627,10 @@ class Build(
trim_allocated_stock = kwargs.pop('trim_allocated_stock', False)
user = kwargs.pop('user', None)
# Prevent completion if there are open child builds
if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and self.has_open_child_builds:
return
if self.incomplete_count > 0:
return
@ -974,7 +986,10 @@ class Build(
items_to_save = []
items_to_delete = []
for build_line in self.untracked_line_items:
lines = self.untracked_line_items
lines = lines.prefetch_related('allocations')
for build_line in lines:
reduce_by = build_line.allocated_quantity() - build_line.quantity

View File

@ -27,6 +27,7 @@ from stock.serializers import StockItemSerializerBrief, LocationBriefSerializer
import common.models
from common.serializers import ProjectCodeSerializer
from common.settings import get_global_setting
from importer.mixins import DataImportExportSerializerMixin
import company.serializers
import part.filters
@ -765,6 +766,9 @@ class BuildCompleteSerializer(serializers.Serializer):
"""Perform validation of this serializer prior to saving"""
build = self.context['build']
if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and build.has_open_child_builds:
raise ValidationError(_("Build order has open child build orders"))
if build.status != BuildStatus.PRODUCTION.value:
raise ValidationError(_("Build order must be in production state"))
@ -1234,6 +1238,7 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
'reference',
'consumable',
'optional',
'testable',
'trackable',
'inherited',
'allow_variants',
@ -1278,6 +1283,7 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
reference = serializers.CharField(source='bom_item.reference', label=_('Reference'), read_only=True)
consumable = serializers.BooleanField(source='bom_item.consumable', label=_('Consumable'), read_only=True)
optional = serializers.BooleanField(source='bom_item.optional', label=_('Optional'), read_only=True)
testable = serializers.BooleanField(source='bom_item.sub_part.testable', label=_('Testable'), read_only=True)
trackable = serializers.BooleanField(source='bom_item.sub_part.trackable', label=_('Trackable'), read_only=True)
inherited = serializers.BooleanField(source='bom_item.inherited', label=_('Inherited'), read_only=True)
allow_variants = serializers.BooleanField(source='bom_item.allow_variants', label=_('Allow Variants'), read_only=True)

View File

@ -332,7 +332,7 @@ src="{% static 'img/blank_image.png' %}"
});
});
{% if build.part.trackable > 0 %}
{% if build.part.testable %}
onPanelLoad("test-statistics", function() {
prepareTestStatisticsTable('build', '{% url "api-test-statistics-by-build" build.pk %}')
});

View File

@ -388,6 +388,7 @@ onPanelLoad('outputs', function() {
source_location: {{ build.take_from.pk }},
{% endif %}
tracked_parts: true,
testable: {% js_bool build.part.testable %},
trackable: {% js_bool build.part.trackable %}
};

View File

@ -20,7 +20,7 @@
{% include "sidebar_item.html" with label='consumed' text=text icon="fa-tasks" %}
{% trans "Child Build Orders" as text %}
{% include "sidebar_item.html" with label='children' text=text icon="fa-sitemap" %}
{% if build.part.trackable %}
{% if build.part.testable %}
{% trans "Test Statistics" as text %}
{% include "sidebar_item.html" with label='test-statistics' text=text icon="fa-chart-line" %}
{% endif %}

View File

@ -1015,7 +1015,7 @@ class BuildOverallocationTest(BuildAPITest):
'accept_overallocated': 'trim',
},
expected_code=201,
max_query_count=555, # TODO: Come back and refactor this
max_query_count=600, # TODO: Come back and refactor this
)
self.build.refresh_from_db()

View File

@ -4,6 +4,7 @@ import json
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.http.response import HttpResponse
from django.urls import include, path, re_path
@ -706,7 +707,7 @@ class AttachmentFilter(rest_filters.FilterSet):
return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct()
class AttachmentList(ListCreateAPI):
class AttachmentList(BulkDeleteMixin, ListCreateAPI):
"""List API endpoint for Attachment objects."""
queryset = common.models.Attachment.objects.all()
@ -725,6 +726,24 @@ class AttachmentList(ListCreateAPI):
attachment.upload_user = self.request.user
attachment.save()
def validate_delete(self, queryset, request) -> None:
"""Ensure that the user has correct permissions for a bulk-delete.
- Extract all model types from the provided queryset
- Ensure that the user has correct 'delete' permissions for each model
"""
from common.validators import attachment_model_class_from_label
from users.models import check_user_permission
model_types = queryset.values_list('model_type', flat=True).distinct()
for model_type in model_types:
if model_class := attachment_model_class_from_label(model_type):
if not check_user_permission(request.user, model_class, 'delete'):
raise ValidationError(
_('User does not have permission to delete these attachments')
)
class AttachmentDetail(RetrieveUpdateDestroyAPI):
"""Detail API endpoint for Attachment objects."""

View File

@ -1838,6 +1838,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False,
'validator': bool,
},
'BUILDORDER_REQUIRE_CLOSED_CHILDS': {
'name': _('Require Closed Child Orders'),
'description': _(
'Prevent build order completion until all child orders are closed'
),
'default': False,
'validator': bool,
},
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
'name': _('Block Until Tests Pass'),
'description': _(

View File

@ -540,12 +540,17 @@ class AttachmentSerializer(InvenTreeModelSerializer):
allow_null=False,
)
def save(self):
def save(self, **kwargs):
"""Override the save method to handle the model_type field."""
from InvenTree.models import InvenTreeAttachmentMixin
from users.models import check_user_permission
model_type = self.validated_data.get('model_type', None)
if model_type is None:
if self.instance:
model_type = self.instance.model_type
# Ensure that the user has permission to attach files to the specified model
user = self.context.get('request').user
@ -556,15 +561,18 @@ class AttachmentSerializer(InvenTreeModelSerializer):
if not issubclass(target_model_class, InvenTreeAttachmentMixin):
raise PermissionDenied(_('Invalid model type specified for attachment'))
permission_error_msg = _(
'User does not have permission to create or edit attachments for this model'
)
if not check_user_permission(user, target_model_class, 'change'):
raise PermissionDenied(permission_error_msg)
# Check that the user has the required permissions to attach files to the target model
if not target_model_class.check_attachment_permission('change', user):
raise PermissionDenied(
_(
'User does not have permission to create or edit attachments for this model'
)
)
raise PermissionDenied(_(permission_error_msg))
return super().save()
return super().save(**kwargs)
class IconSerializer(serializers.Serializer):

View File

@ -157,6 +157,29 @@ class AttachmentTest(InvenTreeAPITestCase):
# Upload should now work!
response = self.post(url, data, expected_code=201)
pk = response.data['pk']
# Edit the attachment via API
response = self.patch(
reverse('api-attachment-detail', kwargs={'pk': pk}),
{'comment': 'New comment'},
expected_code=200,
)
self.assertEqual(response.data['comment'], 'New comment')
attachment = Attachment.objects.get(pk=pk)
self.assertEqual(attachment.comment, 'New comment')
# And check that we cannot edit the attachment without the correct permissions
self.clearRoles()
self.patch(
reverse('api-attachment-detail', kwargs={'pk': pk}),
{'comment': 'New comment 2'},
expected_code=403,
)
# Try to delete the attachment via API (should fail)
attachment = part.attachments.first()
url = reverse('api-attachment-detail', kwargs={'pk': attachment.pk})
@ -407,8 +430,6 @@ class SettingsTest(InvenTreeTestCase):
@override_settings(SITE_URL=None, PLUGIN_TESTING=True, PLUGIN_TESTING_SETUP=True)
def test_defaults(self):
"""Populate the settings with default values."""
N = len(InvenTreeSetting.SETTINGS.keys())
for key in InvenTreeSetting.SETTINGS.keys():
value = InvenTreeSetting.get_setting_default(key)
@ -1103,7 +1124,6 @@ class CommonTest(InvenTreeAPITestCase):
def test_restart_flag(self):
"""Test that the restart flag is reset on start."""
import common.models
from plugin import registry
# set flag true

View File

@ -45,7 +45,7 @@ def validate_attachment_model_type(value):
"""Ensure that the provided attachment model is valid."""
model_names = [el[0] for el in attachment_model_options()]
if value not in model_names:
raise ValidationError(f'Model type does not support attachments')
raise ValidationError('Model type does not support attachments')
def validate_notes_model_type(value):

View File

@ -1,7 +1,6 @@
"""Admin site specification for the 'importer' app."""
from django.contrib import admin
from django.urls import path
import importer.models
import importer.registry

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
"""Django admin interface for the machine app."""
from django.contrib import admin
from django.http.request import HttpRequest
from machine import models

View File

@ -13,7 +13,7 @@ logger = logging.getLogger('inventree')
class MachineRegistry(
get_shared_class_instance_state_mixin(lambda _x: f'machine:registry')
get_shared_class_instance_state_mixin(lambda _x: 'machine:registry')
):
"""Machine registry class."""
@ -44,7 +44,7 @@ class MachineRegistry(
"""Initialize the machine registry."""
# clear cache for machines (only needed for global redis cache)
if main and hasattr(cache, 'delete_pattern'): # pragma: no cover
cache.delete_pattern(f'machine:*')
cache.delete_pattern('machine:*')
self.discover_machine_types()
self.discover_drivers()

View File

@ -292,7 +292,7 @@ class TestLabelPrinterMachineType(TestMachineRegistryMixin, InvenTreeAPITestCase
# test the single print label method calls
self.assertEqual(self.print_label.call_count, 2)
self.assertEqual(self.print_label.call_args.args[0], self.machine.machine)
self.assertEqual(self.print_label.call_args.args[1], label)
self.assertEqual(self.print_label.call_args.args[1], template)
self.assertEqual(self.print_label.call_args.args[2], parts[1])
self.assertIn('printing_options', self.print_labels.call_args.kwargs)
self.assertEqual(

View File

@ -77,7 +77,7 @@ class OrderFilter(rest_filters.FilterSet):
"""Base class for custom API filters for the OrderList endpoint."""
# Filter against order status
status = rest_filters.NumberFilter(label='Order Status', method='filter_status')
status = rest_filters.NumberFilter(label=_('Order Status'), method='filter_status')
def filter_status(self, queryset, name, value):
"""Filter by integer status code."""
@ -85,11 +85,11 @@ class OrderFilter(rest_filters.FilterSet):
# Exact match for reference
reference = rest_filters.CharFilter(
label='Filter by exact reference', field_name='reference', lookup_expr='iexact'
label=_('Order Reference'), field_name='reference', lookup_expr='iexact'
)
assigned_to_me = rest_filters.BooleanFilter(
label='assigned_to_me', method='filter_assigned_to_me'
label=_('Assigned to me'), method='filter_assigned_to_me'
)
def filter_assigned_to_me(self, queryset, name, value):
@ -113,7 +113,7 @@ class OrderFilter(rest_filters.FilterSet):
return queryset.exclude(self.Meta.model.overdue_filter())
outstanding = rest_filters.BooleanFilter(
label='outstanding', method='filter_outstanding'
label=_('Outstanding'), method='filter_outstanding'
)
def filter_outstanding(self, queryset, name, value):
@ -123,11 +123,13 @@ class OrderFilter(rest_filters.FilterSet):
return queryset.exclude(status__in=self.Meta.model.get_status_class().OPEN)
project_code = rest_filters.ModelChoiceFilter(
queryset=common.models.ProjectCode.objects.all(), field_name='project_code'
queryset=common.models.ProjectCode.objects.all(),
field_name='project_code',
label=_('Project Code'),
)
has_project_code = rest_filters.BooleanFilter(
label='has_project_code', method='filter_has_project_code'
method='filter_has_project_code', label=_('Has Project Code')
)
def filter_has_project_code(self, queryset, name, value):
@ -137,7 +139,7 @@ class OrderFilter(rest_filters.FilterSet):
return queryset.filter(project_code=None)
assigned_to = rest_filters.ModelChoiceFilter(
queryset=Owner.objects.all(), field_name='responsible'
queryset=Owner.objects.all(), field_name='responsible', label=_('Responsible')
)

View File

@ -1,7 +1,6 @@
"""Order model definitions."""
import logging
import os
import sys
from datetime import datetime
from decimal import Decimal
@ -1162,7 +1161,8 @@ class SalesOrder(TotalPriceMixin, Order):
# Schedule pricing update for any referenced parts
for line in self.lines.all():
line.part.schedule_pricing_update(create=True)
if line.part:
line.part.schedule_pricing_update(create=True)
trigger_event('salesorder.completed', id=self.pk)

View File

@ -231,6 +231,7 @@ class PurchaseOrderSerializer(
'supplier',
'supplier_detail',
'supplier_reference',
'supplier_name',
'total_price',
'order_currency',
])
@ -278,6 +279,10 @@ class PurchaseOrderSerializer(
return queryset
supplier_name = serializers.CharField(
source='supplier.name', read_only=True, label=_('Supplier Name')
)
supplier_detail = CompanyBriefSerializer(
source='supplier', many=False, read_only=True
)

View File

@ -1126,9 +1126,9 @@ class PartFilter(rest_filters.FilterSet):
# TODO: We should cache BOM checksums to make this process more efficient
pks = []
for part in queryset:
if part.is_bom_valid() == value:
pks.append(part.pk)
for item in queryset:
if item.is_bom_valid() == value:
pks.append(item.pk)
return queryset.filter(pk__in=pks)
@ -1157,6 +1157,8 @@ class PartFilter(rest_filters.FilterSet):
trackable = rest_filters.BooleanFilter()
testable = rest_filters.BooleanFilter()
purchaseable = rest_filters.BooleanFilter()
salable = rest_filters.BooleanFilter()
@ -1748,20 +1750,28 @@ class BomFilter(rest_filters.FilterSet):
# Filters for linked 'part'
part_active = rest_filters.BooleanFilter(
label='Master part is active', field_name='part__active'
label='Assembly part is active', field_name='part__active'
)
part_trackable = rest_filters.BooleanFilter(
label='Master part is trackable', field_name='part__trackable'
label='Assembly part is trackable', field_name='part__trackable'
)
part_testable = rest_filters.BooleanFilter(
label=_('Assembly part is testable'), field_name='part__testable'
)
# Filters for linked 'sub_part'
sub_part_trackable = rest_filters.BooleanFilter(
label='Sub part is trackable', field_name='sub_part__trackable'
label='Component part is trackable', field_name='sub_part__trackable'
)
sub_part_testable = rest_filters.BooleanFilter(
label=_('Component part is testable'), field_name='sub_part__testable'
)
sub_part_assembly = rest_filters.BooleanFilter(
label='Sub part is an assembly', field_name='sub_part__assembly'
label='Component part is an assembly', field_name='sub_part__assembly'
)
available_stock = rest_filters.BooleanFilter(

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