mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cc45c23915 | ||
|
3951b3f56e | ||
|
f725c38804 | ||
|
3961e48fe8 | ||
|
368d59ee55 | ||
|
3911694342 | ||
|
99e37de1d8 | ||
|
9afc6cc6cb | ||
|
99e822213d | ||
|
c096f2f43a | ||
|
93e06a0db5 | ||
|
bcaf298ce3 | ||
|
d5e8227782 | ||
|
7d844e02be | ||
|
450abcd353 | ||
|
313cb4758e | ||
|
594c15b3e3 | ||
|
820a3522da | ||
|
10103e7d76 | ||
|
1634258783 | ||
|
bcbbae0a18 | ||
|
7b750dd04f | ||
|
2d9cce87fe | ||
|
d1c8354859 | ||
|
881220cdb3 | ||
|
d98c396f07 | ||
|
695174810a | ||
|
7e6ca121ec | ||
|
2cf959cb8d | ||
|
eec53ffd82 | ||
|
ebb01c5e5b | ||
|
8a59829ef1 | ||
|
ed2da62a46 | ||
|
df8efa902e | ||
|
d647471588 | ||
|
8f4929d754 | ||
|
b9b44126de | ||
|
cf9dcf0556 | ||
|
58f60d18b6 | ||
|
1dff94db75 | ||
|
ca87df3c3d | ||
|
b0c7a1aed5 | ||
|
725181be5f | ||
|
d5086b2fb1 | ||
|
0c63e509d2 | ||
|
8474b7bf4c | ||
|
7709d8df70 | ||
|
e837e5d7d7 | ||
|
f2f90dd1e4 | ||
|
bbd432c03f | ||
|
9239c82113 | ||
|
8c6275b845 | ||
|
5515e0762e | ||
|
dbe12c2c53 | ||
|
7cbaeb159e | ||
|
6591286e27 | ||
|
0c30f7cc99 | ||
|
9df388da2f | ||
|
9e481ffb0c | ||
|
a7d9600c3d | ||
|
4b140aba8d | ||
|
6572e64144 | ||
|
453dac6d00 | ||
|
d6218b76ff | ||
|
d7d908b74f | ||
|
0ee06ec13e | ||
|
9fbaeba2ab | ||
|
2e10400d7c | ||
|
48ee876d79 | ||
|
05e234fc49 | ||
|
e6765fc7df | ||
|
286091c1e5 | ||
|
5c9f50b43c | ||
|
ea89a4d629 | ||
|
8a0d22d15d | ||
|
8bf4ce573f | ||
|
8a52bd60af | ||
|
7178b95657 | ||
|
ed2ac0f1da | ||
|
8b44dfbc4e | ||
|
09c4710107 | ||
|
e91d741785 | ||
|
3880e6f07f | ||
|
70a52c9385 | ||
|
f14094c1e1 | ||
|
aed5516334 | ||
|
8369b4a44a | ||
|
19ca7bffae | ||
|
e1b0efaa12 | ||
|
697ab1653a | ||
|
2244f5fb27 | ||
|
b0a864a618 | ||
|
a37d21856e | ||
|
27fba9cd02 | ||
|
831b129711 | ||
|
d3c7698029 | ||
|
3f2e47497c | ||
|
6f396eca07 | ||
|
41f6dd69b8 | ||
|
6cf56845e2 | ||
|
e6873d2871 | ||
|
7a97ecfc3d | ||
|
42183a3a3f | ||
|
556a3161e8 | ||
|
3733e8a417 | ||
|
8eea8812e4 | ||
|
d68d52ba88 | ||
|
21f623eea8 | ||
|
a5564090bb | ||
|
90a918e6d2 | ||
|
dce6cf6b01 | ||
|
09bc06108c | ||
|
5d64279e1e | ||
|
f236e01cc3 | ||
|
e0a878467b | ||
|
1d19196632 | ||
|
5a98d1e239 | ||
|
0e8c2973b2 | ||
|
25f162f4b2 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@ -1,5 +1,3 @@
|
||||
github: inventree
|
||||
ko_fi: inventree
|
||||
patreon: inventree
|
||||
polar: inventree
|
||||
github: inventree
|
||||
custom: [paypal.me/inventree]
|
||||
|
4
.github/scripts/check_js_templates.py
vendored
4
.github/scripts/check_js_templates.py
vendored
@ -71,7 +71,7 @@ def check_prohibited_tags(data):
|
||||
for filename in pathlib.Path(js_i18n_dir).rglob('*.js'):
|
||||
print(f"Checking file 'translated/{os.path.basename(filename)}':")
|
||||
|
||||
with open(filename, 'r') as js_file:
|
||||
with open(filename, encoding='utf-8') as js_file:
|
||||
data = js_file.readlines()
|
||||
|
||||
errors += check_invalid_tag(data)
|
||||
@ -81,7 +81,7 @@ for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'):
|
||||
print(f"Checking file 'dynamic/{os.path.basename(filename)}':")
|
||||
|
||||
# Check that the 'dynamic' files do not contains any translated strings
|
||||
with open(filename, 'r') as js_file:
|
||||
with open(filename, encoding='utf-8') as js_file:
|
||||
data = js_file.readlines()
|
||||
|
||||
invalid_tags = ['blocktrans', 'blocktranslate', 'trans', 'translate']
|
||||
|
4
.github/scripts/check_migration_files.py
vendored
4
.github/scripts/check_migration_files.py
vendored
@ -20,9 +20,9 @@ for line in str(out.decode()).split('\n'):
|
||||
if len(migrations) == 0:
|
||||
sys.exit(0)
|
||||
|
||||
print('There are {n} unstaged migration files:'.format(n=len(migrations)))
|
||||
print(f'There are {len(migrations)} unstaged migration files:')
|
||||
|
||||
for m in migrations:
|
||||
print(' - {m}'.format(m=m))
|
||||
print(f' - {m}')
|
||||
|
||||
sys.exit(len(migrations))
|
||||
|
24
.github/scripts/version_check.py
vendored
24
.github/scripts/version_check.py
vendored
@ -10,6 +10,7 @@ tagged branch:
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -22,7 +23,7 @@ REPO = os.getenv('GITHUB_REPOSITORY', 'inventree/inventree')
|
||||
GITHUB_API_URL = os.getenv('GITHUB_API_URL', 'https://api.github.com')
|
||||
|
||||
|
||||
def get_existing_release_tags():
|
||||
def get_existing_release_tags(include_prerelease=True):
|
||||
"""Request information on existing releases via the GitHub API."""
|
||||
# Check for github token
|
||||
token = os.getenv('GITHUB_TOKEN', None)
|
||||
@ -51,6 +52,9 @@ def get_existing_release_tags():
|
||||
print(f"Version '{tag}' did not match expected pattern")
|
||||
continue
|
||||
|
||||
if not include_prerelease and release['prerelease']:
|
||||
continue
|
||||
|
||||
tags.append([int(x) for x in match.groups()])
|
||||
|
||||
return tags
|
||||
@ -74,7 +78,7 @@ def check_version_number(version_string, allow_duplicate=False):
|
||||
version_tuple = [int(x) for x in match.groups()]
|
||||
|
||||
# Look through the existing releases
|
||||
existing = get_existing_release_tags()
|
||||
existing = get_existing_release_tags(include_prerelease=False)
|
||||
|
||||
# Assume that this is the highest release, unless told otherwise
|
||||
highest_release = True
|
||||
@ -85,7 +89,7 @@ def check_version_number(version_string, allow_duplicate=False):
|
||||
|
||||
if release > version_tuple:
|
||||
highest_release = False
|
||||
print(f'Found newer release: {str(release)}')
|
||||
print(f'Found newer release: {release!s}')
|
||||
|
||||
return highest_release
|
||||
|
||||
@ -130,7 +134,7 @@ if __name__ == '__main__':
|
||||
|
||||
version = None
|
||||
|
||||
with open(version_file, 'r') as f:
|
||||
with open(version_file, encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
# Extract the InvenTree software version
|
||||
@ -171,10 +175,7 @@ if __name__ == '__main__':
|
||||
print(f"Version number '{version}' does not match tag '{version_tag}'")
|
||||
sys.exit
|
||||
|
||||
if highest_release:
|
||||
docker_tags = [version_tag, 'stable']
|
||||
else:
|
||||
docker_tags = [version_tag]
|
||||
docker_tags = [version_tag, 'stable'] if highest_release else [version_tag]
|
||||
|
||||
elif GITHUB_REF_TYPE == 'branch':
|
||||
# Otherwise we know we are targeting the 'master' branch
|
||||
@ -195,10 +196,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:
|
||||
with open(os.getenv('GITHUB_ENV'), 'a', encoding='utf-8') 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')
|
||||
|
||||
|
10
.github/workflows/docker.yaml
vendored
10
.github/workflows/docker.yaml
vendored
@ -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@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # pin@v6.7.0
|
||||
with:
|
||||
context: .
|
||||
file: ./contrib/container/Dockerfile
|
||||
|
8
.github/workflows/qc_checks.yaml
vendored
8
.github/workflows/qc_checks.yaml
vendored
@ -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
|
||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
- name: Build frontend
|
||||
run: cd src/frontend && npm run compile && npm run build
|
||||
- name: Create SBOM for frontend
|
||||
uses: anchore/sbom-action@v0
|
||||
uses: anchore/sbom-action@61119d458adab75f756bc0b9e4bde25725f86a7a # pin@v0
|
||||
with:
|
||||
artifact-name: frontend-build.spdx
|
||||
path: src/frontend
|
||||
@ -63,7 +63,7 @@ jobs:
|
||||
zip -r ../frontend-build.zip * .vite
|
||||
- name: Attest Build Provenance
|
||||
id: attest
|
||||
uses: actions/attest-build-provenance@v1
|
||||
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # pin@v1
|
||||
with:
|
||||
subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip"
|
||||
|
||||
|
6
.github/workflows/scorecard.yaml
vendored
6
.github/workflows/scorecard.yaml
vendored
@ -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@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
34
.github/workflows/translations.yaml
vendored
34
.github/workflows/translations.yaml
vendored
@ -13,10 +13,11 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -39,16 +40,29 @@ jobs:
|
||||
apt-dependency: gettext
|
||||
- name: Make Translations
|
||||
run: invoke translate
|
||||
- name: Commit files
|
||||
- name: Remove compiled static files
|
||||
run: rm -rf src/backend/InvenTree/static
|
||||
- name: Remove all local changes that are not *.po files
|
||||
run: |
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git checkout -b l10_local
|
||||
git add "*.po"
|
||||
git commit -m "updated translation base"
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
|
||||
git add src/backend/InvenTree/locale/en/LC_MESSAGES/django.po src/frontend/src/locales/en/messages.po
|
||||
git commit -m "add translations"
|
||||
git reset --hard
|
||||
git reset HEAD~
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@6ed209d411599a981ccb978df3be9dc9b8a81699 # pin@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: l10
|
||||
force: true
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: l10_crowdin
|
||||
create_pull_request: true
|
||||
pull_request_title: 'New Crowdin updates'
|
||||
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)'
|
||||
pull_request_base_branch_name: 'master'
|
||||
pull_request_labels: 'translations'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -31,6 +31,7 @@ var/
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.sqlite3-journal
|
||||
*.backup
|
||||
@ -111,3 +112,4 @@ InvenTree/web/static
|
||||
docs/schema.yml
|
||||
docs/docs/api/*.yml
|
||||
docs/docs/api/schema/*.yml
|
||||
inventree_settings.json
|
||||
|
@ -17,17 +17,18 @@ repos:
|
||||
- id: check-yaml
|
||||
- id: mixed-line-ending
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.5.1
|
||||
rev: v0.6.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
args: [--preview]
|
||||
- id: ruff
|
||||
args: [
|
||||
--fix,
|
||||
# --unsafe-fixes,
|
||||
--preview
|
||||
]
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.2.13
|
||||
rev: 0.2.37
|
||||
hooks:
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements-dev.in
|
||||
@ -77,7 +78,7 @@ repos:
|
||||
- "prettier@^2.4.1"
|
||||
- "@trivago/prettier-plugin-sort-imports"
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: "v9.6.0"
|
||||
rev: "v9.9.0"
|
||||
hooks:
|
||||
- id: eslint
|
||||
additional_dependencies:
|
||||
|
@ -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
|
||||
|
||||
|
@ -17,6 +17,7 @@ gunicorn>=22.0.0
|
||||
# LDAP required packages
|
||||
django-auth-ldap # Django integration for ldap auth
|
||||
python-ldap # LDAP auth support
|
||||
django<5.0 # Force lower to match main project
|
||||
|
||||
# Upgraded python package installer
|
||||
uv
|
||||
|
@ -4,17 +4,19 @@ asgiref==3.8.1 \
|
||||
--hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \
|
||||
--hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590
|
||||
# via django
|
||||
django==4.2.14 \
|
||||
--hash=sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240 \
|
||||
--hash=sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96
|
||||
# via django-auth-ldap
|
||||
django==4.2.15 \
|
||||
--hash=sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30 \
|
||||
--hash=sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a
|
||||
# via
|
||||
# -r contrib/container/requirements.in
|
||||
# django-auth-ldap
|
||||
django-auth-ldap==4.8.0 \
|
||||
--hash=sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce \
|
||||
--hash=sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738
|
||||
# via -r contrib/container/requirements.in
|
||||
gunicorn==22.0.0 \
|
||||
--hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \
|
||||
--hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63
|
||||
gunicorn==23.0.0 \
|
||||
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
|
||||
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
|
||||
# via -r contrib/container/requirements.in
|
||||
invoke==2.2.0 \
|
||||
--hash=sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820 \
|
||||
@ -44,86 +46,74 @@ mysqlclient==2.2.4 \
|
||||
--hash=sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54 \
|
||||
--hash=sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab
|
||||
# via -r contrib/container/requirements.in
|
||||
packaging==24.0 \
|
||||
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
|
||||
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
|
||||
packaging==24.1 \
|
||||
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
|
||||
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
|
||||
# via
|
||||
# gunicorn
|
||||
# mariadb
|
||||
psycopg[binary, pool]==3.1.18 \
|
||||
--hash=sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b \
|
||||
--hash=sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e
|
||||
psycopg[binary, pool]==3.2.1 \
|
||||
--hash=sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7 \
|
||||
--hash=sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175
|
||||
# via -r contrib/container/requirements.in
|
||||
psycopg-binary==3.1.18 \
|
||||
--hash=sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7 \
|
||||
--hash=sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c \
|
||||
--hash=sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31 \
|
||||
--hash=sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5 \
|
||||
--hash=sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e \
|
||||
--hash=sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4 \
|
||||
--hash=sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404 \
|
||||
--hash=sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7 \
|
||||
--hash=sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46 \
|
||||
--hash=sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57 \
|
||||
--hash=sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee \
|
||||
--hash=sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834 \
|
||||
--hash=sha256:3c7afcd6f1d55992f26d9ff7b0bd4ee6b475eb43aa3f054d67d32e09f18b0065 \
|
||||
--hash=sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686 \
|
||||
--hash=sha256:3e7ce4d988112ca6c75765c7f24c83bdc476a6a5ce00878df6c140ca32c3e16d \
|
||||
--hash=sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804 \
|
||||
--hash=sha256:4575da95fc441244a0e2ebaf33a2b2f74164603341d2046b5cde0a9aa86aa7e2 \
|
||||
--hash=sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143 \
|
||||
--hash=sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722 \
|
||||
--hash=sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82 \
|
||||
--hash=sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad \
|
||||
--hash=sha256:59701118c7d8842e451f1e562d08e8708b3f5d14974eefbce9374badd723c4ae \
|
||||
--hash=sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a \
|
||||
--hash=sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f \
|
||||
--hash=sha256:639dd78ac09b144b0119076783cb64e1128cc8612243e9701d1503c816750b2e \
|
||||
--hash=sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83 \
|
||||
--hash=sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd \
|
||||
--hash=sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09 \
|
||||
--hash=sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73 \
|
||||
--hash=sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d \
|
||||
--hash=sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68 \
|
||||
--hash=sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773 \
|
||||
--hash=sha256:812726266ab96de681f2c7dbd6b734d327f493a78357fcc16b2ac86ff4f4e080 \
|
||||
--hash=sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299 \
|
||||
--hash=sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1 \
|
||||
--hash=sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c \
|
||||
--hash=sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d \
|
||||
--hash=sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5 \
|
||||
--hash=sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c \
|
||||
--hash=sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5 \
|
||||
--hash=sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe \
|
||||
--hash=sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921 \
|
||||
--hash=sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1 \
|
||||
--hash=sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a \
|
||||
--hash=sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d \
|
||||
--hash=sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d \
|
||||
--hash=sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741 \
|
||||
--hash=sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c \
|
||||
--hash=sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69 \
|
||||
--hash=sha256:c76659ae29a84f2c14f56aad305dd00eb685bd88f8c0a3281a9a4bc6bd7d2aa7 \
|
||||
--hash=sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38 \
|
||||
--hash=sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c \
|
||||
--hash=sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970 \
|
||||
--hash=sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679 \
|
||||
--hash=sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85 \
|
||||
--hash=sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30 \
|
||||
--hash=sha256:dea4a59da7850192fdead9da888e6b96166e90608cf39e17b503f45826b16f84 \
|
||||
--hash=sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008 \
|
||||
--hash=sha256:e1cf59e0bb12e031a48bb628aae32df3d0c98fd6c759cb89f464b1047f0ca9c8 \
|
||||
--hash=sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e \
|
||||
--hash=sha256:e262398e5d51563093edf30612cd1e20fedd932ad0994697d7781ca4880cdc3d \
|
||||
--hash=sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5 \
|
||||
--hash=sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414 \
|
||||
--hash=sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c \
|
||||
--hash=sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54
|
||||
psycopg-binary==3.2.1 \
|
||||
--hash=sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51 \
|
||||
--hash=sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea \
|
||||
--hash=sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9 \
|
||||
--hash=sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1 \
|
||||
--hash=sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938 \
|
||||
--hash=sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3 \
|
||||
--hash=sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9 \
|
||||
--hash=sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788 \
|
||||
--hash=sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2 \
|
||||
--hash=sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db \
|
||||
--hash=sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7 \
|
||||
--hash=sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46 \
|
||||
--hash=sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674 \
|
||||
--hash=sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e \
|
||||
--hash=sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f \
|
||||
--hash=sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805 \
|
||||
--hash=sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489 \
|
||||
--hash=sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879 \
|
||||
--hash=sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42 \
|
||||
--hash=sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22 \
|
||||
--hash=sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5 \
|
||||
--hash=sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a \
|
||||
--hash=sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f \
|
||||
--hash=sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f \
|
||||
--hash=sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040 \
|
||||
--hash=sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960 \
|
||||
--hash=sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60 \
|
||||
--hash=sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c \
|
||||
--hash=sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707 \
|
||||
--hash=sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f \
|
||||
--hash=sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef \
|
||||
--hash=sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1 \
|
||||
--hash=sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb \
|
||||
--hash=sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d \
|
||||
--hash=sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1 \
|
||||
--hash=sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073 \
|
||||
--hash=sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f \
|
||||
--hash=sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291 \
|
||||
--hash=sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b \
|
||||
--hash=sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7 \
|
||||
--hash=sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801 \
|
||||
--hash=sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35 \
|
||||
--hash=sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b \
|
||||
--hash=sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e \
|
||||
--hash=sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a \
|
||||
--hash=sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d \
|
||||
--hash=sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68 \
|
||||
--hash=sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2 \
|
||||
--hash=sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935 \
|
||||
--hash=sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555 \
|
||||
--hash=sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd \
|
||||
--hash=sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7 \
|
||||
--hash=sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7
|
||||
# via psycopg
|
||||
psycopg-pool==3.2.1 \
|
||||
--hash=sha256:060b551d1b97a8d358c668be58b637780b884de14d861f4f5ecc48b7563aafb7 \
|
||||
--hash=sha256:6509a75c073590952915eddbba7ce8b8332a440a31e77bba69561483492829ad
|
||||
psycopg-pool==3.2.2 \
|
||||
--hash=sha256:273081d0fbfaced4f35e69200c89cb8fbddfe277c38cc86c235b90a2ec2c8153 \
|
||||
--hash=sha256:9e22c370045f6d7f2666a5ad1b0caf345f9f1912195b0b25d0d3bcc4f3a7389c
|
||||
# via psycopg
|
||||
pyasn1==0.6.0 \
|
||||
--hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \
|
||||
@ -140,93 +130,96 @@ python-ldap==3.4.4 \
|
||||
# via
|
||||
# -r contrib/container/requirements.in
|
||||
# django-auth-ldap
|
||||
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/container/requirements.in
|
||||
setuptools==70.3.0 \
|
||||
--hash=sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5 \
|
||||
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
|
||||
setuptools==73.0.1 \
|
||||
--hash=sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e \
|
||||
--hash=sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193
|
||||
# via -r contrib/container/requirements.in
|
||||
sqlparse==0.5.0 \
|
||||
--hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
|
||||
--hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
|
||||
sqlparse==0.5.1 \
|
||||
--hash=sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4 \
|
||||
--hash=sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e
|
||||
# via django
|
||||
typing-extensions==4.11.0 \
|
||||
--hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \
|
||||
--hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a
|
||||
typing-extensions==4.12.2 \
|
||||
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||
# via
|
||||
# psycopg
|
||||
# psycopg-pool
|
||||
uv==0.1.38 \
|
||||
--hash=sha256:03242a734a572733f2b9a5dbb94517e918fe26fc01114b7c51d12296dfbb8f8b \
|
||||
--hash=sha256:067af2d986329db4fa3c7373017d49f0e16ddff23e483b7e5bc3a5a18ce08ea6 \
|
||||
--hash=sha256:0937ad16ae0e0b6bb6dd3c386f8fb33141ad08d1762eaacffb4d2b27fb466a17 \
|
||||
--hash=sha256:0e1d64ac437b0a14fbcec55b1c3f162fa24860711e0d855fcd9c672b149a122a \
|
||||
--hash=sha256:1be7aa46936c0351ccb1400ea95e5381b3f05fef772fa3b9f23af728cc175dea \
|
||||
--hash=sha256:309e73a3ec3a5a536a3efaf434270fc94b483069f1425765165c1c9d786c27fd \
|
||||
--hash=sha256:4251f9771d392d7badc1e5fb934b397b12ca00fef9d955207ade169cc1f7e872 \
|
||||
--hash=sha256:43772e7589f70e954b1ae29230e575ef9e4d8d769138a94dfa5ae7eaf1e26ac5 \
|
||||
--hash=sha256:4a6024256d38b77151e32876be9fcb99cf75df7a86b26e0161cc202bed558adf \
|
||||
--hash=sha256:5a98d6aacd4b57b7e00daf154919e7c9206fefdf40bd28cfb13efe0e0324d491 \
|
||||
--hash=sha256:8de6dbd8f348ee90af044f4cc7b6650521d25ba2d20a813c1e157a3f90069dd9 \
|
||||
--hash=sha256:9133e24db9bdd4f412eab69586d03294419825432a9a27ee1b510a4c01eb7b0b \
|
||||
--hash=sha256:92f65b6e4e5c8126501785af3629dc537d7c82caa56ac9336a86929c73d0e138 \
|
||||
--hash=sha256:afd85029923e712b6b2c45ddc1680c785392220876c766521e45778db3f71f8e \
|
||||
--hash=sha256:b0b15e51a0f8240969bc412ed0dd60cfe3f664b30173139ef263d71c596d631f \
|
||||
--hash=sha256:ea44c07605d1359a7d82bf42706dd86d341f15f4ca2e1f36e51626a7111c2ad5 \
|
||||
--hash=sha256:f87c9711493c53d32012a96b49c4d53aabdf7ed666cbf2c3fb55dd402a6b31a8
|
||||
uv==0.3.0 \
|
||||
--hash=sha256:084551ee0743339aa5d0d4c76a94c9f9df16c33030b850f0cd98f316db7b42cc \
|
||||
--hash=sha256:0da4f060d583325846cde0727a8cc0cb4e8c63b30ac9373dae213a7315056d90 \
|
||||
--hash=sha256:160a1f3b01298942d6cfe21f95a9b7daa3eb73231ba1fc4689157eb9f23b3438 \
|
||||
--hash=sha256:21ebc6ca30df7ff57a8e17e3abeeba8a9d1d4ac79c1adf842fa42d48a5c7f372 \
|
||||
--hash=sha256:24a1388f5e285058f97576b7dfee79bb5007a712a9e368f3fcdcfeb2dfd9ce92 \
|
||||
--hash=sha256:2f937ebdf9976ec1ffe7228fd608ef3e6ce2a61ed68cf7b157ae6900a9c80f41 \
|
||||
--hash=sha256:39a4276afe0808ca6c033e0cd6cb73249f934b4a0c9d7b18a944f3f8ea635e27 \
|
||||
--hash=sha256:3b62e44f61a154303fc9f4aa87ae54891957d49769d21dcf2be9c22e640c3e92 \
|
||||
--hash=sha256:4303364d717b1def58e82b11271259d2ee3bb03da0ca6111819ee254f65b38f4 \
|
||||
--hash=sha256:503fc619238550be222b41422b415677c9b8045c92a9815f80ff5d7477671fe6 \
|
||||
--hash=sha256:52b3a6110705ff27462ddc68657fedf8a296ed545619a90fa73354f130ad632e \
|
||||
--hash=sha256:5c826d9daace67d67790503b0c1152093b3cecd35a91de10f5bb9e26afea9de9 \
|
||||
--hash=sha256:6d1025349cbaeba9a974d413795d0ce8d37de5ad7fb7654c0519968b2c083ba1 \
|
||||
--hash=sha256:a15b2321444f3668bc95863d2b13ce44ea54053189427ea48d112ecd8b3d2f89 \
|
||||
--hash=sha256:a71b7080ee6d7658b22f93aa750cbfd19111cd6c8ac643a73d6778598dd06559 \
|
||||
--hash=sha256:b44ebf501de5eef33e4f3cf4b6ea9a458d1f1b3cf26737c25ac507ab7914076a \
|
||||
--hash=sha256:d3da56b87ec5aa4f2ae572127c754655bad3820dd41a4d37ed4d5e2f67035990 \
|
||||
--hash=sha256:d87ff76da5128036c05db0291db7510a85cb8efb86538e8f49adc8074bb292f0
|
||||
# via -r contrib/container/requirements.in
|
||||
wheel==0.43.0 \
|
||||
--hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \
|
||||
--hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81
|
||||
wheel==0.44.0 \
|
||||
--hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \
|
||||
--hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49
|
||||
# via -r contrib/container/requirements.in
|
||||
|
@ -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
|
||||
|
@ -104,62 +104,64 @@ jc==1.25.3 \
|
||||
--hash=sha256:ea17a8578497f2da92f73924d9d403f4563ba59422fbceff7bb4a16cdf84a54f \
|
||||
--hash=sha256:fa3140ceda6cba1210d1362f363cd79a0514741e8a1dd6167db2b2e2d5f24f7b
|
||||
# via -r contrib/dev_reqs/requirements.in
|
||||
pygments==2.17.2 \
|
||||
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
|
||||
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
|
||||
pygments==2.18.0 \
|
||||
--hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \
|
||||
--hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a
|
||||
# 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 \
|
||||
|
@ -11,7 +11,7 @@ PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
|
||||
. ${APP_HOME}/contrib/packager.io/functions.sh
|
||||
|
||||
# Envs that should be passed to setup commands
|
||||
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON,SETUP_ADMIN_NOCREATION
|
||||
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_SITE_URL,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON,SETUP_ADMIN_NOCREATION
|
||||
|
||||
# Get the envs
|
||||
detect_local_env
|
||||
|
@ -8,8 +8,13 @@ PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
|
||||
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON
|
||||
|
||||
if test -f "${APP_HOME}/env/bin/pip"; then
|
||||
echo "# Clearing precompiled files"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke clear-generated"
|
||||
# Check if clear-generated is available
|
||||
if sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke clear-generated --help" > /dev/null 2>&1; then
|
||||
echo "# Clearing precompiled files"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke clear-generated"
|
||||
else
|
||||
echo "# Clearing precompiled files - skipping"
|
||||
fi
|
||||
else
|
||||
echo "# No python environment found - skipping"
|
||||
fi
|
||||
|
@ -1,8 +1,14 @@
|
||||
# Configuration file for Crowdin project integration
|
||||
# See: https://crowdin.com/project/inventree
|
||||
|
||||
"commit_message": "Fix: New translations %original_file_name% from Crowdin"
|
||||
"append_commit_message": false
|
||||
"preserve_hierarchy": true
|
||||
|
||||
files:
|
||||
- source: /src/backend/InvenTree/locale/en/LC_MESSAGES/django.po
|
||||
dest: /%original_path%/%original_file_name%
|
||||
translation: /src/backend/InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%
|
||||
- source: /src/frontend/src/locales/en/messages.po
|
||||
dest: /%original_path%/%original_file_name%
|
||||
translation: /src/frontend/src/locales/%two_letters_code%/%original_file_name%
|
||||
|
@ -10,7 +10,7 @@ tld = os.path.abspath(os.path.join(here, '..'))
|
||||
|
||||
config_file = os.path.join(tld, 'mkdocs.yml')
|
||||
|
||||
with open(config_file, 'r') as f:
|
||||
with open(config_file, encoding='utf-8') as f:
|
||||
data = yaml.load(f, yaml.BaseLoader)
|
||||
|
||||
assert data['strict'] == 'true'
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 56 KiB |
Binary file not shown.
Before Width: | Height: | Size: 84 KiB |
37
docs/docs/build/build.md
vendored
37
docs/docs/build/build.md
vendored
@ -66,10 +66,11 @@ Each *Build Order* has an associated *Status* flag, which indicates the state of
|
||||
|
||||
| Status | Description |
|
||||
| ----------- | ----------- |
|
||||
| `Pending` | Build has been created and build is ready for subpart allocation |
|
||||
| `Production` | One or more build outputs have been created for this build |
|
||||
| `Cancelled` | Build has been cancelled |
|
||||
| `Completed` | Build has been completed |
|
||||
| `Pending` | Build order has been created, but is not yet in production |
|
||||
| `Production` | Build order is currently in production |
|
||||
| `On Hold` | Build order has been placed on hold, but is still active |
|
||||
| `Cancelled` | Build order has been cancelled |
|
||||
| `Completed` | Build order has been completed |
|
||||
|
||||
**Source Code**
|
||||
|
||||
@ -220,6 +221,10 @@ To create a build order for your part, you have two options:
|
||||
|
||||
Fill-out the form as required, then click the "Submit" button to create the build.
|
||||
|
||||
### Create Child Builds
|
||||
|
||||
When creating a new build order, you have the option to automatically generate build orders for any subassembly parts. This can be useful to create a complete tree of build orders for a complex assembly. *However*, it must be noted that any build orders created for subassemblies will use the default BOM quantity for that part. Any child build orders created in this manner must be manually reviewed, to ensure that the correct quantity is being built as per your production requirements.
|
||||
|
||||
## Complete Build Order
|
||||
|
||||
To complete a build, click on <span class='fas fa-tools'></span> icon on the build detail page, the `Complete Build` form will be displayed.
|
||||
@ -264,18 +269,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") }}
|
||||
|
15
docs/docs/concepts/custom_states.md
Normal file
15
docs/docs/concepts/custom_states.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Custom States
|
||||
---
|
||||
|
||||
## Custom States
|
||||
|
||||
Several models within InvenTree support the use of custom states. The custom states are display only - the business logic is not affected by the state.
|
||||
|
||||
States can be added in the Admin Center under the "Custom States" section. Each state has a name, label and a color that are used to display the state in the user interface. Changes to these settings will only be reflected in the user interface after a full reload of the interface.
|
||||
|
||||
States need to be assigned to a model, state (for example status on a StockItem) and a logical key - that will be used for business logic. These 3 values combined need to be unique throughout the system.
|
||||
|
||||
Custom states can be used in the following models:
|
||||
- StockItem
|
||||
- Orders (PurchaseOrder, SalesOrder, ReturnOrder, ReturnOrderLine)
|
@ -4,4 +4,22 @@ title: InvenTree Demo
|
||||
|
||||
## InvenTree Demo
|
||||
|
||||
This page has moved to [https://inventree.org/demo.html](https://inventree.org/demo.html)
|
||||
If you are interested in trying out InvenTree, you can access the InvenTree demo instance at [https://demo.inventree.org](https://demo.inventree.org).
|
||||
|
||||
This page is populated with a sample dataset, which is reset every 24 hours.
|
||||
|
||||
You can read more about the InvenTree demo here: [https://inventree.org/demo.html](https://inventree.org/demo.html)
|
||||
|
||||
### User Accounts
|
||||
|
||||
The demo instance has a number of user accounts which you can use to explore the system:
|
||||
|
||||
| Username | Password | Staff Access | Enabled | Description |
|
||||
| -------- | -------- | ------------ | ------- | ----------- |
|
||||
| allaccess | nolimits | No | Yes | View / create / edit all pages and items |
|
||||
| reader | readonly | No | Yes | Can view all pages but cannot create, edit or delete database records |
|
||||
| engineer | partsonly | No | Yes | Can manage parts, view stock, but no access to purchase orders or sales orders |
|
||||
| steven | wizardstaff | Yes | Yes | Staff account, can access some admin sections |
|
||||
| ian | inactive | No | No | Inactive account, cannot log in |
|
||||
| susan | inactive | No | No | Inactive account, cannot log in |
|
||||
| admin | inventree | Yes | Yes | Superuser account, can access all parts of the system |
|
||||
|
@ -47,6 +47,8 @@ If you want to create your own machine type, please also take a look at the alre
|
||||
|
||||
```py
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from generic.states import ColorEnum
|
||||
from plugin.machine import BaseDriver, BaseMachineType, MachineStatus
|
||||
|
||||
class ABCBaseDriver(BaseDriver):
|
||||
@ -72,9 +74,9 @@ class ABCMachine(BaseMachineType):
|
||||
base_driver = ABCBaseDriver
|
||||
|
||||
class ABCStatus(MachineStatus):
|
||||
CONNECTED = 100, _('Connected'), 'success'
|
||||
STANDBY = 101, _('Standby'), 'success'
|
||||
PRINTING = 110, _('Printing'), 'primary'
|
||||
CONNECTED = 100, _('Connected'), ColorEnum.success
|
||||
STANDBY = 101, _('Standby'), ColorEnum.success
|
||||
PRINTING = 110, _('Printing'), ColorEnum.primary
|
||||
|
||||
MACHINE_STATUS = ABCStatus
|
||||
default_machine_status = ABCStatus.DISCONNECTED
|
||||
|
@ -108,22 +108,71 @@ By default, part names are not subject to any particular naming conventions or r
|
||||
|
||||
If the custom method determines that the part name is *objectionable*, it should throw a `ValidationError` which will be handled upstream by parent calling methods.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_part_name
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
### Part IPN
|
||||
|
||||
Validation of the Part IPN (Internal Part Number) field is exposed to custom plugins via the `validate_part_IPN` method. Any plugins which extend the `ValidationMixin` class can implement this method, and raise a `ValidationError` if the IPN value does not match a required convention.
|
||||
Validation of the Part IPN (Internal Part Number) field is exposed to custom plugins via the `validate_part_ipn` method. Any plugins which extend the `ValidationMixin` class can implement this method, and raise a `ValidationError` if the IPN value does not match a required convention.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_part_ipn
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
### Part Parameter Values
|
||||
|
||||
[Part parameters](../../part/parameter.md) can also have custom validation rules applied, by implementing the `validate_part_parameter` method. A plugin which implements this method should raise a `ValidationError` with an appropriate message if the part parameter value does not match a required convention.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_part_parameter
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
### Batch Codes
|
||||
|
||||
[Batch codes](../../stock/tracking.md#batch-codes) can be generated and/or validated by custom plugins.
|
||||
|
||||
#### Validate Batch Code
|
||||
|
||||
The `validate_batch_code` method allows plugins to raise an error if a batch code input by the user does not meet a particular pattern.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_batch_code
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
#### Generate Batch Code
|
||||
|
||||
The `generate_batch_code` method can be implemented to generate a new batch code, based on a set of provided information.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.generate_batch_code
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
### Serial Numbers
|
||||
|
||||
Requirements for serial numbers can vary greatly depending on the application. Rather than attempting to provide a "one size fits all" serial number implementation, InvenTree allows custom serial number schemes to be implemented via plugins.
|
||||
@ -134,17 +183,30 @@ The default InvenTree [serial numbering system](../../stock/tracking.md#serial-n
|
||||
|
||||
Custom serial number validation can be implemented using the `validate_serial_number` method. A *proposed* serial number is passed to this method, which then has the opportunity to raise a `ValidationError` to indicate that the serial number is not valid.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_serial_number
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
!!! info "Stock Item"
|
||||
If the `stock_item` argument is provided, then this stock item has already been assigned with the provided serial number. This stock item should be excluded from any subsequent checks for *uniqueness*. The `stock_item` parameter is optional, and may be `None` if the serial number is being validated in a context where no stock item is available.
|
||||
|
||||
##### Example
|
||||
|
||||
A plugin which requires all serial numbers to be valid hexadecimal values may implement this method as follows:
|
||||
|
||||
```python
|
||||
def validate_serial_number(self, serial: str, part: Part):
|
||||
def validate_serial_number(self, serial: str, part: Part, stock_item: StockItem = None):
|
||||
"""Validate the supplied serial number
|
||||
|
||||
Arguments:
|
||||
serial: The proposed serial number (string)
|
||||
part: The Part instance for which this serial number is being validated
|
||||
stock_item: The StockItem instance for which this serial number is being validated
|
||||
"""
|
||||
|
||||
try:
|
||||
@ -160,6 +222,15 @@ While InvenTree supports arbitrary text values in the serial number fields, behi
|
||||
|
||||
A custom plugin can implement the `convert_serial_to_int` method to determine how a particular serial number is converted to an integer representation.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.convert_serial_to_int
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
!!! info "Not Required"
|
||||
If this method is not implemented, or the serial number cannot be converted to an integer, then the sorting algorithm falls back to the text (string) value
|
||||
|
||||
@ -169,6 +240,15 @@ A core component of the InvenTree serial number system is the ability to *increm
|
||||
|
||||
For custom serial number schemes, it is important to provide a method to generate the *next* serial number given a current value. The `increment_serial_number` method can be implemented by a plugin to achieve this.
|
||||
|
||||
::: plugin.base.integration.ValidationMixin.ValidationMixin.increment_serial_number
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
!!! info "Invalid Increment"
|
||||
If the provided number cannot be incremented (or an error occurs) the method should return `None`
|
||||
|
||||
|
@ -57,7 +57,7 @@ def fetch_rtd_versions():
|
||||
versions = sorted(versions, key=lambda x: StrictVersion(x['version']), reverse=True)
|
||||
|
||||
# Add "latest" version first
|
||||
if not any((x['title'] == 'latest' for x in versions)):
|
||||
if not any(x['title'] == 'latest' for x in versions):
|
||||
versions.insert(
|
||||
0,
|
||||
{
|
||||
@ -70,7 +70,7 @@ def fetch_rtd_versions():
|
||||
# Ensure we have the 'latest' version
|
||||
current_version = os.environ.get('READTHEDOCS_VERSION', None)
|
||||
|
||||
if current_version and not any((x['title'] == current_version for x in versions)):
|
||||
if current_version and not any(x['title'] == current_version for x in versions):
|
||||
versions.append({
|
||||
'version': current_version,
|
||||
'title': current_version,
|
||||
@ -82,7 +82,7 @@ def fetch_rtd_versions():
|
||||
print('Discovered the following versions:')
|
||||
print(versions)
|
||||
|
||||
with open(output_filename, 'w') as file:
|
||||
with open(output_filename, 'w', encoding='utf-8') as file:
|
||||
json.dump(versions, file, indent=2)
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ def get_release_data():
|
||||
# Release information has been cached to file
|
||||
|
||||
print("Loading release information from 'releases.json'")
|
||||
with open(json_file) as f:
|
||||
with open(json_file, encoding='utf-8') as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
# Download release information via the GitHub API
|
||||
@ -127,7 +127,7 @@ def get_release_data():
|
||||
page += 1
|
||||
|
||||
# Cache these results to file
|
||||
with open(json_file, 'w') as f:
|
||||
with open(json_file, 'w', encoding='utf-8') as f:
|
||||
print("Saving release information to 'releases.json'")
|
||||
f.write(json.dumps(releases))
|
||||
|
||||
@ -173,7 +173,7 @@ def on_config(config, *args, **kwargs):
|
||||
# Add *all* readthedocs related keys
|
||||
readthedocs = {}
|
||||
|
||||
for key in os.environ.keys():
|
||||
for key in os.environ:
|
||||
if key.startswith('READTHEDOCS_'):
|
||||
k = key.replace('READTHEDOCS_', '').lower()
|
||||
readthedocs[k] = os.environ[key]
|
||||
|
@ -20,6 +20,7 @@ Each Purchase Order has a specific status code which indicates the current state
|
||||
| --- | --- |
|
||||
| Pending | The purchase order has been created, but has not been submitted to the supplier |
|
||||
| In Progress | The purchase order has been issued to the supplier, and is in progress |
|
||||
| On Hold | The purchase order has been placed on hold, but is still active |
|
||||
| Complete | The purchase order has been completed, and is now closed |
|
||||
| Cancelled | The purchase order was cancelled, and is now closed |
|
||||
| Lost | The purchase order was lost, and is now closed |
|
||||
@ -37,6 +38,8 @@ Refer to the source code for the Purchase Order status codes:
|
||||
show_source: True
|
||||
members: []
|
||||
|
||||
Purchase Order Status supports [custom states](../concepts/custom_states.md).
|
||||
|
||||
### Purchase Order Currency
|
||||
|
||||
The currency code can be specified for an individual purchase order. If not specified, the default currency specified against the [supplier](./company.md#suppliers) will be used.
|
||||
@ -138,3 +141,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") }}
|
||||
|
@ -45,6 +45,7 @@ Each Return Order has a specific status code, as follows:
|
||||
| --- | --- |
|
||||
| Pending | The return order has been created, but not sent to the customer |
|
||||
| In Progress | The return order has been issued to the customer |
|
||||
| On Hold | The return order has been placed on hold, but is still active |
|
||||
| Complete | The return order was marked as complete, and is now closed |
|
||||
| Cancelled | The return order was cancelled, and is now closed |
|
||||
|
||||
@ -60,6 +61,8 @@ Refer to the source code for the Return Order status codes:
|
||||
show_source: True
|
||||
members: []
|
||||
|
||||
Return Order Status supports [custom states](../concepts/custom_states.md).
|
||||
|
||||
## Create a Return Order
|
||||
|
||||
From the Return Order index, click on <span class='badge inventree add'><span class='fas fa-plus-circle'></span> New Return Order</span> which opens the "Create Return Order" form.
|
||||
@ -120,3 +123,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") }}
|
||||
|
@ -20,6 +20,7 @@ Each Sales Order has a specific status code, which represents the state of the o
|
||||
| --- | --- |
|
||||
| Pending | The sales order has been created, but has not been finalized or submitted |
|
||||
| In Progress | The sales order has been issued, and is in progress |
|
||||
| On Hold | The sales order has been placed on hold, but is still active |
|
||||
| Shipped | The sales order has been shipped, but is not yet complete |
|
||||
| Complete | The sales order is fully completed, and is now closed |
|
||||
| Cancelled | The sales order was cancelled, and is now closed |
|
||||
@ -38,6 +39,8 @@ Refer to the source code for the Sales Order status codes:
|
||||
show_source: True
|
||||
members: []
|
||||
|
||||
Sales Order Status supports [custom states](../concepts/custom_states.md).
|
||||
|
||||
### Sales Order Currency
|
||||
|
||||
The currency code can be specified for an individual sales order. If not specified, the default currency specified against the [customer](./company.md#customers) will be used.
|
||||
@ -182,3 +185,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") }}
|
||||
|
@ -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.
|
||||
|
@ -46,7 +46,7 @@ Additionally, the following information is stored for each part, in relation to
|
||||
|
||||
InvenTree supports pricing data in multiple currencies, allowing integration with suppliers and customers using different currency systems.
|
||||
|
||||
Supported currencies must be configured as part of [the InvenTree setup process](../start/config.md#supported-currencies).
|
||||
Supported currencies can be configured in the [InvenTree settings](../settings/currency.md).
|
||||
|
||||
!!! info "Currency Support"
|
||||
InvenTree provides multi-currency pricing support via the [django-money](https://django-money.readthedocs.io/en/latest/) library.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -130,8 +130,8 @@ The following keyword arguments are available to the `render_currency` function:
|
||||
| --- | --- |
|
||||
| currency | Specify the currency code to render in (will attempt conversion if different to provided currency) |
|
||||
| decimal_places | Specify the number of decimal places to render |
|
||||
| min_decimal_places | Specify the minimum number of decimal places to render (ignored if *decimal_places* is specified) |
|
||||
| max_decimal_places | Specify the maximum number of decimal places to render (ignored if *decimal_places* is specified) |
|
||||
| min_decimal_places | Specify the minimum number of decimal places to render |
|
||||
| max_decimal_places | Specify the maximum number of decimal places to render |
|
||||
| include_symbol | Include currency symbol in rendered value (default = True) |
|
||||
|
||||
## Maths Operations
|
||||
|
@ -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
|
||||
|
||||
|
@ -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") }}
|
||||
|
@ -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") }}
|
||||
|
@ -199,7 +199,7 @@ Any persistent files generated by the Caddy container (such as certificates, etc
|
||||
|
||||
### Demo Dataset
|
||||
|
||||
To quickly get started with a demo dataset, you can run the following command:
|
||||
To quickly get started with a [demo dataset](../demo.md), you can run the following command:
|
||||
|
||||
```
|
||||
docker compose run --rm inventree-server invoke setup-test -i
|
||||
|
@ -10,7 +10,7 @@ Certain stock item status codes will restrict the availability of the stock item
|
||||
|
||||
Below is the list of available stock status codes and their meaning:
|
||||
|
||||
| Status | Description | Available |
|
||||
| Status | Description | Available |
|
||||
| ----------- | ----------- | --- |
|
||||
| <span class='badge inventree success'>OK</span> | Stock item is healthy, nothing wrong to report | <span class='badge inventree success'>Yes</span> |
|
||||
| <span class='badge inventree warning'>Attention needed</span> | Stock item hasn't been checked or tested yet | <span class='badge inventree success'>Yes</span> |
|
||||
@ -38,6 +38,8 @@ Refer to the source code for the Stock status codes:
|
||||
show_source: True
|
||||
members: []
|
||||
|
||||
Stock Status supports [custom states](../concepts/custom_states.md).
|
||||
|
||||
### Default Status Code
|
||||
|
||||
The default status code for any newly created Stock Item is <span class='badge inventree success'>OK</span>
|
||||
|
@ -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.
|
||||
|
||||
|
@ -46,7 +46,7 @@ def top_level_path(path: str) -> str:
|
||||
|
||||
key = path.split('/')[1]
|
||||
|
||||
if key in SPECIAL_PATHS.keys():
|
||||
if key in SPECIAL_PATHS:
|
||||
return key
|
||||
|
||||
return GENERAL_PATH
|
||||
@ -54,9 +54,7 @@ def top_level_path(path: str) -> str:
|
||||
|
||||
def generate_schema_file(key: str) -> None:
|
||||
"""Generate a schema file for the provided key."""
|
||||
description = (
|
||||
SPECIAL_PATHS[key] if key in SPECIAL_PATHS else 'General API Endpoints'
|
||||
)
|
||||
description = SPECIAL_PATHS.get(key, 'General API Endpoints')
|
||||
|
||||
output = f"""
|
||||
---
|
||||
@ -75,7 +73,7 @@ def generate_schema_file(key: str) -> None:
|
||||
|
||||
print('Writing schema file to:', output_file)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
@ -121,7 +119,7 @@ def generate_index_file(version: str):
|
||||
|
||||
print('Writing index file to:', output_file)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
@ -173,7 +171,7 @@ def parse_api_file(filename: str):
|
||||
|
||||
The intent is to make the API schema easier to peruse on the documentation.
|
||||
"""
|
||||
with open(filename, 'r') as f:
|
||||
with open(filename, encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
paths = data['paths']
|
||||
@ -213,7 +211,7 @@ def parse_api_file(filename: str):
|
||||
|
||||
output_file = os.path.abspath(output_file)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(output, f)
|
||||
|
||||
# Generate a markdown file for the schema
|
||||
|
66
docs/main.py
66
docs/main.py
@ -1,5 +1,6 @@
|
||||
"""Main entry point for the documentation build process."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import textwrap
|
||||
@ -7,12 +8,26 @@ 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, encoding='utf-8') 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."""
|
||||
mkdocs_yml = os.path.join(os.path.dirname(__file__), 'mkdocs.yml')
|
||||
|
||||
with open(mkdocs_yml, 'r') as f:
|
||||
with open(mkdocs_yml, encoding='utf-8') as f:
|
||||
mkdocs_config = yaml.safe_load(f)
|
||||
repo_name = mkdocs_config['repo_name']
|
||||
|
||||
@ -32,7 +47,7 @@ def check_link(url) -> bool:
|
||||
|
||||
# Keep a local cache file of URLs we have already checked
|
||||
if os.path.exists(CACHE_FILE):
|
||||
with open(CACHE_FILE, 'r') as f:
|
||||
with open(CACHE_FILE, encoding='utf-8') as f:
|
||||
cache = f.read().splitlines()
|
||||
|
||||
if url in cache:
|
||||
@ -44,7 +59,7 @@ def check_link(url) -> bool:
|
||||
response = requests.head(url, timeout=5000)
|
||||
if response.status_code == 200:
|
||||
# Update the cache file
|
||||
with open(CACHE_FILE, 'a') as f:
|
||||
with open(CACHE_FILE, 'a', encoding='utf-8') as f:
|
||||
f.write(f'{url}\n')
|
||||
|
||||
return True
|
||||
@ -162,7 +177,7 @@ def define_env(env):
|
||||
|
||||
assert subprocess.call(command, shell=True) == 0
|
||||
|
||||
with open(output, 'r') as f:
|
||||
with open(output, encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
return content
|
||||
@ -185,12 +200,13 @@ def define_env(env):
|
||||
return assets
|
||||
|
||||
@env.macro
|
||||
def includefile(filename: str, title: str, format: str = ''):
|
||||
def includefile(filename: str, title: str, fmt: str = ''):
|
||||
"""Include a file in the documentation, in a 'collapse' block.
|
||||
|
||||
Arguments:
|
||||
- filename: The name of the file to include (relative to the top-level directory)
|
||||
- title:
|
||||
- fmt:
|
||||
"""
|
||||
here = os.path.dirname(__file__)
|
||||
path = os.path.join(here, '..', filename)
|
||||
@ -199,11 +215,11 @@ def define_env(env):
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f'Required file {path} does not exist.')
|
||||
|
||||
with open(path, 'r') as f:
|
||||
with open(path, encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
data = f'??? abstract "{title}"\n\n'
|
||||
data += f' ```{format}\n'
|
||||
data += f' ```{fmt}\n'
|
||||
data += textwrap.indent(content, ' ')
|
||||
data += '\n\n'
|
||||
data += ' ```\n\n'
|
||||
@ -218,4 +234,38 @@ def define_env(env):
|
||||
'src', 'backend', 'InvenTree', 'report', 'templates', filename
|
||||
)
|
||||
|
||||
return includefile(fn, f'Template: {base}', format='html')
|
||||
return includefile(fn, f'Template: {base}', fmt='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')
|
||||
units = setting.get('units')
|
||||
|
||||
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)
|
||||
|
@ -77,6 +77,7 @@ nav:
|
||||
- Core Concepts:
|
||||
- Terminology: concepts/terminology.md
|
||||
- Physical Units: concepts/units.md
|
||||
- Custom States: concepts/custom_states.md
|
||||
- Development:
|
||||
- Contributing: develop/contributing.md
|
||||
- Devcontainer: develop/devcontainer.md
|
||||
|
@ -173,9 +173,9 @@ idna==3.7 \
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
importlib-metadata==8.2.0 \
|
||||
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
|
||||
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
|
||||
importlib-metadata==8.4.0 \
|
||||
--hash=sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1 \
|
||||
--hash=sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5
|
||||
# via
|
||||
# markdown
|
||||
# mkdocs
|
||||
@ -301,21 +301,21 @@ mkdocs-get-deps==0.2.0 \
|
||||
--hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \
|
||||
--hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134
|
||||
# via mkdocs
|
||||
mkdocs-git-revision-date-localized-plugin==1.2.6 \
|
||||
--hash=sha256:e432942ce4ee8aa9b9f4493e993dee9d2cc08b3ea2b40a3d6b03ca0f2a4bcaa2 \
|
||||
--hash=sha256:f015cb0f3894a39b33447b18e270ae391c4e25275cac5a626e80b243784e2692
|
||||
mkdocs-git-revision-date-localized-plugin==1.2.7 \
|
||||
--hash=sha256:2f83b52b4dad642751a79465f80394672cbad022129286f40d36b03aebee490f \
|
||||
--hash=sha256:d2b30ccb74ec8e118298758d75ae4b4f02c620daf776a6c92fcbb58f2b78f19f
|
||||
# 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 \
|
||||
--hash=sha256:fe348d75f01c911f362b6d998c57b3d85b505876dde69db924f2c512c395c328
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material==9.5.30 \
|
||||
--hash=sha256:3fd417dd42d679e3ba08b9e2d72cd8b8af142cc4a3969676ad6b00993dd182ec \
|
||||
--hash=sha256:fc070689c5250a180e9b9d79d8491ef9a3a7acb240db0728728d6c31eeb131d4
|
||||
mkdocs-material==9.5.33 \
|
||||
--hash=sha256:d23a8b5e3243c9b2f29cdfe83051104a8024b767312dc8fde05ebe91ad55d89d \
|
||||
--hash=sha256:dbc79cf0fdc6e2c366aa987de8b0c9d4e2bb9f156e7466786ba2fd0f9bf7ffca
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material-extensions==1.3.1 \
|
||||
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
|
||||
@ -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.1 \
|
||||
--hash=sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064 \
|
||||
--hash=sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b
|
||||
# via importlib-metadata
|
||||
|
@ -20,13 +20,30 @@ src = ["src/backend/InvenTree"]
|
||||
"__init__.py" = ["D104"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["A", "B", "C4", "D", "I", "N"]
|
||||
select = ["A", "B", "C", "C4", "D", "F", "I", "N", "SIM", "PIE", "PLE", "PLW", "RUF", "UP", "W"]
|
||||
# Things that should be enabled in the future:
|
||||
# - LOG
|
||||
# - DJ # for Django stuff
|
||||
# - S # for security stuff (bandit)
|
||||
|
||||
ignore = [
|
||||
"PLE1205",
|
||||
# - PLE1205 - Too many arguments for logging format string
|
||||
"PLW2901",
|
||||
# - PLW2901 - Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
|
||||
"PLW0602","PLW0603","PLW0604", # global variable things
|
||||
"RUF015",
|
||||
# - RUF015 - Prefer next({iterable}) over single element slice
|
||||
"RUF012",
|
||||
# - RUF012 - Mutable class attributes should be annotated with typing.ClassVar
|
||||
"SIM117",
|
||||
# - SIM117 - Use a single with statement with multiple contexts instead of nested with statements
|
||||
"SIM102",
|
||||
# - SIM102 - Use a single if statement instead of nested if statements
|
||||
"SIM105",
|
||||
# - SIM105 - Use contextlib.suppress({exception}) instead of try-except-pass
|
||||
"C901",
|
||||
# - C901 - function is too complex
|
||||
"N999",
|
||||
# - N802 - function name should be lowercase
|
||||
"N802",
|
||||
@ -42,7 +59,7 @@ ignore = [
|
||||
"B904",
|
||||
|
||||
# Remove fast
|
||||
"A001", "A002","A003","B018"
|
||||
"A002", "B018"
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
|
@ -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
|
||||
|
@ -104,14 +104,16 @@ class InvenTreeResource(ModelResource):
|
||||
attribute = getattr(field, 'attribute', field_name)
|
||||
|
||||
# Check if the associated database field is a non-nullable string
|
||||
if db_field := db_fields.get(attribute):
|
||||
if (
|
||||
if (
|
||||
(db_field := db_fields.get(attribute))
|
||||
and (
|
||||
isinstance(db_field, CharField)
|
||||
and db_field.blank
|
||||
and not db_field.null
|
||||
):
|
||||
if column not in self.CONVERT_NULL_FIELDS:
|
||||
self.CONVERT_NULL_FIELDS.append(column)
|
||||
)
|
||||
and column not in self.CONVERT_NULL_FIELDS
|
||||
):
|
||||
self.CONVERT_NULL_FIELDS.append(column)
|
||||
|
||||
return super().before_import(dataset, using_transactions, dry_run, **kwargs)
|
||||
|
||||
|
@ -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,15 +20,13 @@ 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
|
||||
from users.models import ApiToken
|
||||
|
||||
from .email import is_email_configured
|
||||
from .helpers_email import is_email_configured
|
||||
from .mixins import ListAPI, RetrieveUpdateAPI
|
||||
from .status import check_system_health, is_worker_running
|
||||
from .version import inventreeApiText
|
||||
@ -80,7 +77,7 @@ class LicenseView(APIView):
|
||||
# Ensure we do not have any duplicate 'name' values in the list
|
||||
for entry in data:
|
||||
name = None
|
||||
for key in entry.keys():
|
||||
for key in entry:
|
||||
if key.lower() == 'name':
|
||||
name = entry[key]
|
||||
break
|
||||
@ -324,7 +321,6 @@ class BulkDeleteMixin:
|
||||
Raises:
|
||||
ValidationError: If the deletion should not proceed
|
||||
"""
|
||||
pass
|
||||
|
||||
def filter_delete_queryset(self, queryset, request):
|
||||
"""Provide custom filtering for the queryset *before* it is deleted.
|
||||
@ -401,8 +397,6 @@ class BulkDeleteMixin:
|
||||
class ListCreateDestroyAPIView(BulkDeleteMixin, ListCreateAPI):
|
||||
"""Custom API endpoint which provides BulkDelete functionality in addition to List and Create."""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class APISearchViewSerializer(serializers.Serializer):
|
||||
"""Serializer for the APISearchView."""
|
||||
|
@ -1,13 +1,77 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 232
|
||||
INVENTREE_API_VERSION = 249
|
||||
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v249 - 2024-08-23 : https://github.com/inventree/InvenTree/pull/7978
|
||||
- Sort status enums
|
||||
|
||||
v248 - 2024-08-23 : https://github.com/inventree/InvenTree/pull/7965
|
||||
- Small adjustments to labels for new custom status fields
|
||||
|
||||
v247 - 2024-08-22 : https://github.com/inventree/InvenTree/pull/7956
|
||||
- Adjust "attachment" field on StockItemTestResult serializer
|
||||
- Allow null values for attachment
|
||||
|
||||
v246 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7862
|
||||
- Adds custom status fields to various serializers
|
||||
- Adds endpoints to admin custom status fields
|
||||
|
||||
v245 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7520
|
||||
- Documented pagination fields (no functional changes)
|
||||
|
||||
v244 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7941
|
||||
- Adds "create_child_builds" field to the Build API
|
||||
- Write-only field to create child builds from the API
|
||||
- Only available when creating a new build order
|
||||
|
||||
v243 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7940
|
||||
- Expose "ancestor" filter to the BuildOrder API
|
||||
|
||||
v242 - 2024-08-20 : https://github.com/inventree/InvenTree/pull/7932
|
||||
- Adds "level" attribute to BuildOrder serializer
|
||||
- Allow ordering of BuildOrder API by "level" attribute
|
||||
- Allow "parent" filter for BuildOrder API to have "cascade=True" option
|
||||
|
||||
v241 - 2024-08-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
|
||||
|
||||
v234 - 2024-08-08 : https://github.com/inventree/InvenTree/pull/7829
|
||||
- Fixes bug in the plugin metadata endpoint
|
||||
|
||||
v233 - 2024-08-04 : https://github.com/inventree/InvenTree/pull/7807
|
||||
- Adds new endpoints for managing state of build orders
|
||||
- Adds new endpoints for managing state of purchase orders
|
||||
- Adds new endpoints for managing state of sales orders
|
||||
- Adds new endpoints for managing state of return orders
|
||||
|
||||
v232 - 2024-08-03 : https://github.com/inventree/InvenTree/pull/7793
|
||||
- Allow ordering of SalesOrderShipment API by 'shipment_date' and 'delivery_date'
|
||||
|
||||
|
@ -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
|
||||
@ -125,7 +125,7 @@ class InvenTreeConfig(AppConfig):
|
||||
for task in tasks:
|
||||
ref_name = f'{task.func.__module__}.{task.func.__name__}'
|
||||
|
||||
if ref_name in existing_tasks.keys():
|
||||
if ref_name in existing_tasks:
|
||||
# This task already exists - update the details if required
|
||||
existing_task = existing_tasks[ref_name]
|
||||
|
||||
|
@ -131,7 +131,7 @@ def load_config_data(set_cache: bool = False) -> map:
|
||||
|
||||
cfg_file = get_config_file()
|
||||
|
||||
with open(cfg_file, 'r') as cfg:
|
||||
with open(cfg_file, encoding='utf-8') as cfg:
|
||||
data = yaml.safe_load(cfg)
|
||||
|
||||
# Set the cache if requested
|
||||
|
@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Provides extra global data to all templates."""
|
||||
|
||||
import InvenTree.email
|
||||
import InvenTree.helpers_email
|
||||
import InvenTree.ready
|
||||
import InvenTree.status
|
||||
from generic.states import StatusCode
|
||||
from InvenTree.helpers import inheritors
|
||||
from generic.states.custom import get_custom_classes
|
||||
from users.models import RuleSet, check_user_role
|
||||
|
||||
|
||||
@ -27,7 +25,7 @@ def health_status(request):
|
||||
|
||||
status = {
|
||||
'django_q_running': InvenTree.status.is_worker_running(),
|
||||
'email_configured': InvenTree.email.is_email_configured(),
|
||||
'email_configured': InvenTree.helpers_email.is_email_configured(),
|
||||
}
|
||||
|
||||
# The following keys are required to denote system health
|
||||
@ -53,7 +51,10 @@ def status_codes(request):
|
||||
return {}
|
||||
|
||||
request._inventree_status_codes = True
|
||||
return {cls.__name__: cls.template_context() for cls in inheritors(StatusCode)}
|
||||
get_custom = InvenTree.ready.isRebuildingData() is False
|
||||
return {
|
||||
cls.__name__: cls.template_context() for cls in get_custom_classes(get_custom)
|
||||
}
|
||||
|
||||
|
||||
def user_roles(request):
|
||||
@ -72,7 +73,7 @@ def user_roles(request):
|
||||
|
||||
roles = {}
|
||||
|
||||
for role in RuleSet.get_ruleset_models().keys():
|
||||
for role in RuleSet.get_ruleset_models():
|
||||
permissions = {}
|
||||
|
||||
for perm in ['view', 'add', 'change', 'delete']:
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -95,7 +96,7 @@ def from_engineering_notation(value):
|
||||
"""
|
||||
value = str(value).strip()
|
||||
|
||||
pattern = '(\d+)([a-zA-Z]+)(\d+)(.*)'
|
||||
pattern = r'(\d+)([a-zA-Z]+)(\d+)(.*)'
|
||||
|
||||
if match := re.match(pattern, value):
|
||||
left, prefix, right, suffix = match.groups()
|
||||
@ -133,7 +134,7 @@ def convert_value(value, unit):
|
||||
return value
|
||||
|
||||
|
||||
def convert_physical_value(value: str, unit: str = None, strip_units=True):
|
||||
def convert_physical_value(value: str, unit: Optional[str] = None, strip_units=True):
|
||||
"""Validate that the provided value is a valid physical quantity.
|
||||
|
||||
Arguments:
|
||||
@ -153,7 +154,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 +197,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:
|
||||
@ -245,8 +246,4 @@ def is_dimensionless(value):
|
||||
if value.units == ureg.dimensionless:
|
||||
return True
|
||||
|
||||
if value.to_base_units().units == ureg.dimensionless:
|
||||
return True
|
||||
|
||||
# At this point, the value is not dimensionless
|
||||
return False
|
||||
return value.to_base_units().units == ureg.dimensionless
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Custom exception handling for the DRF API."""
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import sys
|
||||
@ -9,7 +8,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
|
||||
@ -42,10 +40,7 @@ def log_error(path, error_name=None, error_info=None, error_data=None):
|
||||
if kind in settings.IGNORED_ERRORS:
|
||||
return
|
||||
|
||||
if error_name:
|
||||
kind = error_name
|
||||
else:
|
||||
kind = getattr(kind, '__name__', 'Unknown Error')
|
||||
kind = error_name or getattr(kind, '__name__', 'Unknown Error')
|
||||
|
||||
if error_info:
|
||||
info = error_info
|
||||
|
@ -31,10 +31,7 @@ class InvenTreeExchange(SimpleExchangeBackend):
|
||||
# Find the selected exchange rate plugin
|
||||
slug = get_global_setting('CURRENCY_UPDATE_PLUGIN', create=False)
|
||||
|
||||
if slug:
|
||||
plugin = registry.get_plugin(slug)
|
||||
else:
|
||||
plugin = None
|
||||
plugin = registry.get_plugin(slug) if slug else None
|
||||
|
||||
if not plugin:
|
||||
# Find the first active currency exchange plugin
|
||||
|
@ -93,9 +93,8 @@ class InvenTreeModelMoneyField(ModelMoneyField):
|
||||
allow_negative = kwargs.pop('allow_negative', False)
|
||||
|
||||
# If no validators are provided, add some "standard" ones
|
||||
if len(validators) == 0:
|
||||
if not allow_negative:
|
||||
validators.append(MinMoneyValidator(0))
|
||||
if len(validators) == 0 and not allow_negative:
|
||||
validators.append(MinMoneyValidator(0))
|
||||
|
||||
kwargs['validators'] = validators
|
||||
|
||||
|
@ -118,10 +118,7 @@ class InvenTreeOrderingFilter(filters.OrderingFilter):
|
||||
field = field[1:]
|
||||
|
||||
# Are aliases defined for this field?
|
||||
if field in aliases:
|
||||
alias = aliases[field]
|
||||
else:
|
||||
alias = field
|
||||
alias = aliases.get(field, field)
|
||||
|
||||
"""
|
||||
Potentially, a single field could be "aliased" to multiple field,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import re
|
||||
import string
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import translation
|
||||
@ -106,10 +107,7 @@ def construct_format_regex(fmt_string: str) -> str:
|
||||
# Add a named capture group for the format entry
|
||||
if name:
|
||||
# Check if integer values are required
|
||||
if _fmt.endswith('d'):
|
||||
c = '\d'
|
||||
else:
|
||||
c = '.'
|
||||
c = '\\d' if _fmt.endswith('d') else '.'
|
||||
|
||||
# Specify width
|
||||
# TODO: Introspect required width
|
||||
@ -160,7 +158,7 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:
|
||||
"""
|
||||
info = parse_format_string(fmt_string)
|
||||
|
||||
if name not in info.keys():
|
||||
if name not in info:
|
||||
raise NameError(_(f"Value '{name}' does not appear in pattern format"))
|
||||
|
||||
# Construct a regular expression for matching against the provided format string
|
||||
@ -182,8 +180,8 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:
|
||||
|
||||
def format_money(
|
||||
money: Money,
|
||||
decimal_places: int = None,
|
||||
format: str = None,
|
||||
decimal_places: Optional[int] = None,
|
||||
fmt: Optional[str] = None,
|
||||
include_symbol: bool = True,
|
||||
) -> str:
|
||||
"""Format money object according to the currently set local.
|
||||
@ -191,7 +189,7 @@ def format_money(
|
||||
Args:
|
||||
money (Money): The money object to format
|
||||
decimal_places (int): Number of decimal places to use
|
||||
format (str): Format pattern according LDML / the babel format pattern syntax (https://babel.pocoo.org/en/latest/numbers.html)
|
||||
fmt (str): Format pattern according LDML / the babel format pattern syntax (https://babel.pocoo.org/en/latest/numbers.html)
|
||||
|
||||
Returns:
|
||||
str: The formatted string
|
||||
@ -199,10 +197,10 @@ def format_money(
|
||||
Raises:
|
||||
ValueError: format string is incorrectly specified
|
||||
"""
|
||||
language = None and translation.get_language() or settings.LANGUAGE_CODE
|
||||
language = (None) or settings.LANGUAGE_CODE
|
||||
locale = Locale.parse(translation.to_locale(language))
|
||||
if format:
|
||||
pattern = parse_pattern(format)
|
||||
if fmt:
|
||||
pattern = parse_pattern(fmt)
|
||||
else:
|
||||
pattern = locale.currency_formats['standard']
|
||||
if decimal_places is not None:
|
||||
|
@ -266,9 +266,8 @@ class RegistratonMixin:
|
||||
raise forms.ValidationError(
|
||||
_('The provided primary email address is not valid.')
|
||||
)
|
||||
else:
|
||||
if split_email[1] == option[1:]:
|
||||
return super().clean_email(email)
|
||||
elif split_email[1] == option[1:]:
|
||||
return super().clean_email(email)
|
||||
|
||||
logger.info('The provided email domain for %s is not approved', email)
|
||||
raise forms.ValidationError(_('The provided email domain is not approved.'))
|
||||
|
@ -3,22 +3,21 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from pathlib import Path
|
||||
from typing import TypeVar, Union
|
||||
from typing import Optional, TypeVar, Union
|
||||
from wsgiref.util import FileWrapper
|
||||
|
||||
import django.utils.timezone as timezone
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.core.files.storage import Storage, default_storage
|
||||
from django.http import StreamingHttpResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import pytz
|
||||
@ -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
|
||||
@ -99,10 +97,7 @@ def generateTestKey(test_name: str) -> str:
|
||||
if char.isidentifier():
|
||||
return True
|
||||
|
||||
if char.isalnum():
|
||||
return True
|
||||
|
||||
return False
|
||||
return bool(char.isalnum())
|
||||
|
||||
# Remove any characters that cannot be used to represent a variable
|
||||
key = ''.join([c for c in key if valid_char(c)])
|
||||
@ -492,10 +487,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
||||
except ValueError:
|
||||
raise ValidationError([_('Invalid quantity provided')])
|
||||
|
||||
if input_string:
|
||||
input_string = str(input_string).strip()
|
||||
else:
|
||||
input_string = ''
|
||||
input_string = str(input_string).strip() if input_string else ''
|
||||
|
||||
if len(input_string) == 0:
|
||||
raise ValidationError([_('Empty serial number string')])
|
||||
@ -802,10 +794,10 @@ def remove_non_printable_characters(
|
||||
if remove_unicode:
|
||||
# Remove Unicode control characters
|
||||
if remove_newline:
|
||||
cleaned = regex.sub('[^\P{C}]+', '', cleaned)
|
||||
cleaned = regex.sub(r'[^\P{C}]+', '', cleaned)
|
||||
else:
|
||||
# Use 'negative-lookahead' to exclude newline character
|
||||
cleaned = regex.sub('(?![\x0a])[^\P{C}]+', '', cleaned)
|
||||
cleaned = regex.sub('(?![\x0a])[^\\P{C}]+', '', cleaned)
|
||||
|
||||
return cleaned
|
||||
|
||||
@ -829,7 +821,7 @@ def hash_barcode(barcode_data):
|
||||
def hash_file(filename: Union[str, Path], storage: Union[Storage, None] = None):
|
||||
"""Return the MD5 hash of a file."""
|
||||
content = (
|
||||
open(filename, 'rb').read()
|
||||
open(filename, 'rb').read() # noqa: SIM115
|
||||
if storage is None
|
||||
else storage.open(str(filename), 'rb').read()
|
||||
)
|
||||
@ -867,7 +859,7 @@ def server_timezone() -> str:
|
||||
return settings.TIME_ZONE
|
||||
|
||||
|
||||
def to_local_time(time, target_tz: str = None):
|
||||
def to_local_time(time, target_tz: Optional[str] = None):
|
||||
"""Convert the provided time object to the local timezone.
|
||||
|
||||
Arguments:
|
||||
@ -955,8 +947,15 @@ def get_objectreference(
|
||||
Inheritors_T = TypeVar('Inheritors_T')
|
||||
|
||||
|
||||
def inheritors(cls: type[Inheritors_T]) -> set[type[Inheritors_T]]:
|
||||
"""Return all classes that are subclasses from the supplied cls."""
|
||||
def inheritors(
|
||||
cls: type[Inheritors_T], subclasses: bool = True
|
||||
) -> set[type[Inheritors_T]]:
|
||||
"""Return all classes that are subclasses from the supplied cls.
|
||||
|
||||
Args:
|
||||
cls: The class to search for subclasses
|
||||
subclasses: Include subclasses of subclasses (default = True)
|
||||
"""
|
||||
subcls = set()
|
||||
work = [cls]
|
||||
|
||||
@ -965,7 +964,8 @@ def inheritors(cls: type[Inheritors_T]) -> set[type[Inheritors_T]]:
|
||||
for child in parent.__subclasses__():
|
||||
if child not in subcls:
|
||||
subcls.add(child)
|
||||
work.append(child)
|
||||
if subclasses:
|
||||
work.append(child)
|
||||
return subcls
|
||||
|
||||
|
||||
|
@ -62,7 +62,7 @@ def send_email(subject, body, recipients, from_email=None, html_message=None):
|
||||
# If we are importing data, don't send emails
|
||||
return
|
||||
|
||||
if not InvenTree.email.is_email_configured() and not settings.TESTING:
|
||||
if not is_email_configured() and not settings.TESTING:
|
||||
# Email is not configured / enabled
|
||||
return
|
||||
|
@ -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)
|
||||
@ -118,10 +114,7 @@ def download_image_from_url(remote_url, timeout=2.5):
|
||||
# Add user specified user-agent to request (if specified)
|
||||
user_agent = get_global_setting('INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT')
|
||||
|
||||
if user_agent:
|
||||
headers = {'User-Agent': user_agent}
|
||||
else:
|
||||
headers = None
|
||||
headers = {'User-Agent': user_agent} if user_agent else None
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
@ -134,7 +127,7 @@ def download_image_from_url(remote_url, timeout=2.5):
|
||||
# Throw an error if anything goes wrong
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
raise Exception(_('Connection error') + f': {str(exc)}')
|
||||
raise Exception(_('Connection error') + f': {exc!s}')
|
||||
except requests.exceptions.Timeout as exc:
|
||||
raise exc
|
||||
except requests.exceptions.HTTPError:
|
||||
@ -142,7 +135,7 @@ def download_image_from_url(remote_url, timeout=2.5):
|
||||
_('Server responded with invalid status code') + f': {response.status_code}'
|
||||
)
|
||||
except Exception as exc:
|
||||
raise Exception(_('Exception occurred') + f': {str(exc)}')
|
||||
raise Exception(_('Exception occurred') + f': {exc!s}')
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(
|
||||
@ -203,8 +196,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 '-'
|
||||
|
||||
@ -219,9 +210,6 @@ def render_currency(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if decimal_places is None:
|
||||
decimal_places = get_global_setting('PRICING_DECIMAL_PLACES', 6)
|
||||
|
||||
if min_decimal_places is None:
|
||||
min_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES_MIN', 0)
|
||||
|
||||
@ -231,17 +219,19 @@ def render_currency(
|
||||
value = Decimal(str(money.amount)).normalize()
|
||||
value = str(value)
|
||||
|
||||
if '.' in value:
|
||||
decimals = len(value.split('.')[-1])
|
||||
|
||||
decimals = max(decimals, min_decimal_places)
|
||||
decimals = min(decimals, decimal_places)
|
||||
|
||||
decimal_places = decimals
|
||||
if decimal_places is not None:
|
||||
# Decimal place count is provided, use it
|
||||
pass
|
||||
elif '.' in value:
|
||||
# If the value has a decimal point, use the number of decimal places in the value
|
||||
decimal_places = len(value.split('.')[-1])
|
||||
else:
|
||||
decimal_places = max(decimal_places, 2)
|
||||
# No decimal point, use 2 as a default
|
||||
decimal_places = 2
|
||||
|
||||
decimal_places = max(decimal_places, max_decimal_places)
|
||||
# Clip the decimal places to the specified range
|
||||
decimal_places = max(decimal_places, min_decimal_places)
|
||||
decimal_places = min(decimal_places, max_decimal_places)
|
||||
|
||||
return format_money(
|
||||
money, decimal_places=decimal_places, include_symbol=include_symbol
|
||||
|
@ -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', encoding='utf-8') as f:
|
||||
json.dump(settings, f, indent=4)
|
||||
|
||||
print(f"Exported InvenTree settings definitions to '{filename}'")
|
@ -103,14 +103,14 @@ class Command(BaseCommand):
|
||||
})
|
||||
|
||||
self.stdout.write(f'Writing icon map for {len(icons.keys())} icons')
|
||||
with open(kwargs['output_file'], 'w') as f:
|
||||
with open(kwargs['output_file'], 'w', encoding='utf-8') as f:
|
||||
json.dump(icons, f, indent=2)
|
||||
|
||||
self.stdout.write(f'Icon map written to {kwargs["output_file"]}')
|
||||
|
||||
# Import icon map file
|
||||
if kwargs['input_file']:
|
||||
with open(kwargs['input_file'], 'r') as f:
|
||||
with open(kwargs['input_file'], encoding='utf-8') as f:
|
||||
icons = json.load(f)
|
||||
|
||||
self.stdout.write(f'Loaded icon map for {len(icons.keys())} icons')
|
||||
|
@ -19,10 +19,11 @@ def render_file(file_name, source, target, locales, ctx):
|
||||
|
||||
target_file = os.path.join(target, locale + '.' + file_name)
|
||||
|
||||
with open(target_file, 'w') as localised_file:
|
||||
with lang_over(locale):
|
||||
rendered = render_to_string(os.path.join(source, file_name), ctx)
|
||||
localised_file.write(rendered)
|
||||
with open(target_file, 'w', encoding='utf-8') as localised_file, lang_over(
|
||||
locale
|
||||
):
|
||||
rendered = render_to_string(os.path.join(source, file_name), ctx)
|
||||
localised_file.write(rendered)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -35,7 +35,7 @@ class Command(BaseCommand):
|
||||
img_paths.append(x.path)
|
||||
|
||||
if len(img_paths) > 0:
|
||||
if all((os.path.exists(path) for path in img_paths)):
|
||||
if all(os.path.exists(path) for path in img_paths):
|
||||
# All images exist - skip further work
|
||||
return
|
||||
|
||||
|
@ -35,4 +35,4 @@ class Command(BaseCommand):
|
||||
mfa_user[0].staticdevice_set.all().delete()
|
||||
# TOTP tokens
|
||||
mfa_user[0].totpdevice_set.all().delete()
|
||||
print(f'Removed all MFA methods for user {str(mfa_user[0])}')
|
||||
print(f'Removed all MFA methods for user {mfa_user[0]!s}')
|
||||
|
@ -204,7 +204,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
|
||||
# Iterate through simple fields
|
||||
for name, field in model_fields.fields.items():
|
||||
if name in serializer_info.keys():
|
||||
if name in serializer_info:
|
||||
if name in read_only_fields:
|
||||
serializer_info[name]['read_only'] = True
|
||||
|
||||
@ -236,7 +236,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
|
||||
# Iterate through relations
|
||||
for name, relation in model_fields.relations.items():
|
||||
if name not in serializer_info.keys():
|
||||
if name not in serializer_info:
|
||||
# Skip relation not defined in serializer
|
||||
continue
|
||||
|
||||
@ -307,12 +307,12 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
instance_filters = instance.api_instance_filters()
|
||||
|
||||
for field_name, field_filters in instance_filters.items():
|
||||
if field_name not in serializer_info.keys():
|
||||
if field_name not in serializer_info:
|
||||
# The field might be missing, but is added later on
|
||||
# This function seems to get called multiple times?
|
||||
continue
|
||||
|
||||
if 'instance_filters' not in serializer_info[field_name].keys():
|
||||
if 'instance_filters' not in serializer_info[field_name]:
|
||||
serializer_info[field_name]['instance_filters'] = {}
|
||||
|
||||
for key, value in field_filters.items():
|
||||
@ -363,7 +363,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
field_info['type'] = 'related field'
|
||||
field_info['model'] = model._meta.model_name
|
||||
|
||||
# Special case for 'user' model
|
||||
# Special case for special models
|
||||
if field_info['model'] == 'user':
|
||||
field_info['api_url'] = '/api/user/'
|
||||
elif field_info['model'] == 'contenttype':
|
||||
@ -381,6 +381,14 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
if field_info['type'] == 'dependent field':
|
||||
field_info['depends_on'] = field.depends_on
|
||||
|
||||
# Extend field info if the field has a get_field_info method
|
||||
if (
|
||||
not field_info.get('read_only')
|
||||
and hasattr(field, 'get_field_info')
|
||||
and callable(field.get_field_info)
|
||||
):
|
||||
field_info = field.get_field_info(field, field_info)
|
||||
|
||||
return field_info
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ def get_token_from_request(request):
|
||||
return None
|
||||
|
||||
|
||||
class AuthRequiredMiddleware(object):
|
||||
class AuthRequiredMiddleware:
|
||||
"""Check for user to be authenticated."""
|
||||
|
||||
def __init__(self, get_response):
|
||||
@ -92,23 +92,18 @@ class AuthRequiredMiddleware(object):
|
||||
|
||||
# Allow static files to be accessed without auth
|
||||
# Important for e.g. login page
|
||||
if request.path_info.startswith('/static/'):
|
||||
authorized = True
|
||||
|
||||
# Unauthorized users can access the login page
|
||||
elif request.path_info.startswith('/accounts/'):
|
||||
authorized = True
|
||||
|
||||
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}'
|
||||
if (
|
||||
request.path_info.startswith('/static/')
|
||||
or request.path_info.startswith('/accounts/')
|
||||
or (
|
||||
request.path_info.startswith(f'/{settings.FRONTEND_URL_BASE}/')
|
||||
or request.path_info.startswith('/assets/')
|
||||
or request.path_info == f'/{settings.FRONTEND_URL_BASE}'
|
||||
)
|
||||
or self.check_token(request)
|
||||
):
|
||||
authorized = True
|
||||
|
||||
elif self.check_token(request):
|
||||
authorized = True
|
||||
|
||||
# No authorization was found for the request
|
||||
if not authorized:
|
||||
path = request.path_info
|
||||
|
@ -128,14 +128,10 @@ class CreateAPI(CleanMixin, generics.CreateAPIView):
|
||||
class RetrieveAPI(generics.RetrieveAPIView):
|
||||
"""View for retrieve API."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RetrieveUpdateAPI(CleanMixin, generics.RetrieveUpdateAPIView):
|
||||
"""View for retrieve and update API."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CustomDestroyModelMixin:
|
||||
"""This mixin was created pass the kwargs from the API to the models."""
|
||||
|
@ -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
|
||||
|
||||
@ -390,10 +390,7 @@ class ReferenceIndexingMixin(models.Model):
|
||||
except Exception:
|
||||
# If anything goes wrong, return the most recent reference
|
||||
recent = cls.get_most_recent_item()
|
||||
if recent:
|
||||
reference = recent.reference
|
||||
else:
|
||||
reference = ''
|
||||
reference = recent.reference if recent else ''
|
||||
|
||||
return reference
|
||||
|
||||
@ -410,14 +407,14 @@ class ReferenceIndexingMixin(models.Model):
|
||||
})
|
||||
|
||||
# Check that only 'allowed' keys are provided
|
||||
for key in info.keys():
|
||||
if key not in ctx.keys():
|
||||
for key in info:
|
||||
if key not in ctx:
|
||||
raise ValidationError({
|
||||
'value': _('Unknown format key specified') + f": '{key}'"
|
||||
})
|
||||
|
||||
# Check that the 'ref' variable is specified
|
||||
if 'ref' not in info.keys():
|
||||
if 'ref' not in info:
|
||||
raise ValidationError({
|
||||
'value': _('Missing required format key') + ": 'ref'"
|
||||
})
|
||||
@ -859,7 +856,7 @@ class InvenTreeTree(MetadataMixin, PluginValidationMixin, MPTTModel):
|
||||
Returns:
|
||||
List of category names from the top level to this category
|
||||
"""
|
||||
return self.parentpath + [self]
|
||||
return [*self.parentpath, self]
|
||||
|
||||
def get_path(self):
|
||||
"""Return a list of element in the item tree.
|
||||
@ -1099,4 +1096,4 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
|
||||
|
||||
except Exception as exc:
|
||||
"""We do not want to throw an exception while reporting an exception"""
|
||||
logger.error(exc) # noqa: LOG005
|
||||
logger.error(exc)
|
||||
|
@ -105,8 +105,7 @@ class IsStaffOrReadOnly(permissions.IsAdminUser):
|
||||
def has_permission(self, request, view):
|
||||
"""Check if the user is a superuser."""
|
||||
return bool(
|
||||
request.user
|
||||
and request.user.is_staff
|
||||
(request.user and request.user.is_staff)
|
||||
or request.method in permissions.SAFE_METHODS
|
||||
)
|
||||
|
||||
|
@ -11,43 +11,37 @@ def isInTestMode():
|
||||
|
||||
def isImportingData():
|
||||
"""Returns True if the database is currently importing (or exporting) data, e.g. 'loaddata' command is performed."""
|
||||
return any((x in sys.argv for x in ['flush', 'loaddata', 'dumpdata']))
|
||||
return any(x in sys.argv for x in ['flush', 'loaddata', 'dumpdata'])
|
||||
|
||||
|
||||
def isRunningMigrations():
|
||||
"""Return True if the database is currently running migrations."""
|
||||
return any(
|
||||
(
|
||||
x in sys.argv
|
||||
for x in ['migrate', 'makemigrations', 'showmigrations', 'runmigrations']
|
||||
)
|
||||
x in sys.argv
|
||||
for x in ['migrate', 'makemigrations', 'showmigrations', 'runmigrations']
|
||||
)
|
||||
|
||||
|
||||
def isRebuildingData():
|
||||
"""Return true if any of the rebuilding commands are being executed."""
|
||||
return any(
|
||||
(
|
||||
x in sys.argv
|
||||
for x in ['prerender', 'rebuild_models', 'rebuild_thumbnails', 'rebuild']
|
||||
)
|
||||
x in sys.argv
|
||||
for x in ['prerender', 'rebuild_models', 'rebuild_thumbnails', 'rebuild']
|
||||
)
|
||||
|
||||
|
||||
def isRunningBackup():
|
||||
"""Return true if any of the backup commands are being executed."""
|
||||
return any(
|
||||
(
|
||||
x in sys.argv
|
||||
for x in [
|
||||
'backup',
|
||||
'restore',
|
||||
'dbbackup',
|
||||
'dbresotore',
|
||||
'mediabackup',
|
||||
'mediarestore',
|
||||
]
|
||||
)
|
||||
x in sys.argv
|
||||
for x in [
|
||||
'backup',
|
||||
'restore',
|
||||
'dbbackup',
|
||||
'dbresotore',
|
||||
'mediabackup',
|
||||
'mediarestore',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@ -64,10 +58,7 @@ def isInServerThread():
|
||||
if 'runserver' in sys.argv:
|
||||
return True
|
||||
|
||||
if 'gunicorn' in sys.argv[0]:
|
||||
return True
|
||||
|
||||
return False
|
||||
return 'gunicorn' in sys.argv[0]
|
||||
|
||||
|
||||
def isInMainThread():
|
||||
@ -128,11 +119,7 @@ def canAppAccessDatabase(
|
||||
if not allow_plugins:
|
||||
excluded_commands.extend(['collectplugins'])
|
||||
|
||||
for cmd in excluded_commands:
|
||||
if cmd in sys.argv:
|
||||
return False
|
||||
|
||||
return True
|
||||
return all(cmd not in sys.argv for cmd in excluded_commands)
|
||||
|
||||
|
||||
def isPluginRegistryLoaded():
|
||||
|
@ -89,7 +89,7 @@ class InvenTreeCurrencySerializer(serializers.ChoiceField):
|
||||
)
|
||||
|
||||
if allow_blank:
|
||||
choices = [('', '---------')] + choices
|
||||
choices = [('', '---------'), *choices]
|
||||
|
||||
kwargs['choices'] = choices
|
||||
|
||||
@ -379,7 +379,7 @@ class InvenTreeTaggitSerializer(TaggitSerializer):
|
||||
|
||||
tag_object = super().update(instance, validated_data)
|
||||
|
||||
for key in to_be_tagged.keys():
|
||||
for key in to_be_tagged:
|
||||
# re-add the tagmanager
|
||||
new_tagobject = tag_object.__class__.objects.get(id=tag_object.id)
|
||||
setattr(tag_object, key, getattr(new_tagobject, key))
|
||||
@ -390,8 +390,6 @@ class InvenTreeTaggitSerializer(TaggitSerializer):
|
||||
class InvenTreeTagModelSerializer(InvenTreeTaggitSerializer, InvenTreeModelSerializer):
|
||||
"""Combination of InvenTreeTaggitSerializer and InvenTreeModelSerializer."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UserSerializer(InvenTreeModelSerializer):
|
||||
"""Serializer for a User."""
|
||||
@ -402,17 +400,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
|
||||
)
|
||||
|
||||
|
||||
@ -426,14 +424,15 @@ class ExendedUserSerializer(UserSerializer):
|
||||
class Meta(UserSerializer.Meta):
|
||||
"""Metaclass defines serializer fields."""
|
||||
|
||||
fields = UserSerializer.Meta.fields + [
|
||||
fields = [
|
||||
*UserSerializer.Meta.fields,
|
||||
'groups',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'is_active',
|
||||
]
|
||||
|
||||
read_only_fields = UserSerializer.Meta.read_only_fields + ['groups']
|
||||
read_only_fields = [*UserSerializer.Meta.read_only_fields, 'groups']
|
||||
|
||||
is_staff = serializers.BooleanField(
|
||||
label=_('Staff'), help_text=_('Does this user have staff permissions')
|
||||
@ -704,7 +703,6 @@ class DataFileUploadSerializer(serializers.Serializer):
|
||||
|
||||
def save(self):
|
||||
"""Empty overwrite for save."""
|
||||
...
|
||||
|
||||
|
||||
class DataFileExtractSerializer(serializers.Serializer):
|
||||
@ -806,11 +804,10 @@ class DataFileExtractSerializer(serializers.Serializer):
|
||||
required = field.get('required', False)
|
||||
|
||||
# Check for missing required columns
|
||||
if required:
|
||||
if name not in self.columns:
|
||||
raise serializers.ValidationError(
|
||||
_(f"Missing required column: '{name}'")
|
||||
)
|
||||
if required and name not in self.columns:
|
||||
raise serializers.ValidationError(
|
||||
_(f"Missing required column: '{name}'")
|
||||
)
|
||||
|
||||
for col in self.columns:
|
||||
if not col:
|
||||
@ -824,7 +821,6 @@ class DataFileExtractSerializer(serializers.Serializer):
|
||||
|
||||
def save(self):
|
||||
"""No "save" action for this serializer."""
|
||||
pass
|
||||
|
||||
|
||||
class NotesFieldMixin:
|
||||
|
@ -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
|
||||
@ -282,7 +281,7 @@ QUERYCOUNT = {
|
||||
'MIN_TIME_TO_LOG': 0.1,
|
||||
'MIN_QUERY_COUNT_TO_LOG': 25,
|
||||
},
|
||||
'IGNORE_REQUEST_PATTERNS': ['^(?!\/(api)?(plugin)?\/).*'],
|
||||
'IGNORE_REQUEST_PATTERNS': [r'^(?!\/(api)?(plugin)?\/).*'],
|
||||
'IGNORE_SQL_PATTERNS': [],
|
||||
'DISPLAY_DUPLICATES': 1,
|
||||
'RESPONSE_HEADER': 'X-Django-Query-Count',
|
||||
@ -299,9 +298,9 @@ if (
|
||||
and INVENTREE_ADMIN_ENABLED
|
||||
and not TESTING
|
||||
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
|
||||
@ -950,10 +949,7 @@ USE_I18N = True
|
||||
|
||||
# Do not use native timezone support in "test" mode
|
||||
# It generates a *lot* of cruft in the logs
|
||||
if not TESTING:
|
||||
USE_TZ = True # pragma: no cover
|
||||
else:
|
||||
USE_TZ = False
|
||||
USE_TZ = bool(not TESTING)
|
||||
|
||||
DATE_INPUT_FORMATS = ['%Y-%m-%d']
|
||||
|
||||
@ -1066,7 +1062,7 @@ COOKIE_MODE = (
|
||||
|
||||
valid_cookie_modes = {'lax': 'Lax', 'strict': 'Strict', 'none': None, 'null': None}
|
||||
|
||||
if COOKIE_MODE not in valid_cookie_modes.keys():
|
||||
if COOKIE_MODE not in valid_cookie_modes:
|
||||
logger.error('Invalid cookie samesite mode: %s', COOKIE_MODE)
|
||||
sys.exit(-1)
|
||||
|
||||
|
@ -94,20 +94,19 @@ for name, provider in providers.registry.provider_map.items():
|
||||
urls = []
|
||||
if len(adapters) == 1:
|
||||
urls = handle_oauth2(adapter=adapters[0])
|
||||
elif provider.id in legacy:
|
||||
logger.warning(
|
||||
'`%s` is not supported on platform UI. Use `%s` instead.',
|
||||
provider.id,
|
||||
legacy[provider.id],
|
||||
)
|
||||
continue
|
||||
else:
|
||||
if provider.id in legacy:
|
||||
logger.warning(
|
||||
'`%s` is not supported on platform UI. Use `%s` instead.',
|
||||
provider.id,
|
||||
legacy[provider.id],
|
||||
)
|
||||
continue
|
||||
else:
|
||||
logger.error(
|
||||
'Found handler that is not yet ready for platform UI: `%s`. Open an feature request on GitHub if you need it implemented.',
|
||||
provider.id,
|
||||
)
|
||||
continue
|
||||
logger.error(
|
||||
'Found handler that is not yet ready for platform UI: `%s`. Open an feature request on GitHub if you need it implemented.',
|
||||
provider.id,
|
||||
)
|
||||
continue
|
||||
provider_urlpatterns += [path(f'{provider.id}/', include(urls))]
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Provides system status functionality checks."""
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
@ -10,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django_q.models import Success
|
||||
from django_q.status import Stat
|
||||
|
||||
import InvenTree.email
|
||||
import InvenTree.helpers_email
|
||||
import InvenTree.ready
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
@ -65,7 +64,7 @@ def check_system_health(**kwargs):
|
||||
result = False
|
||||
logger.warning(_('Background worker check failed'))
|
||||
|
||||
if not InvenTree.email.is_email_configured(): # pragma: no cover
|
||||
if not InvenTree.helpers_email.is_email_configured(): # pragma: no cover
|
||||
result = False
|
||||
logger.warning(_('Email backend not configured'))
|
||||
|
||||
|
@ -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
|
||||
|
@ -9,7 +9,7 @@ import time
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Callable
|
||||
from typing import Callable, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import AppRegistryNotReady
|
||||
@ -206,7 +206,7 @@ def offload_task(
|
||||
raise_warning(f"WARNING: '{taskname}' not offloaded - Function not found")
|
||||
return False
|
||||
except Exception as exc:
|
||||
raise_warning(f"WARNING: '{taskname}' not offloaded due to {str(exc)}")
|
||||
raise_warning(f"WARNING: '{taskname}' not offloaded due to {exc!s}")
|
||||
log_error('InvenTree.offload_task')
|
||||
return False
|
||||
else:
|
||||
@ -256,7 +256,7 @@ def offload_task(
|
||||
_func(*args, **kwargs)
|
||||
except Exception as exc:
|
||||
log_error('InvenTree.offload_task')
|
||||
raise_warning(f"WARNING: '{taskname}' failed due to {str(exc)}")
|
||||
raise_warning(f"WARNING: '{taskname}' failed due to {exc!s}")
|
||||
raise exc
|
||||
|
||||
# Finally, task either completed successfully or was offloaded
|
||||
@ -291,7 +291,7 @@ class TaskRegister:
|
||||
|
||||
task_list: list[ScheduledTask] = []
|
||||
|
||||
def register(self, task, schedule, minutes: int = None):
|
||||
def register(self, task, schedule, minutes: Optional[int] = None):
|
||||
"""Register a task with the que."""
|
||||
self.task_list.append(ScheduledTask(task, schedule, minutes))
|
||||
|
||||
@ -299,7 +299,9 @@ class TaskRegister:
|
||||
tasks = TaskRegister()
|
||||
|
||||
|
||||
def scheduled_task(interval: str, minutes: int = None, tasklist: TaskRegister = None):
|
||||
def scheduled_task(
|
||||
interval: str, minutes: Optional[int] = None, tasklist: TaskRegister = None
|
||||
):
|
||||
"""Register the given task as a scheduled task.
|
||||
|
||||
Example:
|
||||
|
@ -80,7 +80,7 @@ def do_translate(parser, token):
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) < 2:
|
||||
raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])
|
||||
raise TemplateSyntaxError(f"'{bits[0]}' takes at least one argument")
|
||||
message_string = parser.compile_filter(bits[1])
|
||||
remaining = bits[2:]
|
||||
|
||||
@ -95,7 +95,7 @@ def do_translate(parser, token):
|
||||
option = remaining.pop(0)
|
||||
if option in seen:
|
||||
raise TemplateSyntaxError(
|
||||
"The '%s' option was specified more than once." % option
|
||||
f"The '{option}' option was specified more than once."
|
||||
)
|
||||
elif option == 'noop':
|
||||
noop = True
|
||||
@ -104,13 +104,12 @@ def do_translate(parser, token):
|
||||
value = remaining.pop(0)
|
||||
except IndexError:
|
||||
raise TemplateSyntaxError(
|
||||
"No argument provided to the '%s' tag for the context option."
|
||||
% bits[0]
|
||||
f"No argument provided to the '{bits[0]}' tag for the context option."
|
||||
)
|
||||
if value in invalid_context:
|
||||
raise TemplateSyntaxError(
|
||||
"Invalid argument '%s' provided to the '%s' tag for the context "
|
||||
'option' % (value, bits[0])
|
||||
f"Invalid argument '{value}' provided to the '{bits[0]}' tag for the context "
|
||||
'option'
|
||||
)
|
||||
message_context = parser.compile_filter(value)
|
||||
elif option == 'as':
|
||||
@ -118,16 +117,15 @@ def do_translate(parser, token):
|
||||
value = remaining.pop(0)
|
||||
except IndexError:
|
||||
raise TemplateSyntaxError(
|
||||
"No argument provided to the '%s' tag for the as option." % bits[0]
|
||||
f"No argument provided to the '{bits[0]}' tag for the as option."
|
||||
)
|
||||
asvar = value
|
||||
elif option == 'escape':
|
||||
escape = True
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
"Unknown argument for '%s' tag: '%s'. The only options "
|
||||
f"Unknown argument for '{bits[0]}' tag: '{option}'. The only options "
|
||||
"available are 'noop', 'context' \"xxx\", and 'as VAR'."
|
||||
% (bits[0], option)
|
||||
)
|
||||
seen.add(option)
|
||||
|
||||
|
@ -410,10 +410,7 @@ def progress_bar(val, max_val, *args, **kwargs):
|
||||
else:
|
||||
style = ''
|
||||
|
||||
if max_val != 0:
|
||||
percent = float(val / max_val) * 100
|
||||
else:
|
||||
percent = 0
|
||||
percent = float(val / max_val) * 100 if max_val != 0 else 0
|
||||
|
||||
if percent > 100:
|
||||
percent = 100
|
||||
@ -498,7 +495,7 @@ def primitive_to_javascript(primitive):
|
||||
elif type(primitive) in [int, float]:
|
||||
return primitive
|
||||
# Wrap with quotes
|
||||
return format_html("'{}'", primitive) # noqa: P103
|
||||
return format_html("'{}'", primitive)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
|
@ -70,11 +70,11 @@ class APITests(InvenTreeAPITestCase):
|
||||
"""Helper function to use basic auth."""
|
||||
# Use basic authentication
|
||||
|
||||
authstring = bytes('{u}:{p}'.format(u=self.username, p=self.password), 'ascii')
|
||||
authstring = bytes(f'{self.username}:{self.password}', 'ascii')
|
||||
|
||||
# Use "basic" auth by default
|
||||
auth = b64encode(authstring).decode('ascii')
|
||||
self.client.credentials(HTTP_AUTHORIZATION='Basic {auth}'.format(auth=auth))
|
||||
self.client.credentials(HTTP_AUTHORIZATION=f'Basic {auth}')
|
||||
|
||||
def tokenAuth(self):
|
||||
"""Helper function to use token auth."""
|
||||
@ -92,9 +92,7 @@ class APITests(InvenTreeAPITestCase):
|
||||
"""Test token resolve endpoint does not work without basic auth."""
|
||||
# Test token endpoint without basic auth
|
||||
url = reverse('api-token')
|
||||
response = self.client.get(url, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
self.get(url, expected_code=401)
|
||||
self.assertIsNone(self.token)
|
||||
|
||||
def test_token_success(self):
|
||||
@ -106,7 +104,7 @@ class APITests(InvenTreeAPITestCase):
|
||||
"""Test that we can read the 'info-view' endpoint."""
|
||||
url = reverse('api-inventree-info')
|
||||
|
||||
response = self.client.get(url, format='json')
|
||||
response = self.get(url)
|
||||
|
||||
data = response.json()
|
||||
self.assertIn('server', data)
|
||||
@ -126,7 +124,7 @@ class APITests(InvenTreeAPITestCase):
|
||||
self.group.rule_sets.all().delete()
|
||||
update_group_roles(self.group)
|
||||
|
||||
response = self.client.get(url, format='json')
|
||||
response = self.get(url, expected_code=401)
|
||||
|
||||
# Not logged in, so cannot access user role data
|
||||
self.assertIn(response.status_code, [401, 403])
|
||||
|
@ -13,12 +13,12 @@ class ApiVersionTests(InvenTreeAPITestCase):
|
||||
def test_api(self):
|
||||
"""Test that the API text is correct."""
|
||||
url = reverse('api-version-text')
|
||||
response = self.client.get(url, format='json')
|
||||
response = self.get(url, format='json')
|
||||
data = response.json()
|
||||
|
||||
self.assertEqual(len(data), 10)
|
||||
|
||||
response = self.client.get(reverse('api-version'), format='json').json()
|
||||
response = self.get(reverse('api-version')).json()
|
||||
self.assertIn('version', response)
|
||||
self.assertIn('dev', response)
|
||||
self.assertIn('up_to_date', response)
|
||||
|
@ -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:
|
||||
|
@ -70,7 +70,7 @@ class URLTest(TestCase):
|
||||
|
||||
pattern = '{% url [\'"]([^\'"]+)[\'"]([^%]*)%}'
|
||||
|
||||
with open(input_file, 'r') as f:
|
||||
with open(input_file, encoding='utf-8') as f:
|
||||
data = f.read()
|
||||
|
||||
results = re.findall(pattern, data)
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Test general functions and helpers."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
@ -544,22 +543,22 @@ class FormatTest(TestCase):
|
||||
def test_currency_formatting(self):
|
||||
"""Test that currency formatting works correctly for multiple currencies."""
|
||||
test_data = (
|
||||
(Money(3651.285718, 'USD'), 4, True, '$3,651.2857'), # noqa: E201,E202
|
||||
(Money(487587.849178, 'CAD'), 5, True, 'CA$487,587.84918'), # noqa: E201,E202
|
||||
(Money(0.348102, 'EUR'), 1, False, '0.3'), # noqa: E201,E202
|
||||
(Money(0.916530, 'GBP'), 1, True, '£0.9'), # noqa: E201,E202
|
||||
(Money(61.031024, 'JPY'), 3, False, '61.031'), # noqa: E201,E202
|
||||
(Money(49609.694602, 'JPY'), 1, True, '¥49,609.7'), # noqa: E201,E202
|
||||
(Money(155565.264777, 'AUD'), 2, False, '155,565.26'), # noqa: E201,E202
|
||||
(Money(0.820437, 'CNY'), 4, True, 'CN¥0.8204'), # noqa: E201,E202
|
||||
(Money(7587.849178, 'EUR'), 0, True, '€7,588'), # noqa: E201,E202
|
||||
(Money(0.348102, 'GBP'), 3, False, '0.348'), # noqa: E201,E202
|
||||
(Money(0.652923, 'CHF'), 0, True, 'CHF1'), # noqa: E201,E202
|
||||
(Money(0.820437, 'CNY'), 1, True, 'CN¥0.8'), # noqa: E201,E202
|
||||
(Money(98789.5295680, 'CHF'), 0, False, '98,790'), # noqa: E201,E202
|
||||
(Money(0.585787, 'USD'), 1, True, '$0.6'), # noqa: E201,E202
|
||||
(Money(0.690541, 'CAD'), 3, True, 'CA$0.691'), # noqa: E201,E202
|
||||
(Money(427.814104, 'AUD'), 5, True, 'A$427.81410'), # noqa: E201,E202
|
||||
(Money(3651.285718, 'USD'), 4, True, '$3,651.2857'),
|
||||
(Money(487587.849178, 'CAD'), 5, True, 'CA$487,587.84918'),
|
||||
(Money(0.348102, 'EUR'), 1, False, '0.3'),
|
||||
(Money(0.916530, 'GBP'), 1, True, '£0.9'),
|
||||
(Money(61.031024, 'JPY'), 3, False, '61.031'),
|
||||
(Money(49609.694602, 'JPY'), 1, True, '¥49,609.7'),
|
||||
(Money(155565.264777, 'AUD'), 2, False, '155,565.26'),
|
||||
(Money(0.820437, 'CNY'), 4, True, 'CN¥0.8204'),
|
||||
(Money(7587.849178, 'EUR'), 0, True, '€7,588'),
|
||||
(Money(0.348102, 'GBP'), 3, False, '0.348'),
|
||||
(Money(0.652923, 'CHF'), 0, True, 'CHF1'),
|
||||
(Money(0.820437, 'CNY'), 1, True, 'CN¥0.8'),
|
||||
(Money(98789.5295680, 'CHF'), 0, False, '98,790'),
|
||||
(Money(0.585787, 'USD'), 1, True, '$0.6'),
|
||||
(Money(0.690541, 'CAD'), 3, True, 'CA$0.691'),
|
||||
(Money(427.814104, 'AUD'), 5, True, 'A$427.81410'),
|
||||
)
|
||||
|
||||
with self.settings(LANGUAGE_CODE='en-us'):
|
||||
@ -795,7 +794,7 @@ class TestDownloadFile(TestCase):
|
||||
def test_download(self):
|
||||
"""Tests for DownloadFile."""
|
||||
helpers.DownloadFile('hello world', 'out.txt')
|
||||
helpers.DownloadFile(bytes(b'hello world'), 'out.bin')
|
||||
helpers.DownloadFile(b'hello world', 'out.bin')
|
||||
|
||||
|
||||
class TestMPTT(TestCase):
|
||||
@ -1034,12 +1033,12 @@ class TestVersionNumber(TestCase):
|
||||
|
||||
# Check that the current .git values work too
|
||||
|
||||
hash = str(
|
||||
git_hash = str(
|
||||
subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8'
|
||||
).strip()
|
||||
|
||||
# On some systems the hash is a different length, so just check the first 6 characters
|
||||
self.assertEqual(hash[:6], version.inventreeCommitHash()[:6])
|
||||
self.assertEqual(git_hash[:6], version.inventreeCommitHash()[:6])
|
||||
|
||||
d = (
|
||||
str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8')
|
||||
@ -1439,8 +1438,8 @@ class BarcodeMixinTest(InvenTreeTestCase):
|
||||
'{"part": 17, "stockitem": 12}': 'c88c11ed0628eb7fef0d59b098b96975',
|
||||
}
|
||||
|
||||
for barcode, hash in hashing_tests.items():
|
||||
self.assertEqual(InvenTree.helpers.hash_barcode(barcode), hash)
|
||||
for barcode, expected in hashing_tests.items():
|
||||
self.assertEqual(InvenTree.helpers.hash_barcode(barcode), expected)
|
||||
|
||||
|
||||
class SanitizerTest(TestCase):
|
||||
@ -1558,15 +1557,12 @@ class ClassValidationMixinTest(TestCase):
|
||||
|
||||
def test(self):
|
||||
"""Test function."""
|
||||
...
|
||||
|
||||
def test1(self):
|
||||
"""Test function."""
|
||||
...
|
||||
|
||||
def test2(self):
|
||||
"""Test function."""
|
||||
...
|
||||
|
||||
required_attributes = ['NAME']
|
||||
required_overrides = [test, [test1, test2]]
|
||||
@ -1574,8 +1570,6 @@ class ClassValidationMixinTest(TestCase):
|
||||
class InvalidClass:
|
||||
"""An invalid class that does not inherit from ClassValidationMixin."""
|
||||
|
||||
pass
|
||||
|
||||
def test_valid_class(self):
|
||||
"""Test that a valid class passes the validation."""
|
||||
|
||||
@ -1586,11 +1580,9 @@ class ClassValidationMixinTest(TestCase):
|
||||
|
||||
def test(self):
|
||||
"""Test function."""
|
||||
...
|
||||
|
||||
def test2(self):
|
||||
"""Test function."""
|
||||
...
|
||||
|
||||
TestClass.validate()
|
||||
|
||||
@ -1613,7 +1605,6 @@ class ClassValidationMixinTest(TestCase):
|
||||
|
||||
def test2(self):
|
||||
"""Test function."""
|
||||
...
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
NotImplementedError,
|
||||
@ -1628,8 +1619,6 @@ class ClassProviderMixinTest(TestCase):
|
||||
class TestClass(ClassProviderMixin):
|
||||
"""This class is a dummy class to test the ClassProviderMixin."""
|
||||
|
||||
pass
|
||||
|
||||
def test_get_provider_file(self):
|
||||
"""Test the get_provider_file function."""
|
||||
self.assertEqual(self.TestClass.get_provider_file(), __file__)
|
||||
|
@ -15,7 +15,7 @@ def reload_translation_stats():
|
||||
STATS_FILE = settings.BASE_DIR.joinpath('InvenTree/locale_stats.json').absolute()
|
||||
|
||||
try:
|
||||
with open(STATS_FILE, 'r') as f:
|
||||
with open(STATS_FILE, encoding='utf-8') as f:
|
||||
_translation_stats = json.load(f)
|
||||
except Exception:
|
||||
_translation_stats = None
|
||||
|
@ -246,8 +246,6 @@ class ExchangeRateMixin:
|
||||
class InvenTreeTestCase(ExchangeRateMixin, UserMixin, TestCase):
|
||||
"""Testcase with user setup build in."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
"""Base class for running InvenTree API tests."""
|
||||
@ -283,9 +281,7 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
) # pragma: no cover
|
||||
|
||||
if verbose or n >= value:
|
||||
msg = '\r\n%s' % json.dumps(
|
||||
context.captured_queries, indent=4
|
||||
) # pragma: no cover
|
||||
msg = f'\r\n{json.dumps(context.captured_queries, indent=4)}' # pragma: no cover
|
||||
else:
|
||||
msg = None
|
||||
|
||||
|
@ -456,7 +456,7 @@ urlpatterns = []
|
||||
if settings.INVENTREE_ADMIN_ENABLED:
|
||||
admin_url = settings.INVENTREE_ADMIN_URL
|
||||
|
||||
if settings.ADMIN_SHELL_ENABLE: # noqa
|
||||
if settings.ADMIN_SHELL_ENABLE:
|
||||
urlpatterns += [path(f'{admin_url}/shell/', include('django_admin_shell.urls'))]
|
||||
|
||||
urlpatterns += [
|
||||
|
@ -28,9 +28,7 @@ def validate_physical_units(unit):
|
||||
|
||||
try:
|
||||
ureg(unit)
|
||||
except AttributeError:
|
||||
raise ValidationError(_('Invalid physical unit'))
|
||||
except pint.errors.UndefinedUnitError:
|
||||
except (AssertionError, AttributeError, pint.errors.UndefinedUnitError):
|
||||
raise ValidationError(_('Invalid physical unit'))
|
||||
|
||||
|
||||
@ -96,7 +94,6 @@ def validate_sales_order_reference(value):
|
||||
|
||||
def validate_tree_name(value):
|
||||
"""Placeholder for legacy function used in migrations."""
|
||||
...
|
||||
|
||||
|
||||
def validate_overage(value):
|
||||
|
@ -18,7 +18,7 @@ from django.conf import settings
|
||||
from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION
|
||||
|
||||
# InvenTree software version
|
||||
INVENTREE_SW_VERSION = '0.16.0 dev'
|
||||
INVENTREE_SW_VERSION = '0.17.0 dev'
|
||||
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
@ -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():
|
||||
|
@ -180,7 +180,7 @@ class InvenTreeRoleMixin(PermissionRequiredMixin):
|
||||
AjaxUpdateView: 'change',
|
||||
}
|
||||
|
||||
for view_class in permission_map.keys():
|
||||
for view_class in permission_map:
|
||||
if issubclass(type(self), view_class):
|
||||
return permission_map[view_class]
|
||||
|
||||
@ -238,7 +238,6 @@ class AjaxMixin(InvenTreeRoleMixin):
|
||||
Ref: https://docs.djangoproject.com/en/dev/topics/forms/
|
||||
"""
|
||||
# Do nothing by default
|
||||
pass
|
||||
|
||||
def renderJsonResponse(self, request, form=None, data=None, context=None):
|
||||
"""Render a JSON response based on specific class context.
|
||||
@ -286,7 +285,7 @@ class AjaxMixin(InvenTreeRoleMixin):
|
||||
# Custom feedback`data
|
||||
fb = self.get_data()
|
||||
|
||||
for key in fb.keys():
|
||||
for key in fb:
|
||||
data[key] = fb[key]
|
||||
|
||||
return JsonResponse(data, safe=False)
|
||||
@ -329,11 +328,11 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
|
||||
request, self.get_form(), context=self.get_context_data()
|
||||
)
|
||||
|
||||
def save(self, object, form, **kwargs):
|
||||
def save(self, obj, form, **kwargs):
|
||||
"""Method for updating the object in the database. Default implementation is very simple, but can be overridden if required.
|
||||
|
||||
Args:
|
||||
object: The current object, to be updated
|
||||
obj: The current object, to be updated
|
||||
form: The validated form
|
||||
|
||||
Returns:
|
||||
@ -578,14 +577,10 @@ class UserSessionOverride:
|
||||
class CustomSessionDeleteView(UserSessionOverride, SessionDeleteView):
|
||||
"""Revert to settings after session delete."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CustomSessionDeleteOtherView(UserSessionOverride, SessionDeleteOtherView):
|
||||
"""Revert to settings after session delete."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CustomLoginView(LoginView):
|
||||
"""Custom login view that allows login with urlargs."""
|
||||
|
@ -34,10 +34,8 @@ class BuildFilter(rest_filters.FilterSet):
|
||||
"""Metaclass options."""
|
||||
model = Build
|
||||
fields = [
|
||||
'parent',
|
||||
'sales_order',
|
||||
'part',
|
||||
'issued_by',
|
||||
]
|
||||
|
||||
status = rest_filters.NumberFilter(label='Status')
|
||||
@ -50,6 +48,24 @@ class BuildFilter(rest_filters.FilterSet):
|
||||
return queryset.filter(status__in=BuildStatusGroups.ACTIVE_CODES)
|
||||
return queryset.exclude(status__in=BuildStatusGroups.ACTIVE_CODES)
|
||||
|
||||
parent = rest_filters.ModelChoiceFilter(
|
||||
queryset=Build.objects.all(),
|
||||
label=_('Parent Build'),
|
||||
field_name='parent',
|
||||
)
|
||||
|
||||
ancestor = rest_filters.ModelChoiceFilter(
|
||||
queryset=Build.objects.all(),
|
||||
label=_('Ancestor Build'),
|
||||
method='filter_ancestor'
|
||||
)
|
||||
|
||||
def filter_ancestor(self, queryset, name, parent):
|
||||
"""Filter by 'parent' build order."""
|
||||
|
||||
builds = parent.get_descendants(include_self=False)
|
||||
return queryset.filter(pk__in=[b.pk for b in builds])
|
||||
|
||||
overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue')
|
||||
|
||||
def filter_overdue(self, queryset, name, value):
|
||||
@ -58,7 +74,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 +90,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
|
||||
@ -150,6 +192,7 @@ class BuildList(DataExportViewMixin, BuildMixin, ListCreateAPI):
|
||||
'responsible',
|
||||
'project_code',
|
||||
'priority',
|
||||
'level',
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
@ -198,22 +241,6 @@ class BuildList(DataExportViewMixin, BuildMixin, ListCreateAPI):
|
||||
except (ValueError, Build.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Filter by "ancestor" builds
|
||||
ancestor = params.get('ancestor', None)
|
||||
|
||||
if ancestor is not None:
|
||||
try:
|
||||
ancestor = Build.objects.get(pk=ancestor)
|
||||
|
||||
descendants = ancestor.get_descendants(include_self=True)
|
||||
|
||||
queryset = queryset.filter(
|
||||
parent__pk__in=[b.pk for b in descendants]
|
||||
)
|
||||
|
||||
except (ValueError, Build.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Filter by 'date range'
|
||||
min_date = params.get('min_date', None)
|
||||
max_date = params.get('max_date', None)
|
||||
@ -226,11 +253,12 @@ class BuildList(DataExportViewMixin, BuildMixin, ListCreateAPI):
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Add extra context information to the endpoint serializer."""
|
||||
try:
|
||||
part_detail = str2bool(self.request.GET.get('part_detail', None))
|
||||
part_detail = str2bool(self.request.GET.get('part_detail', True))
|
||||
except AttributeError:
|
||||
part_detail = None
|
||||
part_detail = True
|
||||
|
||||
kwargs['part_detail'] = part_detail
|
||||
kwargs['create'] = True
|
||||
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
@ -290,7 +318,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')
|
||||
|
||||
@ -470,9 +500,19 @@ class BuildFinish(BuildOrderContextMixin, CreateAPI):
|
||||
"""API endpoint for marking a build as finished (completed)."""
|
||||
|
||||
queryset = Build.objects.none()
|
||||
|
||||
serializer_class = build.serializers.BuildCompleteSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return the queryset for the BuildFinish API endpoint."""
|
||||
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.prefetch_related(
|
||||
'build_lines',
|
||||
'build_lines__allocations'
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class BuildAutoAllocate(BuildOrderContextMixin, CreateAPI):
|
||||
"""API endpoint for 'automatically' allocating stock against a build order.
|
||||
@ -484,7 +524,6 @@ class BuildAutoAllocate(BuildOrderContextMixin, CreateAPI):
|
||||
"""
|
||||
|
||||
queryset = Build.objects.none()
|
||||
|
||||
serializer_class = build.serializers.BuildAutoAllocationSerializer
|
||||
|
||||
|
||||
@ -500,10 +539,22 @@ class BuildAllocate(BuildOrderContextMixin, CreateAPI):
|
||||
"""
|
||||
|
||||
queryset = Build.objects.none()
|
||||
|
||||
serializer_class = build.serializers.BuildAllocationSerializer
|
||||
|
||||
|
||||
class BuildIssue(BuildOrderContextMixin, CreateAPI):
|
||||
"""API endpoint for issuing a BuildOrder."""
|
||||
|
||||
queryset = Build.objects.all()
|
||||
serializer_class = build.serializers.BuildIssueSerializer
|
||||
|
||||
|
||||
class BuildHold(BuildOrderContextMixin, CreateAPI):
|
||||
"""API endpoint for placing a BuildOrder on hold."""
|
||||
|
||||
queryset = Build.objects.all()
|
||||
serializer_class = build.serializers.BuildHoldSerializer
|
||||
|
||||
class BuildCancel(BuildOrderContextMixin, CreateAPI):
|
||||
"""API endpoint for cancelling a BuildOrder."""
|
||||
|
||||
@ -663,6 +714,8 @@ build_api_urls = [
|
||||
path('create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
|
||||
path('delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
|
||||
path('scrap-outputs/', BuildOutputScrap.as_view(), name='api-build-output-scrap'),
|
||||
path('issue/', BuildIssue.as_view(), name='api-build-issue'),
|
||||
path('hold/', BuildHold.as_view(), name='api-build-hold'),
|
||||
path('finish/', BuildFinish.as_view(), name='api-build-finish'),
|
||||
path('cancel/', BuildCancel.as_view(), name='api-build-cancel'),
|
||||
path('unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
|
||||
|
@ -0,0 +1,39 @@
|
||||
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations
|
||||
|
||||
import generic.states.fields
|
||||
import InvenTree.status_codes
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("build", "0051_delete_buildorderattachment"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="status_custom_key",
|
||||
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Additional status information for this item",
|
||||
null=True,
|
||||
verbose_name="Custom status key",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="status",
|
||||
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||
choices=InvenTree.status_codes.BuildStatus.items(),
|
||||
default=10,
|
||||
help_text="Build status code",
|
||||
validators=[django.core.validators.MinValueValidator(0)],
|
||||
verbose_name="Build Status",
|
||||
),
|
||||
),
|
||||
]
|
@ -2,7 +2,6 @@
|
||||
|
||||
import decimal
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
|
||||
@ -26,6 +25,7 @@ from build.status_codes import BuildStatus, BuildStatusGroups
|
||||
from stock.status_codes import StockStatus, StockHistoryCode
|
||||
|
||||
from build.validators import generate_next_build_reference, validate_build_order_reference
|
||||
from generic.states import StateTransitionMixin
|
||||
|
||||
import InvenTree.fields
|
||||
import InvenTree.helpers
|
||||
@ -43,6 +43,7 @@ import part.models
|
||||
import report.mixins
|
||||
import stock.models
|
||||
import users.models
|
||||
import generic.states
|
||||
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
@ -56,6 +57,7 @@ class Build(
|
||||
InvenTree.models.MetadataMixin,
|
||||
InvenTree.models.PluginValidationMixin,
|
||||
InvenTree.models.ReferenceIndexingMixin,
|
||||
StateTransitionMixin,
|
||||
MPTTModel):
|
||||
"""A Build object organises the creation of new StockItem objects from other existing StockItem objects.
|
||||
|
||||
@ -314,7 +316,7 @@ class Build(
|
||||
help_text=_('Number of stock items which have been completed')
|
||||
)
|
||||
|
||||
status = models.PositiveIntegerField(
|
||||
status = generic.states.fields.InvenTreeCustomStatusModelField(
|
||||
verbose_name=_('Build Status'),
|
||||
default=BuildStatus.PENDING.value,
|
||||
choices=BuildStatus.items(),
|
||||
@ -394,9 +396,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.
|
||||
@ -406,6 +408,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".
|
||||
@ -574,6 +581,13 @@ class Build(
|
||||
- Completed count must meet the required quantity
|
||||
- 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
|
||||
|
||||
if self.incomplete_count > 0:
|
||||
return False
|
||||
|
||||
@ -602,8 +616,22 @@ class Build(
|
||||
def complete_build(self, user, trim_allocated_stock=False):
|
||||
"""Mark this build as complete."""
|
||||
|
||||
return self.handle_transition(
|
||||
self.status, BuildStatus.COMPLETE.value, self, self._action_complete, user=user, trim_allocated_stock=trim_allocated_stock
|
||||
)
|
||||
|
||||
def _action_complete(self, *args, **kwargs):
|
||||
"""Action to be taken when a build is completed."""
|
||||
|
||||
import build.tasks
|
||||
|
||||
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
|
||||
|
||||
@ -665,6 +693,59 @@ class Build(
|
||||
target_exclude=[user],
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def issue_build(self):
|
||||
"""Mark the Build as IN PRODUCTION.
|
||||
|
||||
Args:
|
||||
user: The user who is issuing the build
|
||||
"""
|
||||
return self.handle_transition(
|
||||
self.status, BuildStatus.PENDING.value, self, self._action_issue
|
||||
)
|
||||
|
||||
@property
|
||||
def can_issue(self):
|
||||
"""Returns True if this BuildOrder can be issued."""
|
||||
return self.status in [
|
||||
BuildStatus.PENDING.value,
|
||||
BuildStatus.ON_HOLD.value,
|
||||
]
|
||||
|
||||
def _action_issue(self, *args, **kwargs):
|
||||
"""Perform the action to mark this order as PRODUCTION."""
|
||||
|
||||
if self.can_issue:
|
||||
self.status = BuildStatus.PRODUCTION.value
|
||||
self.save()
|
||||
|
||||
trigger_event('build.issued', id=self.pk)
|
||||
|
||||
@transaction.atomic
|
||||
def hold_build(self):
|
||||
"""Mark the Build as ON HOLD."""
|
||||
|
||||
return self.handle_transition(
|
||||
self.status, BuildStatus.ON_HOLD.value, self, self._action_hold
|
||||
)
|
||||
|
||||
@property
|
||||
def can_hold(self):
|
||||
"""Returns True if this BuildOrder can be placed on hold"""
|
||||
return self.status in [
|
||||
BuildStatus.PENDING.value,
|
||||
BuildStatus.PRODUCTION.value,
|
||||
]
|
||||
|
||||
def _action_hold(self, *args, **kwargs):
|
||||
"""Action to be taken when a build is placed on hold."""
|
||||
|
||||
if self.can_hold:
|
||||
self.status = BuildStatus.ON_HOLD.value
|
||||
self.save()
|
||||
|
||||
trigger_event('build.hold', id=self.pk)
|
||||
|
||||
@transaction.atomic
|
||||
def cancel_build(self, user, **kwargs):
|
||||
"""Mark the Build as CANCELLED.
|
||||
@ -674,8 +755,17 @@ class Build(
|
||||
- Save the Build object
|
||||
"""
|
||||
|
||||
return self.handle_transition(
|
||||
self.status, BuildStatus.CANCELLED.value, self, self._action_cancel, user=user, **kwargs
|
||||
)
|
||||
|
||||
def _action_cancel(self, *args, **kwargs):
|
||||
"""Action to be taken when a build is cancelled."""
|
||||
|
||||
import build.tasks
|
||||
|
||||
user = kwargs.pop('user', None)
|
||||
|
||||
remove_allocated_stock = kwargs.get('remove_allocated_stock', False)
|
||||
remove_incomplete_outputs = kwargs.get('remove_incomplete_outputs', False)
|
||||
|
||||
@ -897,7 +987,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
|
||||
|
||||
@ -1276,7 +1369,7 @@ class Build(
|
||||
@property
|
||||
def is_complete(self):
|
||||
"""Returns True if the build status is COMPLETE."""
|
||||
return self.status == BuildStatus.COMPLETE
|
||||
return self.status == BuildStatus.COMPLETE.value
|
||||
|
||||
@transaction.atomic
|
||||
def create_build_line_items(self, prevent_duplicates=True):
|
||||
|
@ -2,41 +2,53 @@
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import transaction
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import ExpressionWrapper, F, FloatField
|
||||
from django.db.models import Case, Sum, When, Value
|
||||
from django.db.models import BooleanField, Q
|
||||
from django.db import models, transaction
|
||||
from django.db.models import (
|
||||
BooleanField,
|
||||
Case,
|
||||
ExpressionWrapper,
|
||||
F,
|
||||
FloatField,
|
||||
Q,
|
||||
Sum,
|
||||
Value,
|
||||
When,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from InvenTree.serializers import InvenTreeModelSerializer, UserSerializer
|
||||
|
||||
import InvenTree.helpers
|
||||
from InvenTree.serializers import InvenTreeDecimalField, NotesFieldMixin
|
||||
from stock.status_codes import StockStatus
|
||||
|
||||
from stock.generators import generate_batch_code
|
||||
from stock.models import StockItem, StockLocation
|
||||
from stock.serializers import StockItemSerializerBrief, LocationBriefSerializer
|
||||
|
||||
import build.tasks
|
||||
import common.models
|
||||
from common.serializers import ProjectCodeSerializer
|
||||
from importer.mixins import DataImportExportSerializerMixin
|
||||
import company.serializers
|
||||
import InvenTree.helpers
|
||||
import InvenTree.tasks
|
||||
import part.filters
|
||||
import part.serializers as part_serializers
|
||||
from common.serializers import ProjectCodeSerializer
|
||||
from common.settings import get_global_setting
|
||||
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
||||
from importer.mixins import DataImportExportSerializerMixin
|
||||
from InvenTree.serializers import (
|
||||
InvenTreeDecimalField,
|
||||
InvenTreeModelSerializer,
|
||||
NotesFieldMixin,
|
||||
UserSerializer,
|
||||
)
|
||||
from stock.generators import generate_batch_code
|
||||
from stock.models import StockItem, StockLocation
|
||||
from stock.serializers import LocationBriefSerializer, StockItemSerializerBrief
|
||||
from stock.status_codes import StockStatus
|
||||
from users.serializers import OwnerSerializer
|
||||
|
||||
from .models import Build, BuildLine, BuildItem
|
||||
from .models import Build, BuildItem, BuildLine
|
||||
from .status_codes import BuildStatus
|
||||
|
||||
|
||||
class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer):
|
||||
class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTreeCustomStatusSerializerMixin, InvenTreeModelSerializer):
|
||||
"""Serializes a Build object."""
|
||||
|
||||
class Meta:
|
||||
@ -65,6 +77,7 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
||||
'quantity',
|
||||
'status',
|
||||
'status_text',
|
||||
'status_custom_key',
|
||||
'target_date',
|
||||
'take_from',
|
||||
'notes',
|
||||
@ -74,6 +87,10 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
||||
'responsible',
|
||||
'responsible_detail',
|
||||
'priority',
|
||||
'level',
|
||||
|
||||
# Additional fields used only for build order creation
|
||||
'create_child_builds',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
@ -82,8 +99,13 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
||||
'completion_data',
|
||||
'status',
|
||||
'status_text',
|
||||
'level',
|
||||
]
|
||||
|
||||
reference = serializers.CharField(required=True)
|
||||
|
||||
level = serializers.IntegerField(label=_('Build Level'), read_only=True)
|
||||
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
|
||||
status_text = serializers.CharField(source='get_status_display', read_only=True)
|
||||
@ -106,6 +128,12 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
||||
|
||||
project_code_detail = ProjectCodeSerializer(source='project_code', many=False, read_only=True)
|
||||
|
||||
create_child_builds = serializers.BooleanField(
|
||||
default=False, required=False, write_only=True,
|
||||
label=_('Create Child Builds'),
|
||||
help_text=_('Automatically generate child build orders'),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(queryset):
|
||||
"""Add custom annotations to the BuildSerializer queryset, performing database queries as efficiently as possible.
|
||||
@ -130,13 +158,19 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Determine if extra serializer fields are required"""
|
||||
part_detail = kwargs.pop('part_detail', True)
|
||||
create = kwargs.pop('create', False)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if part_detail is not True:
|
||||
if not create:
|
||||
self.fields.pop('create_child_builds', None)
|
||||
|
||||
if not part_detail:
|
||||
self.fields.pop('part_detail', None)
|
||||
|
||||
reference = serializers.CharField(required=True)
|
||||
def skip_create_fields(self):
|
||||
"""Return a list of fields to skip during model creation."""
|
||||
return ['create_child_builds']
|
||||
|
||||
def validate_reference(self, reference):
|
||||
"""Custom validation for the Build reference field"""
|
||||
@ -145,6 +179,22 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
||||
|
||||
return reference
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Save the Build object."""
|
||||
|
||||
build_order = super().create(validated_data)
|
||||
|
||||
create_child_builds = self.validated_data.pop('create_child_builds', False)
|
||||
|
||||
if create_child_builds:
|
||||
# Pass child build creation off to the background thread
|
||||
InvenTree.tasks.offload_task(
|
||||
build.tasks.create_child_builds,
|
||||
build_order.pk,
|
||||
)
|
||||
|
||||
return build_order
|
||||
|
||||
|
||||
class BuildOutputSerializer(serializers.Serializer):
|
||||
"""Serializer for a "BuildOutput".
|
||||
@ -597,6 +647,33 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class BuildIssueSerializer(serializers.Serializer):
|
||||
"""DRF serializer for issuing a build order."""
|
||||
|
||||
class Meta:
|
||||
"""Serializer metaclass"""
|
||||
fields = []
|
||||
|
||||
def save(self):
|
||||
"""Issue the specified build order"""
|
||||
build = self.context['build']
|
||||
build.issue_build()
|
||||
|
||||
|
||||
class BuildHoldSerializer(serializers.Serializer):
|
||||
"""DRF serializer for placing a BuildOrder on hold."""
|
||||
|
||||
class Meta:
|
||||
"""Serializer metaclass."""
|
||||
fields = []
|
||||
|
||||
def save(self):
|
||||
"""Place the specified build on hold."""
|
||||
build = self.context['build']
|
||||
|
||||
build.hold_build()
|
||||
|
||||
|
||||
class BuildCancelSerializer(serializers.Serializer):
|
||||
"""DRF serializer class for cancelling an active BuildOrder"""
|
||||
|
||||
@ -737,6 +814,12 @@ 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"))
|
||||
|
||||
if build.incomplete_count > 0:
|
||||
raise ValidationError(_("Build order has incomplete outputs"))
|
||||
|
||||
@ -808,8 +891,8 @@ class BuildUnallocationSerializer(serializers.Serializer):
|
||||
data = self.validated_data
|
||||
|
||||
build.deallocate_stock(
|
||||
build_line=data['build_line'],
|
||||
output=data['output']
|
||||
build_line=data.get('build_line', None),
|
||||
output=data.get('output', None),
|
||||
)
|
||||
|
||||
|
||||
@ -1203,6 +1286,7 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
||||
'reference',
|
||||
'consumable',
|
||||
'optional',
|
||||
'testable',
|
||||
'trackable',
|
||||
'inherited',
|
||||
'allow_variants',
|
||||
@ -1247,6 +1331,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)
|
||||
|
@ -2,16 +2,17 @@
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from generic.states import StatusCode
|
||||
from generic.states import ColorEnum, StatusCode
|
||||
|
||||
|
||||
class BuildStatus(StatusCode):
|
||||
"""Build status codes."""
|
||||
|
||||
PENDING = 10, _('Pending'), 'secondary' # Build is pending / active
|
||||
PRODUCTION = 20, _('Production'), 'primary' # Build is in production
|
||||
CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled
|
||||
COMPLETE = 40, _('Complete'), 'success' # Build is complete
|
||||
PENDING = 10, _('Pending'), ColorEnum.secondary # Build is pending / active
|
||||
PRODUCTION = 20, _('Production'), ColorEnum.primary # Build is in production
|
||||
ON_HOLD = 25, _('On Hold'), ColorEnum.warning # Build is on hold
|
||||
CANCELLED = 30, _('Cancelled'), ColorEnum.danger # Build was cancelled
|
||||
COMPLETE = 40, _('Complete'), ColorEnum.success # Build is complete
|
||||
|
||||
|
||||
class BuildStatusGroups:
|
||||
@ -19,5 +20,6 @@ class BuildStatusGroups:
|
||||
|
||||
ACTIVE_CODES = [
|
||||
BuildStatus.PENDING.value,
|
||||
BuildStatus.ON_HOLD.value,
|
||||
BuildStatus.PRODUCTION.value,
|
||||
]
|
||||
|
@ -1,34 +1,31 @@
|
||||
"""Background task definitions for the BuildOrder app"""
|
||||
"""Background task definitions for the BuildOrder app."""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allauth.account.models import EmailAddress
|
||||
|
||||
from plugin.events import trigger_event
|
||||
import common.notifications
|
||||
import build.models
|
||||
import InvenTree.email
|
||||
import common.notifications
|
||||
import InvenTree.helpers
|
||||
import InvenTree.helpers_email
|
||||
import InvenTree.helpers_model
|
||||
import InvenTree.tasks
|
||||
from InvenTree.ready import isImportingData
|
||||
from build.status_codes import BuildStatusGroups
|
||||
|
||||
import part.models as part_models
|
||||
|
||||
from build.status_codes import BuildStatusGroups
|
||||
from InvenTree.ready import isImportingData
|
||||
from plugin.events import trigger_event
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def auto_allocate_build(build_id: int, **kwargs):
|
||||
"""Run auto-allocation for a specified BuildOrder."""
|
||||
|
||||
build_order = build.models.Build.objects.filter(pk=build_id).first()
|
||||
|
||||
if not build_order:
|
||||
@ -40,7 +37,6 @@ def auto_allocate_build(build_id: int, **kwargs):
|
||||
|
||||
def complete_build_allocations(build_id: int, user_id: int):
|
||||
"""Complete build allocations for a specified BuildOrder."""
|
||||
|
||||
build_order = build.models.Build.objects.filter(pk=build_id).first()
|
||||
|
||||
if user_id:
|
||||
@ -185,11 +181,47 @@ def check_build_stock(build: build.models.Build):
|
||||
|
||||
recipients = emails.values_list('email', flat=True)
|
||||
|
||||
InvenTree.email.send_email(subject, '', recipients, html_message=html_message)
|
||||
InvenTree.helpers_email.send_email(subject, '', recipients, html_message=html_message)
|
||||
|
||||
|
||||
def create_child_builds(build_id: int) -> None:
|
||||
"""Create child build orders for a given parent build.
|
||||
|
||||
- Will create a build order for each assembly part in the BOM
|
||||
- Runs recursively, also creating child builds for each sub-assembly part
|
||||
"""
|
||||
|
||||
try:
|
||||
build_order = build.models.Build.objects.get(pk=build_id)
|
||||
except (Build.DoesNotExist, ValueError):
|
||||
return
|
||||
|
||||
assembly_items = build_order.part.get_bom_items().filter(sub_part__assembly=True)
|
||||
|
||||
for item in assembly_items:
|
||||
quantity = item.quantity * build_order.quantity
|
||||
|
||||
sub_order = build.models.Build.objects.create(
|
||||
part=item.sub_part,
|
||||
quantity=quantity,
|
||||
title=build_order.title,
|
||||
batch=build_order.batch,
|
||||
parent=build_order,
|
||||
target_date=build_order.target_date,
|
||||
sales_order=build_order.sales_order,
|
||||
issued_by=build_order.issued_by,
|
||||
responsible=build_order.responsible,
|
||||
)
|
||||
|
||||
# Offload the child build order creation to the background task queue
|
||||
InvenTree.tasks.offload_task(
|
||||
create_child_builds,
|
||||
sub_order.pk
|
||||
)
|
||||
|
||||
|
||||
def notify_overdue_build_order(bo: build.models.Build):
|
||||
"""Notify appropriate users that a Build has just become 'overdue'"""
|
||||
"""Notify appropriate users that a Build has just become 'overdue'."""
|
||||
targets = []
|
||||
|
||||
if bo.issued_by:
|
||||
@ -229,7 +261,7 @@ def notify_overdue_build_order(bo: build.models.Build):
|
||||
|
||||
@InvenTree.tasks.scheduled_task(InvenTree.tasks.ScheduledTask.DAILY)
|
||||
def check_overdue_build_orders():
|
||||
"""Check if any outstanding BuildOrders have just become overdue
|
||||
"""Check if any outstanding BuildOrders have just become overdue.
|
||||
|
||||
- This check is performed daily
|
||||
- Look at the 'target_date' of any outstanding BuildOrder objects
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user