Merge branch 'master' into pr/ChristianSchindler/6305
This commit is contained in:
commit
c46fd85ad0
|
|
@ -11,10 +11,10 @@ python3 -m venv /home/inventree/dev/venv --system-site-packages --upgrade-deps
|
|||
invoke update -s
|
||||
|
||||
# Configure dev environment
|
||||
invoke setup-dev
|
||||
invoke dev.setup-dev
|
||||
|
||||
# Install required frontend packages
|
||||
invoke frontend-install
|
||||
invoke dev.frontend-install
|
||||
|
||||
# remove existing gitconfig created by "Avoiding Dubious Ownership" step
|
||||
# so that it gets copied from host to the container to have your global
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ runs:
|
|||
shell: bash
|
||||
run: |
|
||||
invoke migrate
|
||||
invoke import-fixtures
|
||||
invoke dev.import-fixtures
|
||||
invoke export-records -f data.json
|
||||
python3 ./src/backend/InvenTree/manage.py flush --noinput
|
||||
invoke migrate
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
"""Script to check source strings for translations."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import rapidfuzz
|
||||
|
||||
BACKEND_SOURCE_FILE = [
|
||||
'..',
|
||||
'..',
|
||||
'src',
|
||||
'backend',
|
||||
'InvenTree',
|
||||
'locale',
|
||||
'en',
|
||||
'LC_MESSAGES',
|
||||
'django.po',
|
||||
]
|
||||
|
||||
FRONTEND_SOURCE_FILE = [
|
||||
'..',
|
||||
'..',
|
||||
'src',
|
||||
'frontend',
|
||||
'src',
|
||||
'locales',
|
||||
'en',
|
||||
'messages.po',
|
||||
]
|
||||
|
||||
|
||||
def extract_source_strings(file_path):
|
||||
"""Extract source strings from the provided file."""
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
abs_file_path = os.path.abspath(os.path.join(here, *file_path))
|
||||
|
||||
sources = []
|
||||
|
||||
with open(abs_file_path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('msgid '):
|
||||
msgid = line[6:].strip()
|
||||
|
||||
if msgid in sources:
|
||||
print(f'Duplicate source string: {msgid}')
|
||||
else:
|
||||
sources.append(msgid)
|
||||
|
||||
return sources
|
||||
|
||||
|
||||
def compare_source_strings(sources, threshold):
|
||||
"""Compare source strings to find duplicates (or close matches)."""
|
||||
issues = 0
|
||||
|
||||
for i, source in enumerate(sources):
|
||||
for other in sources[i + 1 :]:
|
||||
if other.lower() == source.lower():
|
||||
print(f'- Duplicate: {source} ~ {other}')
|
||||
issues += 1
|
||||
continue
|
||||
|
||||
ratio = rapidfuzz.fuzz.ratio(source, other)
|
||||
if ratio > threshold:
|
||||
print(f'- Close match: {source} ~ {other} ({ratio:.1f}%)')
|
||||
issues += 1
|
||||
|
||||
if issues:
|
||||
print(f' - Found {issues} issues.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Check source strings for translations.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--backend', action='store_true', help='Check backend source strings'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--frontend', action='store_true', help='Check frontend source strings'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--threshold',
|
||||
type=int,
|
||||
help='Set the threshold for string comparison',
|
||||
default=99,
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.backend:
|
||||
backend_sources = extract_source_strings(BACKEND_SOURCE_FILE)
|
||||
print('Backend source strings:', len(backend_sources))
|
||||
compare_source_strings(backend_sources, args.threshold)
|
||||
|
||||
if args.frontend:
|
||||
frontend_sources = extract_source_strings(FRONTEND_SOURCE_FILE)
|
||||
print('Frontend source strings:', len(frontend_sources))
|
||||
compare_source_strings(frontend_sources, args.threshold)
|
||||
|
|
@ -89,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
|
||||
|
||||
|
|
@ -134,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
|
||||
|
|
@ -175,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
|
||||
|
|
@ -202,7 +199,7 @@ if __name__ == '__main__':
|
|||
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
|
||||
tag_list = [[f'{r}:{t}' for t in docker_tags] for r in target_repos]
|
||||
tags = ','.join(itertools.chain(*tag_list))
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
)
|
||||
steps:
|
||||
- name: Backport Action
|
||||
uses: sqren/backport-github-action@f54e19901f2a57f8b82360f2490d47ee82ec82c6 # pin@v9.2.2
|
||||
uses: sqren/backport-github-action@ad888e978060bc1b2798690dd9d03c4036560947 # pin@v9.2.2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto_backport_label_prefix: backport-to-
|
||||
|
|
|
|||
|
|
@ -30,13 +30,16 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: true
|
||||
apt-dependency: gettext
|
||||
- name: Test Translations
|
||||
run: invoke translate
|
||||
run: invoke dev.translate
|
||||
- name: Check for Duplicates
|
||||
run: |
|
||||
python ./.github/scripts/check_source_strings.py --frontend --backend
|
||||
- name: Check Migration Files
|
||||
run: python3 .github/scripts/check_migration_files.py
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
docker: ${{ steps.filter.outputs.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
|
||||
id: filter
|
||||
with:
|
||||
|
|
@ -66,9 +66,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Set Up Python ${{ env.python_version }}
|
||||
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # pin@v5.1.1
|
||||
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # pin@v5.2.0
|
||||
with:
|
||||
python-version: ${{ env.python_version }}
|
||||
- name: Version Check
|
||||
|
|
@ -97,7 +97,7 @@ jobs:
|
|||
run: |
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke install
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke update
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke setup-dev
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke dev.setup-dev
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml up -d
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run inventree-dev-server invoke wait
|
||||
- name: Check Data Directory
|
||||
|
|
@ -115,10 +115,10 @@ jobs:
|
|||
- name: Run Unit Tests
|
||||
run: |
|
||||
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> contrib/container/docker.dev.env
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke test --disable-pty
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke dev.test --disable-pty
|
||||
- name: Run Migration Tests
|
||||
run: |
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke test --migrations
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke dev.test --migrations
|
||||
- name: Clean up test folder
|
||||
run: |
|
||||
rm -rf InvenTree/_testfolder
|
||||
|
|
@ -127,10 +127,10 @@ 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@988b5a0280414f521da01fcc63a27aeeb4b104db # pin@v3.6.1
|
||||
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # pin@v3.7.1
|
||||
- name: Set up cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # pin@v3.6.0
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # pin@v3.7.0
|
||||
- name: Check if Dockerhub login is required
|
||||
id: docker_login
|
||||
run: |
|
||||
|
|
@ -166,7 +166,7 @@ jobs:
|
|||
- name: Push Docker Images
|
||||
id: push-docker
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # pin@v6.7.0
|
||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # pin@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./contrib/container/Dockerfile
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
force: ${{ steps.force.outputs.force }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
|
||||
id: filter
|
||||
with:
|
||||
|
|
@ -70,7 +70,7 @@ jobs:
|
|||
needs: ["pre-commit"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -92,9 +92,9 @@ jobs:
|
|||
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Set up Python ${{ env.python_version }}
|
||||
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # pin@v5.1.1
|
||||
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # pin@v5.2.0
|
||||
with:
|
||||
python-version: ${{ env.python_version }}
|
||||
cache: "pip"
|
||||
|
|
@ -113,9 +113,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Set up Python ${{ env.python_version }}
|
||||
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # pin@v5.1.1
|
||||
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # pin@v5.2.0
|
||||
with:
|
||||
python-version: ${{ env.python_version }}
|
||||
- name: Check Config
|
||||
|
|
@ -149,7 +149,7 @@ jobs:
|
|||
version: ${{ steps.version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -157,9 +157,9 @@ jobs:
|
|||
dev-install: true
|
||||
update: true
|
||||
- name: Export API Documentation
|
||||
run: invoke schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
|
||||
run: invoke dev.schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
|
||||
- name: Upload schema
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4.3.6
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # pin@v4.4.3
|
||||
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@a2ff6682b27d175162a74c09ace8771bd3d512f8 # pin@main
|
||||
uses: oasdiff/oasdiff-action/diff@1c611ffb1253a72924624aa4fb662e302b3565d3 # pin@main
|
||||
with:
|
||||
base: 'api.yaml'
|
||||
revision: 'src/backend/InvenTree/schema.yml'
|
||||
|
|
@ -191,7 +191,7 @@ jobs:
|
|||
diff --color -u src/backend/InvenTree/schema.yml api.yaml
|
||||
diff -u src/backend/InvenTree/schema.yml api.yaml && echo "no difference in API schema " || exit 2
|
||||
- name: Check schema - including warnings
|
||||
run: invoke schema
|
||||
run: invoke dev.schema
|
||||
continue-on-error: true
|
||||
- name: Extract version for publishing
|
||||
id: version
|
||||
|
|
@ -211,7 +211,7 @@ jobs:
|
|||
version: ${{ needs.schema.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
name: Checkout Code
|
||||
with:
|
||||
repository: inventree/schema
|
||||
|
|
@ -250,7 +250,7 @@ jobs:
|
|||
INVENTREE_SITE_URL: http://127.0.0.1:12345
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -262,9 +262,9 @@ jobs:
|
|||
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }}
|
||||
- name: Start InvenTree Server
|
||||
run: |
|
||||
invoke delete-data -f
|
||||
invoke import-fixtures
|
||||
invoke server -a 127.0.0.1:12345 &
|
||||
invoke dev.delete-data -f
|
||||
invoke dev.import-fixtures
|
||||
invoke dev.server -a 127.0.0.1:12345 &
|
||||
invoke wait
|
||||
- name: Run Tests For `${{ env.wrapper_name }}`
|
||||
run: |
|
||||
|
|
@ -292,7 +292,7 @@ jobs:
|
|||
python_version: ${{ matrix.python_version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -302,13 +302,13 @@ jobs:
|
|||
- name: Data Export Test
|
||||
uses: ./.github/actions/migration
|
||||
- name: Test Translations
|
||||
run: invoke translate
|
||||
run: invoke dev.translate
|
||||
- name: Check Migration Files
|
||||
run: python3 .github/scripts/check_migration_files.py
|
||||
- name: Coverage Tests
|
||||
run: invoke test --coverage
|
||||
run: invoke dev.test --coverage
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4.5.0
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v4.6.0
|
||||
if: always()
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
@ -346,7 +346,7 @@ jobs:
|
|||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -355,7 +355,7 @@ jobs:
|
|||
dev-install: true
|
||||
update: true
|
||||
- name: Run Tests
|
||||
run: invoke test
|
||||
run: invoke dev.test
|
||||
- name: Data Export Test
|
||||
uses: ./.github/actions/migration
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ jobs:
|
|||
- 3306:3306
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -399,7 +399,7 @@ jobs:
|
|||
dev-install: true
|
||||
update: true
|
||||
- name: Run Tests
|
||||
run: invoke test
|
||||
run: invoke dev.test
|
||||
- name: Data Export Test
|
||||
uses: ./.github/actions/migration
|
||||
|
||||
|
|
@ -429,7 +429,7 @@ jobs:
|
|||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -438,9 +438,9 @@ jobs:
|
|||
dev-install: true
|
||||
update: true
|
||||
- name: Run Tests
|
||||
run: invoke test --migrations --report --coverage
|
||||
run: invoke dev.test --migrations --report --coverage
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4.5.0
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v4.6.0
|
||||
if: always()
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
@ -460,7 +460,7 @@ jobs:
|
|||
INVENTREE_PLUGINS_ENABLED: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
name: Checkout Code
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
|
@ -517,7 +517,7 @@ jobs:
|
|||
VITE_COVERAGE: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -525,17 +525,17 @@ jobs:
|
|||
install: true
|
||||
update: true
|
||||
- name: Set up test data
|
||||
run: invoke setup-test -i
|
||||
run: invoke dev.setup-test -i
|
||||
- name: Rebuild thumbnails
|
||||
run: invoke rebuild-thumbnails
|
||||
run: invoke int.rebuild-thumbnails
|
||||
- name: Install dependencies
|
||||
run: inv frontend-compile
|
||||
run: invoke int.frontend-compile
|
||||
- name: Install Playwright Browsers
|
||||
run: cd src/frontend && npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
id: tests
|
||||
run: cd src/frontend && npx nyc playwright test
|
||||
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # pin@v4
|
||||
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
|
||||
with:
|
||||
name: playwright-report
|
||||
|
|
@ -545,12 +545,19 @@ jobs:
|
|||
if: always()
|
||||
run: cd src/frontend && npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --exclude-after-remap false
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4.5.0
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v4.6.0
|
||||
if: always()
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
slug: inventree/InvenTree
|
||||
flags: pui
|
||||
- name: Upload bundler info
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
cd src/frontend
|
||||
yarn install
|
||||
yarn run build
|
||||
|
||||
platform_ui_build:
|
||||
name: Build - UI Platform
|
||||
|
|
@ -558,7 +565,7 @@ jobs:
|
|||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -573,7 +580,7 @@ jobs:
|
|||
run: |
|
||||
cd src/backend/InvenTree/web/static
|
||||
zip -r frontend-build.zip web/ web/.vite
|
||||
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4.3.6
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # pin@v4.4.3
|
||||
with:
|
||||
name: frontend-build
|
||||
path: src/backend/InvenTree/web/static/web
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Version Check
|
||||
run: |
|
||||
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
|
||||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
contents: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -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@8d0a6505bf28ced3e85154d13dc6af83299e13f1 # 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@1c608d11d69870c2092266b3f9a6f3abbf17002c # pin@v1
|
||||
with:
|
||||
subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip"
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -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@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
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@883d8588e56d1753a8a58c1c86e88976f0c23449 # v3.26.3
|
||||
uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # pin@v4.2.1
|
||||
- name: Environment Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -39,9 +39,19 @@ jobs:
|
|||
npm: true
|
||||
apt-dependency: gettext
|
||||
- name: Make Translations
|
||||
run: invoke translate
|
||||
run: invoke dev.translate
|
||||
- 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 add src/backend/InvenTree/locale/en/LC_MESSAGES/django.po src/frontend/src/locales/en/messages.po
|
||||
git commit -m "add translations" || true
|
||||
git reset --hard
|
||||
git reset HEAD~
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@6ed209d411599a981ccb978df3be9dc9b8a81699 # pin@v2
|
||||
uses: crowdin/github-action@95d6e895e871c3c7acf0cfb962f296baa41e63c6 # pin@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
|
@ -50,7 +60,7 @@ jobs:
|
|||
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: 'l10'
|
||||
pull_request_base_branch_name: 'master'
|
||||
pull_request_labels: 'translations'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ var/
|
|||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.sqlite3-journal
|
||||
*.backup
|
||||
|
|
|
|||
|
|
@ -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.7.0
|
||||
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.4.24
|
||||
hooks:
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements-dev.in
|
||||
|
|
@ -50,7 +51,7 @@ repos:
|
|||
args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt, --python-version=3.11, --no-strip-extras, --generate-hashes]
|
||||
files: contrib/container/requirements\.(in|txt)$
|
||||
- repo: https://github.com/Riverside-Healthcare/djLint
|
||||
rev: v1.34.1
|
||||
rev: v1.35.2
|
||||
hooks:
|
||||
- id: djlint-django
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
|
|
@ -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.12.0"
|
||||
hooks:
|
||||
- id: eslint
|
||||
additional_dependencies:
|
||||
|
|
@ -89,7 +90,7 @@ repos:
|
|||
- "@typescript-eslint/parser"
|
||||
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.18.4
|
||||
rev: v8.21.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
#- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||
|
|
|
|||
|
|
@ -9,61 +9,61 @@
|
|||
{
|
||||
"label": "worker",
|
||||
"type": "shell",
|
||||
"command": "inv worker",
|
||||
"command": "invoke worker",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "clean-settings",
|
||||
"type": "shell",
|
||||
"command": "inv clean-settings",
|
||||
"command": "invoke int.clean-settings",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "delete-data",
|
||||
"type": "shell",
|
||||
"command": "inv delete-data",
|
||||
"command": "invoke dev.delete-data",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "migrate",
|
||||
"type": "shell",
|
||||
"command": "inv migrate",
|
||||
"command": "invoke migrate",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "server",
|
||||
"type": "shell",
|
||||
"command": "inv server",
|
||||
"command": "invoke dev.server",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "setup-dev",
|
||||
"type": "shell",
|
||||
"command": "inv setup-dev",
|
||||
"command": "invoke dev.setup-dev",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "setup-test",
|
||||
"type": "shell",
|
||||
"command": "inv setup-test -i --path dev/inventree-demo-dataset",
|
||||
"command": "invoke dev.setup-test -i --path dev/inventree-demo-dataset",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "superuser",
|
||||
"type": "shell",
|
||||
"command": "inv superuser",
|
||||
"command": "invoke superuser",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"type": "shell",
|
||||
"command": "inv test",
|
||||
"command": "invoke dev.test",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "update",
|
||||
"type": "shell",
|
||||
"command": "inv update",
|
||||
"command": "invoke update",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017-2022 InvenTree
|
||||
Copyright (c) 2017 - InvenTree Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
20
README.md
20
README.md
|
|
@ -4,7 +4,7 @@
|
|||
<p>Open Source Inventory Management System </p>
|
||||
|
||||
<!-- Badges -->
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://opensource.org/license/MIT)
|
||||

|
||||
[](https://inventree.readthedocs.io/en/latest/?badge=latest)
|
||||

|
||||
|
|
@ -81,7 +81,7 @@ InvenTree is designed to be **extensible**, and provides multiple options for **
|
|||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Client</summary>
|
||||
<summary>Client - CUI</summary>
|
||||
<ul>
|
||||
<li><a href="https://getbootstrap.com/">Bootstrap</a></li>
|
||||
<li><a href="https://jquery.com/">jQuery</a></li>
|
||||
|
|
@ -89,13 +89,27 @@ InvenTree is designed to be **extensible**, and provides multiple options for **
|
|||
</ul>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Client - PUI</summary>
|
||||
<ul>
|
||||
<li><a href="https://react.dev/">React</a></li>
|
||||
<li><a href="https://lingui.dev/">Lingui</a></li>
|
||||
<li><a href="https://reactrouter.com/">React Router</a></li>
|
||||
<li><a href="https://tanstack.com/query/">TanStack Query</a></li>
|
||||
<li><a href="https://github.com/pmndrs/zustand">Zustand</a></li>
|
||||
<li><a href="https://mantine.dev/">Mantine</a></li>
|
||||
<li><a href="https://icflorescu.github.io/mantine-datatable/">Mantine Data Table</a></li>
|
||||
<li><a href="https://codemirror.net/">CodeMirror</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DevOps</summary>
|
||||
<ul>
|
||||
<li><a href="https://hub.docker.com/r/inventree/inventree">Docker</a></li>
|
||||
<li><a href="https://crowdin.com/project/inventree">Crowdin</a></li>
|
||||
<li><a href="https://app.codecov.io/gh/inventree/InvenTree">Codecov</a></li>
|
||||
<li><a href="https://app.deepsource.com/gh/inventree/InvenTree">DeepSource</a></li>
|
||||
<li><a href="https://sonarcloud.io/project/overview?id=inventree_InvenTree">SonarCloud</a></li>
|
||||
<li><a href="https://packager.io/gh/inventree/InvenTree">Packager.io</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -27,3 +27,11 @@ flag_management:
|
|||
statuses:
|
||||
- type: project
|
||||
target: 45%
|
||||
|
||||
comment:
|
||||
require_bundle_changes: True
|
||||
bundle_change_threshold: "1Kb"
|
||||
|
||||
bundle_analysis:
|
||||
warning_threshold: "5%"
|
||||
status: "informational"
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ RUN apk add --no-cache --update nodejs npm yarn
|
|||
RUN yarn config set network-timeout 600000 -g
|
||||
COPY src ${INVENTREE_HOME}/src
|
||||
COPY tasks.py ${INVENTREE_HOME}/tasks.py
|
||||
RUN cd ${INVENTREE_HOME} && inv frontend-compile
|
||||
RUN cd ${INVENTREE_HOME} && invoke int.frontend-compile
|
||||
|
||||
COPY contrib/container/execute.sh ${INVENTREE_HOME}/execute.sh
|
||||
RUN chmod +x ${INVENTREE_HOME}/execute.sh
|
||||
|
|
|
|||
|
|
@ -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.15 \
|
||||
--hash=sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30 \
|
||||
--hash=sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a
|
||||
# via django-auth-ldap
|
||||
django==4.2.16 \
|
||||
--hash=sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898 \
|
||||
--hash=sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad
|
||||
# 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 \
|
||||
|
|
@ -33,200 +35,201 @@ mariadb==1.1.10 \
|
|||
--hash=sha256:d7b09ec4abd02ed235257feb769f90cd4066e8f536b55b92f5166103d5b66a63 \
|
||||
--hash=sha256:dff8b28ce4044574870d7bdd2d9f9f5da8e5f95a7ff6d226185db733060d1a93
|
||||
# via -r contrib/container/requirements.in
|
||||
mysqlclient==2.2.4 \
|
||||
--hash=sha256:329e4eec086a2336fe3541f1ce095d87a6f169d1cc8ba7b04ac68bcb234c9711 \
|
||||
--hash=sha256:33bc9fb3464e7d7c10b1eaf7336c5ff8f2a3d3b88bab432116ad2490beb3bf41 \
|
||||
--hash=sha256:3c318755e06df599338dad7625f884b8a71fcf322a9939ef78c9b3db93e1de7a \
|
||||
--hash=sha256:4e80dcad884dd6e14949ac6daf769123223a52a6805345608bf49cdaf7bc8b3a \
|
||||
--hash=sha256:9d3310295cb682232cadc28abd172f406c718b9ada41d2371259098ae37779d3 \
|
||||
--hash=sha256:9d4c015480c4a6b2b1602eccd9846103fc70606244788d04aa14b31c4bd1f0e2 \
|
||||
--hash=sha256:ac44777eab0a66c14cb0d38965572f762e193ec2e5c0723bcd11319cc5b693c5 \
|
||||
--hash=sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54 \
|
||||
--hash=sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab
|
||||
mysqlclient==2.2.5 \
|
||||
--hash=sha256:1d2e2ca0fe8405d8d6464edd01bf059951279e4bc27284d39341bd4737b2bc64 \
|
||||
--hash=sha256:3f9625bea2b9bcde0ace76b32708762d44597491092c819fd1bff5b4e27f709b \
|
||||
--hash=sha256:8012c633aab8c91ea8172ac479807135b171501b9cad1a7cd9b58c4dc8dcdab5 \
|
||||
--hash=sha256:add8643c32f738014d252d2bdebb478623b04802e8396d5903905db36474d3ff \
|
||||
--hash=sha256:aee14f1872114865679fcb09aac3772de4595fa7dcf2f83a4c7afee15e508854 \
|
||||
--hash=sha256:b54511648c1455b43ac28f8b4c1f732c5b0c343e87f7a3bd6fc9f9fe0f91934e \
|
||||
--hash=sha256:b78438314199504c64f69e1e3521f2c9b419f19fcd85158b44c997b64409a6af \
|
||||
--hash=sha256:e871ede4261d0d42b8ed20a2459db411c7deafedd8e77b7e4ba760be4a6a752b
|
||||
# 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.3 \
|
||||
--hash=sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907 \
|
||||
--hash=sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2
|
||||
# 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.3 \
|
||||
--hash=sha256:0463a11b1cace5a6aeffaf167920707b912b8986a9c7920341c75e3686277920 \
|
||||
--hash=sha256:05a1bdce30356e70a05428928717765f4a9229999421013f41338d9680d03a63 \
|
||||
--hash=sha256:06b5cc915e57621eebf2393f4173793ed7e3387295f07fed93ed3fb6a6ccf585 \
|
||||
--hash=sha256:07d019a786eb020c0f984691aa1b994cb79430061065a694cf6f94056c603d26 \
|
||||
--hash=sha256:09baa041856b35598d335b1a74e19a49da8500acedf78164600694c0ba8ce21b \
|
||||
--hash=sha256:1303bf8347d6be7ad26d1362af2c38b3a90b8293e8d56244296488ee8591058e \
|
||||
--hash=sha256:192a5f8496e6e1243fdd9ac20e117e667c0712f148c5f9343483b84435854c78 \
|
||||
--hash=sha256:1985ab05e9abebfbdf3163a16ebb37fbc5d49aff2bf5b3d7375ff0920bbb54cd \
|
||||
--hash=sha256:1f8b0d0e99d8e19923e6e07379fa00570be5182c201a8c0b5aaa9a4d4a4ea20b \
|
||||
--hash=sha256:257c4aea6f70a9aef39b2a77d0658a41bf05c243e2bf41895eb02220ac6306f3 \
|
||||
--hash=sha256:261f0031ee6074765096a19b27ed0f75498a8338c3dcd7f4f0d831e38adf12d1 \
|
||||
--hash=sha256:2773f850a778575dd7158a6dd072f7925b67f3ba305e2003538e8831fec77a1d \
|
||||
--hash=sha256:2a29f5294b0b6360bfda69653697eff70aaf2908f58d1073b0acd6f6ab5b5a4f \
|
||||
--hash=sha256:2bb342a01c76f38a12432848e6013c57eb630103e7556cf79b705b53814c3949 \
|
||||
--hash=sha256:2c0419cdad8c70eaeb3116bb28e7b42d546f91baf5179d7556f230d40942dc78 \
|
||||
--hash=sha256:3bffb61e198a91f712cc3d7f2d176a697cb05b284b2ad150fb8edb308eba9002 \
|
||||
--hash=sha256:41fdec0182efac66b27478ac15ef54c9ebcecf0e26ed467eb7d6f262a913318b \
|
||||
--hash=sha256:48f8ca6ee8939bab760225b2ab82934d54330eec10afe4394a92d3f2a0c37dd6 \
|
||||
--hash=sha256:4926ea5c46da30bec4a85907aa3f7e4ea6313145b2aa9469fdb861798daf1502 \
|
||||
--hash=sha256:4c57615791a337378fe5381143259a6c432cdcbb1d3e6428bfb7ce59fff3fb5c \
|
||||
--hash=sha256:4e76ce2475ed4885fe13b8254058be710ec0de74ebd8ef8224cf44a9a3358e5f \
|
||||
--hash=sha256:5361ea13c241d4f0ec3f95e0bf976c15e2e451e9cc7ef2e5ccfc9d170b197a40 \
|
||||
--hash=sha256:5905729668ef1418bd36fbe876322dcb0f90b46811bba96d505af89e6fbdce2f \
|
||||
--hash=sha256:5938b257b04c851c2d1e6cb2f8c18318f06017f35be9a5fe761ee1e2e344dfb7 \
|
||||
--hash=sha256:5e37d5027e297a627da3551a1e962316d0f88ee4ada74c768f6c9234e26346d9 \
|
||||
--hash=sha256:64a607e630d9f4b2797f641884e52b9f8e239d35943f51bef817a384ec1678fe \
|
||||
--hash=sha256:64dc6e9ec64f592f19dc01a784e87267a64a743d34f68488924251253da3c818 \
|
||||
--hash=sha256:69320f05de8cdf4077ecd7fefdec223890eea232af0d58f2530cbda2871244a0 \
|
||||
--hash=sha256:6d8f2144e0d5808c2e2aed40fbebe13869cd00c2ae745aca4b3b16a435edb056 \
|
||||
--hash=sha256:700679c02f9348a0d0a2adcd33a0275717cd0d0aee9d4482b47d935023629505 \
|
||||
--hash=sha256:709447bd7203b0b2debab1acec23123eb80b386f6c29e7604a5d4326a11e5bd6 \
|
||||
--hash=sha256:71adcc8bc80a65b776510bc39992edf942ace35b153ed7a9c6c573a6849ce308 \
|
||||
--hash=sha256:71db8896b942770ed7ab4efa59b22eee5203be2dfdee3c5258d60e57605d688c \
|
||||
--hash=sha256:74fbf5dd3ef09beafd3557631e282f00f8af4e7a78fbfce8ab06d9cd5a789aae \
|
||||
--hash=sha256:79498df398970abcee3d326edd1d4655de7d77aa9aecd578154f8af35ce7bbd2 \
|
||||
--hash=sha256:7ad357e426b0ea5c3043b8ec905546fa44b734bf11d33b3da3959f6e4447d350 \
|
||||
--hash=sha256:7d784f614e4d53050cbe8abf2ae9d1aaacf8ed31ce57b42ce3bf2a48a66c3a5c \
|
||||
--hash=sha256:80a2337e2dfb26950894c8301358961430a0304f7bfe729d34cc036474e9c9b1 \
|
||||
--hash=sha256:824c867a38521d61d62b60aca7db7ca013a2b479e428a0db47d25d8ca5067410 \
|
||||
--hash=sha256:842da42a63ecb32612bb7f5b9e9f8617eab9bc23bd58679a441f4150fcc51c96 \
|
||||
--hash=sha256:8b7be9a6c06518967b641fb15032b1ed682fd3b0443f64078899c61034a0bca6 \
|
||||
--hash=sha256:9099e443d4cc24ac6872e6a05f93205ba1a231b1a8917317b07c9ef2b955f1f4 \
|
||||
--hash=sha256:94253be2b57ef2fea7ffe08996067aabf56a1eb9648342c9e3bad9e10c46e045 \
|
||||
--hash=sha256:949551752930d5e478817e0b49956350d866b26578ced0042a61967e3fcccdea \
|
||||
--hash=sha256:96334bb64d054e36fed346c50c4190bad9d7c586376204f50bede21a913bf942 \
|
||||
--hash=sha256:965455eac8547f32b3181d5ec9ad8b9be500c10fe06193543efaaebe3e4ce70c \
|
||||
--hash=sha256:967b47a0fd237aa17c2748fdb7425015c394a6fb57cdad1562e46a6eb070f96d \
|
||||
--hash=sha256:9994f7db390c17fc2bd4c09dca722fd792ff8a49bb3bdace0c50a83f22f1767d \
|
||||
--hash=sha256:9b60b465773a52c7d4705b0a751f7f1cdccf81dd12aee3b921b31a6e76b07b0e \
|
||||
--hash=sha256:aeddf7b3b3f6e24ccf7d0edfe2d94094ea76b40e831c16eff5230e040ce3b76b \
|
||||
--hash=sha256:c64c4cd0d50d5b2288ab1bcb26c7126c772bbdebdfadcd77225a77df01c4a57e \
|
||||
--hash=sha256:cb987f14af7da7c24f803111dbc7392f5070fd350146af3345103f76ea82e339 \
|
||||
--hash=sha256:dc4fa2240c9fceddaa815a58f29212826fafe43ce80ff666d38c4a03fb036955 \
|
||||
--hash=sha256:e56b1fd529e5dde2d1452a7d72907b37ed1b4f07fdced5d8fb1e963acfff6749 \
|
||||
--hash=sha256:e8630943143c6d6ca9aefc88bbe5e76c90553f4e1a3b2dc339e67dc34aa86f7e \
|
||||
--hash=sha256:e8eb9a4e394926b93ad919cad1b0a918e9b4c846609e8c1cfb6b743683f64da0 \
|
||||
--hash=sha256:e90352d7b610b4693fad0feea48549d4315d10f1eba5605421c92bb834e90170 \
|
||||
--hash=sha256:f0b018e37608c3bfc6039a1dc4eb461e89334465a19916be0153c757a78ea426 \
|
||||
--hash=sha256:f73adc05452fb85e7a12ed3f69c81540a8875960739082e6ea5e28c373a30774 \
|
||||
--hash=sha256:fa33ead69ed133210d96af0c63448b1385df48b9c0247eda735c5896b9e6dbbf \
|
||||
--hash=sha256:fc6d87a1c44df8d493ef44988a3ded751e284e02cdf785f746c2d357e99782a6 \
|
||||
--hash=sha256:fd40af959173ea0d087b6b232b855cfeaa6738f47cb2a0fd10a7f4fa8b74293f \
|
||||
--hash=sha256:fd65774ed7d65101b314808b6893e1a75b7664f680c3ef18d2e5c84d570fa393 \
|
||||
--hash=sha256:fda0162b0dbfa5eaed6cdc708179fa27e148cb8490c7d62e5cf30713909658ea
|
||||
# via psycopg
|
||||
psycopg-pool==3.2.1 \
|
||||
--hash=sha256:060b551d1b97a8d358c668be58b637780b884de14d861f4f5ecc48b7563aafb7 \
|
||||
--hash=sha256:6509a75c073590952915eddbba7ce8b8332a440a31e77bba69561483492829ad
|
||||
psycopg-pool==3.2.3 \
|
||||
--hash=sha256:53bd8e640625e01b2927b2ad96df8ed8e8f91caea4597d45e7673fc7bbb85eb1 \
|
||||
--hash=sha256:bb942f123bef4b7fbe4d55421bd3fb01829903c95c0f33fd42b7e94e5ac9b52a
|
||||
# via psycopg
|
||||
pyasn1==0.6.0 \
|
||||
--hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \
|
||||
--hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473
|
||||
pyasn1==0.6.1 \
|
||||
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
|
||||
--hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034
|
||||
# via
|
||||
# pyasn1-modules
|
||||
# python-ldap
|
||||
pyasn1-modules==0.4.0 \
|
||||
--hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \
|
||||
--hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b
|
||||
pyasn1-modules==0.4.1 \
|
||||
--hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \
|
||||
--hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c
|
||||
# via python-ldap
|
||||
python-ldap==3.4.4 \
|
||||
--hash=sha256:7edb0accec4e037797705f3a05cbf36a9fde50d08c8f67f2aef99a2628fab828
|
||||
# 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==75.2.0 \
|
||||
--hash=sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec \
|
||||
--hash=sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8
|
||||
# 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.4.25 \
|
||||
--hash=sha256:18100f0f36419a154306ed6211e3490bf18384cdf3f1a0950848bf64b62fa251 \
|
||||
--hash=sha256:2d29a78f011ecc2f31c13605acb6574c2894c06d258b0f8d0dbb899986800450 \
|
||||
--hash=sha256:2fc35b5273f1e018aecd66b70e0fd7d2eb6698853dde3e2fc644e7ebf9f825b1 \
|
||||
--hash=sha256:3d7680795ea78cdbabbcce73d039b2651cf1fa635ddc1aa3082660f6d6255c50 \
|
||||
--hash=sha256:4c55040e67470f2b73e95e432aba06f103a0b348ea0b9c6689b1029c8d9e89fd \
|
||||
--hash=sha256:50c7d0d9e7f392f81b13bf3b7e37768d1486f2fc9d533a54982aa0ed11e4db23 \
|
||||
--hash=sha256:578ae385fad6bd6f3868828e33d54994c716b315b1bc49106ec1f54c640837e4 \
|
||||
--hash=sha256:6e981b1465e30102e41946adede9cb08051a5d70c6daf09f91a7ea84f0b75c08 \
|
||||
--hash=sha256:7d266e02fefef930609328c31c075084295c3cb472bab3f69549fad4fd9d82b3 \
|
||||
--hash=sha256:94fb2b454afa6bdfeeea4b4581c878944ca9cf3a13712e6762f245f5fbaaf952 \
|
||||
--hash=sha256:a7022a71ff63a3838796f40e954b76bf7820fc27e96fe002c537e75ff8e34f1d \
|
||||
--hash=sha256:a7c3a18c20ddb527d296d1222bddf42b78031c50b5b4609d426569b5fb61f5b0 \
|
||||
--hash=sha256:aae9dcafd20d5ba978c8a4939ab942e8e2e155c109e9945207fbbd81d2892c9e \
|
||||
--hash=sha256:bdbfd0c476b9e80a3f89af96aed6dd7d2782646311317a9c72614ccce99bb2ad \
|
||||
--hash=sha256:be2a4fc4fcade9ea5e67e51738c95644360d6e59b6394b74fc579fb617f902f7 \
|
||||
--hash=sha256:d39077cdfe3246885fcdf32e7066ae731a166101d063629f9cea08738f79e6a3 \
|
||||
--hash=sha256:e02afb0f6d4b58718347f7d7cfa5a801e985ce42181ba971ed85ef149f6658ca \
|
||||
--hash=sha256:ec181be2bda10651a3558156409ac481549983e0276d0e3645e3b1464e7f8715
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -104,9 +104,9 @@ 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.2 \
|
||||
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ function detect_docker() {
|
|||
else
|
||||
DOCKER="no"
|
||||
fi
|
||||
echo "# POI04| Running in docker: ${DOCKER}"
|
||||
}
|
||||
|
||||
function detect_initcmd() {
|
||||
|
|
@ -30,6 +31,7 @@ function detect_initcmd() {
|
|||
if [ "${DOCKER}" == "yes" ]; then
|
||||
INIT_CMD="initctl"
|
||||
fi
|
||||
echo "# POI05| Using init command: ${INIT_CMD}"
|
||||
}
|
||||
|
||||
function detect_ip() {
|
||||
|
|
@ -37,36 +39,36 @@ function detect_ip() {
|
|||
|
||||
if [ "${SETUP_NO_CALLS}" == "true" ]; then
|
||||
# Use local IP address
|
||||
echo "# Getting the IP address of the first local IP address"
|
||||
echo "# POI06| Getting the IP address of the first local IP address"
|
||||
export INVENTREE_IP=$(hostname -I | awk '{print $1}')
|
||||
else
|
||||
# Use web service to get the IP address
|
||||
echo "# Getting the IP address of the server via web service"
|
||||
echo "# POI06| Getting the IP address of the server via web service"
|
||||
export INVENTREE_IP=$(curl -s https://checkip.amazonaws.com)
|
||||
fi
|
||||
|
||||
echo "IP address is ${INVENTREE_IP}"
|
||||
echo "# POI06| IP address is ${INVENTREE_IP}"
|
||||
}
|
||||
|
||||
function detect_python() {
|
||||
# Detect if there is already a python version installed in /opt/inventree/env/lib
|
||||
if test -f "${APP_HOME}/env/bin/python"; then
|
||||
echo "# Python environment already present"
|
||||
echo "# POI07| Python environment already present"
|
||||
# Extract earliest python version initialised from /opt/inventree/env/lib
|
||||
SETUP_PYTHON=$(ls -1 ${APP_HOME}/env/bin/python* | sort | head -n 1)
|
||||
echo "# Found earlier used version: ${SETUP_PYTHON}"
|
||||
echo "# POI07| Found earlier used version: ${SETUP_PYTHON}"
|
||||
else
|
||||
echo "# No python environment found - using environment variable: ${SETUP_PYTHON}"
|
||||
echo "# POI07| No python environment found - using environment variable: ${SETUP_PYTHON}"
|
||||
fi
|
||||
|
||||
# Try to detect a python between 3.9 and 3.12 in reverse order
|
||||
if [ -z "$(which ${SETUP_PYTHON})" ]; then
|
||||
echo "# Trying to detecting python3.${PYTHON_FROM} to python3.${PYTHON_TO} - using newest version"
|
||||
echo "# POI07| Trying to detecting python3.${PYTHON_FROM} to python3.${PYTHON_TO} - using newest version"
|
||||
for i in $(seq $PYTHON_TO -1 $PYTHON_FROM); do
|
||||
echo "# Checking for python3.${i}"
|
||||
echo "# POI07| Checking for python3.${i}"
|
||||
if [ -n "$(which python3.${i})" ]; then
|
||||
SETUP_PYTHON="python3.${i}"
|
||||
echo "# Found python3.${i} installed - using for setup ${SETUP_PYTHON}"
|
||||
echo "# POI07| Found python3.${i} installed - using for setup ${SETUP_PYTHON}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
|
@ -75,12 +77,14 @@ function detect_python() {
|
|||
# Ensure python can be executed - abort if not
|
||||
if [ -z "$(which ${SETUP_PYTHON})" ]; then
|
||||
echo "${On_Red}"
|
||||
echo "# Python ${SETUP_PYTHON} not found - aborting!"
|
||||
echo "# Please ensure python can be executed with the command '$SETUP_PYTHON' by the current user '$USER'."
|
||||
echo "# If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.10'."
|
||||
echo "# POI07| Python ${SETUP_PYTHON} not found - aborting!"
|
||||
echo "# POI07| Please ensure python can be executed with the command '$SETUP_PYTHON' by the current user '$USER'."
|
||||
echo "# POI07| If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.10'."
|
||||
echo "${Color_Off}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "# POI07| Using python command: ${SETUP_PYTHON}"
|
||||
}
|
||||
|
||||
function get_env() {
|
||||
|
|
@ -95,7 +99,7 @@ function get_env() {
|
|||
done
|
||||
|
||||
if [ -n "${SETUP_DEBUG}" ]; then
|
||||
echo "Done getting env $envname: ${!envname}"
|
||||
echo "# POI02| Done getting env $envname: ${!envname}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +107,7 @@ function detect_local_env() {
|
|||
# Get all possible envs for the install
|
||||
|
||||
if [ -n "${SETUP_DEBUG}" ]; then
|
||||
echo "# Printing local envs - before #++#"
|
||||
echo "# POI02| Printing local envs - before #++#"
|
||||
printenv
|
||||
fi
|
||||
|
||||
|
|
@ -113,7 +117,7 @@ function detect_local_env() {
|
|||
done
|
||||
|
||||
if [ -n "${SETUP_DEBUG}" ]; then
|
||||
echo "# Printing local envs - after #++#"
|
||||
echo "# POI02| Printing local envs - after #++#"
|
||||
printenv
|
||||
fi
|
||||
}
|
||||
|
|
@ -121,15 +125,17 @@ function detect_local_env() {
|
|||
function detect_envs() {
|
||||
# Detect all envs that should be passed to setup commands
|
||||
|
||||
echo "# Setting base environment variables"
|
||||
echo "# POI03| Setting base environment variables"
|
||||
|
||||
export INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE:-${CONF_DIR}/config.yaml}
|
||||
|
||||
if test -f "${INVENTREE_CONFIG_FILE}"; then
|
||||
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
||||
echo "# POI03| Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
||||
|
||||
# Install parser
|
||||
echo "# POI03| Installing requirements"
|
||||
pip install --require-hashes -r ${APP_HOME}/contrib/dev_reqs/requirements.txt -q
|
||||
echo "# POI03| Installed requirements"
|
||||
|
||||
# Load config
|
||||
export INVENTREE_CONF_DATA=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
|
||||
|
|
@ -149,10 +155,10 @@ function detect_envs() {
|
|||
export INVENTREE_DB_HOST=$(jq -r '.[].database.HOST' <<< ${INVENTREE_CONF_DATA})
|
||||
export INVENTREE_DB_PORT=$(jq -r '.[].database.PORT' <<< ${INVENTREE_CONF_DATA})
|
||||
else
|
||||
echo "# No config file found: ${INVENTREE_CONFIG_FILE}, using envs or defaults"
|
||||
echo "# POI03| No config file found: ${INVENTREE_CONFIG_FILE}, using envs or defaults"
|
||||
|
||||
if [ -n "${SETUP_DEBUG}" ]; then
|
||||
echo "# Print current envs"
|
||||
echo "# POI03| Print current envs"
|
||||
printenv | grep INVENTREE_
|
||||
printenv | grep SETUP_
|
||||
fi
|
||||
|
|
@ -175,43 +181,43 @@ function detect_envs() {
|
|||
fi
|
||||
|
||||
# For debugging pass out the envs
|
||||
echo "# Collected environment variables:"
|
||||
echo "# INVENTREE_MEDIA_ROOT=${INVENTREE_MEDIA_ROOT}"
|
||||
echo "# INVENTREE_STATIC_ROOT=${INVENTREE_STATIC_ROOT}"
|
||||
echo "# INVENTREE_BACKUP_DIR=${INVENTREE_BACKUP_DIR}"
|
||||
echo "# INVENTREE_PLUGINS_ENABLED=${INVENTREE_PLUGINS_ENABLED}"
|
||||
echo "# INVENTREE_PLUGIN_FILE=${INVENTREE_PLUGIN_FILE}"
|
||||
echo "# INVENTREE_SECRET_KEY_FILE=${INVENTREE_SECRET_KEY_FILE}"
|
||||
echo "# INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE}"
|
||||
echo "# INVENTREE_DB_NAME=${INVENTREE_DB_NAME}"
|
||||
echo "# INVENTREE_DB_USER=${INVENTREE_DB_USER}"
|
||||
echo "# POI03| Collected environment variables:"
|
||||
echo "# POI03| INVENTREE_MEDIA_ROOT=${INVENTREE_MEDIA_ROOT}"
|
||||
echo "# POI03| INVENTREE_STATIC_ROOT=${INVENTREE_STATIC_ROOT}"
|
||||
echo "# POI03| INVENTREE_BACKUP_DIR=${INVENTREE_BACKUP_DIR}"
|
||||
echo "# POI03| INVENTREE_PLUGINS_ENABLED=${INVENTREE_PLUGINS_ENABLED}"
|
||||
echo "# POI03| INVENTREE_PLUGIN_FILE=${INVENTREE_PLUGIN_FILE}"
|
||||
echo "# POI03| INVENTREE_SECRET_KEY_FILE=${INVENTREE_SECRET_KEY_FILE}"
|
||||
echo "# POI03| INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE}"
|
||||
echo "# POI03| INVENTREE_DB_NAME=${INVENTREE_DB_NAME}"
|
||||
echo "# POI03| INVENTREE_DB_USER=${INVENTREE_DB_USER}"
|
||||
if [ -n "${SETUP_DEBUG}" ]; then
|
||||
echo "# INVENTREE_DB_PASSWORD=${INVENTREE_DB_PASSWORD}"
|
||||
echo "# POI03| INVENTREE_DB_PASSWORD=${INVENTREE_DB_PASSWORD}"
|
||||
fi
|
||||
echo "# INVENTREE_DB_HOST=${INVENTREE_DB_HOST}"
|
||||
echo "# INVENTREE_DB_PORT=${INVENTREE_DB_PORT}"
|
||||
echo "# POI03| INVENTREE_DB_HOST=${INVENTREE_DB_HOST}"
|
||||
echo "# POI03| INVENTREE_DB_PORT=${INVENTREE_DB_PORT}"
|
||||
}
|
||||
|
||||
function create_initscripts() {
|
||||
|
||||
# Make sure python env exists
|
||||
if test -f "${APP_HOME}/env"; then
|
||||
echo "# python environment already present - skipping"
|
||||
echo "# POI09| python environment already present - skipping"
|
||||
else
|
||||
echo "# Setting up python environment"
|
||||
echo "# POI09| Setting up python environment"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && ${SETUP_PYTHON} -m venv env"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install invoke wheel"
|
||||
|
||||
# Check INSTALLER_EXTRA exists and load it
|
||||
if test -f "${APP_HOME}/INSTALLER_EXTRA"; then
|
||||
echo "# Loading extra packages from INSTALLER_EXTRA"
|
||||
echo "# POI09| Loading extra packages from INSTALLER_EXTRA"
|
||||
source ${APP_HOME}/INSTALLER_EXTRA
|
||||
fi
|
||||
|
||||
if [ -n "${SETUP_EXTRA_PIP}" ]; then
|
||||
echo "# Installing extra pip packages"
|
||||
echo "# POI09| Installing extra pip packages"
|
||||
if [ -n "${SETUP_DEBUG}" ]; then
|
||||
echo "# Extra pip packages: ${SETUP_EXTRA_PIP}"
|
||||
echo "# POI09| Extra pip packages: ${SETUP_EXTRA_PIP}"
|
||||
fi
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install ${SETUP_EXTRA_PIP}"
|
||||
# Write extra packages to INSTALLER_EXTRA
|
||||
|
|
@ -221,41 +227,45 @@ function create_initscripts() {
|
|||
|
||||
# Unlink default config if it exists
|
||||
if test -f "/etc/nginx/sites-enabled/default"; then
|
||||
echo "# Unlinking default nginx config\n# Old file still in /etc/nginx/sites-available/default"
|
||||
echo "# POI09| Unlinking default nginx config\n# POI09| Old file still in /etc/nginx/sites-available/default"
|
||||
sudo unlink /etc/nginx/sites-enabled/default
|
||||
echo "# POI09| Unlinked default nginx config"
|
||||
fi
|
||||
|
||||
# Create InvenTree specific nginx config
|
||||
echo "# Stopping nginx"
|
||||
echo "# POI09| Stopping nginx"
|
||||
${INIT_CMD} stop nginx
|
||||
echo "# Setting up nginx to ${SETUP_NGINX_FILE}"
|
||||
echo "# POI09| Stopped nginx"
|
||||
echo "# POI09| Setting up nginx to ${SETUP_NGINX_FILE}"
|
||||
# Always use the latest nginx config; important if new headers are added / needed for security
|
||||
cp ${APP_HOME}/contrib/packager.io/nginx.prod.conf ${SETUP_NGINX_FILE}
|
||||
sed -i s/inventree-server:8000/localhost:6000/g ${SETUP_NGINX_FILE}
|
||||
sed -i s=var/www=opt/inventree/data=g ${SETUP_NGINX_FILE}
|
||||
# Start nginx
|
||||
echo "# Starting nginx"
|
||||
echo "# POI09| Starting nginx"
|
||||
${INIT_CMD} start nginx
|
||||
echo "# POI09| Started nginx"
|
||||
|
||||
echo "# (Re)creating init scripts"
|
||||
echo "# POI09| (Re)creating init scripts"
|
||||
# This resets scale parameters to a known state
|
||||
inventree scale web="1" worker="1"
|
||||
|
||||
echo "# Enabling InvenTree on boot"
|
||||
echo "# POI09| Enabling InvenTree on boot"
|
||||
${INIT_CMD} enable inventree
|
||||
echo "# POI09| Enabled InvenTree on boot"
|
||||
}
|
||||
|
||||
function create_admin() {
|
||||
# Create data for admin users - stop with setting SETUP_ADMIN_NOCREATION to true
|
||||
if [ "${SETUP_ADMIN_NOCREATION}" == "true" ]; then
|
||||
echo "# Admin creation is disabled - skipping"
|
||||
echo "# POI10| Admin creation is disabled - skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
if test -f "${SETUP_ADMIN_PASSWORD_FILE}"; then
|
||||
echo "# Admin data already exists - skipping"
|
||||
echo "# POI10| Admin data already exists - skipping"
|
||||
else
|
||||
echo "# Creating admin user data"
|
||||
echo "# POI10| Creating admin user data"
|
||||
|
||||
# Static admin data
|
||||
export INVENTREE_ADMIN_USER=${INVENTREE_ADMIN_USER:-admin}
|
||||
|
|
@ -270,13 +280,15 @@ function create_admin() {
|
|||
}
|
||||
|
||||
function start_inventree() {
|
||||
echo "# Starting InvenTree"
|
||||
echo "# POI15| Starting InvenTree"
|
||||
${INIT_CMD} start inventree
|
||||
echo "# POI15| Started InvenTree"
|
||||
}
|
||||
|
||||
function stop_inventree() {
|
||||
echo "# Stopping InvenTree"
|
||||
echo "# POI11| Stopping InvenTree"
|
||||
${INIT_CMD} stop inventree
|
||||
echo "# POI11| Stopped InvenTree"
|
||||
}
|
||||
|
||||
function update_or_install() {
|
||||
|
|
@ -285,23 +297,23 @@ function update_or_install() {
|
|||
chown ${APP_USER}:${APP_GROUP} ${APP_HOME} -R
|
||||
|
||||
# Run update as app user
|
||||
echo "# Updating InvenTree"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && pip install wheel"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke update | sed -e 's/^/# inv update| /;'"
|
||||
echo "# POI12| Updating InvenTree"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && pip install uv wheel"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke update --uv | sed -e 's/^/# POI12| u | /;'"
|
||||
|
||||
# Make sure permissions are correct again
|
||||
echo "# Set permissions for data dir and media: ${DATA_DIR}"
|
||||
echo "# POI12| Set permissions for data dir and media: ${DATA_DIR}"
|
||||
chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} -R
|
||||
chown ${APP_USER}:${APP_GROUP} ${CONF_DIR} -R
|
||||
}
|
||||
|
||||
function set_env() {
|
||||
echo "# Setting up InvenTree config values"
|
||||
echo "# POI13| Setting up InvenTree config values"
|
||||
|
||||
inventree config:set INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE}
|
||||
|
||||
# Changing the config file
|
||||
echo "# Writing the settings to the config file ${INVENTREE_CONFIG_FILE}"
|
||||
echo "# POI13| Writing the settings to the config file ${INVENTREE_CONFIG_FILE}"
|
||||
# Media Root
|
||||
sed -i s=#media_root:\ \'/home/inventree/data/media\'=media_root:\ \'${INVENTREE_MEDIA_ROOT}\'=g ${INVENTREE_CONFIG_FILE}
|
||||
# Static Root
|
||||
|
|
@ -332,23 +344,28 @@ function set_env() {
|
|||
|
||||
# Fixing the permissions
|
||||
chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} ${INVENTREE_CONFIG_FILE}
|
||||
|
||||
echo "# POI13| Done setting up InvenTree config values"
|
||||
}
|
||||
|
||||
function set_site() {
|
||||
# Ensure IP is known
|
||||
if [ -z "${INVENTREE_IP}" ]; then
|
||||
echo "# No IP address found - skipping"
|
||||
echo "# POI14| No IP address found - skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
# Check if INVENTREE_SITE_URL in inventree config
|
||||
if [ -z "$(inventree config:get INVENTREE_SITE_URL)" ]; then
|
||||
echo "# Setting up InvenTree site URL"
|
||||
echo "# POI14| Setting up InvenTree site URL"
|
||||
inventree config:set INVENTREE_SITE_URL=http://${INVENTREE_IP}
|
||||
else
|
||||
echo "# POI14| Site URL already set - skipping"
|
||||
fi
|
||||
}
|
||||
|
||||
function final_message() {
|
||||
echo "# POI16| Printing Final message"
|
||||
echo -e "####################################################################################"
|
||||
echo -e "This InvenTree install uses nginx, the settings for the webserver can be found in"
|
||||
echo -e "${SETUP_NGINX_FILE}"
|
||||
|
|
@ -362,10 +379,12 @@ function final_message() {
|
|||
|
||||
|
||||
function update_checks() {
|
||||
echo "# Running upgrade"
|
||||
echo "# POI08| Running upgrade"
|
||||
local old_version=$1
|
||||
local old_version_rev=$(echo ${old_version} | cut -d'-' -f1 | cut -d'.' -f2)
|
||||
echo "# Old version is: ${old_version} - release: ${old_version_rev}"
|
||||
local new_version=$(dpkg-query --show --showformat='${Version}' inventree)
|
||||
local new_version_rev=$(echo ${new_version} | cut -d'-' -f1 | cut -d'.' -f2)
|
||||
echo "# POI08| Old version is: ${old_version} | ${old_version_rev} - updating to ${new_version} | ${old_version_rev}"
|
||||
|
||||
local ABORT=false
|
||||
function check_config_value() {
|
||||
|
|
@ -378,25 +397,25 @@ function update_checks() {
|
|||
value=$(jq -r ".[].${config_key}" <<< ${INVENTREE_CONF_DATA})
|
||||
fi
|
||||
if [ -z "${value}" ] || [ "$value" == "null" ]; then
|
||||
echo "# No setting for ${name} found - please set it manually either in ${INVENTREE_CONFIG_FILE} under '${config_key}' or with 'inventree config:set ${env_key}=value'"
|
||||
echo "# POI08| No setting for ${name} found - please set it manually either in ${INVENTREE_CONFIG_FILE} under '${config_key}' or with 'inventree config:set ${env_key}=value'"
|
||||
ABORT=true
|
||||
else
|
||||
echo "# Found setting for ${name} - ${value}"
|
||||
echo "# POI08| Found setting for ${name} - ${value}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Custom checks if old version is below 0.8.0
|
||||
if [ "${old_version_rev}" -lt "9" ]; then
|
||||
echo "# Old version is below 0.9.0 - You might be missing some configs"
|
||||
echo "# POI08| Old version is below 0.9.0 - You might be missing some configs"
|
||||
|
||||
# Check for BACKUP_DIR and SITE_URL in INVENTREE_CONF_DATA and config
|
||||
check_config_value "INVENTREE_SITE_URL" "site_url" "site URL"
|
||||
check_config_value "INVENTREE_BACKUP_DIR" "backup_dir" "backup dir"
|
||||
|
||||
if [ "${ABORT}" = true ]; then
|
||||
echo "# Aborting - please set the missing values and run the update again"
|
||||
echo "# POI08| Aborting - please set the missing values and run the update again"
|
||||
exit 1
|
||||
fi
|
||||
echo "# All checks passed - continuing with the update"
|
||||
echo "# POI08| All checks passed - continuing with the update"
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,18 @@
|
|||
# packager.io postinstall script
|
||||
#
|
||||
|
||||
echo "# POI01| Running postinstall script - start - $(date)"
|
||||
exec > >(tee ${APP_HOME}/log/setup_$(date +"%F_%H_%M_%S").log) 2>&1
|
||||
|
||||
PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
|
||||
|
||||
# import functions
|
||||
echo "# POI01| Importing functions"
|
||||
. ${APP_HOME}/contrib/packager.io/functions.sh
|
||||
echo "# POI01| Functions imported"
|
||||
|
||||
# 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
|
||||
|
|
@ -37,9 +40,9 @@ detect_ip
|
|||
detect_python
|
||||
|
||||
# Check if we are updating and need to alert
|
||||
echo "# Checking if update checks are needed"
|
||||
echo "# POI08| Checking if update checks are needed"
|
||||
if [ -z "$2" ]; then
|
||||
echo "# Normal install - no need for checks"
|
||||
echo "# POI08| Normal install - no need for checks"
|
||||
else
|
||||
update_checks $2
|
||||
fi
|
||||
|
|
@ -60,3 +63,4 @@ start_inventree
|
|||
|
||||
# show info
|
||||
final_message
|
||||
echo "# POI17| Running postinstall script - done - $(date)"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,22 @@
|
|||
#
|
||||
# packager.io preinstall/preremove script
|
||||
#
|
||||
echo "# PRI01| Running preinstall script - start - $(date)"
|
||||
PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
|
||||
|
||||
# Envs that should be passed to setup commands
|
||||
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,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 int.clear-generated --help" > /dev/null 2>&1; then
|
||||
echo "# PRI02| Clearing precompiled files"
|
||||
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke int.clear-generated"
|
||||
else
|
||||
echo "# PRI02| Clearing precompiled files - skipping"
|
||||
fi
|
||||
else
|
||||
echo "# No python environment found - skipping"
|
||||
echo "# PRI02| No python environment found - skipping"
|
||||
fi
|
||||
|
||||
echo "# PRI03| Running preinstall script - done - $(date)"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ The API is self-documenting, and the documentation is provided alongside any Inv
|
|||
|
||||
### Generating Schema File
|
||||
|
||||
If you want to generate the API schema file yourself (for example to use with an external client, use the `invoke schema` command. Run with the `-help` command to see available options.
|
||||
If you want to generate the API schema file yourself (for example to use with an external client, use the `invoke dev.schema` command. Run with the `-help` command to see available options.
|
||||
|
||||
```
|
||||
invoke schema -help
|
||||
invoke dev.schema -help
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -56,3 +56,11 @@ If no match is found for the scanned barcode, the following error message is dis
|
|||
## App Integration
|
||||
|
||||
Barcode scanning is a key feature of the [companion mobile app](../app/barcode.md).
|
||||
|
||||
## Barcode History
|
||||
|
||||
If enabled, InvenTree can retain logs of the most recent barcode scans. This can be very useful for debugging or auditing purpopes.
|
||||
|
||||
Refer to the [barcode settings](../settings/global.md#barcodes) to enable barcode history logging.
|
||||
|
||||
The barcode history can be viewed via the admin panel in the web interface.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ The demo instance has a number of user accounts which you can use to explore the
|
|||
|
||||
| Username | Password | Staff Access | Enabled | Description |
|
||||
| -------- | -------- | ------------ | ------- | ----------- |
|
||||
| noaccess | youshallnotpass | No | Yes | Can login, but has no permissions |
|
||||
| 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 |
|
||||
|
|
@ -23,3 +24,35 @@ The demo instance has a number of user accounts which you can use to explore the
|
|||
| 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 |
|
||||
|
||||
### Dataset
|
||||
|
||||
The demo instance is populated with a sample dataset, which is reset every 24 hours.
|
||||
|
||||
The source data used in the demo instance can be found on our [GitHub page](https://github.com/inventree/demo-dataset).
|
||||
|
||||
### Local Setup
|
||||
|
||||
If you wish to install the demo dataset locally (for initial testing), you can run the following command (via [invoke](./start/invoke.md)):
|
||||
|
||||
```bash
|
||||
invoke dev.setup-test -i
|
||||
```
|
||||
|
||||
*(Note: The command above may be slightly different if you are running in docker.)*
|
||||
|
||||
This will install the demo dataset into your local InvenTree instance.
|
||||
|
||||
!!! warning "Warning"
|
||||
This command will **delete all existing data** in your InvenTree instance! It is not intended to be used on a production system, or loaded into an existing dataset.
|
||||
|
||||
### Clear Data
|
||||
|
||||
To clear demo data from your instance, and start afresh with a clean database, you can run the following command (via [invoke](./start/invoke.md)):
|
||||
|
||||
```bash
|
||||
invoke dev.delete-data
|
||||
```
|
||||
|
||||
!!! warning "Warning"
|
||||
This command will **delete all existing data** in your InvenTree instance, including any data that you have added yourself.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ To setup a development environment using [docker](../start/docker.md), run the f
|
|||
```bash
|
||||
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke install
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke setup-test --dev
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml run --rm inventree-dev-server invoke dev.setup-test --dev
|
||||
docker compose --project-directory . -f contrib/container/dev-docker-compose.yml up -d
|
||||
```
|
||||
|
||||
|
|
@ -34,25 +34,28 @@ A "bare metal" development setup can be installed as follows:
|
|||
```bash
|
||||
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
|
||||
python3 -m venv env && source env/bin/activate
|
||||
pip install invoke && invoke
|
||||
pip install invoke && invoke setup-dev --tests
|
||||
pip install django invoke && invoke
|
||||
invoke dev.setup-dev --tests
|
||||
```
|
||||
|
||||
Read the [InvenTree setup documentation](../start/intro.md) for a complete installation reference guide.
|
||||
|
||||
!!! note "Required Packages"
|
||||
Depending on your system, you may need to install additional software packages as required.
|
||||
|
||||
### Setup Devtools
|
||||
|
||||
Run the following command to set up all toolsets for development.
|
||||
|
||||
```bash
|
||||
invoke setup-dev
|
||||
invoke dev.setup-dev
|
||||
```
|
||||
|
||||
*We recommend you run this command before starting to contribute. This will install and set up `pre-commit` to run some checks before each commit and help reduce errors.*
|
||||
|
||||
## Branches and Versioning
|
||||
|
||||
InvenTree roughly follow the [GitLab flow](https://docs.gitlab.com/ee/topics/gitlab_flow.html) branching style, to allow simple management of multiple tagged releases, short-lived branches, and development on the main branch.
|
||||
InvenTree roughly follow the [GitLab flow](https://about.gitlab.com/topics/version-control/what-are-gitlab-flow-best-practices/) branching style, to allow simple management of multiple tagged releases, short-lived branches, and development on the main branch.
|
||||
|
||||
There are nominally 5 active branches:
|
||||
- `master` - The main development branch
|
||||
|
|
@ -169,19 +172,20 @@ The various github actions can be found in the `./github/workflows` directory
|
|||
### Run tests locally
|
||||
|
||||
To run test locally, use:
|
||||
|
||||
```
|
||||
invoke test
|
||||
invoke dev.test
|
||||
```
|
||||
|
||||
To run only partial tests, for example for a module use:
|
||||
```
|
||||
invoke test --runtest order
|
||||
invoke dev.test --runtest order
|
||||
```
|
||||
|
||||
To see all the available options:
|
||||
|
||||
```
|
||||
invoke test --help
|
||||
invoke dev.test --help
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ Tasks can help you executing scripts. You can run them by open the command panel
|
|||
|
||||
#### Setup demo dataset
|
||||
|
||||
If you need some demo test-data, run the `setup-test` task. This will import an `admin` user with the password `inventree`. For more info on what this dataset contains see [inventree/demo-dataset](https://github.com/inventree/demo-dataset).
|
||||
If you need some demo test-data, run the `setup-test` task. This will import an `admin` user with the password `inventree`. For more info on what this dataset contains see [inventree/demo-dataset](../demo.md).
|
||||
|
||||
#### Setup a superuser
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +1,48 @@
|
|||
---
|
||||
title: Platform UI / React
|
||||
title: React Frontend Development
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
The new React-based UI will not be available by default. In order to set your development environment up to view the frontend, follow this guide.
|
||||
The new UI requires a separate frontend server to run to serve data for the new Frontend.
|
||||
The following documentation details how to setup and run a development installation of the InvenTree frontend user interface.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To run the frontend development server, you will need to have the following installed:
|
||||
|
||||
- Node.js
|
||||
- Yarn
|
||||
|
||||
!!! note "Devcontainer"
|
||||
The [devcontainer](./devcontainer.md) setup already includes all prerequisite packages, and is ready to run the frontend server.
|
||||
|
||||
### Install
|
||||
|
||||
The React frontend requires its own packages that aren't installed via the usual invoke tasks.
|
||||
The React frontend requires its own packages that aren't installed via the usual [invoke](../start/invoke.md) tasks.
|
||||
|
||||
#### Docker
|
||||
|
||||
Run the following command:
|
||||
`docker compose run inventree-dev-server invoke frontend-compile`
|
||||
`docker compose run inventree-dev-server invoke int.frontend-compile`
|
||||
This will install the required packages for running the React frontend on your InvenTree dev server.
|
||||
|
||||
#### Devcontainer
|
||||
|
||||
!!! warning "This guide assumes you already have a running devcontainer"
|
||||
|
||||
!!! info "All these steps are performed within Visual Studio Code"
|
||||
|
||||
Open a new terminal from the top menu by clicking `Terminal > New Terminal`
|
||||
Make sure this terminal is running within the virtual env. The start of the last line should display `(venv)`
|
||||
|
||||
Run the command `invoke frontend-compile`. Wait for this to finish
|
||||
Run the command `invoke int.frontend-compile`. Wait for this to finish
|
||||
|
||||
### Running
|
||||
|
||||
After finishing the install, you need to launch a frontend server to be able to view the new UI.
|
||||
|
||||
Using the previously described ways of running commands, execute the following:
|
||||
`invoke frontend-dev` in your environment
|
||||
`invoke dev.frontend-server` in your environment
|
||||
This command does not run as a background daemon, and will occupy the window it's ran in.
|
||||
|
||||
### Accessing
|
||||
|
|
@ -40,7 +51,7 @@ When the frontend server is running, it will be available on port 5173.
|
|||
i.e: https://localhost:5173/
|
||||
|
||||
!!! note "Backend Server"
|
||||
The InvenTree backend server must also be running, for the frontend interface to have something to connect to! To launch a backend server, use the `invoke server` command.
|
||||
The InvenTree backend server must also be running, for the frontend interface to have something to connect to! To launch a backend server, use the `invoke dev.server` command.
|
||||
|
||||
### Debugging
|
||||
|
||||
|
|
@ -77,3 +88,27 @@ When running the frontend development server, some features may not work entirel
|
|||
#### SSO Login
|
||||
|
||||
When logging into the frontend dev server via SSO, the redirect URL may not redirect correctly.
|
||||
|
||||
## Testing
|
||||
|
||||
The frontend codebase it tested using [Playwright](https://playwright.dev/). There are a large number of tests that cover the frontend codebase, which are run automatically as part of the CI pipeline.
|
||||
|
||||
### Install Playwright
|
||||
|
||||
To install the required packages to run the tests, you can use the following command:
|
||||
|
||||
```bash
|
||||
cd src/frontend
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
To run the tests locally, in an interactive editor, you can use the following command:
|
||||
|
||||
```bash
|
||||
cd src/frontend
|
||||
npx playwright test --ui
|
||||
```
|
||||
|
||||
This will first launch the backend server (at `http://localhost:8000`), and then run the tests against the frontend server (at `http://localhost:5173`). An interactive browser window will open, and you can run the tests individually or as a group.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ title: Panel Mixin
|
|||
|
||||
## PanelMixin
|
||||
|
||||
!!! warning "Legacy User Interface"
|
||||
This plugin mixin class is designed specifically for the the *legacy* user interface (which is rendered on the server using django templates). The new user interface (which is rendered on the client using React) does not support this mixin class. Instead, refer to the new [User Interface Mixin](./ui.md) class.
|
||||
|
||||
!!! warning "Deprecated Class"
|
||||
This mixin class is considered deprecated, and will be removed in the 1.0.0 release.
|
||||
|
||||
The `PanelMixin` enables plugins to render custom content to "panels" on individual pages in the web interface.
|
||||
|
||||
Most pages in the web interface support multiple panels, which are selected via the sidebar menu on the left side of the screen:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
---
|
||||
title: User Interface Mixin
|
||||
---
|
||||
|
||||
## User Interface Mixin
|
||||
|
||||
The `UserInterfaceMixin` class provides a set of methods to implement custom functionality for the InvenTree web interface.
|
||||
|
||||
### Enable User Interface Mixin
|
||||
|
||||
To enable user interface plugins, the global setting `ENABLE_PLUGINS_INTERFACE` must be enabled, in the [plugin settings](../../settings/global.md#plugin-settings).
|
||||
|
||||
## Custom UI Features
|
||||
|
||||
The InvenTree user interface functionality can be extended in various ways using plugins. Multiple types of user interface *features* can be added to the InvenTree user interface.
|
||||
|
||||
The entrypoint for user interface plugins is the `UserInterfaceMixin` class, which provides a number of methods which can be overridden to provide custom functionality. The `get_ui_features` method is used to extract available user interface features from the plugin:
|
||||
|
||||
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_features
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
Note here that the `get_ui_features` calls other methods to extract the available features from the plugin, based on the requested feature type. These methods can be overridden to provide custom functionality.
|
||||
|
||||
!!! info "Implementation"
|
||||
Your custom plugin does not need to override the `get_ui_features` method. Instead, override one of the other methods to provide custom functionality.
|
||||
|
||||
### UIFeature Return Type
|
||||
|
||||
The `get_ui_features` method should return a list of `UIFeature` objects, which define the available user interface features for the plugin. The `UIFeature` class is defined as follows:
|
||||
|
||||
::: plugin.base.ui.mixins.UIFeature
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
Note that the *options* field contains fields which may be specific to a particular feature type - read the documentation below on each feature type for more information.
|
||||
|
||||
### Dynamic Feature Loading
|
||||
|
||||
Each of the provided feature types can be loaded dynamically by the plugin, based on the information provided in the API request. For example, the plugin can choose to show or hide a particular feature based on the user permissions, or the current state of the system.
|
||||
|
||||
For examples of this dynamic feature loading, refer to the [sample plugin](#sample-plugin) implementation which demonstrates how to dynamically load custom panels based on the provided context.
|
||||
|
||||
### Javascript Source Files
|
||||
|
||||
The rendering function for the custom user interface features expect that the plugin provides a Javascript source file which contains the necessary code to render the custom content. The path to this file should be provided in the `source` field of the `UIFeature` object.
|
||||
|
||||
Note that the `source` field can include the name of the function to be called (if this differs from the expected default function name).
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
"source": "/static/plugins/my_plugin/my_plugin.js:my_custom_function"
|
||||
```
|
||||
|
||||
## Available UI Feature Types
|
||||
|
||||
The following user interface feature types are available:
|
||||
|
||||
### Dashboard Items
|
||||
|
||||
The InvenTree dashboard is a collection of "items" which are displayed on the main dashboard page. Custom dashboard items can be added to the dashboard by implementing the `get_ui_dashboard_items` method:
|
||||
|
||||
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_dashboard_items
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
#### Dashboard Item Options
|
||||
|
||||
The *options* field in the returned `UIFeature` object can contain the following properties:
|
||||
|
||||
::: plugin.base.ui.mixins.CustomDashboardItemOptions
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
#### Source Function
|
||||
|
||||
The frontend code expects a path to a javascript file containing a function named `renderDashboardItem` which will be called to render the custom dashboard item. Note that this function name can be overridden by appending the function name in the `source` field of the `UIFeature` object.
|
||||
|
||||
#### Example
|
||||
|
||||
Refer to the [sample plugin](#sample-plugin) for an example of how to implement server side rendering for custom panels.
|
||||
|
||||
### Panels
|
||||
|
||||
Many of the pages in the InvenTree web interface are built using a series of "panels" which are displayed on the page. Custom panels can be added to these pages, by implementing the `get_ui_panels` method:
|
||||
|
||||
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_panels
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
#### Panel Options
|
||||
|
||||
The *options* field in the returned `UIFeature` object can contain the following properties:
|
||||
|
||||
::: plugin.base.ui.mixins.CustomPanelOptions
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
#### Source Function
|
||||
|
||||
The frontend code expects a path to a javascript file containing a function named `renderPanel` which will be called to render the custom panel. Note that this function name can be overridden by appending the function name in the `source` field of the `UIFeature` object.
|
||||
|
||||
#### Example
|
||||
|
||||
Refer to the [sample plugin](#sample-plugin) for an example of how to implement server side rendering for custom panels.
|
||||
|
||||
### Template Editors
|
||||
|
||||
The `get_ui_template_editors` feature type can be used to provide custom template editors.
|
||||
|
||||
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_template_editors
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
### Template previews
|
||||
|
||||
The `get_ui_template_previews` feature type can be used to provide custom template previews:
|
||||
|
||||
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_template_previews
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_sources: True
|
||||
summary: False
|
||||
members: []
|
||||
|
||||
## Plugin Context
|
||||
|
||||
When rendering certain content in the user interface, the rendering functions are passed a `context` object which contains information about the current page being rendered. The type of the `context` object is defined in the `PluginContext` file:
|
||||
|
||||
{{ includefile("src/frontend/src/components/plugins/PluginContext.tsx", title="Plugin Context", fmt="javascript") }}
|
||||
|
||||
This context data can be used to provide additional information to the rendering functions, and can be used to dynamically render content based on the current state of the system.
|
||||
|
||||
### Additional Context
|
||||
|
||||
Note that additional context can be passed to the rendering functions by adding additional key-value pairs to the `context` field in the `UIFeature` return type (provided by the backend via the API). This field is optional, and can be used at the discretion of the plugin developer.
|
||||
|
||||
## Sample Plugin
|
||||
|
||||
A sample plugin which implements custom user interface functionality is provided in the InvenTree source code, which provides a full working example of how to implement custom user interface functionality.
|
||||
|
||||
::: plugin.samples.integration.user_interface_sample.SampleUserInterfacePlugin
|
||||
options:
|
||||
show_bases: False
|
||||
show_root_heading: False
|
||||
show_root_toc_entry: False
|
||||
show_source: True
|
||||
members: []
|
||||
|
|
@ -14,37 +14,17 @@ InvenTree installation is not officially supported natively on Windows. However
|
|||
|
||||
### Command 'invoke' not found
|
||||
|
||||
If the `invoke` command does not work, it means that the [invoke](https://pypi.org/project/invoke/) python library has not been correctly installed.
|
||||
If the `invoke` command does not work, it means that the invoke tool has not been correctly installed.
|
||||
|
||||
Update the installed python packages with PIP:
|
||||
Refer to the [invoke installation guide](./start/invoke.md#installation) for more information.
|
||||
|
||||
```
|
||||
pip3 install -U --require-hashes -r requirements.txt
|
||||
```
|
||||
### Can't find any collection named tasks
|
||||
|
||||
Refer to the [invoke guide](./start/invoke.md#cant-find-any-collection-named-tasks) for more information.
|
||||
|
||||
### Invoke Version
|
||||
|
||||
If the installed version of invoke is too old, users may see error messages during the installation procedure, such as:
|
||||
|
||||
- *'update' did not receive all required positional arguments!*
|
||||
- *Function has keyword-only arguments or annotations*
|
||||
|
||||
As per the [invoke guide](./start/intro.md#invoke), the minimum required version of Invoke is `{{ config.extra.min_invoke_version }}`.
|
||||
|
||||
To determine the version of invoke you have installed, run either:
|
||||
|
||||
```
|
||||
invoke --version
|
||||
```
|
||||
```
|
||||
python -m invoke --version
|
||||
```
|
||||
|
||||
If you are running an older version of invoke, ensure it is updated to the latest version:
|
||||
|
||||
```
|
||||
pip install -U invoke
|
||||
```
|
||||
If the installed version of invoke is too old, users may see error messages during the installation procedure. Refer to the [invoke guide](./start/invoke.md#minimum-version) for more information.
|
||||
|
||||
### No module named 'django'
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -93,6 +93,14 @@ There are two options to mark items as "received":
|
|||
!!! note "Permissions"
|
||||
Marking line items as received requires the "Purchase order" ADD permission.
|
||||
|
||||
### Item Location
|
||||
|
||||
When receiving items from a purchase order, the location of the items must be specified. There are multiple ways to specify the location:
|
||||
|
||||
* **Order Destination**: The *destination* field of the purchase order can be set to a specific location. When receiving items, the location will default to the destination location.
|
||||
|
||||
* **Line Item Location**: Each line item can have a specific location set. When receiving items, the location will default to the line item location. *Note: A destination specified at the line item level will override the destination specified at the order level.*
|
||||
|
||||
### Received Items
|
||||
|
||||
Each item marked as "received" is automatically converted into a stock item.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ The following report templates are provided "out of the box" and can be used as
|
|||
| [Purchase Order](#purchase-order) | [PurchaseOrder](../order/purchase_order.md) | Purchase Order report |
|
||||
| [Return Order](#return-order) | [ReturnOrder](../order/return_order.md) | Return Order report |
|
||||
| [Sales Order](#sales-order) | [SalesOrder](../order/sales_order.md) | Sales Order report |
|
||||
| [Sales Order Shipment](#sales-order-shipment) | [SalesOrderShipment](../order/sales_order.md) | Sales Order Shipment report |
|
||||
| [Stock Location](#stock-location) | [StockLocation](../stock/stock.md#stock-location) | Stock Location report |
|
||||
| [Test Report](#test-report) | [StockItem](../stock/stock.md#stock-item) | Test Report |
|
||||
|
||||
|
|
@ -42,6 +43,10 @@ The following report templates are provided "out of the box" and can be used as
|
|||
|
||||
{{ templatefile("report/inventree_sales_order_report.html") }}
|
||||
|
||||
### Sales Order Shipment
|
||||
|
||||
{{ templatefile("report/inventree_sales_order_shipment_report.html") }}
|
||||
|
||||
### Stock Location
|
||||
|
||||
{{ templatefile("report/inventree_stock_location_report.html") }}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ Configuration of barcode functionality:
|
|||
{{ globalsetting("BARCODE_WEBCAM_SUPPORT") }}
|
||||
{{ globalsetting("BARCODE_SHOW_TEXT") }}
|
||||
{{ globalsetting("BARCODE_GENERATION_PLUGIN") }}
|
||||
{{ globalsetting("BARCODE_STORE_RESULTS") }}
|
||||
{{ globalsetting("BARCODE_RESULTS_MAX_NUM") }}
|
||||
|
||||
### Pricing and Currency
|
||||
|
||||
|
|
@ -121,8 +123,6 @@ Configuration of report generation:
|
|||
{{ globalsetting("REPORT_DEFAULT_PAGE_SIZE") }}
|
||||
{{ globalsetting("REPORT_DEBUG_MODE") }}
|
||||
{{ globalsetting("REPORT_LOG_ERRORS") }}
|
||||
{{ globalsetting("REPORT_ENABLE_TEST_REPORT") }}
|
||||
{{ globalsetting("REPORT_ATTACH_TEST_REPORT") }}
|
||||
|
||||
### Parts
|
||||
|
||||
|
|
@ -188,6 +188,7 @@ Configuration of stock item options
|
|||
{{ globalsetting("STOCK_ENFORCE_BOM_INSTALLATION") }}
|
||||
{{ globalsetting("STOCK_ALLOW_OUT_OF_STOCK_TRANSFER") }}
|
||||
{{ globalsetting("TEST_STATION_DATA") }}
|
||||
{{ globalsetting("TEST_UPLOAD_CREATE_TEMPLATE") }}
|
||||
|
||||
### Build Orders
|
||||
|
||||
|
|
@ -207,7 +208,6 @@ Refer to the [return order settings](../order/return_order.md#return-order-setti
|
|||
|
||||
### Plugin Settings
|
||||
|
||||
|
||||
| Name | Description | Default | Units |
|
||||
| ---- | ----------- | ------- | ----- |
|
||||
{{ globalsetting("PLUGIN_ON_STARTUP") }}
|
||||
|
|
@ -217,3 +217,4 @@ Refer to the [return order settings](../order/return_order.md#return-order-setti
|
|||
{{ globalsetting("ENABLE_PLUGINS_APP") }}
|
||||
{{ globalsetting("ENABLE_PLUGINS_SCHEDULE") }}
|
||||
{{ globalsetting("ENABLE_PLUGINS_EVENTS") }}
|
||||
{{ globalsetting("ENABLE_PLUGINS_INTERFACE") }}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: Account Management
|
||||
---
|
||||
|
||||
## User Accounts
|
||||
|
||||
By default, InvenTree does not ship with any user accounts. Configuring user accounts is the first step to login to the InvenTree server.
|
||||
|
||||
### Administrator Account
|
||||
|
||||
You can configure InvenTree to create an administrator account on the first run. This account will have full *superuser* access to the InvenTree server.
|
||||
|
||||
This account is created when you first run the InvenTree server instance. The username / password for this account can be configured in the configuration file, or environment variables.
|
||||
|
||||
!!! info "More Information"
|
||||
For more information on configuring the administrator account, refer to the [configuration documentation](./config.md#administrator-account).
|
||||
|
||||
### Create Superuser
|
||||
|
||||
Another way to create an administrator account is to use the `superuser` command (via [invoke](./invoke.md)). This will create a new superuser account with the specified username and password.
|
||||
|
||||
```bash
|
||||
invoke superuser
|
||||
```
|
||||
|
||||
Or, if you are running InvenTree in a Docker container:
|
||||
|
||||
```bash
|
||||
docker exec -rm -it inventree-server invoke superuser
|
||||
```
|
||||
|
||||
### User Management
|
||||
|
||||
Once you have created an administrator account, you can create and manage additional user accounts from the InvenTree web interface.
|
||||
|
||||
## Password Management
|
||||
|
||||
### Reset Password via Command Line
|
||||
|
||||
If a password has been lost, and other backup options (such as email recovery) are unavailable, the system administrator can reset the password for a user account from the command line.
|
||||
|
||||
Log into the machine running the InvenTree server, and run the following command (from the top-level source directory):
|
||||
|
||||
```bash
|
||||
cd src/backend/InvenTree
|
||||
python ./manage.py changepassword <username>
|
||||
```
|
||||
|
||||
The system will prompt you to enter a new password for the specified user account.
|
||||
|
|
@ -17,7 +17,7 @@ InvenTree includes a simple server application, suitable for use in a developmen
|
|||
To run the development server on a local machine, run the command:
|
||||
|
||||
```
|
||||
(env) invoke server
|
||||
(env) invoke dev.server
|
||||
```
|
||||
|
||||
This will launch the InvenTree web interface at `http://127.0.0.1:8000`.
|
||||
|
|
@ -25,7 +25,7 @@ This will launch the InvenTree web interface at `http://127.0.0.1:8000`.
|
|||
A different port can be specified using the `-a` flag:
|
||||
|
||||
```
|
||||
(env) invoke server -a 127.0.0.1:8123
|
||||
(env) invoke dev.server -a 127.0.0.1:8123
|
||||
```
|
||||
|
||||
Serving on the address `127.0.0.1` means that InvenTree will only be available *on that computer*. The server will be accessible from a web browser on the same computer, but not from any other computers on the local network.
|
||||
|
|
@ -35,12 +35,12 @@ Serving on the address `127.0.0.1` means that InvenTree will only be available *
|
|||
To enable access to the InvenTree server from other computers on a local network, you need to know the IP of the computer running the server. For example, if the server IP address is `192.168.120.1`:
|
||||
|
||||
```
|
||||
(env) invoke server -a 192.168.120.1:8000
|
||||
(env) invoke dev.server -a 192.168.120.1:8000
|
||||
```
|
||||
|
||||
## Background Worker
|
||||
|
||||
The background task manager must also be started. The InvenTree server is already running in the foreground, so open a *new shell window* to start the server.
|
||||
The [background task manager](./processes.md#background-worker) must also be started. The InvenTree server is already running in the foreground, so open a *new shell window* to start the server.
|
||||
|
||||
### Activate Virtual Environment
|
||||
|
||||
|
|
@ -55,4 +55,4 @@ source ./env/bin/activate
|
|||
(env) invoke worker
|
||||
```
|
||||
|
||||
This will start the background process manager in the current shell.
|
||||
This will start an instance of the background worker in the current shell.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ The InvenTree web server is hosted using [Gunicorn](https://gunicorn.org/). Guni
|
|||
|
||||
### Supervisor
|
||||
|
||||
[Supervisor](http://supervisord.org/) is a process control system which monitors and controls multiple background processes. It is used in the InvenTree production setup to ensure that the server and background worker processes are always running.
|
||||
[Supervisor](http://supervisord.org/) is a process control system which monitors and controls multiple background processes. It is used in the InvenTree production setup to ensure that the [web server](./processes.md#web-server) and [background worker](./processes.md#background-worker) processes are always running.
|
||||
|
||||
## Gunicorn
|
||||
|
||||
|
|
@ -98,11 +98,12 @@ The InvenTree server (and background task manager) should now be running!
|
|||
In addition to the InvenTree server, you will need a method of delivering static and media files (this is *not* handled by the InvenTree server in a production environment).
|
||||
|
||||
!!! info "Read More"
|
||||
Refer to the [Serving Files](./serving_files.md) section for more details
|
||||
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details
|
||||
|
||||
### Next Steps
|
||||
|
||||
You (or your system administrator) may wish to perform further steps such as placing the InvenTree server behind a reverse-proxy such as [caddy](https://caddyserver.com/), or [nginx](https://www.nginx.com/).
|
||||
You (or your system administrator) may wish to perform further steps such as placing the InvenTree server behind a [reverse proxy](./processes.md#proxy-server) such as [caddy](https://caddyserver.com/), or [nginx](https://www.nginx.com/).
|
||||
|
||||
As production environment options are many and varied, such tasks are outside the scope of this documentation.
|
||||
|
||||
There are many great online tutorials about running django applications in production!
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ The InvenTree server tries to locate the `config.yaml` configuration file on sta
|
|||
|
||||
The configuration file *template* can be found on [GitHub]({{ sourcefile("src/backend/InvenTree/config_template.yaml") }}), and is shown below:
|
||||
|
||||
{{ includefile("src/backend/InvenTree/config_template.yaml", "Configuration File Template", format="yaml") }}
|
||||
{{ includefile("src/backend/InvenTree/config_template.yaml", "Configuration File Template", fmt="yaml") }}
|
||||
|
||||
!!! info "Template File"
|
||||
The default configuration file (as defined by the template linked above) will be copied to the specified configuration file location on first run, if a configuration file is not found in that location.
|
||||
|
|
@ -107,7 +107,22 @@ Depending on how your InvenTree installation is configured, you will need to pay
|
|||
| INVENTREE_USE_X_FORWARDED_HOST | use_x_forwarded_host | Use forwarded host header | `False` |
|
||||
| INVENTREE_USE_X_FORWARDED_PORT | use_x_forwarded_port | Use forwarded port header | `False` |
|
||||
| INVENTREE_SESSION_COOKIE_SECURE | cookie.secure | Enforce secure session cookies | `False` |
|
||||
| INVENTREE_COOKIE_SAMESITE | cookie.samesite | Session cookie mode. Must be one of `Strict | Lax | None`. Refer to the [mozilla developer docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for more information. | `None` |
|
||||
| INVENTREE_COOKIE_SAMESITE | cookie.samesite | Session cookie mode. Must be one of `Strict | Lax | None | False`. Refer to the [mozilla developer docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) and the [django documentation]({% include "django.html" %}/ref/settings/#std-setting-SESSION_COOKIE_SAMESITE) for more information. | False |
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Note that in [debug mode](./intro.md#debug-mode), some of the above settings are automatically adjusted to allow for easier development:
|
||||
|
||||
| Setting | Value in Debug Mode | Description |
|
||||
| --- | --- | --- |
|
||||
| `INVENTREE_ALLOWED_HOSTS` | `*` | Allow all host in debug mode |
|
||||
| `CSRF_TRUSTED_ORIGINS` | Value is appended to allow `http://*.localhost:*` | Allow all connections from localhost, for development purposes |
|
||||
| `INVENTREE_COOKIE_SAMESITE` | `False` | Disable all same-site cookie checks in debug mode |
|
||||
| `INVENTREE_SESSION_COOKIE_SECURE` | `False` | Disable secure session cookies in debug mode (allow non-https cookies) |
|
||||
|
||||
### INVENTREE_COOKIE_SAMESITE vs INVENTREE_SESSION_COOKIE_SECURE
|
||||
|
||||
Note that if you set the `INVENTREE_COOKIE_SAMESITE` to `None`, then `INVENTREE_SESSION_COOKIE_SECURE` is automatically set to `True` to ensure that the session cookie is secure! This means that the session cookie will only be sent over secure (https) connections.
|
||||
|
||||
### Proxy Settings
|
||||
|
||||
|
|
@ -264,12 +279,12 @@ InvenTree requires some external directories for storing files:
|
|||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_STATIC_ROOT | static_root | [Static files](./serving_files.md#static-files) directory | *Not specified* |
|
||||
| INVENTREE_MEDIA_ROOT | media_root | [Media files](./serving_files.md#media-files) directory | *Not specified* |
|
||||
| INVENTREE_STATIC_ROOT | static_root | [Static files](./processes.md#static-files) directory | *Not specified* |
|
||||
| INVENTREE_MEDIA_ROOT | media_root | [Media files](./processes.md#media-files) directory | *Not specified* |
|
||||
| INVENTREE_BACKUP_DIR | backup_dir | Backup files directory | *Not specified* |
|
||||
|
||||
!!! tip "Serving Files"
|
||||
Read the [Serving Files](./serving_files.md) section for more information on hosting *static* and *media* files
|
||||
Read the [proxy server documentation](./processes.md#proxy-server) for more information on hosting *static* and *media* files
|
||||
|
||||
### Static File Storage
|
||||
|
||||
|
|
@ -354,6 +369,15 @@ The logo and custom messages can be changed/set:
|
|||
| INVENTREE_CUSTOMIZE | customize.navbar_message | Custom message for navbar | *Not specified* |
|
||||
| INVENTREE_CUSTOMIZE | customize.hide_pui_banner | Disable PUI banner | False |
|
||||
|
||||
The INVENTREE_CUSTOMIZE environment variable must contain a json object with the keys from the table above and
|
||||
the wanted values. Example:
|
||||
|
||||
```
|
||||
INVENTREE_CUSTOMIZE={"login_message":"Hallo Michi","hide_pui_banner":"True"}
|
||||
```
|
||||
|
||||
This example removes the PUI banner and sets a login message. Take care of the double quotes.
|
||||
|
||||
If you want to remove the InvenTree branding as far as possible from your end-user also check the [global server settings](../settings/global.md#server-settings).
|
||||
|
||||
!!! info "Custom Splash Screen Path"
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ Plugins are supported natively when running under docker. There are two ways to
|
|||
The production docker compose configuration outlined on this page uses [Caddy](https://caddyserver.com/) to serve static files and media files. If you change this configuration, you will need to ensure that static and media files are served correctly.
|
||||
|
||||
!!! info "Read More"
|
||||
Refer to the [Serving Files](./serving_files.md) section for more details
|
||||
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details
|
||||
|
||||
### SSL Certificates
|
||||
|
||||
|
|
@ -99,45 +99,11 @@ The example docker compose file launches the following containers:
|
|||
|
||||
| Container | Description |
|
||||
| --- | --- |
|
||||
| inventree-db | PostgreSQL database |
|
||||
| inventree-server | Gunicorn web server |
|
||||
| inventree-worker | django-q background worker |
|
||||
| inventree-proxy | Caddy file server and reverse proxy |
|
||||
| *inventree-cache* | *redis cache (optional)* |
|
||||
|
||||
#### PostgreSQL Database
|
||||
|
||||
A PostgreSQL database container which requires a username:password combination (which can be changed). This uses the official [PostgreSQL image](https://hub.docker.com/_/postgres).
|
||||
|
||||
#### Web Server
|
||||
|
||||
Runs an InvenTree web server instance, powered by a Gunicorn web server.
|
||||
|
||||
#### Background Worker
|
||||
|
||||
Runs the InvenTree background worker process. This spins up a second instance of the *inventree* container, with a different entrypoint command.
|
||||
|
||||
#### Proxy Server
|
||||
|
||||
Caddy working as a reverse proxy, separating requests for static and media files, and directing everything else to Gunicorn.
|
||||
|
||||
This container uses the official [caddy image](https://hub.docker.com/_/caddy).
|
||||
|
||||
!!! info "Nginx Proxy"
|
||||
An alternative is to run nginx as the reverse proxy. A sample configuration file is provided in the `./contrib/container/` source directory.
|
||||
|
||||
#### Redis Cache
|
||||
|
||||
Redis is used as cache storage for the InvenTree server. This provides a more performant caching system which can useful in larger installations.
|
||||
|
||||
This container uses the official [redis image](https://hub.docker.com/_/redis).
|
||||
|
||||
!!! info "Redis on Docker"
|
||||
Docker adds an additional network layer - that might lead to lower performance than bare metal.
|
||||
To optimize and configure your redis deployment follow the [official docker guide](https://redis.io/docs/getting-started/install-stack/docker/#configuration).
|
||||
|
||||
!!! tip "Enable Cache"
|
||||
While a redis container is provided in the default configuration, by default it is not enabled in the Inventree server. You can enable redis cache support by following the [caching configuration guide](./config.md#caching)
|
||||
| inventree-db | [PostgreSQL database](./processes.md#database) |
|
||||
| inventree-server | [InvenTree web server](./processes.md#web-server) |
|
||||
| inventree-worker | [django-q background worker](./processes.md#background-worker) |
|
||||
| inventree-proxy | [Caddy file server and reverse proxy](./processes.md#proxy-server) |
|
||||
| *inventree-cache* | [*redis cache (optional)*](./processes.md#cache-server) |
|
||||
|
||||
### Data Volume
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,11 @@ The following files required for this setup are provided with the InvenTree sour
|
|||
|
||||
Download these files to a directory on your local machine.
|
||||
|
||||
!!! warning "File Extensions"
|
||||
If your computer adds *.txt* extensions to any of the downloaded files, rename the file and remove the added extension before continuing!
|
||||
|
||||
!!! success "Working Directory"
|
||||
This tutorial assumes you are working from a direction where all of these files are located.
|
||||
This tutorial assumes you are working from a directory where all of these files are located.
|
||||
|
||||
!!! tip "No Source Required"
|
||||
For a production setup you do not need the InvenTree source code. Simply download the three required files from the links above!
|
||||
|
|
@ -101,10 +104,11 @@ docker compose up -d
|
|||
|
||||
This command launches the following containers:
|
||||
|
||||
- `inventree-db` - PostgreSQL database
|
||||
- `inventree-server` - InvenTree web server
|
||||
- `inventree-worker` - Background worker
|
||||
- `inventree-proxy` - Caddy reverse proxy
|
||||
- `inventree-db` - [PostgreSQL database](./processes.md#database)
|
||||
- `inventree-server` - [InvenTree web server](./processes.md#web-server)
|
||||
- `inventree-worker` - [Background worker](./processes.md#background-worker)
|
||||
- `inventree-proxy` - [Caddy reverse proxy](./processes.md#proxy-server)
|
||||
- `inventree-cache` - [Redis cache](./processes.md#cache-server)
|
||||
|
||||
!!! success "Up and Running!"
|
||||
You should now be able to view the InvenTree login screen at [http://inventree.localhost](http://inventree.localhost) (or whatever custom domain you have configured in the `.env` file).
|
||||
|
|
@ -202,7 +206,7 @@ Any persistent files generated by the Caddy container (such as certificates, etc
|
|||
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
|
||||
docker compose run --rm inventree-server invoke dev.setup-test -i
|
||||
```
|
||||
|
||||
This will install the InvenTree demo dataset into your instance.
|
||||
|
|
@ -210,7 +214,7 @@ This will install the InvenTree demo dataset into your instance.
|
|||
To start afresh (and completely remove the existing database), run the following command:
|
||||
|
||||
```
|
||||
docker compose run --rm inventree-server invoke delete-data
|
||||
docker compose run --rm inventree-server invoke dev.delete-data
|
||||
```
|
||||
|
||||
## Install custom packages
|
||||
|
|
|
|||
|
|
@ -66,14 +66,14 @@ In addition to the location where the InvenTree source code is located, you will
|
|||
InvenTree requires a directory for storage of [static files](./config.md#static-file-storage).
|
||||
|
||||
!!! info "Read More"
|
||||
Refer to the [Serving Files](./serving_files.md) section for more details
|
||||
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details
|
||||
|
||||
#### Media Files
|
||||
|
||||
InvenTree requires a directory for storage of [user uploaded files](./config.md#uploaded-file-storage)
|
||||
|
||||
!!! info "Read More"
|
||||
Refer to the [Serving Files](./serving_files.md) section for more details
|
||||
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details
|
||||
|
||||
#### Backup Directory
|
||||
|
||||
|
|
|
|||
|
|
@ -132,11 +132,14 @@ To update InvenTree run `apt install --only-upgrade inventree` - this might need
|
|||
## Controlling InvenTree
|
||||
|
||||
### Services
|
||||
|
||||
InvenTree installs multiple services that can be controlled with your local system runner (`service` or `systemctl`).
|
||||
The service `inventree` controls everything, `inventree-web` the (internal) webserver and `inventree-worker` the background worker(s).
|
||||
The service `inventree` controls everything, `inventree-web` (the [InvenTree web server](./processes.md#web-server)) and `inventree-worker` the [background worker(s)](./processes.md#background-worker).
|
||||
|
||||
More instances of the worker can be instantiated from the command line. This is only meant for advanced users.
|
||||
|
||||
This sample script launches 3 services. By default, 1 is launched.
|
||||
|
||||
```bash
|
||||
inventree scale worker=3
|
||||
```
|
||||
|
|
|
|||
|
|
@ -29,30 +29,7 @@ Independent of the preferred installation method, InvenTree provides a number of
|
|||
|
||||
## System Components
|
||||
|
||||
The InvenTree server ecosystem consists of the following components:
|
||||
|
||||
### Database
|
||||
|
||||
A persistent database is required for data storage. By default, InvenTree is configured to use [PostgreSQL](https://www.postgresql.org/) - and this is the recommended database backend to use. However, InvenTree can also be configured to connect to any database backend [supported by Django]({% include "django.html" %}/ref/databases/)
|
||||
|
||||
|
||||
### Web Server
|
||||
|
||||
The bulk of the InvenTree code base supports the custom web server application. The web server application services user requests and facilitates database access. The webserver provides access to the [API](../api/api.md) for performing database query actions.
|
||||
|
||||
InvenTree uses [Gunicorn](https://gunicorn.org/) as the web server - a Python WSGI HTTP server.
|
||||
|
||||
### Background Tasks
|
||||
|
||||
A separate application handles management of [background tasks](../settings/tasks.md), separate to user-facing web requests. The background task manager is required to perform asynchronous tasks, such as sending emails, generating reports, and other long-running tasks.
|
||||
|
||||
InvenTree uses [django-q2](https://django-q2.readthedocs.io/en/master/) as the background task manager.
|
||||
|
||||
### File Storage
|
||||
|
||||
Uploaded *media* files (images, attachments, reports, etc) and *static* files (javascript, html) are stored to a persistent storage volume. A *file server* is required to serve these files to the user.
|
||||
|
||||
InvenTree uses [Caddy](https://caddyserver.com/) as a file server, which is configured to serve both *static* and *media* files. Additionally, Caddy provides SSL termination and reverse proxy services.
|
||||
The InvenTree software stack is composed of multiple components, each of which is required for a fully functional server environment. Your can read more about the [InvenTree processes here](./processes.md).
|
||||
|
||||
## OS Requirements
|
||||
|
||||
|
|
@ -70,25 +47,7 @@ InvenTree requires a minimum Python version of {{ config.extra.min_python_versio
|
|||
|
||||
### Invoke
|
||||
|
||||
InvenTree makes use of the [invoke](https://www.pyinvoke.org/) python toolkit for performing various administrative actions.
|
||||
|
||||
!!! warning "Invoke Version"
|
||||
InvenTree requires invoke version {{ config.extra.min_invoke_version }} or newer. Some platforms may be shipped with older versions of invoke!
|
||||
|
||||
!!! tip "Updating Invoke"
|
||||
To update your invoke version, run `pip install -U invoke`
|
||||
|
||||
To display a list of the available InvenTree administration actions, run the following commands from the top level source directory:
|
||||
|
||||
```
|
||||
invoke --list
|
||||
```
|
||||
|
||||
This provides a list of the available invoke commands - also displayed below:
|
||||
|
||||
```
|
||||
{{ invoke_commands() }}
|
||||
```
|
||||
InvenTree makes use of the [invoke](https://www.pyinvoke.org/) python toolkit for performing various administrative actions. You can read [more about out use of the invoke tool here](./invoke.md)
|
||||
|
||||
### Virtual Environment
|
||||
|
||||
|
|
@ -150,4 +109,4 @@ So, for a production setup, you should set `INVENTREE_DEBUG=false` in the [confi
|
|||
Turning off DEBUG mode creates further work for the system administrator. In particular, when running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that the InvenTree server can run "monolithically" without the need for a separate web server.
|
||||
|
||||
!!! info "Read More"
|
||||
Refer to the [Serving Files](./serving_files.md) section for more details
|
||||
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
title: Invoke Tool
|
||||
---
|
||||
|
||||
## Invoke Tool
|
||||
|
||||
InvenTree uses the [invoke](https://www.pyinvoke.org/) tool to manage various system administration tasks. Invoke is a powerful python-based task execution tool, which allows for the creation of custom tasks and command-line utilities.
|
||||
|
||||
### Installation
|
||||
|
||||
InvenTree setup and administration requires that the invoke tool is installed. This is usually installed automatically as part of the InvenTree installation process - however (if you are configuring InvenTree from source) you may need to install it manually.
|
||||
|
||||
To install the invoke tool, run the following command:
|
||||
|
||||
```
|
||||
pip install -U invoke
|
||||
```
|
||||
|
||||
### Minimum Version
|
||||
|
||||
The minimum required version of the invoke tool is `{{ config.extra.min_invoke_version }}`.
|
||||
|
||||
To determine the version of invoke you have installed, run either:
|
||||
|
||||
```
|
||||
invoke --version
|
||||
```
|
||||
```
|
||||
python -m invoke --version
|
||||
```
|
||||
|
||||
If you are running an older version of invoke, ensure it is updated to the latest version:
|
||||
|
||||
```
|
||||
pip install -U invoke
|
||||
```
|
||||
|
||||
### Running from Command Line
|
||||
|
||||
To run the `invoke` tool from the command line, you must be in the top-level InvenTree source directory. This is the directory that contains the [tasks.py]({{ sourcefile("tasks.py") }}) file.
|
||||
|
||||
### Running in Docker Mode
|
||||
|
||||
If you have installed InvenTree via [docker](./docker_install.md), then you need to ensure that the `invoke` commands are called from within the docker container context.
|
||||
|
||||
For example, to run the `update` task, you might use the following command to run the `invoke` command - using the `docker compose` tool.
|
||||
|
||||
```
|
||||
docker compose run --rm inventree-server invoke update
|
||||
```
|
||||
|
||||
!!! note "Docker Compose Directory"
|
||||
The `docker compose` command should be run from the directory where the `docker-compose.yml` file is located.
|
||||
|
||||
Alternatively, to manually run the command within the environment of the running docker container:
|
||||
|
||||
```
|
||||
docker exec -it inventree-server invoke update
|
||||
```
|
||||
|
||||
!!! note "Container Name"
|
||||
The container name may be different depending on how you have configured the docker environment.
|
||||
|
||||
### Running in Installer Mode
|
||||
|
||||
If you have installed InvenTree using the [package installer](./installer.md), then you need to prefix all `invoke` commands with `inventree run`.
|
||||
|
||||
For example, to run the `update` task, use:
|
||||
|
||||
```
|
||||
inventree run invoke update
|
||||
```
|
||||
|
||||
## Available Tasks
|
||||
|
||||
To display a list of the available InvenTree administration actions, run the following commands from the top level source directory:
|
||||
|
||||
```
|
||||
invoke --list
|
||||
```
|
||||
|
||||
This provides a list of the available invoke commands - also displayed below:
|
||||
|
||||
```
|
||||
{{ invoke_commands() }}
|
||||
```
|
||||
|
||||
### Task Information
|
||||
|
||||
Each task has a brief description of its purpose, which is displayed when running the `invoke --list` command. To find more detailed information about a specific task, run the command with the `--help` flag.
|
||||
|
||||
For example, to find more information about the `update` task, run:
|
||||
|
||||
```
|
||||
invoke update --help
|
||||
```
|
||||
|
||||
### Internal Tasks
|
||||
|
||||
Tasks with the `int.` prefix are internal tasks, and are not intended for general use. These are called by other tasks, and should generally not be called directly.
|
||||
|
||||
### Developer Tasks
|
||||
|
||||
Tasks with the `dev.` prefix are tasks intended for InvenTree developers, and are also not intended for general use.
|
||||
|
||||
## Common Issues
|
||||
|
||||
Below are some common issues that users may encounter when using the `invoke` tool, and how to resolve them.
|
||||
|
||||
### Command 'invoke' not found
|
||||
|
||||
If the `invoke` command does not work, it means that the invoke tool has not been [installed correctly](#installation).
|
||||
|
||||
### Invoke Version
|
||||
|
||||
If the installed version of invoke is too old, users may see error messages during the installation procedure, such as:
|
||||
|
||||
- *'update' did not receive all required positional arguments!*
|
||||
- *Function has keyword-only arguments or annotations*
|
||||
|
||||
Ensure that the installed version of invoke is [up to date](#minimum-version).
|
||||
|
||||
### Can't find any collection named 'tasks'
|
||||
|
||||
It means that the `invoke` tool is not able to locate the InvenTree task collection.
|
||||
|
||||
- If running in docker, ensure that you are running the `invoke` command from within the [docker container](#running-in-docker-mode)
|
||||
- If running in installer mode, ensure that you are running the `invoke` command with the [correct prefix](#running-in-installer-mode)
|
||||
- If running via command line, ensure that you are running the `invoke` command from the [correct directory](#running-from-command-line)
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
title: InvenTree Processes
|
||||
---
|
||||
|
||||
## InvenTree Processes
|
||||
|
||||
InvenTree is a complex application, and there are a number of processes which must be running in order for the system to operate correctly. Typically, these processes are started automatically when the InvenTree application stack is launched. However, in some cases, it may be necessary to start these processes manually.
|
||||
|
||||
System administrators should be aware of the following processes when configuring an InvenTree installation:
|
||||
|
||||
### Database
|
||||
|
||||
At the core of the InvenTree application is the SQL database. The database is responsible for storing all of the persistent data which is used by the InvenTree application.
|
||||
|
||||
InvenTree supports a [number of database backends]({% include "django.html" %}/ref/databases) - which typically require their own process to be running.
|
||||
|
||||
Refer to the [database configuration guide](./config.md#database-options) for more information on selecting and configuring the database backend.
|
||||
|
||||
In running InvenTree via [docker compose](./docker_install.md), the database process is managed by the `inventree-db` service which provides a [Postgres docker container](https://hub.docker.com/_/postgres).
|
||||
|
||||
### Web Server
|
||||
|
||||
The InvenTree web server is responsible for serving the InvenTree web interface to users. The web server is a [Django](https://www.djangoproject.com/) application, which is run using the [Gunicorn](https://gunicorn.org/) WSGI server.
|
||||
|
||||
The web server interfaces with the backend database and provides a [REST API](../api/api.md) (via the [Django REST framework](https://www.django-rest-framework.org/)) which is used by the frontend web interface.
|
||||
|
||||
In running InvenTree via [docker compose](./docker_install.md), the web server process is managed by the `inventree-server` service, which runs from a custom docker image.
|
||||
|
||||
### Proxy Server
|
||||
|
||||
In a production installation, the InvenTree web server application *does not* provide hosting of static files, or user-uploaded (media) files. Instead, these files should be served by a separate web server, such as [Caddy](https://caddyserver.com/), [Nginx](https://www.nginx.com/), or [Apache](https://httpd.apache.org/).
|
||||
|
||||
!!! info "Debug Mode"
|
||||
When running in [production mode](./bare_prod.md) (i.e. the `INVENTREE_DEBUG` flag is disabled), a separate web server is required for serving *static* and *media* files. In `DEBUG` mode, the django webserver facilitates delivery of *static* and *media* files, but this is explicitly not suitable for a production environment.
|
||||
|
||||
!!! tip "Read More"
|
||||
You can find further information in the [django documentation]({% include "django.html" %}/howto/static-files/deployment/).
|
||||
|
||||
A proxy server is required to store and serve static files (such as images, documents, etc) which are used by the InvenTree application. As django is not designed to serve static files in a production environment, a separate file server is required. For our docker compose setup, we use the `inventree-proxy` service, which runs a [Caddy](https://caddyserver.com/) proxy server to serve static files.
|
||||
|
||||
In addition to serving static files, the proxy server also provides a reverse proxy to the InvenTree web server, allowing the InvenTree web interface to be accessed via a standard HTTP/HTTPS port.
|
||||
|
||||
Further, it provides an authentication endpoint for accessing files in the `/static/` and `/media/` directories.
|
||||
|
||||
Finally, it provides a [Let's Encrypt](https://letsencrypt.org/) endpoint for automatic SSL certificate generation and renewal.
|
||||
|
||||
#### Static Files
|
||||
|
||||
Static files can be served without any need for authentication. In fact, they must be accessible *without* authentication, otherwise the unauthenticated views (such as the login screen) will not function correctly.
|
||||
|
||||
#### Media Files
|
||||
|
||||
It is highly recommended that the *media* files are served behind an authentication layer. This is because the media files are user-uploaded, and may contain sensitive information. Most modern web servers provide a way to serve files behind an authentication layer.
|
||||
|
||||
#### Example Configuration
|
||||
|
||||
The [docker production example](./docker.md) provides an example using [Caddy](https://caddyserver.com) to serve *static* and *media* files, and redirecting other requests to the InvenTree web server itself.
|
||||
|
||||
Caddy is a modern web server which is easy to configure and provides a number of useful features, including automatic SSL certificate generation.
|
||||
|
||||
#### Alternatives to Caddy
|
||||
|
||||
An alternative is to run nginx as the reverse proxy. A sample configuration file is provided in the `./contrib/container/` source directory.
|
||||
|
||||
#### Integrating with Existing Proxy
|
||||
|
||||
You may wish to integrate the InvenTree web server with an existing reverse proxy server. This is possible, but requires careful configuration to ensure that the static and media files are served correctly.
|
||||
|
||||
*Note: A custom configuration of the proxy server is outside the scope of this documentation!*
|
||||
|
||||
### Background Worker
|
||||
|
||||
The InvenTree background worker is responsible for handling [asynchronous tasks](../settings/tasks.md) which are not suitable for the main web server process. This includes tasks such as sending emails, generating reports, and other long-running tasks.
|
||||
|
||||
InvenTree uses the [django-q2](https://django-q2.readthedocs.io/en/master/) package to manage background tasks.
|
||||
|
||||
The background worker process is managed by the `inventree-worker` service in the [docker compose](./docker_install.md) setup. Note that this services runs a duplicate copy of the `inventree-server` container, but with a different entrypoint command which starts the background worker process.
|
||||
|
||||
#### Important Information
|
||||
|
||||
If the background worker process is not running, InvenTree will not be able to perform background tasks. This can lead to issues such as emails not being sent, or reports not being generated. Additionally, certain data may not be updated correctly if the background worker is not running.
|
||||
|
||||
!!! warning "Background Worker"
|
||||
The background worker process is a critical part of the InvenTree application stack. It is important that this process is running correctly in order for the InvenTree application to operate correctly.
|
||||
|
||||
#### Limitations
|
||||
|
||||
If the [cache server](#cache-server) is not running, the background worker will be limited to running a single threaded worker. This is because the background worker uses the cache server to manage task locking, and without a global cache server to communicate between processes, concurrency issues can occur.
|
||||
|
||||
### Cache Server
|
||||
|
||||
The InvenTree cache server is used to store temporary data which is shared between the InvenTree web server and the background worker processes. The cache server is also used to store task information, and to manage task locking between the background worker processes.
|
||||
|
||||
Using a cache server can significantly improve the performance of the InvenTree application, as it reduces the need to query the database for frequently accessed data.
|
||||
|
||||
InvenTree uses the [Redis](https://redis.io/) cache server to manage cache data. When running in docker, we use the official [redis image](https://hub.docker.com/_/redis).
|
||||
|
||||
!!! info "Redis on Docker"
|
||||
Docker adds an additional network layer - that might lead to lower performance than bare metal.
|
||||
To optimize and configure your redis deployment follow the [official docker guide](https://redis.io/docs/getting-started/install-stack/docker/#configuration).
|
||||
|
||||
!!! tip "Enable Cache"
|
||||
While a redis container is provided in the default configuration, by default it is not enabled in the Inventree server. You can enable redis cache support by following the [caching configuration guide](./config.md#caching)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
---
|
||||
title: Serving Static and Media Files
|
||||
---
|
||||
|
||||
## Serving Files
|
||||
|
||||
In a production installation, the InvenTree web server application *does not* provide hosting of static files, or user-uploaded (media) files. Instead, these files should be served by a separate web server, such as [Caddy](https://caddyserver.com/), [Nginx](https://www.nginx.com/), or [Apache](https://httpd.apache.org/).
|
||||
|
||||
!!! info "Debug Mode"
|
||||
When running in [production mode](./bare_prod.md) (i.e. the `INVENTREE_DEBUG` flag is disabled), a separate web server is required for serving *static* and *media* files. In `DEBUG` mode, the django webserver facilitates delivery of *static* and *media* files, but this is explicitly not suitable for a production environment.
|
||||
|
||||
!!! tip "Read More"
|
||||
You can find further information in the [django documentation]({% include "django.html" %}/howto/static-files/deployment/).
|
||||
|
||||
There are *many* different ways that a sysadmin might wish to handle this - and it depends on your particular installation requirements.
|
||||
|
||||
### Static Files
|
||||
|
||||
Static files can be served without any need for authentication. In fact, they must be accessible *without* authentication, otherwise the unauthenticated views (such as the login screen) will not function correctly.
|
||||
|
||||
### Media Files
|
||||
|
||||
It is highly recommended that the *media* files are served behind an authentication layer. This is because the media files are user-uploaded, and may contain sensitive information. Most modern web servers provide a way to serve files behind an authentication layer.
|
||||
|
||||
### Example Configuration
|
||||
|
||||
The [docker production example](./docker.md) provides an example using [Caddy](https://caddyserver.com) to serve *static* and *media* files, and redirecting other requests to the InvenTree web server itself.
|
||||
|
||||
Caddy is a modern web server which is easy to configure and provides a number of useful features, including automatic SSL certificate generation.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
23
docs/main.py
23
docs/main.py
|
|
@ -16,7 +16,7 @@ global USER_SETTINGS
|
|||
here = os.path.dirname(__file__)
|
||||
settings_file = os.path.join(here, 'inventree_settings.json')
|
||||
|
||||
with open(settings_file, 'r') as sf:
|
||||
with open(settings_file, encoding='utf-8') as sf:
|
||||
settings = json.load(sf)
|
||||
|
||||
GLOBAL_SETTINGS = settings['global']
|
||||
|
|
@ -27,7 +27,7 @@ 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']
|
||||
|
||||
|
|
@ -47,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:
|
||||
|
|
@ -59,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
|
||||
|
|
@ -177,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
|
||||
|
|
@ -200,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)
|
||||
|
|
@ -214,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'
|
||||
|
|
@ -233,15 +234,15 @@ 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', None)
|
||||
units = setting.get('units', None)
|
||||
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 ""} |'
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ nav:
|
|||
- Security: security.md
|
||||
- Install:
|
||||
- Introduction: start/intro.md
|
||||
- Processes: start/processes.md
|
||||
- Configuration: start/config.md
|
||||
- Docker:
|
||||
- Introduction: start/docker.md
|
||||
|
|
@ -97,8 +98,9 @@ nav:
|
|||
- Installer: start/installer.md
|
||||
- Production: start/bare_prod.md
|
||||
- Development: start/bare_dev.md
|
||||
- Serving Files: start/serving_files.md
|
||||
- User Accounts: start/accounts.md
|
||||
- Data Backup: start/backup.md
|
||||
- Invoke: start/invoke.md
|
||||
- Migrating Data: start/migrate.md
|
||||
- Advanced Topics: start/advanced.md
|
||||
- Parts:
|
||||
|
|
@ -213,6 +215,7 @@ nav:
|
|||
- Schedule Mixin: extend/plugins/schedule.md
|
||||
- Settings Mixin: extend/plugins/settings.md
|
||||
- URL Mixin: extend/plugins/urls.md
|
||||
- User Interface Mixin: extend/plugins/ui.md
|
||||
- Validation Mixin: extend/plugins/validation.md
|
||||
- Machines:
|
||||
- Overview: extend/machines/overview.md
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
{
|
||||
"pattern": "http://localhost"
|
||||
},
|
||||
{
|
||||
"pattern": "https://localhost:5173/"
|
||||
},
|
||||
{
|
||||
"pattern": "http://127.0.0.1"
|
||||
},
|
||||
|
|
@ -14,6 +17,9 @@
|
|||
},
|
||||
{
|
||||
"pattern": "https://www.reddit.com/r/InvenTree/"
|
||||
},
|
||||
{
|
||||
"pattern": "https://opensource.org/license/MIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mkdocs==1.6.0
|
||||
mkdocs==1.6.1
|
||||
mkdocs-macros-plugin>=0.7,<2.0
|
||||
mkdocs-material>=9.0,<10.0
|
||||
mkdocs-git-revision-date-localized-plugin>=1.1,<2.0
|
||||
|
|
|
|||
|
|
@ -158,6 +158,12 @@ h11==0.14.0 \
|
|||
--hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \
|
||||
--hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761
|
||||
# via httpcore
|
||||
hjson==3.1.0 \
|
||||
--hash=sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75 \
|
||||
--hash=sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89
|
||||
# via
|
||||
# mkdocs-macros-plugin
|
||||
# super-collections
|
||||
httpcore==1.0.5 \
|
||||
--hash=sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61 \
|
||||
--hash=sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5
|
||||
|
|
@ -173,9 +179,9 @@ idna==3.7 \
|
|||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
importlib-metadata==8.2.0 \
|
||||
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
|
||||
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
|
||||
importlib-metadata==8.5.0 \
|
||||
--hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \
|
||||
--hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7
|
||||
# via
|
||||
# markdown
|
||||
# mkdocs
|
||||
|
|
@ -280,9 +286,9 @@ mergedeep==1.3.4 \
|
|||
# via
|
||||
# mkdocs
|
||||
# mkdocs-get-deps
|
||||
mkdocs==1.6.0 \
|
||||
--hash=sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7 \
|
||||
--hash=sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512
|
||||
mkdocs==1.6.1 \
|
||||
--hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \
|
||||
--hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocs-autorefs
|
||||
|
|
@ -293,29 +299,29 @@ mkdocs==1.6.0 \
|
|||
# mkdocs-simple-hooks
|
||||
# mkdocstrings
|
||||
# neoteroi-mkdocs
|
||||
mkdocs-autorefs==1.0.1 \
|
||||
--hash=sha256:aacdfae1ab197780fb7a2dac92ad8a3d8f7ca8049a9cbe56a4218cd52e8da570 \
|
||||
--hash=sha256:f684edf847eced40b570b57846b15f0bf57fb93ac2c510450775dcf16accb971
|
||||
mkdocs-autorefs==1.2.0 \
|
||||
--hash=sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f \
|
||||
--hash=sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f
|
||||
# via mkdocstrings
|
||||
mkdocs-get-deps==0.2.0 \
|
||||
--hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \
|
||||
--hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134
|
||||
# via mkdocs
|
||||
mkdocs-git-revision-date-localized-plugin==1.2.7 \
|
||||
--hash=sha256:2f83b52b4dad642751a79465f80394672cbad022129286f40d36b03aebee490f \
|
||||
--hash=sha256:d2b30ccb74ec8e118298758d75ae4b4f02c620daf776a6c92fcbb58f2b78f19f
|
||||
mkdocs-git-revision-date-localized-plugin==1.3.0 \
|
||||
--hash=sha256:439e2f14582204050a664c258861c325064d97cdc848c541e48bb034a6c4d0cb \
|
||||
--hash=sha256:c99377ee119372d57a9e47cff4e68f04cce634a74831c06bc89b33e456e840a1
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-include-markdown-plugin==6.2.2 \
|
||||
--hash=sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7 \
|
||||
--hash=sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d
|
||||
mkdocs-include-markdown-plugin==7.0.0 \
|
||||
--hash=sha256:a8eac8f2e6aa391d82d1d5e473b819b52393d91464060c02db5741834fe9008b \
|
||||
--hash=sha256:bf8d19245ae3fb2eea395888e80c60bc91806a0d879279707d707896c24319c3
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-macros-plugin==1.0.5 \
|
||||
--hash=sha256:f60e26f711f5a830ddf1e7980865bf5c0f1180db56109803cdd280073c1a050a \
|
||||
--hash=sha256:fe348d75f01c911f362b6d998c57b3d85b505876dde69db924f2c512c395c328
|
||||
mkdocs-macros-plugin==1.3.7 \
|
||||
--hash=sha256:02432033a5b77fb247d6ec7924e72fc4ceec264165b1644ab8d0dc159c22ce59 \
|
||||
--hash=sha256:17c7fd1a49b94defcdb502fd453d17a1e730f8836523379d21292eb2be4cb523
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material==9.5.32 \
|
||||
--hash=sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120 \
|
||||
--hash=sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f
|
||||
mkdocs-material==9.5.43 \
|
||||
--hash=sha256:4aae0664c456fd12837a3192e0225c17960ba8bf55d7f0a7daef7e4b0b914a34 \
|
||||
--hash=sha256:83be7ff30b65a1e4930dfa4ab911e75780a3afc9583d162692e434581cb46979
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material-extensions==1.3.1 \
|
||||
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
|
||||
|
|
@ -325,9 +331,9 @@ mkdocs-simple-hooks==0.1.5 \
|
|||
--hash=sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c \
|
||||
--hash=sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a
|
||||
# via -r docs/requirements.in
|
||||
mkdocstrings[python]==0.25.2 \
|
||||
--hash=sha256:5cf57ad7f61e8be3111a2458b4e49c2029c9cb35525393b179f9c916ca8042dc \
|
||||
--hash=sha256:9e2cda5e2e12db8bb98d21e3410f3f27f8faab685a24b03b06ba7daa5b92abfc
|
||||
mkdocstrings[python]==0.26.2 \
|
||||
--hash=sha256:1248f3228464f3b8d1a15bd91249ce1701fe3104ac517a5f167a0e01ca850ba5 \
|
||||
--hash=sha256:34a8b50f1e6cfd29546c6c09fbe02154adfb0b361bb758834bf56aa284ba876e
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocstrings-python
|
||||
|
|
@ -342,14 +348,18 @@ neoteroi-mkdocs==1.1.0 \
|
|||
packaging==24.0 \
|
||||
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
|
||||
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
|
||||
# via mkdocs
|
||||
# via
|
||||
# mkdocs
|
||||
# mkdocs-macros-plugin
|
||||
paginate==0.5.6 \
|
||||
--hash=sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d
|
||||
# via mkdocs-material
|
||||
pathspec==0.12.1 \
|
||||
--hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \
|
||||
--hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712
|
||||
# via mkdocs
|
||||
# via
|
||||
# mkdocs
|
||||
# mkdocs-macros-plugin
|
||||
platformdirs==4.2.2 \
|
||||
--hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
|
||||
--hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
|
||||
|
|
@ -443,86 +453,101 @@ pyyaml-env-tag==0.1 \
|
|||
--hash=sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb \
|
||||
--hash=sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069
|
||||
# via mkdocs
|
||||
regex==2024.7.24 \
|
||||
--hash=sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c \
|
||||
--hash=sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535 \
|
||||
--hash=sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24 \
|
||||
--hash=sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce \
|
||||
--hash=sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc \
|
||||
--hash=sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5 \
|
||||
--hash=sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce \
|
||||
--hash=sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53 \
|
||||
--hash=sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d \
|
||||
--hash=sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c \
|
||||
--hash=sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908 \
|
||||
--hash=sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8 \
|
||||
--hash=sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024 \
|
||||
--hash=sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281 \
|
||||
--hash=sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a \
|
||||
--hash=sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169 \
|
||||
--hash=sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364 \
|
||||
--hash=sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa \
|
||||
--hash=sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be \
|
||||
--hash=sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53 \
|
||||
--hash=sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759 \
|
||||
--hash=sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e \
|
||||
--hash=sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b \
|
||||
--hash=sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52 \
|
||||
--hash=sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610 \
|
||||
--hash=sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05 \
|
||||
--hash=sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2 \
|
||||
--hash=sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca \
|
||||
--hash=sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0 \
|
||||
--hash=sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293 \
|
||||
--hash=sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289 \
|
||||
--hash=sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e \
|
||||
--hash=sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f \
|
||||
--hash=sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c \
|
||||
--hash=sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94 \
|
||||
--hash=sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad \
|
||||
--hash=sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46 \
|
||||
--hash=sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9 \
|
||||
--hash=sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9 \
|
||||
--hash=sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee \
|
||||
--hash=sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9 \
|
||||
--hash=sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1 \
|
||||
--hash=sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9 \
|
||||
--hash=sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799 \
|
||||
--hash=sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1 \
|
||||
--hash=sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b \
|
||||
--hash=sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf \
|
||||
--hash=sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5 \
|
||||
--hash=sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2 \
|
||||
--hash=sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e \
|
||||
--hash=sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51 \
|
||||
--hash=sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506 \
|
||||
--hash=sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73 \
|
||||
--hash=sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7 \
|
||||
--hash=sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5 \
|
||||
--hash=sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57 \
|
||||
--hash=sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4 \
|
||||
--hash=sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd \
|
||||
--hash=sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b \
|
||||
--hash=sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41 \
|
||||
--hash=sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe \
|
||||
--hash=sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59 \
|
||||
--hash=sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8 \
|
||||
--hash=sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f \
|
||||
--hash=sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e \
|
||||
--hash=sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750 \
|
||||
--hash=sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1 \
|
||||
--hash=sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96 \
|
||||
--hash=sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc \
|
||||
--hash=sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440 \
|
||||
--hash=sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe \
|
||||
--hash=sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38 \
|
||||
--hash=sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950 \
|
||||
--hash=sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2 \
|
||||
--hash=sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd \
|
||||
--hash=sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce \
|
||||
--hash=sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66 \
|
||||
--hash=sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3 \
|
||||
--hash=sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86
|
||||
regex==2024.9.11 \
|
||||
--hash=sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623 \
|
||||
--hash=sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199 \
|
||||
--hash=sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664 \
|
||||
--hash=sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f \
|
||||
--hash=sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca \
|
||||
--hash=sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066 \
|
||||
--hash=sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca \
|
||||
--hash=sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39 \
|
||||
--hash=sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d \
|
||||
--hash=sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6 \
|
||||
--hash=sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35 \
|
||||
--hash=sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408 \
|
||||
--hash=sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5 \
|
||||
--hash=sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a \
|
||||
--hash=sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9 \
|
||||
--hash=sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92 \
|
||||
--hash=sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766 \
|
||||
--hash=sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168 \
|
||||
--hash=sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca \
|
||||
--hash=sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508 \
|
||||
--hash=sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df \
|
||||
--hash=sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf \
|
||||
--hash=sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b \
|
||||
--hash=sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4 \
|
||||
--hash=sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268 \
|
||||
--hash=sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6 \
|
||||
--hash=sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c \
|
||||
--hash=sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62 \
|
||||
--hash=sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231 \
|
||||
--hash=sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36 \
|
||||
--hash=sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba \
|
||||
--hash=sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4 \
|
||||
--hash=sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e \
|
||||
--hash=sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822 \
|
||||
--hash=sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4 \
|
||||
--hash=sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d \
|
||||
--hash=sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71 \
|
||||
--hash=sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50 \
|
||||
--hash=sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d \
|
||||
--hash=sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad \
|
||||
--hash=sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8 \
|
||||
--hash=sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8 \
|
||||
--hash=sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8 \
|
||||
--hash=sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd \
|
||||
--hash=sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16 \
|
||||
--hash=sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664 \
|
||||
--hash=sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a \
|
||||
--hash=sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f \
|
||||
--hash=sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd \
|
||||
--hash=sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a \
|
||||
--hash=sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9 \
|
||||
--hash=sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199 \
|
||||
--hash=sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d \
|
||||
--hash=sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963 \
|
||||
--hash=sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009 \
|
||||
--hash=sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a \
|
||||
--hash=sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679 \
|
||||
--hash=sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96 \
|
||||
--hash=sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42 \
|
||||
--hash=sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8 \
|
||||
--hash=sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e \
|
||||
--hash=sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7 \
|
||||
--hash=sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8 \
|
||||
--hash=sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802 \
|
||||
--hash=sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366 \
|
||||
--hash=sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137 \
|
||||
--hash=sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784 \
|
||||
--hash=sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29 \
|
||||
--hash=sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3 \
|
||||
--hash=sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771 \
|
||||
--hash=sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60 \
|
||||
--hash=sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a \
|
||||
--hash=sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4 \
|
||||
--hash=sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0 \
|
||||
--hash=sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84 \
|
||||
--hash=sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd \
|
||||
--hash=sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1 \
|
||||
--hash=sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776 \
|
||||
--hash=sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142 \
|
||||
--hash=sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89 \
|
||||
--hash=sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c \
|
||||
--hash=sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8 \
|
||||
--hash=sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35 \
|
||||
--hash=sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a \
|
||||
--hash=sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86 \
|
||||
--hash=sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9 \
|
||||
--hash=sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64 \
|
||||
--hash=sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554 \
|
||||
--hash=sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85 \
|
||||
--hash=sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb \
|
||||
--hash=sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0 \
|
||||
--hash=sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8 \
|
||||
--hash=sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb \
|
||||
--hash=sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919
|
||||
# via mkdocs-material
|
||||
requests==2.32.3 \
|
||||
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
||||
|
|
@ -546,6 +571,10 @@ sniffio==1.3.1 \
|
|||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
super-collections==0.5.3 \
|
||||
--hash=sha256:907d35b25dc4070910e8254bf2f5c928348af1cf8a1f1e8259e06c666e902cff \
|
||||
--hash=sha256:94c1ec96c0a0d5e8e7d389ed8cde6882ac246940507c5e6b86e91945c2968d46
|
||||
# via mkdocs-macros-plugin
|
||||
termcolor==2.4.0 \
|
||||
--hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \
|
||||
--hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a
|
||||
|
|
@ -595,7 +624,7 @@ wcmatch==8.5.2 \
|
|||
--hash=sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478 \
|
||||
--hash=sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2
|
||||
# via mkdocs-include-markdown-plugin
|
||||
zipp==3.20.0 \
|
||||
--hash=sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31 \
|
||||
--hash=sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d
|
||||
zipp==3.20.2 \
|
||||
--hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \
|
||||
--hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29
|
||||
# via importlib-metadata
|
||||
|
|
|
|||
|
|
@ -20,13 +20,30 @@ src = ["src/backend/InvenTree"]
|
|||
"__init__.py" = ["D104"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["A", "B", "C4", "D", "I", "N", "F"]
|
||||
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",
|
||||
|
|
@ -36,13 +53,15 @@ ignore = [
|
|||
"N812",
|
||||
# - D417 Missing argument descriptions in the docstring
|
||||
"D417",
|
||||
# - RUF032 - decimal-from-float-literal
|
||||
"RUF032",
|
||||
|
||||
# TODO These should be followed up and fixed
|
||||
# - B904 Within an `except` clause, raise exceptions
|
||||
"B904",
|
||||
|
||||
# Remove fast
|
||||
"A001", "A002","A003","B018"
|
||||
"A002", "B018"
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
|
|
|
|||
|
|
@ -17,6 +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
|
||||
- invoke int.export-settings-definitions --filename docs/inventree_settings.json --overwrite
|
||||
- invoke dev.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)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ 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
|
||||
|
|
@ -77,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
|
||||
|
|
@ -221,6 +221,7 @@ class InfoView(AjaxView):
|
|||
'instance': InvenTree.version.inventreeInstanceName(),
|
||||
'apiVersion': InvenTree.version.inventreeApiVersion(),
|
||||
'worker_running': is_worker_running(),
|
||||
'worker_count': settings.BACKGROUND_WORKER_COUNT,
|
||||
'worker_pending_tasks': self.worker_pending_tasks(),
|
||||
'plugins_enabled': settings.PLUGINS_ENABLED,
|
||||
'plugins_install_disabled': settings.PLUGINS_INSTALL_DISABLED,
|
||||
|
|
@ -321,7 +322,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.
|
||||
|
|
@ -380,11 +380,26 @@ class BulkDeleteMixin:
|
|||
|
||||
# Filter by provided item ID values
|
||||
if items:
|
||||
queryset = queryset.filter(id__in=items)
|
||||
try:
|
||||
queryset = queryset.filter(id__in=items)
|
||||
except Exception:
|
||||
raise ValidationError({
|
||||
'non_field_errors': _('Invalid items list provided')
|
||||
})
|
||||
|
||||
# Filter by provided filters
|
||||
if filters:
|
||||
queryset = queryset.filter(**filters)
|
||||
try:
|
||||
queryset = queryset.filter(**filters)
|
||||
except Exception:
|
||||
raise ValidationError({
|
||||
'non_field_errors': _('Invalid filters provided')
|
||||
})
|
||||
|
||||
if queryset.count() == 0:
|
||||
raise ValidationError({
|
||||
'non_field_errors': _('No items found to delete')
|
||||
})
|
||||
|
||||
# Run a final validation step (should raise an error if the deletion should not proceed)
|
||||
self.validate_delete(queryset, request)
|
||||
|
|
@ -398,8 +413,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,12 +1,106 @@
|
|||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 248
|
||||
INVENTREE_API_VERSION = 277
|
||||
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v277 - 2024-11-01 : https://github.com/inventree/InvenTree/pull/8278
|
||||
- Allow build order list to be filtered by "outstanding" (alias for "active")
|
||||
|
||||
v276 - 2024-10-31 : https://github.com/inventree/InvenTree/pull/8403
|
||||
- Adds 'destination' field to the PurchaseOrder model and API endpoints
|
||||
|
||||
v275 - 2024-10-31 : https://github.com/inventree/InvenTree/pull/8396
|
||||
- Adds SKU and MPN fields to the StockItem serializer
|
||||
- Additional export options for the StockItem serializer
|
||||
|
||||
v274 - 2024-10-29 : https://github.com/inventree/InvenTree/pull/8392
|
||||
- Add more detailed information to NotificationEntry API serializer
|
||||
|
||||
v273 - 2024-10-28 : https://github.com/inventree/InvenTree/pull/8376
|
||||
- Fixes for the BuildLine API endpoint
|
||||
|
||||
v272 - 2024-10-25 : https://github.com/inventree/InvenTree/pull/8343
|
||||
- Adjustments to BuildLine API serializers
|
||||
|
||||
v271 - 2024-10-22 : https://github.com/inventree/InvenTree/pull/8331
|
||||
- Fixes for SalesOrderLineItem endpoints
|
||||
|
||||
v270 - 2024-10-19 : https://github.com/inventree/InvenTree/pull/8307
|
||||
- Adds missing date fields from order API endpoint(s)
|
||||
|
||||
v269 - 2024-10-16 : https://github.com/inventree/InvenTree/pull/8295
|
||||
- Adds "include_variants" filter to the BuildOrder API endpoint
|
||||
- Adds "include_variants" filter to the SalesOrder API endpoint
|
||||
- Adds "include_variants" filter to the PurchaseOrderLineItem API endpoint
|
||||
- Adds "include_variants" filter to the ReturnOrder API endpoint
|
||||
|
||||
268 - 2024-10-11 : https://github.com/inventree/InvenTree/pull/8274
|
||||
- Adds "in_stock" attribute to the StockItem serializer
|
||||
|
||||
267 - 2024-10-8 : https://github.com/inventree/InvenTree/pull/8250
|
||||
- Remove "allocations" field from the SalesOrderShipment API endpoint(s)
|
||||
- Add "allocated_items" field to the SalesOrderShipment API endpoint(s)
|
||||
|
||||
266 - 2024-10-07 : https://github.com/inventree/InvenTree/pull/8249
|
||||
- Tweak SalesOrderShipment API for more efficient data retrieval
|
||||
|
||||
265 - 2024-10-07 : https://github.com/inventree/InvenTree/pull/8228
|
||||
- Adds API endpoint for providing custom admin integration details for plugins
|
||||
|
||||
264 - 2024-10-03 : https://github.com/inventree/InvenTree/pull/8231
|
||||
- Adds Sales Order Shipment attachment model type
|
||||
|
||||
263 - 2024-09-30 : https://github.com/inventree/InvenTree/pull/8194
|
||||
- Adds Sales Order Shipment report
|
||||
|
||||
262 - 2024-09-30 : https://github.com/inventree/InvenTree/pull/8220
|
||||
- Tweak permission requirements for uninstalling plugins via API
|
||||
|
||||
261 - 2024-09-26 : https://github.com/inventree/InvenTree/pull/8184
|
||||
- Fixes for BuildOrder API serializers
|
||||
|
||||
v260 - 2024-09-26 : https://github.com/inventree/InvenTree/pull/8190
|
||||
- Adds facility for server-side context data to be passed to client-side plugins
|
||||
|
||||
v259 - 2024-09-20 : https://github.com/inventree/InvenTree/pull/8137
|
||||
- Implements new API endpoint for enabling custom UI features via plugins
|
||||
|
||||
v258 - 2024-09-24 : https://github.com/inventree/InvenTree/pull/8163
|
||||
- Enhances the existing PartScheduling API endpoint
|
||||
- Adds a formal DRF serializer to the endpoint
|
||||
|
||||
v257 - 2024-09-22 : https://github.com/inventree/InvenTree/pull/8150
|
||||
- Adds API endpoint for reporting barcode scan history
|
||||
|
||||
v256 - 2024-09-19 : https://github.com/inventree/InvenTree/pull/7704
|
||||
- Adjustments for "stocktake" (stock history) API endpoints
|
||||
|
||||
v255 - 2024-09-19 : https://github.com/inventree/InvenTree/pull/8145
|
||||
- Enables copying line items when duplicating an order
|
||||
|
||||
v254 - 2024-09-14 : https://github.com/inventree/InvenTree/pull/7470
|
||||
- Implements new API endpoints for enabling custom UI functionality via plugins
|
||||
|
||||
v253 - 2024-09-14 : https://github.com/inventree/InvenTree/pull/7944
|
||||
- Adjustments for user API endpoints
|
||||
|
||||
v252 - 2024-09-13 : https://github.com/inventree/InvenTree/pull/8040
|
||||
- Add endpoint for listing all known units
|
||||
|
||||
v251 - 2024-09-06 : https://github.com/inventree/InvenTree/pull/8018
|
||||
- Adds "attach_to_model" field to the ReporTemplate model
|
||||
|
||||
v250 - 2024-09-04 : https://github.com/inventree/InvenTree/pull/8069
|
||||
- Fixes 'revision' field definition in Part serializer
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -40,9 +40,14 @@ class InvenTreeConfig(AppConfig):
|
|||
- Adding users set in the current environment
|
||||
"""
|
||||
# skip loading if plugin registry is not loaded or we run in a background thread
|
||||
|
||||
if not InvenTree.ready.isPluginRegistryLoaded():
|
||||
return
|
||||
|
||||
# Skip if not in worker or main thread
|
||||
if (
|
||||
not InvenTree.ready.isPluginRegistryLoaded()
|
||||
or not InvenTree.ready.isInMainThread()
|
||||
not InvenTree.ready.isInMainThread()
|
||||
and not InvenTree.ready.isInWorkerThread()
|
||||
):
|
||||
return
|
||||
|
||||
|
|
@ -52,7 +57,6 @@ class InvenTreeConfig(AppConfig):
|
|||
|
||||
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
|
||||
self.remove_obsolete_tasks()
|
||||
|
||||
self.collect_tasks()
|
||||
self.start_background_tasks()
|
||||
|
||||
|
|
@ -125,7 +129,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,8 +1,6 @@
|
|||
# -*- 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.custom import get_custom_classes
|
||||
|
|
@ -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
|
||||
|
|
@ -75,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:
|
||||
|
|
@ -203,7 +204,7 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
|
|||
if unit:
|
||||
raise ValidationError(_(f'Could not convert {original} to {unit}'))
|
||||
else:
|
||||
raise ValidationError(_('Invalid quantity supplied'))
|
||||
raise ValidationError(_('Invalid quantity provided'))
|
||||
|
||||
# Calculate the "magnitude" of the value, as a float
|
||||
# If the value is specified strangely (e.g. as a fraction or a dozen), this can cause issues
|
||||
|
|
@ -217,7 +218,7 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
|
|||
|
||||
magnitude = float(ureg.Quantity(magnitude).to_base_units().magnitude)
|
||||
except Exception as exc:
|
||||
raise ValidationError(_(f'Invalid quantity supplied ({exc})'))
|
||||
raise ValidationError(_('Invalid quantity provided') + f': ({exc})')
|
||||
|
||||
if strip_units:
|
||||
return magnitude
|
||||
|
|
@ -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
|
||||
|
|
@ -41,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
|
||||
|
||||
|
|
@ -134,9 +133,9 @@ class DatePickerFormField(forms.DateField):
|
|||
def __init__(self, **kwargs):
|
||||
"""Set up custom values."""
|
||||
help_text = kwargs.get('help_text', _('Enter date'))
|
||||
label = kwargs.get('label', None)
|
||||
label = kwargs.get('label')
|
||||
required = kwargs.get('required', False)
|
||||
initial = kwargs.get('initial', None)
|
||||
initial = kwargs.get('initial')
|
||||
|
||||
widget = forms.DateInput(attrs={'type': 'date'})
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import datetime
|
||||
import hashlib
|
||||
import inspect
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -9,17 +10,18 @@ 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 bleach
|
||||
import pytz
|
||||
import regex
|
||||
from bleach import clean
|
||||
|
|
@ -97,10 +99,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)])
|
||||
|
|
@ -435,7 +434,7 @@ def DownloadFile(
|
|||
return response
|
||||
|
||||
|
||||
def increment_serial_number(serial):
|
||||
def increment_serial_number(serial, part=None):
|
||||
"""Given a serial number, (attempt to) generate the *next* serial number.
|
||||
|
||||
Note: This method is exposed to custom plugins.
|
||||
|
|
@ -446,6 +445,7 @@ def increment_serial_number(serial):
|
|||
Returns:
|
||||
incremented value, or None if incrementing could not be performed.
|
||||
"""
|
||||
from InvenTree.exceptions import log_error
|
||||
from plugin.registry import registry
|
||||
|
||||
# Ensure we start with a string value
|
||||
|
|
@ -454,16 +454,30 @@ def increment_serial_number(serial):
|
|||
|
||||
# First, let any plugins attempt to increment the serial number
|
||||
for plugin in registry.with_mixin('validation'):
|
||||
result = plugin.increment_serial_number(serial)
|
||||
if result is not None:
|
||||
return str(result)
|
||||
try:
|
||||
if not hasattr(plugin, 'increment_serial_number'):
|
||||
continue
|
||||
|
||||
signature = inspect.signature(plugin.increment_serial_number)
|
||||
|
||||
# Note: 2024-08-21 - The 'part' parameter has been added to the signature
|
||||
if 'part' in signature.parameters:
|
||||
result = plugin.increment_serial_number(serial, part=part)
|
||||
else:
|
||||
result = plugin.increment_serial_number(serial)
|
||||
if result is not None:
|
||||
return str(result)
|
||||
except Exception:
|
||||
log_error(f'{plugin.slug}.increment_serial_number')
|
||||
|
||||
# If we get to here, no plugins were able to "increment" the provided serial value
|
||||
# Attempt to perform increment according to some basic rules
|
||||
return increment(serial)
|
||||
|
||||
|
||||
def extract_serial_numbers(input_string, expected_quantity: int, starting_value=None):
|
||||
def extract_serial_numbers(
|
||||
input_string, expected_quantity: int, starting_value=None, part=None
|
||||
):
|
||||
"""Extract a list of serial numbers from a provided input string.
|
||||
|
||||
The input string can be specified using the following concepts:
|
||||
|
|
@ -483,27 +497,29 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||
starting_value: Provide a starting value for the sequence (or None)
|
||||
"""
|
||||
if starting_value is None:
|
||||
starting_value = increment_serial_number(None)
|
||||
starting_value = increment_serial_number(None, part=part)
|
||||
|
||||
try:
|
||||
expected_quantity = int(expected_quantity)
|
||||
except ValueError:
|
||||
raise ValidationError([_('Invalid quantity provided')])
|
||||
|
||||
if input_string:
|
||||
input_string = str(input_string).strip()
|
||||
else:
|
||||
input_string = ''
|
||||
if expected_quantity > 1000:
|
||||
raise ValidationError({
|
||||
'quantity': [_('Cannot serialize more than 1000 items at once')]
|
||||
})
|
||||
|
||||
input_string = str(input_string).strip() if input_string else ''
|
||||
|
||||
if len(input_string) == 0:
|
||||
raise ValidationError([_('Empty serial number string')])
|
||||
|
||||
next_value = increment_serial_number(starting_value)
|
||||
next_value = increment_serial_number(starting_value, part=part)
|
||||
|
||||
# Substitute ~ character with latest value
|
||||
while '~' in input_string and next_value:
|
||||
input_string = input_string.replace('~', str(next_value), 1)
|
||||
next_value = increment_serial_number(next_value)
|
||||
next_value = increment_serial_number(next_value, part=part)
|
||||
|
||||
# Split input string by whitespace or comma (,) characters
|
||||
groups = re.split(r'[\s,]+', input_string)
|
||||
|
|
@ -557,7 +573,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||
|
||||
if a == b:
|
||||
# Invalid group
|
||||
add_error(_(f'Invalid group range: {group}'))
|
||||
add_error(_(f'Invalid group: {group}'))
|
||||
continue
|
||||
|
||||
group_items = []
|
||||
|
|
@ -600,7 +616,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||
for item in group_items:
|
||||
add_serial(item)
|
||||
else:
|
||||
add_error(_(f'Invalid group range: {group}'))
|
||||
add_error(_(f'Invalid group: {group}'))
|
||||
|
||||
else:
|
||||
# In the case of a different number of hyphens, simply add the entire group
|
||||
|
|
@ -618,14 +634,14 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||
sequence_count = max(0, expected_quantity - len(serials))
|
||||
|
||||
if len(items) > 2 or len(items) == 0:
|
||||
add_error(_(f'Invalid group sequence: {group}'))
|
||||
add_error(_(f'Invalid group: {group}'))
|
||||
continue
|
||||
elif len(items) == 2:
|
||||
try:
|
||||
if items[1]:
|
||||
sequence_count = int(items[1]) + 1
|
||||
except ValueError:
|
||||
add_error(_(f'Invalid group sequence: {group}'))
|
||||
add_error(_(f'Invalid group: {group}'))
|
||||
continue
|
||||
|
||||
value = items[0]
|
||||
|
|
@ -644,7 +660,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||
for item in sequence_items:
|
||||
add_serial(item)
|
||||
else:
|
||||
add_error(_(f'Invalid group sequence: {group}'))
|
||||
add_error(_(f'Invalid group: {group}'))
|
||||
|
||||
else:
|
||||
# At this point, we assume that the 'group' is just a single serial value
|
||||
|
|
@ -800,14 +816,73 @@ 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
|
||||
|
||||
|
||||
def clean_markdown(value: str):
|
||||
"""Clean a markdown string.
|
||||
|
||||
This function will remove javascript and other potentially harmful content from the markdown string.
|
||||
"""
|
||||
import markdown
|
||||
|
||||
try:
|
||||
markdownify_settings = settings.MARKDOWNIFY['default']
|
||||
except (AttributeError, KeyError):
|
||||
markdownify_settings = {}
|
||||
|
||||
extensions = markdownify_settings.get('MARKDOWN_EXTENSIONS', [])
|
||||
extension_configs = markdownify_settings.get('MARKDOWN_EXTENSION_CONFIGS', {})
|
||||
|
||||
# Generate raw HTML from provided markdown (without sanitizing)
|
||||
# Note: The 'html' output_format is required to generate self closing tags, e.g. <tag> instead of <tag />
|
||||
html = markdown.markdown(
|
||||
value or '',
|
||||
extensions=extensions,
|
||||
extension_configs=extension_configs,
|
||||
output_format='html',
|
||||
)
|
||||
|
||||
# Bleach settings
|
||||
whitelist_tags = markdownify_settings.get(
|
||||
'WHITELIST_TAGS', bleach.sanitizer.ALLOWED_TAGS
|
||||
)
|
||||
whitelist_attrs = markdownify_settings.get(
|
||||
'WHITELIST_ATTRS', bleach.sanitizer.ALLOWED_ATTRIBUTES
|
||||
)
|
||||
whitelist_styles = markdownify_settings.get(
|
||||
'WHITELIST_STYLES', bleach.css_sanitizer.ALLOWED_CSS_PROPERTIES
|
||||
)
|
||||
whitelist_protocols = markdownify_settings.get(
|
||||
'WHITELIST_PROTOCOLS', bleach.sanitizer.ALLOWED_PROTOCOLS
|
||||
)
|
||||
strip = markdownify_settings.get('STRIP', True)
|
||||
|
||||
css_sanitizer = bleach.css_sanitizer.CSSSanitizer(
|
||||
allowed_css_properties=whitelist_styles
|
||||
)
|
||||
cleaner = bleach.Cleaner(
|
||||
tags=whitelist_tags,
|
||||
attributes=whitelist_attrs,
|
||||
css_sanitizer=css_sanitizer,
|
||||
protocols=whitelist_protocols,
|
||||
strip=strip,
|
||||
)
|
||||
|
||||
# Clean the HTML content (for comparison). This must be the same as the original content
|
||||
clean_html = cleaner.clean(html)
|
||||
|
||||
if html != clean_html:
|
||||
raise ValidationError(_('Data contains prohibited markdown content'))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def hash_barcode(barcode_data):
|
||||
"""Calculate a 'unique' hash for a barcode string.
|
||||
|
||||
|
|
@ -827,7 +902,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()
|
||||
)
|
||||
|
|
@ -865,7 +940,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:
|
||||
|
|
@ -947,7 +1022,14 @@ def get_objectreference(
|
|||
ret = {}
|
||||
if url_fnc:
|
||||
ret['link'] = url_fnc()
|
||||
return {'name': str(item), 'model': str(model_cls._meta.verbose_name), **ret}
|
||||
|
||||
return {
|
||||
'name': str(item),
|
||||
'model_name': str(model_cls._meta.verbose_name),
|
||||
'model_type': str(model_cls._meta.model_name),
|
||||
'model_id': getattr(item, 'pk', None),
|
||||
**ret,
|
||||
}
|
||||
|
||||
|
||||
Inheritors_T = TypeVar('Inheritors_T')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -86,4 +86,5 @@ def send_email(subject, body, recipients, from_email=None, html_message=None):
|
|||
recipients,
|
||||
fail_silently=False,
|
||||
html_message=html_message,
|
||||
group='notification',
|
||||
)
|
||||
|
|
@ -114,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(
|
||||
|
|
@ -130,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:
|
||||
|
|
@ -138,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(
|
||||
|
|
@ -213,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)
|
||||
|
||||
|
|
@ -225,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
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ Additionally, update the following files with the new locale code:
|
|||
|
||||
- /src/frontend/.linguirc file
|
||||
- /src/frontend/src/contexts/LanguageContext.tsx
|
||||
|
||||
(and then run "invoke int.frontend-trans")
|
||||
"""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
|
@ -34,6 +36,7 @@ LOCALES = [
|
|||
('it', _('Italian')),
|
||||
('ja', _('Japanese')),
|
||||
('ko', _('Korean')),
|
||||
('lt', _('Lithuanian')),
|
||||
('lv', _('Latvian')),
|
||||
('nl', _('Dutch')),
|
||||
('no', _('Norwegian')),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def send_simple_login_email(user, link):
|
|||
)
|
||||
|
||||
send_mail(
|
||||
_(f'[{site_name}] Log in to the app'),
|
||||
f'[{site_name}] ' + _('Log in to the app'),
|
||||
email_plaintext_message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[user.email],
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class Command(BaseCommand):
|
|||
|
||||
def handle(self, *args, **kwargs):
|
||||
"""Run the management command."""
|
||||
from plugin.staticfiles import collect_plugins_static_files
|
||||
import plugin.staticfiles
|
||||
|
||||
collect_plugins_static_files()
|
||||
plugin.staticfiles.collect_plugins_static_files()
|
||||
plugin.staticfiles.clear_plugins_static_files()
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class Command(BaseCommand):
|
|||
|
||||
filename = kwargs.get('filename', 'inventree_settings.json')
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
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}')
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
import logging
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
|
||||
from rest_framework import exceptions, serializers
|
||||
from rest_framework.fields import empty
|
||||
from rest_framework.metadata import SimpleMetadata
|
||||
from rest_framework.request import clone_request
|
||||
from rest_framework.utils import model_meta
|
||||
|
||||
import common.models
|
||||
|
|
@ -29,6 +33,40 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||
so we can perform lookup for ForeignKey related fields.
|
||||
"""
|
||||
|
||||
def determine_actions(self, request, view):
|
||||
"""Determine the 'actions' available to the user for the given view.
|
||||
|
||||
Note that this differs from the standard DRF implementation,
|
||||
in that we also allow annotation for the 'GET' method.
|
||||
|
||||
This allows the client to determine what fields are available,
|
||||
even if they are only for a read (GET) operation.
|
||||
|
||||
See SimpleMetadata.determine_actions for more information.
|
||||
"""
|
||||
actions = {}
|
||||
|
||||
for method in {'PUT', 'POST', 'GET'} & set(view.allowed_methods):
|
||||
view.request = clone_request(request, method)
|
||||
try:
|
||||
# Test global permissions
|
||||
if hasattr(view, 'check_permissions'):
|
||||
view.check_permissions(view.request)
|
||||
# Test object permissions
|
||||
if method == 'PUT' and hasattr(view, 'get_object'):
|
||||
view.get_object()
|
||||
except (exceptions.APIException, PermissionDenied, Http404):
|
||||
pass
|
||||
else:
|
||||
# If user has appropriate permissions for the view, include
|
||||
# appropriate metadata about the fields that should be supplied.
|
||||
serializer = view.get_serializer()
|
||||
actions[method] = self.get_serializer_info(serializer)
|
||||
finally:
|
||||
view.request = request
|
||||
|
||||
return actions
|
||||
|
||||
def determine_metadata(self, request, view):
|
||||
"""Overwrite the metadata to adapt to the request user."""
|
||||
self.request = request
|
||||
|
|
@ -81,6 +119,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||
|
||||
# Map the request method to a permission type
|
||||
rolemap = {
|
||||
'GET': 'view',
|
||||
'POST': 'add',
|
||||
'PUT': 'change',
|
||||
'PATCH': 'change',
|
||||
|
|
@ -102,10 +141,6 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||
if 'DELETE' in view.allowed_methods and check(user, table, 'delete'):
|
||||
actions['DELETE'] = {}
|
||||
|
||||
# Add a 'VIEW' action if we are allowed to view
|
||||
if 'GET' in view.allowed_methods and check(user, table, 'view'):
|
||||
actions['GET'] = {}
|
||||
|
||||
metadata['actions'] = actions
|
||||
|
||||
except AttributeError:
|
||||
|
|
@ -204,7 +239,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 +271,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 +342,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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ from rest_framework import generics, mixins, status
|
|||
from rest_framework.response import Response
|
||||
|
||||
from InvenTree.fields import InvenTreeNotesField
|
||||
from InvenTree.helpers import remove_non_printable_characters, strip_html_tags
|
||||
from InvenTree.helpers import (
|
||||
clean_markdown,
|
||||
remove_non_printable_characters,
|
||||
strip_html_tags,
|
||||
)
|
||||
|
||||
|
||||
class CleanMixin:
|
||||
|
|
@ -57,6 +61,7 @@ class CleanMixin:
|
|||
|
||||
# By default, newline characters are removed
|
||||
remove_newline = True
|
||||
is_markdown = False
|
||||
|
||||
try:
|
||||
if hasattr(self, 'serializer_class'):
|
||||
|
|
@ -64,11 +69,12 @@ class CleanMixin:
|
|||
field = model._meta.get_field(field)
|
||||
|
||||
# The following field types allow newline characters
|
||||
allow_newline = [InvenTreeNotesField]
|
||||
allow_newline = [(InvenTreeNotesField, True)]
|
||||
|
||||
for field_type in allow_newline:
|
||||
if issubclass(type(field), field_type):
|
||||
if issubclass(type(field), field_type[0]):
|
||||
remove_newline = False
|
||||
is_markdown = field_type[1]
|
||||
break
|
||||
|
||||
except AttributeError:
|
||||
|
|
@ -80,6 +86,9 @@ class CleanMixin:
|
|||
cleaned, remove_newline=remove_newline
|
||||
)
|
||||
|
||||
if is_markdown:
|
||||
cleaned = clean_markdown(cleaned)
|
||||
|
||||
return cleaned
|
||||
|
||||
def clean_data(self, data: dict) -> dict:
|
||||
|
|
@ -128,14 +137,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."""
|
||||
|
|
@ -184,5 +189,9 @@ class RetrieveUpdateDestroyAPI(CleanMixin, generics.RetrieveUpdateDestroyAPIView
|
|||
"""View for retrieve, update and destroy API."""
|
||||
|
||||
|
||||
class RetrieveDestroyAPI(generics.RetrieveDestroyAPIView):
|
||||
"""View for retrieve and destroy API."""
|
||||
|
||||
|
||||
class UpdateAPI(CleanMixin, generics.UpdateAPIView):
|
||||
"""View for update API."""
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ from django.db import models
|
|||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_q.models import Task
|
||||
from error_report.models import Error
|
||||
from mptt.exceptions import InvalidMove
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
|
@ -390,10 +392,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 +409,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'"
|
||||
})
|
||||
|
|
@ -442,7 +441,7 @@ class ReferenceIndexingMixin(models.Model):
|
|||
)
|
||||
|
||||
# Check that the reference field can be rebuild
|
||||
cls.rebuild_reference_field(value, validate=True)
|
||||
return cls.rebuild_reference_field(value, validate=True)
|
||||
|
||||
@classmethod
|
||||
def rebuild_reference_field(cls, reference, validate=False):
|
||||
|
|
@ -859,7 +858,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.
|
||||
|
|
@ -1054,6 +1053,71 @@ class InvenTreeBarcodeMixin(models.Model):
|
|||
self.save()
|
||||
|
||||
|
||||
def notify_staff_users_of_error(instance, label: str, context: dict):
|
||||
"""Helper function to notify staff users of an error."""
|
||||
import common.models
|
||||
import common.notifications
|
||||
|
||||
try:
|
||||
# Get all staff users
|
||||
staff_users = get_user_model().objects.filter(is_staff=True)
|
||||
|
||||
target_users = []
|
||||
|
||||
# Send a notification to each staff user (unless they have disabled error notifications)
|
||||
for user in staff_users:
|
||||
if common.models.InvenTreeUserSetting.get_setting(
|
||||
'NOTIFICATION_ERROR_REPORT', True, user=user
|
||||
):
|
||||
target_users.append(user)
|
||||
|
||||
if len(target_users) > 0:
|
||||
common.notifications.trigger_notification(
|
||||
instance,
|
||||
label,
|
||||
context=context,
|
||||
targets=target_users,
|
||||
delivery_methods={common.notifications.UIMessageNotification},
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
# We do not want to throw an exception while reporting an exception!
|
||||
logger.error(exc)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Task, dispatch_uid='failure_post_save_notification')
|
||||
def after_failed_task(sender, instance: Task, created: bool, **kwargs):
|
||||
"""Callback when a new task failure log is generated."""
|
||||
from django.conf import settings
|
||||
|
||||
max_attempts = int(settings.Q_CLUSTER.get('max_attempts', 5))
|
||||
n = instance.attempt_count
|
||||
|
||||
# Only notify once the maximum number of attempts has been reached
|
||||
if not instance.success and n >= max_attempts:
|
||||
try:
|
||||
url = InvenTree.helpers_model.construct_absolute_url(
|
||||
reverse(
|
||||
'admin:django_q_failure_change', kwargs={'object_id': instance.pk}
|
||||
)
|
||||
)
|
||||
except (ValueError, NoReverseMatch):
|
||||
url = ''
|
||||
|
||||
notify_staff_users_of_error(
|
||||
instance,
|
||||
'inventree.task_failure',
|
||||
{
|
||||
'failure': instance,
|
||||
'name': _('Task Failure'),
|
||||
'message': _(
|
||||
f"Background worker task '{instance.func}' failed after {n} attempts"
|
||||
),
|
||||
'link': url,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Error, dispatch_uid='error_post_save_notification')
|
||||
def after_error_logged(sender, instance: Error, created: bool, **kwargs):
|
||||
"""Callback when a server error is logged.
|
||||
|
|
@ -1062,41 +1126,21 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
|
|||
"""
|
||||
if created:
|
||||
try:
|
||||
import common.models
|
||||
import common.notifications
|
||||
|
||||
users = get_user_model().objects.filter(is_staff=True)
|
||||
|
||||
link = InvenTree.helpers_model.construct_absolute_url(
|
||||
url = InvenTree.helpers_model.construct_absolute_url(
|
||||
reverse(
|
||||
'admin:error_report_error_change', kwargs={'object_id': instance.pk}
|
||||
)
|
||||
)
|
||||
except NoReverseMatch:
|
||||
url = ''
|
||||
|
||||
context = {
|
||||
notify_staff_users_of_error(
|
||||
instance,
|
||||
'inventree.error_log',
|
||||
{
|
||||
'error': instance,
|
||||
'name': _('Server Error'),
|
||||
'message': _('An error has been logged by the server.'),
|
||||
'link': link,
|
||||
}
|
||||
|
||||
target_users = []
|
||||
|
||||
for user in users:
|
||||
if common.models.InvenTreeUserSetting.get_setting(
|
||||
'NOTIFICATION_ERROR_REPORT', True, user=user
|
||||
):
|
||||
target_users.append(user)
|
||||
|
||||
if len(target_users) > 0:
|
||||
common.notifications.trigger_notification(
|
||||
instance,
|
||||
'inventree.error_log',
|
||||
context=context,
|
||||
targets=target_users,
|
||||
delivery_methods={common.notifications.UIMessageNotification},
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
"""We do not want to throw an exception while reporting an exception"""
|
||||
logger.error(exc) # noqa: LOG005
|
||||
'link': url,
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ class RolePermission(permissions.BasePermission):
|
|||
# Extract the model name associated with this request
|
||||
model = get_model_for_view(view)
|
||||
|
||||
if model is None:
|
||||
return True
|
||||
|
||||
app_label = model._meta.app_label
|
||||
model_name = model._meta.model_name
|
||||
|
||||
|
|
@ -99,14 +102,24 @@ class IsSuperuser(permissions.IsAdminUser):
|
|||
return bool(request.user and request.user.is_superuser)
|
||||
|
||||
|
||||
class IsSuperuserOrReadOnly(permissions.IsAdminUser):
|
||||
"""Allow read-only access to any user, but write access is restricted to superuser users."""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
"""Check if the user is a superuser."""
|
||||
return bool(
|
||||
(request.user and request.user.is_superuser)
|
||||
or request.method in permissions.SAFE_METHODS
|
||||
)
|
||||
|
||||
|
||||
class IsStaffOrReadOnly(permissions.IsAdminUser):
|
||||
"""Allows read-only access to any user, but write access is restricted to staff users."""
|
||||
|
||||
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():
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from django.http import Http404
|
|||
|
||||
import rest_framework.exceptions
|
||||
import sentry_sdk
|
||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
|
||||
import InvenTree.version
|
||||
|
|
@ -27,6 +28,7 @@ def sentry_ignore_errors():
|
|||
"""
|
||||
return [
|
||||
Http404,
|
||||
MissingRate,
|
||||
ValidationError,
|
||||
rest_framework.exceptions.AuthenticationFailed,
|
||||
rest_framework.exceptions.NotAuthenticated,
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
@ -405,18 +403,21 @@ class UserSerializer(InvenTreeModelSerializer):
|
|||
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'), allow_blank=True
|
||||
)
|
||||
|
||||
last_name = serializers.CharField(
|
||||
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'), allow_blank=True
|
||||
)
|
||||
|
||||
|
||||
class ExendedUserSerializer(UserSerializer):
|
||||
class ExtendedUserSerializer(UserSerializer):
|
||||
"""Serializer for a User with a bit more info."""
|
||||
|
||||
from users.serializers import GroupSerializer
|
||||
|
|
@ -426,21 +427,24 @@ 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')
|
||||
)
|
||||
|
||||
is_superuser = serializers.BooleanField(
|
||||
label=_('Superuser'), help_text=_('Is this user a superuser')
|
||||
)
|
||||
|
||||
is_active = serializers.BooleanField(
|
||||
label=_('Active'), help_text=_('Is this user account active')
|
||||
)
|
||||
|
|
@ -465,9 +469,33 @@ class ExendedUserSerializer(UserSerializer):
|
|||
return super().validate(attrs)
|
||||
|
||||
|
||||
class UserCreateSerializer(ExendedUserSerializer):
|
||||
class MeUserSerializer(ExtendedUserSerializer):
|
||||
"""API serializer specifically for the 'me' endpoint."""
|
||||
|
||||
class Meta(ExtendedUserSerializer.Meta):
|
||||
"""Metaclass options.
|
||||
|
||||
Extends the ExtendedUserSerializer.Meta options,
|
||||
but ensures that certain fields are read-only.
|
||||
"""
|
||||
|
||||
read_only_fields = [
|
||||
*ExtendedUserSerializer.Meta.read_only_fields,
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
]
|
||||
|
||||
|
||||
class UserCreateSerializer(ExtendedUserSerializer):
|
||||
"""Serializer for creating a new User."""
|
||||
|
||||
class Meta(ExtendedUserSerializer.Meta):
|
||||
"""Metaclass options for the UserCreateSerializer."""
|
||||
|
||||
# Prevent creation of users with superuser or staff permissions
|
||||
read_only_fields = ['groups', 'is_staff', 'is_superuser']
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Expanded valiadation for auth."""
|
||||
# Check that the user trying to create a new user is a superuser
|
||||
|
|
@ -482,6 +510,7 @@ class UserCreateSerializer(ExendedUserSerializer):
|
|||
def create(self, validated_data):
|
||||
"""Send an e email to the user after creation."""
|
||||
from InvenTree.helpers_model import get_base_url
|
||||
from InvenTree.tasks import email_user, offload_task
|
||||
|
||||
base_url = get_base_url()
|
||||
|
||||
|
|
@ -499,8 +528,12 @@ class UserCreateSerializer(ExendedUserSerializer):
|
|||
if base_url:
|
||||
message += f'\n\nURL: {base_url}'
|
||||
|
||||
subject = _('Welcome to InvenTree')
|
||||
|
||||
# Send the user an onboarding email (from current site)
|
||||
instance.email_user(subject=_('Welcome to InvenTree'), message=message)
|
||||
offload_task(
|
||||
email_user, instance.pk, str(subject), str(message), force_async=True
|
||||
)
|
||||
|
||||
return instance
|
||||
|
||||
|
|
@ -596,7 +629,7 @@ class DataFileUploadSerializer(serializers.Serializer):
|
|||
accepted_file_types = ['xls', 'xlsx', 'csv', 'tsv', 'xml']
|
||||
|
||||
if ext not in accepted_file_types:
|
||||
raise serializers.ValidationError(_('Unsupported file type'))
|
||||
raise serializers.ValidationError(_('Unsupported file format'))
|
||||
|
||||
# Impose a 50MB limit on uploaded BOM files
|
||||
max_upload_file_size = 50 * 1024 * 1024
|
||||
|
|
@ -704,7 +737,6 @@ class DataFileUploadSerializer(serializers.Serializer):
|
|||
|
||||
def save(self):
|
||||
"""Empty overwrite for save."""
|
||||
...
|
||||
|
||||
|
||||
class DataFileExtractSerializer(serializers.Serializer):
|
||||
|
|
@ -806,11 +838,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 +855,6 @@ class DataFileExtractSerializer(serializers.Serializer):
|
|||
|
||||
def save(self):
|
||||
"""No "save" action for this serializer."""
|
||||
pass
|
||||
|
||||
|
||||
class NotesFieldMixin:
|
||||
|
|
@ -882,8 +912,8 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
|
|||
|
||||
try:
|
||||
self.remote_image_file = download_image_from_url(url)
|
||||
except Exception as exc:
|
||||
except Exception:
|
||||
self.remote_image_file = None
|
||||
raise ValidationError(str(exc))
|
||||
raise ValidationError(_('Failed to download image from remote URL'))
|
||||
|
||||
return url
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ from . import config, locales
|
|||
|
||||
checkMinPythonVersion()
|
||||
|
||||
INVENTREE_NEWS_URL = 'https://inventree.org/news/feed.atom'
|
||||
INVENTREE_BASE_URL = 'https://inventree.org'
|
||||
INVENTREE_NEWS_URL = f'{INVENTREE_BASE_URL}/news/feed.atom'
|
||||
|
||||
# Determine if we are running in "test" mode e.g. "manage.py test"
|
||||
TESTING = 'test' in sys.argv or 'TESTING' in os.environ
|
||||
|
|
@ -133,6 +134,34 @@ STATIC_URL = '/static/'
|
|||
# Web URL endpoint for served media files
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# Are plugins enabled?
|
||||
PLUGINS_ENABLED = get_boolean_setting(
|
||||
'INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False
|
||||
)
|
||||
|
||||
PLUGINS_INSTALL_DISABLED = get_boolean_setting(
|
||||
'INVENTREE_PLUGIN_NOINSTALL', 'plugin_noinstall', False
|
||||
)
|
||||
|
||||
PLUGIN_FILE = config.get_plugin_file()
|
||||
|
||||
# Plugin test settings
|
||||
PLUGIN_TESTING = get_setting(
|
||||
'INVENTREE_PLUGIN_TESTING', 'PLUGIN_TESTING', TESTING
|
||||
) # Are plugins being tested?
|
||||
|
||||
PLUGIN_TESTING_SETUP = get_setting(
|
||||
'INVENTREE_PLUGIN_TESTING_SETUP', 'PLUGIN_TESTING_SETUP', False
|
||||
) # Load plugins from setup hooks in testing?
|
||||
|
||||
PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now
|
||||
|
||||
PLUGIN_RETRY = get_setting(
|
||||
'INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 3, typecast=int
|
||||
) # How often should plugin loading be tried?
|
||||
|
||||
PLUGIN_FILE_CHECKED = False # Was the plugin file checked?
|
||||
|
||||
STATICFILES_DIRS = []
|
||||
|
||||
# Translated Template settings
|
||||
|
|
@ -153,6 +182,12 @@ if DEBUG and 'collectstatic' not in sys.argv:
|
|||
if web_dir.exists():
|
||||
STATICFILES_DIRS.append(web_dir)
|
||||
|
||||
# Append directory for sample plugin static content (if in debug mode)
|
||||
if PLUGINS_ENABLED:
|
||||
print('Adding plugin sample static content')
|
||||
STATICFILES_DIRS.append(BASE_DIR.joinpath('plugin', 'samples', 'static'))
|
||||
|
||||
print('-', STATICFILES_DIRS[-1])
|
||||
STATFILES_I18_PROCESSORS = ['InvenTree.context.status_codes']
|
||||
|
||||
# Color Themes Directory
|
||||
|
|
@ -169,10 +204,11 @@ DBBACKUP_STORAGE = get_setting(
|
|||
|
||||
# Default backup configuration
|
||||
DBBACKUP_STORAGE_OPTIONS = get_setting(
|
||||
'INVENTREE_BACKUP_OPTIONS', 'backup_options', None
|
||||
'INVENTREE_BACKUP_OPTIONS',
|
||||
'backup_options',
|
||||
default_value={'location': config.get_backup_dir()},
|
||||
typecast=dict,
|
||||
)
|
||||
if DBBACKUP_STORAGE_OPTIONS is None:
|
||||
DBBACKUP_STORAGE_OPTIONS = {'location': config.get_backup_dir()}
|
||||
|
||||
INVENTREE_ADMIN_ENABLED = get_boolean_setting(
|
||||
'INVENTREE_ADMIN_ENABLED', config_key='admin_enabled', default_value=True
|
||||
|
|
@ -281,7 +317,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',
|
||||
|
|
@ -298,7 +334,7 @@ if (
|
|||
and INVENTREE_ADMIN_ENABLED
|
||||
and not TESTING
|
||||
and get_boolean_setting('INVENTREE_DEBUG_SHELL', 'debug_shell', False)
|
||||
): # noqa
|
||||
):
|
||||
try:
|
||||
import django_admin_shell # noqa: F401
|
||||
|
||||
|
|
@ -555,6 +591,9 @@ for key in db_keys:
|
|||
# Check that required database configuration options are specified
|
||||
required_keys = ['ENGINE', 'NAME']
|
||||
|
||||
# Ensure all database keys are upper case
|
||||
db_config = {key.upper(): value for key, value in db_config.items()}
|
||||
|
||||
for key in required_keys:
|
||||
if key not in db_config: # pragma: no cover
|
||||
error_msg = f'Missing required database configuration value {key}'
|
||||
|
|
@ -812,13 +851,20 @@ _q_worker_timeout = int(
|
|||
get_setting('INVENTREE_BACKGROUND_TIMEOUT', 'background.timeout', 90)
|
||||
)
|
||||
|
||||
|
||||
# Prevent running multiple background workers if global cache is disabled
|
||||
# This is to prevent scheduling conflicts due to the lack of a shared cache
|
||||
BACKGROUND_WORKER_COUNT = (
|
||||
int(get_setting('INVENTREE_BACKGROUND_WORKERS', 'background.workers', 4))
|
||||
if GLOBAL_CACHE_ENABLED
|
||||
else 1
|
||||
)
|
||||
|
||||
# django-q background worker configuration
|
||||
Q_CLUSTER = {
|
||||
'name': 'InvenTree',
|
||||
'label': 'Background Tasks',
|
||||
'workers': int(
|
||||
get_setting('INVENTREE_BACKGROUND_WORKERS', 'background.workers', 4)
|
||||
),
|
||||
'workers': BACKGROUND_WORKER_COUNT,
|
||||
'timeout': _q_worker_timeout,
|
||||
'retry': max(120, _q_worker_timeout + 30),
|
||||
'max_attempts': int(
|
||||
|
|
@ -949,10 +995,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']
|
||||
|
||||
|
|
@ -1058,26 +1101,40 @@ if (
|
|||
sys.exit(-1)
|
||||
|
||||
COOKIE_MODE = (
|
||||
str(get_setting('INVENTREE_COOKIE_SAMESITE', 'cookie.samesite', 'None'))
|
||||
str(get_setting('INVENTREE_COOKIE_SAMESITE', 'cookie.samesite', 'False'))
|
||||
.lower()
|
||||
.strip()
|
||||
)
|
||||
|
||||
valid_cookie_modes = {'lax': 'Lax', 'strict': 'Strict', 'none': None, 'null': None}
|
||||
# Valid modes (as per the django settings documentation)
|
||||
valid_cookie_modes = ['lax', 'strict', 'none']
|
||||
|
||||
if COOKIE_MODE not in valid_cookie_modes.keys():
|
||||
logger.error('Invalid cookie samesite mode: %s', COOKIE_MODE)
|
||||
sys.exit(-1)
|
||||
|
||||
COOKIE_MODE = valid_cookie_modes[COOKIE_MODE.lower()]
|
||||
if not DEBUG and not TESTING and COOKIE_MODE in valid_cookie_modes:
|
||||
# Set the cookie mode (in production mode only)
|
||||
COOKIE_MODE = COOKIE_MODE.capitalize()
|
||||
else:
|
||||
# Default to False, as per the Django settings
|
||||
COOKIE_MODE = False
|
||||
|
||||
# Additional CSRF settings
|
||||
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
|
||||
CSRF_COOKIE_NAME = 'csrftoken'
|
||||
|
||||
CSRF_COOKIE_SAMESITE = COOKIE_MODE
|
||||
SESSION_COOKIE_SAMESITE = COOKIE_MODE
|
||||
SESSION_COOKIE_SECURE = get_boolean_setting(
|
||||
'INVENTREE_SESSION_COOKIE_SECURE', 'cookie.secure', False
|
||||
|
||||
"""Set the SESSION_COOKIE_SECURE value based on the following rules:
|
||||
- False if the server is running in DEBUG mode
|
||||
- True if samesite cookie setting is set to 'None'
|
||||
- Otherwise, use the value specified in the configuration file (or env var)
|
||||
"""
|
||||
SESSION_COOKIE_SECURE = (
|
||||
False
|
||||
if DEBUG
|
||||
else (
|
||||
SESSION_COOKIE_SAMESITE == 'None'
|
||||
or get_boolean_setting('INVENTREE_SESSION_COOKIE_SECURE', 'cookie.secure', True)
|
||||
)
|
||||
)
|
||||
|
||||
USE_X_FORWARDED_HOST = get_boolean_setting(
|
||||
|
|
@ -1229,23 +1286,29 @@ MARKDOWNIFY = {
|
|||
'abbr',
|
||||
'b',
|
||||
'blockquote',
|
||||
'code',
|
||||
'em',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'hr',
|
||||
'i',
|
||||
'img',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
's',
|
||||
'strong',
|
||||
'ul',
|
||||
'table',
|
||||
'thead',
|
||||
'tbody',
|
||||
'th',
|
||||
'tr',
|
||||
'td',
|
||||
'ul',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -1257,32 +1320,12 @@ IGNORED_ERRORS = [Http404, django.core.exceptions.PermissionDenied]
|
|||
MAINTENANCE_MODE_RETRY_AFTER = 10
|
||||
MAINTENANCE_MODE_STATE_BACKEND = 'InvenTree.backends.InvenTreeMaintenanceModeBackend'
|
||||
|
||||
# Are plugins enabled?
|
||||
PLUGINS_ENABLED = get_boolean_setting(
|
||||
'INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False
|
||||
)
|
||||
PLUGINS_INSTALL_DISABLED = get_boolean_setting(
|
||||
'INVENTREE_PLUGIN_NOINSTALL', 'plugin_noinstall', False
|
||||
)
|
||||
|
||||
PLUGIN_FILE = config.get_plugin_file()
|
||||
|
||||
# Plugin test settings
|
||||
PLUGIN_TESTING = get_setting(
|
||||
'INVENTREE_PLUGIN_TESTING', 'PLUGIN_TESTING', TESTING
|
||||
) # Are plugins being tested?
|
||||
PLUGIN_TESTING_SETUP = get_setting(
|
||||
'INVENTREE_PLUGIN_TESTING_SETUP', 'PLUGIN_TESTING_SETUP', False
|
||||
) # Load plugins from setup hooks in testing?
|
||||
PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now
|
||||
PLUGIN_RETRY = get_setting(
|
||||
'INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 3, typecast=int
|
||||
) # How often should plugin loading be tried?
|
||||
PLUGIN_FILE_CHECKED = False # Was the plugin file checked?
|
||||
|
||||
# Flag to allow table events during testing
|
||||
TESTING_TABLE_EVENTS = False
|
||||
|
||||
# Flag to allow pricing recalculations during testing
|
||||
TESTING_PRICING = False
|
||||
|
||||
# User interface customization values
|
||||
CUSTOM_LOGO = get_custom_file(
|
||||
'INVENTREE_CUSTOM_LOGO', 'customize.logo', 'custom logo', lookup_media=True
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,16 +1,14 @@
|
|||
"""Provides system status functionality checks."""
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
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')
|
||||
|
|
@ -63,13 +61,13 @@ def check_system_health(**kwargs):
|
|||
|
||||
if not is_worker_running(**kwargs): # pragma: no cover
|
||||
result = False
|
||||
logger.warning(_('Background worker check failed'))
|
||||
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'))
|
||||
logger.warning('Email backend not configured')
|
||||
|
||||
if not result: # pragma: no cover
|
||||
logger.warning(_('InvenTree system health checks failed'))
|
||||
logger.warning('InvenTree system health checks failed')
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ 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.contrib.auth import get_user_model
|
||||
from django.core.exceptions import AppRegistryNotReady
|
||||
from django.core.management import call_command
|
||||
from django.db import DEFAULT_DB_ALIAS, connections
|
||||
|
|
@ -174,6 +175,9 @@ def offload_task(
|
|||
"""
|
||||
from InvenTree.exceptions import log_error
|
||||
|
||||
# Extract group information from kwargs
|
||||
group = kwargs.pop('group', 'inventree')
|
||||
|
||||
try:
|
||||
import importlib
|
||||
|
||||
|
|
@ -200,13 +204,13 @@ def offload_task(
|
|||
if force_async or (is_worker_running() and not force_sync):
|
||||
# Running as asynchronous task
|
||||
try:
|
||||
task = AsyncTask(taskname, *args, **kwargs)
|
||||
task = AsyncTask(taskname, *args, group=group, **kwargs)
|
||||
task.run()
|
||||
except ImportError:
|
||||
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 +260,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 +295,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 +303,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:
|
||||
|
|
@ -688,3 +694,14 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True):
|
|||
# We should be current now - triggering full reload to make sure all models
|
||||
# are loaded fully in their new state.
|
||||
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
|
||||
|
||||
|
||||
def email_user(user_id: int, subject: str, message: str) -> None:
|
||||
"""Send a message to a user."""
|
||||
try:
|
||||
user = get_user_model().objects.get(pk=user_id)
|
||||
except Exception:
|
||||
logger.warning('User <%s> not found - cannot send welcome message', user_id)
|
||||
return
|
||||
|
||||
user.email_user(subject=subject, message=message)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue