mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-17 20:35:01 -06:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed43b0822 | ||
|
|
88c6696fd2 | ||
|
|
0f3b719c80 | ||
|
|
0d8eb2e0b3 | ||
|
|
56b16cb1ac | ||
|
|
e5a36f6936 | ||
|
|
61b5a7d393 |
2
.github/workflows/qc_checks.yaml
vendored
2
.github/workflows/qc_checks.yaml
vendored
@@ -554,6 +554,8 @@ jobs:
|
||||
run: cd src/frontend && yarn install
|
||||
- name: Build frontend
|
||||
run: cd src/frontend && yarn run compile && yarn run build
|
||||
- name: Write version file - SHA
|
||||
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
|
||||
- name: Zip frontend
|
||||
run: |
|
||||
cd src/backend/InvenTree/web/static
|
||||
|
||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -43,6 +43,10 @@ jobs:
|
||||
run: cd src/frontend && yarn install
|
||||
- name: Build frontend
|
||||
run: cd src/frontend && npm run compile && npm run build
|
||||
- name: Write version file - SHA
|
||||
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
|
||||
- name: Write version file - TAG
|
||||
run: cd src/backend/InvenTree/web/static/web/.vite && echo "${{ github.ref_name }}" > tag.txt
|
||||
- name: Zip frontend
|
||||
run: |
|
||||
cd src/backend/InvenTree/web/static/web
|
||||
|
||||
@@ -32,7 +32,7 @@ dependencies:
|
||||
- gettext
|
||||
- nginx
|
||||
- jq
|
||||
- libffi7
|
||||
- "libffi7 | libffi8"
|
||||
targets:
|
||||
ubuntu-20.04: true
|
||||
debian-11: true
|
||||
|
||||
@@ -75,6 +75,7 @@ root_command() {
|
||||
;;
|
||||
"Debian GNU/Linux" | "debian gnu/linux" | Raspbian)
|
||||
if [[ $VER == "12" ]]; then
|
||||
DIST_VER="11"
|
||||
SUPPORTED=true
|
||||
elif [[ $VER == "11" ]]; then
|
||||
SUPPORTED=true
|
||||
|
||||
@@ -5,33 +5,41 @@
|
||||
|
||||
set -eu
|
||||
|
||||
VERSION="$APP_PKG_VERSION-$APP_PKG_ITERATION"
|
||||
echo "Setting VERSION information to $VERSION"
|
||||
echo "$VERSION" > VERSION
|
||||
|
||||
# The sha is the second element in APP_PKG_ITERATION
|
||||
REPO="inventree/InvenTree"
|
||||
VERSION="$APP_PKG_VERSION-$APP_PKG_ITERATION"
|
||||
SHA=$(echo $APP_PKG_ITERATION | cut -d'.' -f2)
|
||||
|
||||
# Download info
|
||||
echo "Getting info from github for commit $SHA"
|
||||
curl -L \
|
||||
echo "INFO collection | Getting info from github for commit $SHA"
|
||||
curl -L -s -f \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/InvenTree/InvenTree/commits/$SHA > commit.json
|
||||
curl -L \
|
||||
https://api.github.com/repos/$REPO/commits/$SHA > commit.json
|
||||
echo "INFO collection | Got commit.json with size $(wc -c commit.json)"
|
||||
curl -L -s -f \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/InvenTree/InvenTree/commits/$SHA/branches-where-head > branches.json
|
||||
https://api.github.com/repos/$REPO/commits/$SHA/branches-where-head > branches.json
|
||||
echo "INFO collection | Got branches.json with size $(wc -c branches.json)"
|
||||
curl -L -s -f \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/$REPO/commits/$APP_PKG_VERSION > tag.json
|
||||
echo "INFO collection | Got tag.json with size $(wc -c tag.json)"
|
||||
|
||||
# Extract info
|
||||
echo "Extracting info from github"
|
||||
echo "INFO extract | Extracting info from github"
|
||||
DATE=$(jq -r '.commit.committer.date' commit.json)
|
||||
BRANCH=$(jq -r '.[].name' branches.json)
|
||||
NODE_ID=$(jq -r '.node_id' commit.json)
|
||||
SIGNATURE=$(jq -r '.commit.verification.signature' commit.json)
|
||||
FULL_SHA=$(jq -r '.sha' commit.json)
|
||||
|
||||
echo "Write VERSION information"
|
||||
echo "INFO write | Write VERSION information"
|
||||
echo "$VERSION" > VERSION
|
||||
echo "INVENTREE_COMMIT_HASH='$SHA'" >> VERSION
|
||||
echo "INVENTREE_COMMIT_SHA='$FULL_SHA'" >> VERSION
|
||||
echo "INVENTREE_COMMIT_DATE='$DATE'" >> VERSION
|
||||
echo "INVENTREE_PKG_INSTALLER='PKG'" >> VERSION
|
||||
echo "INVENTREE_PKG_BRANCH='$BRANCH'" >> VERSION
|
||||
@@ -39,5 +47,22 @@ echo "INVENTREE_PKG_TARGET='$TARGET'" >> VERSION
|
||||
echo "NODE_ID='$NODE_ID'" >> VERSION
|
||||
echo "SIGNATURE='$SIGNATURE'" >> VERSION
|
||||
|
||||
echo "Written VERSION information"
|
||||
echo "INFO write | Written VERSION information"
|
||||
echo "### VERSION ###"
|
||||
cat VERSION
|
||||
echo "### VERSION ###"
|
||||
|
||||
# Try to get frontend
|
||||
echo "INFO frontend | Trying to get frontend"
|
||||
# Check if tag sha is the same as the commit sha
|
||||
TAG_SHA=$(jq -r '.sha' tag.json)
|
||||
if [ "$TAG_SHA" != "$FULL_SHA" ]; then
|
||||
echo "INFO frontend | Tag sha '$TAG_SHA' is not the same as commit sha $FULL_SHA, can not download frontend"
|
||||
else
|
||||
echo "INFO frontend | Getting frontend from github via tag"
|
||||
curl https://github.com/$REPO/releases/download/$APP_PKG_VERSION/frontend-build.zip -L -O -f
|
||||
mkdir -p src/backend/InvenTree/web/static
|
||||
echo "INFO frontend | Unzipping frontend"
|
||||
unzip -qq frontend-build.zip -d src/backend/InvenTree/web/static/web
|
||||
echo "INFO frontend | Unzipped frontend"
|
||||
fi
|
||||
|
||||
4
docs/.gitignore
vendored
4
docs/.gitignore
vendored
@@ -13,6 +13,10 @@ site/
|
||||
# Generated API schema files
|
||||
docs/api/schema/*.yml
|
||||
|
||||
# Temporary cache files
|
||||
url_cache.txt
|
||||
invoke-commands.txt
|
||||
|
||||
# Temp files
|
||||
releases.json
|
||||
versions.json
|
||||
|
||||
@@ -96,7 +96,7 @@ The HEAD of the "stable" branch represents the latest stable release code.
|
||||
|
||||
## API versioning
|
||||
|
||||
The [API version](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/InvenTree/api_version.py) needs to be bumped every time when the API is changed.
|
||||
The [API version]({{ sourcefile("src/backend/InvenTree/InvenTree/api_version.py") }}) needs to be bumped every time when the API is changed.
|
||||
|
||||
## Environment
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ For further information, read more about [installing plugins](./plugins/install.
|
||||
|
||||
### Plugin Base Class
|
||||
|
||||
Custom plugins must inherit from the [InvenTreePlugin class](https://github.com/inventree/InvenTree/blob/2d1776a151721d65d0ae007049d358085b2fcfd5/InvenTree/plugin/plugin.py#L204). Any plugins installed via the methods outlined above will be "discovered" when the InvenTree server launches.
|
||||
Custom plugins must inherit from the [InvenTreePlugin class]({{ sourcefile("src/backend/InvenTree/plugin/plugin.py") }}). Any plugins installed via the methods outlined above will be "discovered" when the InvenTree server launches.
|
||||
|
||||
!!! warning "Namechange"
|
||||
The name of the base class was changed with `0.7.0` from `IntegrationPluginBase` to `InvenTreePlugin`. While the old name is still available till `0.8.0` we strongly suggest upgrading your plugins. Deprecation warnings are raised if the old name is used.
|
||||
@@ -28,7 +28,7 @@ Please read all release notes and watch out for warnings - we generally provide
|
||||
|
||||
#### Plugins
|
||||
|
||||
General classes and mechanisms are provided under the `plugin` [namespaces](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/plugin/__init__.py). These include:
|
||||
General classes and mechanisms are provided under the `plugin` [namespaces]({{ sourcefile("src/backend/InvenTree/plugin/__init__.py") }}). These include:
|
||||
|
||||
```python
|
||||
# Management objects
|
||||
@@ -44,7 +44,7 @@ MixinNotImplementedError # Is raised if a mixin was not implemented (core mec
|
||||
|
||||
#### Mixins
|
||||
|
||||
Mixins are split up internally to keep the source tree clean and enable better testing separation. All public APIs that should be used are exposed under `plugin.mixins`. These include all built-in mixins and notification methods. An up-to-date reference can be found in the source code (current master can be [found here](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/plugin/mixins/__init__.py)).
|
||||
Mixins are split up internally to keep the source tree clean and enable better testing separation. All public APIs that should be used are exposed under `plugin.mixins`. These include all built-in mixins and notification methods. An up-to-date reference can be found in the source code [can be found here]({{ sourcefile("src/backend/InvenTree/plugin/mixins/__init__.py") }}).
|
||||
|
||||
#### Models and other internal InvenTree APIs
|
||||
|
||||
@@ -72,7 +72,7 @@ MIN_VERSION = None # Lowest InvenTree version number that is supported by the p
|
||||
MAX_VERSION = None # Highest InvenTree version number that is supported by the plugin
|
||||
```
|
||||
|
||||
Refer to the [sample plugins](https://github.com/inventree/InvenTree/tree/master/src/backend/InvenTree/plugin/samples) for further examples.
|
||||
Refer to the [sample plugins]({{ sourcedir("src/backend/InvenTree/plugin/samples") }}) for further examples.
|
||||
|
||||
### Plugin Config
|
||||
|
||||
|
||||
@@ -28,4 +28,4 @@ If a locate plugin is installed and activated, the [InvenTree mobile app](../../
|
||||
|
||||
### Implementation
|
||||
|
||||
Refer to the [InvenTree source code](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/plugin/samples/locate/locate_sample.py) for a simple implementation example.
|
||||
Refer to the [InvenTree source code]({{ sourcefile("src/backend/InvenTree/plugin/samples/locate/locate_sample.py") }}) for a simple implementation example.
|
||||
|
||||
@@ -65,7 +65,7 @@ Additionally, add the following imports after the extended line.
|
||||
#### Blocks
|
||||
The page_base file is split into multiple sections called blocks. This allows you to implement sections of the webpage while getting many items like navbars, sidebars, and general layout provided for you.
|
||||
|
||||
The current default page base can be found [here](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/templates/page_base.html). Look through this file to determine overridable blocks. The [stock app](https://github.com/inventree/InvenTree/tree/master/src/backend/InvenTree/stock) offers a great example of implementing these blocks.
|
||||
The current default page base can be found [here]({{ sourcefile("src/backend/InvenTree/templates/page_base.html") }}). Look through this file to determine overridable blocks. The [stock app]({{ sourcedir("src/backend/InvenTree/stock") }}) offers a great example of implementing these blocks.
|
||||
|
||||
!!! warning "Sidebar Block"
|
||||
You may notice that implementing the `sidebar` block doesn't initially work. Be sure to enable the sidebar using JavaScript. This can be achieved by appending the following code, replacing `label` with a label of your choosing, to the end of your template file.
|
||||
|
||||
@@ -9,7 +9,7 @@ The `ValidationMixin` class enables plugins to perform custom validation of obje
|
||||
Any of the methods described below can be implemented in a custom plugin to provide functionality as required.
|
||||
|
||||
!!! info "More Info"
|
||||
For more information on any of the methods described below, refer to the InvenTree source code. [A working example is available as a starting point](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/plugin/samples/integration/validation_sample.py).
|
||||
For more information on any of the methods described below, refer to the InvenTree source code. [A working example is available as a starting point]({{ sourcefile("src/backend/InvenTree/plugin/samples/integration/validation_sample.py") }}).
|
||||
|
||||
!!! info "Multi Plugin Support"
|
||||
It is possible to have multiple plugins loaded simultaneously which support validation methods. For example when validating a field, if one plugin returns a null value (`None`) then the *next* plugin (if available) will be queried.
|
||||
|
||||
@@ -12,7 +12,7 @@ Some common functions are provided for use in custom report and label templates.
|
||||
```
|
||||
|
||||
!!! tip "Use the Source, Luke"
|
||||
To see the full range of available helper functions, refer to the source file [report.py](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/report/templatetags/report.py) where these functions are defined!
|
||||
To see the full range of available helper functions, refer to the source file [report.py]({{ sourcefile("src/backend/InvenTree/report/templatetags/report.py") }}) where these functions are defined!
|
||||
|
||||
## Assigning Variables
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ To that end, we have implemented a number of security measures over the years, w
|
||||
The InvenTree project is managed by a small team of developers, who are responsible for the ongoing development and maintenance of the software. Two geographically distributed users have administrative access to the InvenTree codebase. Merges are only done by one of these two users, the maintainer Oliver.
|
||||
InvenTree is open-source, and we welcome contributions from the community. However, all contributions are reviewed and scrutinised before being merged into the codebase.
|
||||
|
||||
We provide a written [Security Policy](https://github.com/inventree/InvenTree/blob/master/SECURITY.md) in our main repo to ensure that all security issues are handled in a timely manner.
|
||||
We provide a written [Security Policy]({{ sourcefile("SECURITY.md") }}) in our main repo to ensure that all security issues are handled in a timely manner.
|
||||
|
||||
If we become aware of a security issue, we will take immediate action to address the issue, and will provide a public disclosure of the issue once it has been resolved. We support assigning CVEs to security issues where appropriate. Our past security advisories can be found [here](https://github.com/inventree/InvenTree/security/advisories).
|
||||
If we become aware of a security issue, we will take immediate action to address the issue, and will provide a public disclosure of the issue once it has been resolved. We support assigning CVEs to security issues where appropriate. Our [past security advisories can be found here](https://github.com/inventree/InvenTree/security/advisories).
|
||||
|
||||
## Technical measures
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ The InvenTree server tries to locate the `config.yaml` configuration file on sta
|
||||
!!! tip "Config File Location"
|
||||
When the InvenTree server boots, it will report the location where it expects to find the configuration file
|
||||
|
||||
The configuration file *template* can be found on [GitHub](https://github.com/inventree/InvenTree/blob/0.15.x/src/backend/InvenTree/config_template.yaml)
|
||||
The configuration file *template* can be found on [GitHub]({{ sourcefile("src/backend/InvenTree/config_template.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.
|
||||
|
||||
@@ -27,13 +27,13 @@ The following guide provides a streamlined production InvenTree installation, wi
|
||||
|
||||
### Required Files
|
||||
|
||||
The following files required for this setup are provided with the InvenTree source, located in the `/contrib/container/` directory of the [InvenTree source code](https://github.com/inventree/InvenTree/tree/master/contrib/container/):
|
||||
The following files required for this setup are provided with the InvenTree source, located in the `contrib/container/` directory of the [InvenTree source code]({{ sourcedir("/contrib/container/") }}):
|
||||
|
||||
| Filename | Description |
|
||||
| --- | --- |
|
||||
| [docker-compose.yml](https://raw.githubusercontent.com/inventree/InvenTree/master/contrib/container/docker-compose.yml)| The docker compose script |
|
||||
| [.env](https://raw.githubusercontent.com/inventree/InvenTree/master/contrib/container/.env) | Environment variables |
|
||||
| [Caddyfile](https://raw.githubusercontent.com/inventree/InvenTree/master/contrib/container/Caddyfile) | Caddy configuration file |
|
||||
| [docker-compose.yml]({{ sourcefile("contrib/container/docker-compose.yml", raw=True) }}) | The docker compose script |
|
||||
| [.env]({{ sourcefile("contrib/container/.env", raw=True) }}) | Environment variables |
|
||||
| [Caddyfile]({{ sourcefile("contrib/container/Caddyfile", raw=True) }}) | Caddy configuration file |
|
||||
|
||||
Download these files to a directory on your local machine.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Install required system packages (as superuser):
|
||||
The following packages are required on a debian system. A different distribution may require a slightly different set of packages
|
||||
|
||||
!!! info "Python Version"
|
||||
InvenTree requires a modern Python version check [here](https://github.com/inventree/InvenTree/blob/master/CONTRIBUTING.md#target-version) for the current minimums.
|
||||
InvenTree requires a modern Python version [check here]({{ sourcefile("CONTRIBUTING.md") }}) for the current minimums.
|
||||
|
||||
```
|
||||
sudo apt-get update
|
||||
|
||||
@@ -84,6 +84,12 @@ To display a list of the available InvenTree administration actions, run the fol
|
||||
invoke --list
|
||||
```
|
||||
|
||||
This provides a list of the available invoke commands - also displayed below:
|
||||
|
||||
```
|
||||
{{ invoke_commands() }}
|
||||
```
|
||||
|
||||
### Virtual Environment
|
||||
|
||||
Installing the required Python packages inside a virtual environment allows a local install separate to the system-wide Python installation. While not strictly necessary, using a virtual environment is **highly recommended** as it prevents conflicts between the different Python installations.
|
||||
|
||||
160
docs/main.py
160
docs/main.py
@@ -1,11 +1,171 @@
|
||||
"""Main entry point for the documentation build process."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
|
||||
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:
|
||||
mkdocs_config = yaml.safe_load(f)
|
||||
repo_name = mkdocs_config['repo_name']
|
||||
|
||||
if raw:
|
||||
return f'https://raw.githubusercontent.com/{repo_name}'
|
||||
else:
|
||||
return f'https://github.com/{repo_name}'
|
||||
|
||||
|
||||
def check_link(url) -> bool:
|
||||
"""Check that a provided URL is valid.
|
||||
|
||||
We allow a number attempts and a lengthy timeout,
|
||||
as we do not want false negatives.
|
||||
"""
|
||||
CACHE_FILE = os.path.join(os.path.dirname(__file__), 'url_cache.txt')
|
||||
|
||||
# Keep a local cache file of URLs we have already checked
|
||||
if os.path.exists(CACHE_FILE):
|
||||
with open(CACHE_FILE, 'r') as f:
|
||||
cache = f.read().splitlines()
|
||||
|
||||
if url in cache:
|
||||
return True
|
||||
|
||||
attempts = 5
|
||||
|
||||
while attempts > 0:
|
||||
response = requests.head(url, timeout=5000)
|
||||
if response.status_code == 200:
|
||||
# Update the cache file
|
||||
with open(CACHE_FILE, 'a') as f:
|
||||
f.write(f'{url}\n')
|
||||
|
||||
return True
|
||||
|
||||
attempts -= 1
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_build_enviroment() -> str:
|
||||
"""Returns the branch we are currently building on, based on the environment variables of the various CI platforms."""
|
||||
# Check if we are in ReadTheDocs
|
||||
if os.environ.get('READTHEDOCS') == 'True':
|
||||
return os.environ.get('READTHEDOCS_GIT_IDENTIFIER')
|
||||
# We are in GitHub Actions
|
||||
elif os.environ.get('GITHUB_ACTIONS') == 'true':
|
||||
return os.environ.get('GITHUB_REF')
|
||||
else:
|
||||
return 'master'
|
||||
|
||||
|
||||
def define_env(env):
|
||||
"""Define custom environment variables for the documentation build process."""
|
||||
|
||||
@env.macro
|
||||
def sourcedir(dirname, branch=None):
|
||||
"""Return a link to a directory within the source code repository.
|
||||
|
||||
Arguments:
|
||||
- dirname: The name of the directory to link to (relative to the top-level directory)
|
||||
|
||||
Returns:
|
||||
- A fully qualified URL to the source code directory on GitHub
|
||||
|
||||
Raises:
|
||||
- FileNotFoundError: If the directory does not exist, or the generated URL is invalid
|
||||
"""
|
||||
if branch == None:
|
||||
branch = get_build_enviroment()
|
||||
|
||||
if dirname.startswith('/'):
|
||||
dirname = dirname[1:]
|
||||
|
||||
# This file exists at ./docs/main.py, so any directory we link to must be relative to the top-level directory
|
||||
here = os.path.dirname(__file__)
|
||||
root = os.path.abspath(os.path.join(here, '..'))
|
||||
|
||||
directory = os.path.join(root, dirname)
|
||||
directory = os.path.abspath(directory)
|
||||
|
||||
if not os.path.exists(directory) or not os.path.isdir(directory):
|
||||
raise FileNotFoundError(f'Source directory {dirname} does not exist.')
|
||||
|
||||
repo_url = get_repo_url()
|
||||
|
||||
url = f'{repo_url}/tree/{branch}/{dirname}'
|
||||
|
||||
# Check that the URL exists before returning it
|
||||
if not check_link(url):
|
||||
raise FileNotFoundError(f'URL {url} does not exist.')
|
||||
|
||||
return url
|
||||
|
||||
@env.macro
|
||||
def sourcefile(filename, branch=None, raw=False):
|
||||
"""Return a link to a file within the source code repository.
|
||||
|
||||
Arguments:
|
||||
- filename: The name of the file to link to (relative to the top-level directory)
|
||||
|
||||
Returns:
|
||||
- A fully qualified URL to the source code file on GitHub
|
||||
|
||||
Raises:
|
||||
- FileNotFoundError: If the file does not exist, or the generated URL is invalid
|
||||
"""
|
||||
if branch == None:
|
||||
branch = get_build_enviroment()
|
||||
|
||||
if filename.startswith('/'):
|
||||
filename = filename[1:]
|
||||
|
||||
# This file exists at ./docs/main.py, so any file we link to must be relative to the top-level directory
|
||||
here = os.path.dirname(__file__)
|
||||
root = os.path.abspath(os.path.join(here, '..'))
|
||||
|
||||
file_path = os.path.join(root, filename)
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f'Source file {filename} does not exist.')
|
||||
|
||||
repo_url = get_repo_url(raw=raw)
|
||||
|
||||
if raw:
|
||||
url = f'{repo_url}/{branch}/{filename}'
|
||||
else:
|
||||
url = f'{repo_url}/blob/{branch}/{filename}'
|
||||
|
||||
# Check that the URL exists before returning it
|
||||
if not check_link(url):
|
||||
raise FileNotFoundError(f'URL {url} does not exist.')
|
||||
|
||||
return url
|
||||
|
||||
@env.macro
|
||||
def invoke_commands():
|
||||
"""Provides an output of the available commands."""
|
||||
here = os.path.dirname(__file__)
|
||||
base = os.path.join(here, '..')
|
||||
base = os.path.abspath(base)
|
||||
tasks = os.path.join(base, 'tasks.py')
|
||||
output = os.path.join(here, 'invoke-commands.txt')
|
||||
|
||||
command = f'invoke -f {tasks} --list > {output}'
|
||||
|
||||
assert subprocess.call(command, shell=True) == 0
|
||||
|
||||
with open(output, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
return content
|
||||
|
||||
@env.macro
|
||||
def listimages(subdir):
|
||||
"""Return a listing of all asset files in the provided subdir."""
|
||||
|
||||
@@ -19,7 +19,7 @@ from dulwich.repo import NotGitRepository, Repo
|
||||
from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION
|
||||
|
||||
# InvenTree software version
|
||||
INVENTREE_SW_VERSION = '0.15.6'
|
||||
INVENTREE_SW_VERSION = '0.15.8'
|
||||
|
||||
# Discover git
|
||||
try:
|
||||
|
||||
@@ -299,15 +299,12 @@ def annotate_default_location(reference=''):
|
||||
rght__gt=OuterRef(f'{reference}rght'),
|
||||
level__lte=OuterRef(f'{reference}level'),
|
||||
parent__isnull=False,
|
||||
)
|
||||
default_location__isnull=False,
|
||||
).order_by('-level')
|
||||
|
||||
return Coalesce(
|
||||
F(f'{reference}default_location'),
|
||||
Subquery(
|
||||
subquery.order_by('-level')
|
||||
.filter(default_location__isnull=False)
|
||||
.values('default_location')
|
||||
),
|
||||
Subquery(subquery.values('default_location')[:1]),
|
||||
Value(None),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
|
||||
@@ -505,6 +505,82 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
|
||||
self.assertEqual(item['parent'], parent)
|
||||
self.assertEqual(item['subcategories'], subcategories)
|
||||
|
||||
def test_part_category_default_location(self):
|
||||
"""Test default location propagation through location trees."""
|
||||
"""Making a tree structure like this:
|
||||
main
|
||||
loc 2
|
||||
sub1
|
||||
sub2
|
||||
loc 3
|
||||
sub3
|
||||
loc 4
|
||||
sub4
|
||||
sub5
|
||||
Expected behaviour:
|
||||
main parent loc: Out of test scope. Parent category data not controlled by the test
|
||||
sub1 parent loc: loc 2
|
||||
sub2 parent loc: loc 2
|
||||
sub3 parent loc: loc 3
|
||||
sub4 parent loc: loc 4
|
||||
sub5 parent loc: loc 3
|
||||
"""
|
||||
main = PartCategory.objects.create(
|
||||
name='main',
|
||||
parent=PartCategory.objects.first(),
|
||||
default_location=StockLocation.objects.get(id=2),
|
||||
)
|
||||
sub1 = PartCategory.objects.create(name='sub1', parent=main)
|
||||
sub2 = PartCategory.objects.create(
|
||||
name='sub2', parent=sub1, default_location=StockLocation.objects.get(id=3)
|
||||
)
|
||||
sub3 = PartCategory.objects.create(
|
||||
name='sub3', parent=sub2, default_location=StockLocation.objects.get(id=4)
|
||||
)
|
||||
sub4 = PartCategory.objects.create(name='sub4', parent=sub3)
|
||||
sub5 = PartCategory.objects.create(name='sub5', parent=sub2)
|
||||
part = Part.objects.create(name='test', category=sub4)
|
||||
PartCategory.objects.rebuild()
|
||||
|
||||
# This query will trigger an internal server error if annotation results are not limited to 1
|
||||
url = reverse('api-part-list')
|
||||
response = self.get(url, expected_code=200)
|
||||
|
||||
# sub1, expect main to be propagated
|
||||
url = reverse('api-part-category-detail', kwargs={'pk': sub1.pk})
|
||||
response = self.get(url, expected_code=200)
|
||||
self.assertEqual(
|
||||
response.data['parent_default_location'], main.default_location.pk
|
||||
)
|
||||
|
||||
# sub2, expect main to be propagated
|
||||
url = reverse('api-part-category-detail', kwargs={'pk': sub2.pk})
|
||||
response = self.get(url, expected_code=200)
|
||||
self.assertEqual(
|
||||
response.data['parent_default_location'], main.default_location.pk
|
||||
)
|
||||
|
||||
# sub3, expect sub2 to be propagated
|
||||
url = reverse('api-part-category-detail', kwargs={'pk': sub3.pk})
|
||||
response = self.get(url, expected_code=200)
|
||||
self.assertEqual(
|
||||
response.data['parent_default_location'], sub2.default_location.pk
|
||||
)
|
||||
|
||||
# sub4, expect sub3 to be propagated
|
||||
url = reverse('api-part-category-detail', kwargs={'pk': sub4.pk})
|
||||
response = self.get(url, expected_code=200)
|
||||
self.assertEqual(
|
||||
response.data['parent_default_location'], sub3.default_location.pk
|
||||
)
|
||||
|
||||
# sub5, expect sub2 to be propagated
|
||||
url = reverse('api-part-category-detail', kwargs={'pk': sub5.pk})
|
||||
response = self.get(url, expected_code=200)
|
||||
self.assertEqual(
|
||||
response.data['parent_default_location'], sub2.default_location.pk
|
||||
)
|
||||
|
||||
|
||||
class PartOptionsAPITest(InvenTreeAPITestCase):
|
||||
"""Tests for the various OPTIONS endpoints in the /part/ API.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from django import template
|
||||
from django.conf import settings as djangosettings
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
@@ -96,3 +97,17 @@ def notification_list(context, *args, **kwargs):
|
||||
}
|
||||
for a in storage.liste
|
||||
]
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def plugin_static(file: str, **kwargs):
|
||||
"""Return the URL for a static file within a plugin.
|
||||
|
||||
Arguments:
|
||||
file: The path to the file within the plugin static directory
|
||||
|
||||
Keyword Arguments:
|
||||
plugin: The plugin slug (optional, will be inferred from the context if not provided)
|
||||
|
||||
"""
|
||||
return static(file)
|
||||
|
||||
52
tasks.py
52
tasks.py
@@ -1221,6 +1221,34 @@ def frontend_download(
|
||||
|
||||
handle_extract(dst.name)
|
||||
|
||||
def check_already_current(tag=None, sha=None):
|
||||
"""Check if the currently available frontend is already the requested one."""
|
||||
ref = 'tag' if tag else 'commit'
|
||||
|
||||
if tag:
|
||||
current = managePyDir().joinpath('web', 'static', 'web', '.vite', 'tag.txt')
|
||||
elif sha:
|
||||
current = managePyDir().joinpath('web', 'static', 'web', '.vite', 'sha.txt')
|
||||
else:
|
||||
raise ValueError('Either tag or sha needs to be set')
|
||||
|
||||
if not current.exists():
|
||||
print(
|
||||
f'Current frontend information for {ref} is not available - this is expected in some cases'
|
||||
)
|
||||
return False
|
||||
|
||||
current_content = current.read_text().strip()
|
||||
ref_value = tag or sha
|
||||
if current_content == ref_value:
|
||||
print(f'Frontend {ref} is already `{ref_value}`')
|
||||
return True
|
||||
else:
|
||||
print(
|
||||
f'Frontend {ref} is not expected `{ref_value}` but `{current_content}` - new version will be downloaded'
|
||||
)
|
||||
return False
|
||||
|
||||
# if zip file is specified, try to extract it directly
|
||||
if file:
|
||||
handle_extract(file)
|
||||
@@ -1237,8 +1265,24 @@ def frontend_download(
|
||||
['git', 'rev-parse', 'HEAD'], encoding='utf-8'
|
||||
).strip()
|
||||
except Exception:
|
||||
print("[ERROR] Cannot get current ref via 'git rev-parse HEAD'")
|
||||
return
|
||||
# .deb Packages contain extra information in the VERSION file
|
||||
version_file = localDir().joinpath('VERSION')
|
||||
if not version_file.exists():
|
||||
return
|
||||
from dotenv import dotenv_values # noqa: WPS433
|
||||
|
||||
content = dotenv_values(version_file)
|
||||
if (
|
||||
'INVENTREE_PKG_INSTALLER' in content
|
||||
and content['INVENTREE_PKG_INSTALLER'] == 'PKG'
|
||||
):
|
||||
ref = content.get('INVENTREE_COMMIT_SHA')
|
||||
print(
|
||||
f'[INFO] Running in package environment, got commit "{ref}" from VERSION file'
|
||||
)
|
||||
else:
|
||||
print("[ERROR] Cannot get current ref via 'git rev-parse HEAD'")
|
||||
return
|
||||
|
||||
if ref is None and tag is None:
|
||||
print('[ERROR] Either ref or tag needs to be set.')
|
||||
@@ -1246,6 +1290,8 @@ def frontend_download(
|
||||
if tag:
|
||||
tag = tag.lstrip('v')
|
||||
try:
|
||||
if check_already_current(tag=tag):
|
||||
return
|
||||
handle_download(
|
||||
f'https://github.com/{repo}/releases/download/{tag}/frontend-build.zip'
|
||||
)
|
||||
@@ -1261,6 +1307,8 @@ Then try continuing by running: invoke frontend-download --file <path-to-downloa
|
||||
return
|
||||
|
||||
if ref:
|
||||
if check_already_current(sha=ref):
|
||||
return
|
||||
# get workflow run from all workflow runs on that particular ref
|
||||
workflow_runs = requests.get(
|
||||
f'https://api.github.com/repos/{repo}/actions/runs?head_sha={ref}',
|
||||
|
||||
Reference in New Issue
Block a user