Merge branch 'master' of https://github.com/inventree/InvenTree into add-changelog

This commit is contained in:
Matthias Mair 2024-02-29 23:45:17 +01:00
commit 01b7ba30c9
No known key found for this signature in database
GPG Key ID: A593429DDA23B66A
152 changed files with 79008 additions and 69186 deletions

View File

@ -1,16 +1,11 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/python-3
{
"name": "InvenTree",
"build": {
"dockerfile": "../Dockerfile",
"context": "..",
"target": "devcontainer",
"args": {
"base_image": "mcr.microsoft.com/vscode/devcontainers/base:alpine-3.18",
"workspace": "${containerWorkspaceFolder}"
}
},
"name": "InvenTree devcontainer",
"dockerComposeFile": "docker-compose.yml",
"service": "inventree",
"overrideCommand": true,
"workspaceFolder": "/home/inventree/",
// Configure tool-specific properties.
"customizations": {
@ -42,37 +37,27 @@
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [5173, 8000],
"forwardPorts": [5173, 8000, 8080],
"portsAttributes": {
"5173": {
"label": "Vite server"
"label": "Vite Server"
},
"8000": {
"label": "InvenTree server"
"label": "InvenTree Server"
},
"8080": {
"label": "mkdocs server"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "./.devcontainer/postCreateCommand.sh ${containerWorkspaceFolder}",
"postCreateCommand": ".devcontainer/postCreateCommand.sh",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"containerUser": "vscode",
"remoteEnv": {
// InvenTree config
"INVENTREE_DEBUG": "True",
"INVENTREE_LOG_LEVEL": "INFO",
"INVENTREE_DB_ENGINE": "sqlite3",
"INVENTREE_DB_NAME": "${containerWorkspaceFolder}/dev/database.sqlite3",
"INVENTREE_MEDIA_ROOT": "${containerWorkspaceFolder}/dev/media",
"INVENTREE_STATIC_ROOT": "${containerWorkspaceFolder}/dev/static",
"INVENTREE_BACKUP_DIR": "${containerWorkspaceFolder}/dev/backup",
"INVENTREE_CONFIG_FILE": "${containerWorkspaceFolder}/dev/config.yaml",
"INVENTREE_SECRET_KEY_FILE": "${containerWorkspaceFolder}/dev/secret_key.txt",
"INVENTREE_PLUGINS_ENABLED": "True",
"INVENTREE_PLUGIN_DIR": "${containerWorkspaceFolder}/dev/plugins",
"INVENTREE_PLUGIN_FILE": "${containerWorkspaceFolder}/dev/plugins.txt",
// Python config
"PIP_USER": "no",

View File

@ -0,0 +1,38 @@
version: "3"
services:
db:
image: postgres:13
restart: unless-stopped
expose:
- 5432/tcp
volumes:
- ../dev:/var/lib/postgresql/data:z
environment:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password
inventree:
build:
context: ..
target: dev
args:
base_image: "mcr.microsoft.com/vscode/devcontainers/base:alpine-3.18"
workspace: "${containerWorkspaceFolder}"
data_dir: "dev"
volumes:
- ../:/home/inventree:z
environment:
INVENTREE_DEBUG: True
INVENTREE_DB_ENGINE: postgresql
INVENTREE_DB_NAME: inventree
INVENTREE_DB_HOST: db
INVENTREE_DB_USER: inventree_user
INVENTREE_DB_PASSWORD: inventree_password
INVENTREE_PLUGINS_ENABLED: True
INVENTREE_PY_ENV: /home/inventree/dev/venv
depends_on:
- db

View File

@ -1,20 +1,14 @@
#!/bin/bash
# Avoiding Dubious Ownership in Dev Containers for setup commands that use git
# Note that the local workspace directory is passed through as the first argument $1
git config --global --add safe.directory $1
# create folders
mkdir -p $1/dev/{commandhistory,plugins}
cd $1
git config --global --add safe.directory /home/inventree
# create venv
python3 -m venv $1/dev/venv
. $1/dev/venv/bin/activate
python3 -m venv /home/inventree/dev/venv --system-site-packages --upgrade-deps
. /home/inventree/dev/venv/bin/activate
# setup InvenTree server
pip install invoke
invoke update
invoke update -s
invoke setup-dev
invoke frontend-install

View File

@ -86,8 +86,8 @@ runs:
- name: Run invoke install
if: ${{ inputs.install == 'true' }}
shell: bash
run: invoke install
run: invoke install --uv
- name: Run invoke update
if: ${{ inputs.update == 'true' }}
shell: bash
run: invoke update
run: invoke update --uv

View File

@ -156,7 +156,7 @@ jobs:
sbom: true
provenance: false
target: production
tags: ${{ steps.meta.outputs.tags }}
tags: ${{ env.docker_tags }}
build-args: |
commit_hash=${{ env.git_commit_hash }}
commit_date=${{ env.git_commit_date }}

View File

@ -111,9 +111,12 @@ jobs:
pip install -r docs/requirements.txt
python docs/ci/check_mkdocs_config.py
- name: Check Links
run: |
pip install linkcheckmd requests
python -m linkcheckmd docs --recurse
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
folder-path: docs
config-file: docs/mlc_config.json
check-modified-files-only: 'yes'
use-quiet-mode: 'yes'
schema:
name: Tests - API Schema Documentation

2
.vscode/launch.json vendored
View File

@ -14,7 +14,7 @@
"justMyCode": true
},
{
"name": "Python: Django - 3rd party",
"name": "InvenTree Server - 3rd party",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/InvenTree/manage.py",

2
.vscode/tasks.json vendored
View File

@ -45,7 +45,7 @@
{
"label": "setup-test",
"type": "shell",
"command": "inv setup-test --path dev/inventree-demo-dataset",
"command": "inv setup-test -i --path dev/inventree-demo-dataset",
"problemMatcher": [],
},
{

View File

@ -1,263 +1,6 @@
### Contributing to InvenTree
Hi there, thank you for your interest in contributing!
Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase.
Please read our contribution guidelines, before submitting your first pull request to the InvenTree codebase.
## Quickstart
The following commands will get you quickly configure and run a development server, complete with a demo dataset to work with:
### Bare Metal
```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
```
### Docker
```bash
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
docker compose run inventree-dev-server invoke install
docker compose run inventree-dev-server invoke setup-test --dev
docker compose up -d
```
Read the [InvenTree setup documentation](https://docs.inventree.org/en/latest/start/intro/) for a complete installation reference guide.
### Setup Devtools
Run the following command to set up all toolsets for development.
```bash
invoke 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.
### Version Numbering
InvenTree version numbering follows the [semantic versioning](https://semver.org/) specification.
### Master Branch
The HEAD of the "main" or "master" branch of InvenTree represents the current "latest" state of code development.
- All feature branches are merged into master
- All bug fixes are merged into master
**No pushing to master:** New features must be submitted as a pull request from a separate branch (one branch per feature).
### Feature Branches
Feature branches should be branched *from* the *master* branch.
- One major feature per branch / pull request
- Feature pull requests are merged back *into* the master branch
- Features *may* also be merged into a release candidate branch
### Stable Branch
The HEAD of the "stable" branch represents the latest stable release code.
- Versioned releases are merged into the "stable" branch
- Bug fix branches are made *from* the "stable" branch
#### Release Candidate Branches
- Release candidate branches are made from master, and merged into stable.
- RC branches are targeted at a major/minor version e.g. "0.5"
- When a release candidate branch is merged into *stable*, the release is tagged
#### Bugfix Branches
- If a bug is discovered in a tagged release version of InvenTree, a "bugfix" or "hotfix" branch should be made *from* that tagged release
- When approved, the branch is merged back *into* stable, with an incremented PATCH number (e.g. 0.4.1 -> 0.4.2)
- The bugfix *must* also be cherry picked into the *master* branch.
## API versioning
The [API version](https://github.com/inventree/InvenTree/blob/master/InvenTree/InvenTree/api_version.py) needs to be bumped every time when the API is changed.
## Environment
### Target version
We are currently targeting:
| Name | Minimum version | Note |
|---|---| --- |
| Python | 3.9 | |
| Django | 3.2 | |
| Node | 18 | Only needed for frontend development |
### Auto creating updates
The following tools can be used to auto-upgrade syntax that was depreciated in new versions:
```bash
pip install pyupgrade
pip install django-upgrade
```
To update the codebase run the following script.
```bash
pyupgrade `find . -name "*.py"`
django-upgrade --target-version 3.2 `find . -name "*.py"`
```
## Credits
If you add any new dependencies / libraries, they need to be added to [the docs](https://github.com/inventree/inventree/blob/master/docs/docs/credits.md). Please try to do that as timely as possible.
## Migration Files
Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `invoke migrate` and commit the migration files before submitting the PR.
*Note: A github action checks for unstaged migration files and will reject the PR if it finds any!*
## Unit Testing
Any new code should be covered by unit tests - a submitted PR may not be accepted if the code coverage for any new features is insufficient, or the overall code coverage is decreased.
The InvenTree code base makes use of [GitHub actions](https://github.com/features/actions) to run a suite of automated tests against the code base every time a new pull request is received. These actions include (but are not limited to):
- Checking Python and Javascript code against standard style guides
- Running unit test suite
- Automated building and pushing of docker images
- Generating translation files
The various github actions can be found in the `./github/workflows` directory
### Run tests locally
To run test locally, use:
```
invoke test
```
To run only partial tests, for example for a module use:
```
invoke test --runtest order
```
To see all the available options:
```
invoke test --help
```
## Code Style
Code style is automatically checked as part of the project's CI pipeline on GitHub. This means that any pull requests which do not conform to the style guidelines will fail CI checks.
### Backend Code
Backend code (Python) is checked against the [PEP style guidelines](https://peps.python.org/pep-0008/). Please write docstrings for each function and class - we follow the [google doc-style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for python.
### Frontend Code
Frontend code (Javascript) is checked using [eslint](https://eslint.org/). While docstrings are not enforced for front-end code, good code documentation is encouraged!
### Running Checks Locally
If you have followed the setup devtools procedure, then code style checking is performend automatically whenever you commit changes to the code.
### Django templates
Django are checked by [djlint](https://github.com/Riverside-Healthcare/djlint) through pre-commit.
The following rules out of the [default set](https://djlint.com/docs/linter/) are not applied:
```bash
D018: (Django) Internal links should use the { % url ... % } pattern
H006: Img tag should have height and width attributes
H008: Attributes should be double quoted
H021: Inline styles should be avoided
H023: Do not use entity references
H025: Tag seems to be an orphan
H030: Consider adding a meta description
H031: Consider adding meta keywords
T002: Double quotes should be used in tags
```
## Documentation
New features or updates to existing features should be accompanied by user documentation.
## Translations
Any user-facing strings *must* be passed through the translation engine.
- InvenTree code is written in English
- User translatable strings are provided in English as the primary language
- Secondary language translations are provided [via Crowdin](https://crowdin.com/project/inventree)
*Note: Translation files are updated via GitHub actions - you do not need to compile translations files before submitting a pull request!*
### Python Code
For strings exposed via Python code, use the following format:
```python
from django.utils.translation import gettext_lazy as _
user_facing_string = _('This string will be exposed to the translation engine!')
```
### Templated Strings
HTML and javascript files are passed through the django templating engine. Translatable strings are implemented as follows:
```html
{ % load i18n % }
<span>{ % trans "This string will be translated" % } - this string will not!</span>
```
## Github use
### Tags
The tags describe issues and PRs in multiple areas:
| Area | Name | Description |
| --- | --- | --- |
| Triage Labels | | |
| | triage:not-checked | Item was not checked by the core team |
| | triage:not-approved | Item is not green-light by maintainer |
| Type Labels | | |
| | breaking | Indicates a major update or change which breaks compatibility |
| | bug | Identifies a bug which needs to be addressed |
| | dependency | Relates to a project dependency |
| | duplicate | Duplicate of another issue or PR |
| | enhancement | This is an suggested enhancement, extending the functionality of an existing feature |
| | experimental | This is a new *experimental* feature which needs to be enabled manually |
| | feature | This is a new feature, introducing novel functionality |
| | help wanted | Assistance required |
| | invalid | This issue or PR is considered invalid |
| | inactive | Indicates lack of activity |
| | migration | Database migration, requires special attention |
| | question | This is a question |
| | roadmap | This is a roadmap feature with no immediate plans for implementation |
| | security | Relates to a security issue |
| | starter | Good issue for a developer new to the project |
| | wontfix | No work will be done against this issue or PR |
| Feature Labels | | |
| | API | Relates to the API |
| | barcode | Barcode scanning and integration |
| | build | Build orders |
| | importer | Data importing and processing |
| | order | Purchase order and sales orders |
| | part | Parts |
| | plugin | Plugin ecosystem |
| | pricing | Pricing functionality |
| | report | Report generation |
| | stock | Stock item management |
| | user interface | User interface |
| Ecosystem Labels | | |
| | backport | Tags that the issue will be backported to a stable branch as a bug-fix |
| | demo | Relates to the InvenTree demo server or dataset |
| | docker | Docker / docker-compose |
| | CI | CI / unit testing ecosystem |
| | refactor | Refactoring existing code |
| | setup | Relates to the InvenTree setup / installation process |
Refer to our [contribution guidelines](https://docs.inventree.org/en/latest/develop/contributing/) for more information!

View File

@ -17,6 +17,8 @@ ARG commit_tag=""
ARG commit_hash=""
ARG commit_date=""
ARG data_dir="data"
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
ENV INVOKE_RUN_SHELL="/bin/ash"
@ -27,7 +29,7 @@ ENV INVENTREE_DOCKER="true"
# InvenTree paths
ENV INVENTREE_HOME="/home/inventree"
ENV INVENTREE_MNG_DIR="${INVENTREE_HOME}/InvenTree"
ENV INVENTREE_DATA_DIR="${INVENTREE_HOME}/data"
ENV INVENTREE_DATA_DIR="${INVENTREE_HOME}/${data_dir}"
ENV INVENTREE_STATIC_ROOT="${INVENTREE_DATA_DIR}/static"
ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DATA_DIR}/media"
ENV INVENTREE_BACKUP_DIR="${INVENTREE_DATA_DIR}/backup"
@ -62,7 +64,9 @@ RUN apk add --no-cache \
# Image format support
libjpeg libwebp zlib \
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12
py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils openldap && \
py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils openldap \
# Core database packages
postgresql13-client && \
# fonts
apk --update --upgrade --no-cache add fontconfig ttf-freefont font-noto terminus-font && fc-cache -f
@ -92,7 +96,7 @@ FROM inventree_base as prebuild
ENV PATH=/root/.local/bin:$PATH
RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
pip install --user uv --no-cache-dir && uv pip install -r base_requirements.txt -r requirements.txt --no-cache && \
pip install --user uv --no-cache-dir && pip install -r base_requirements.txt -r requirements.txt --no-cache && \
apk --purge del .build-deps
# Frontend builder image:
@ -137,7 +141,7 @@ EXPOSE 5173
# Install packages required for building python packages
RUN ./install_build_packages.sh
RUN pip install uv --no-cache-dir && uv pip install -r base_requirements.txt --no-cache
RUN pip install uv --no-cache-dir && pip install -r base_requirements.txt --no-cache
# Install nodejs / npm / yarn
@ -160,10 +164,3 @@ ENTRYPOINT ["/bin/ash", "./docker/init.sh"]
# Launch the development server
CMD ["invoke", "server", "-a", "${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}"]
# Image target for devcontainer
FROM dev as devcontainer
ARG workspace="/workspaces/InvenTree"
WORKDIR ${WORKSPACE}

View File

@ -1,11 +1,17 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 177
INVENTREE_API_VERSION = 178
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v178 - 2024-02-29 : https://github.com/inventree/InvenTree/pull/6604
- Adds "external_stock" field to the Part API endpoint
- Adds "external_stock" field to the BomItem API endpoint
- Adds "external_stock" field to the BuildLine API endpoint
- Stock quantites represented in the BuildLine API endpoint are now filtered by Build.source_location
v177 - 2024-02-27 : https://github.com/inventree/InvenTree/pull/6581
- Adds "subcategoies" count to PartCategoryTree serializer
- Adds "sublocations" count to StockLocationTree serializer

View File

@ -10,6 +10,9 @@ import string
import warnings
from pathlib import Path
from django.core.files.base import ContentFile
from django.core.files.storage import Storage
logger = logging.getLogger('inventree')
CONFIG_DATA = None
CONFIG_LOOKUPS = {}
@ -69,11 +72,16 @@ def get_base_dir() -> Path:
return Path(__file__).parent.parent.resolve()
def ensure_dir(path: Path) -> None:
def ensure_dir(path: Path, storage=None) -> None:
"""Ensure that a directory exists.
If it does not exist, create it.
"""
if storage and isinstance(storage, Storage):
if not storage.exists(str(path)):
storage.save(str(path / '.empty'), ContentFile(''))
return
if not path.exists():
path.mkdir(parents=True, exist_ok=True)

View File

@ -136,6 +136,11 @@ STATICFILES_DIRS = []
STATICFILES_I18_PREFIX = 'i18n'
STATICFILES_I18_SRC = BASE_DIR.joinpath('templates', 'js', 'translated')
STATICFILES_I18_TRG = BASE_DIR.joinpath('InvenTree', 'static_i18n')
# Create the target directory if it does not exist
if not STATICFILES_I18_TRG.exists():
STATICFILES_I18_TRG.mkdir(parents=True)
STATICFILES_DIRS.append(STATICFILES_I18_TRG)
STATICFILES_I18_TRG = STATICFILES_I18_TRG.joinpath(STATICFILES_I18_PREFIX)
@ -989,6 +994,9 @@ ALLOWED_HOSTS = get_setting(
typecast=list,
)
if SITE_URL and SITE_URL not in ALLOWED_HOSTS:
ALLOWED_HOSTS.append(SITE_URL)
# List of trusted origins for unsafe requests
# Ref: https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins
CSRF_TRUSTED_ORIGINS = get_setting(
@ -999,7 +1007,7 @@ CSRF_TRUSTED_ORIGINS = get_setting(
)
# If a list of trusted is not specified, but a site URL has been specified, use that
if SITE_URL and len(CSRF_TRUSTED_ORIGINS) == 0:
if SITE_URL and SITE_URL not in CSRF_TRUSTED_ORIGINS:
CSRF_TRUSTED_ORIGINS.append(SITE_URL)
USE_X_FORWARDED_HOST = get_boolean_setting(
@ -1040,7 +1048,7 @@ CORS_ALLOWED_ORIGINS = get_setting(
)
# If no CORS origins are specified, but a site URL has been specified, use that
if SITE_URL and len(CORS_ALLOWED_ORIGINS) == 0:
if SITE_URL and SITE_URL not in CORS_ALLOWED_ORIGINS:
CORS_ALLOWED_ORIGINS.append(SITE_URL)
for app in SOCIAL_BACKENDS:

View File

@ -314,11 +314,21 @@ class BuildLineEndpoint:
queryset = BuildLine.objects.all()
serializer_class = build.serializers.BuildLineSerializer
def get_source_build(self) -> Build:
"""Return the source Build object for the BuildLine queryset.
This source build is used to filter the available stock for each BuildLine.
- If this is a "detail" view, use the build associated with the line
- If this is a "list" view, use the build associated with the request
"""
raise NotImplementedError("get_source_build must be implemented in the child class")
def get_queryset(self):
"""Override queryset to select-related and annotate"""
queryset = super().get_queryset()
queryset = build.serializers.BuildLineSerializer.annotate_queryset(queryset)
source_build = self.get_source_build()
queryset = build.serializers.BuildLineSerializer.annotate_queryset(queryset, build=source_build)
return queryset
@ -353,10 +363,26 @@ class BuildLineList(BuildLineEndpoint, ListCreateAPI):
'bom_item__reference',
]
def get_source_build(self) -> Build:
"""Return the target build for the BuildLine queryset."""
try:
build_id = self.request.query_params.get('build', None)
if build_id:
build = Build.objects.get(pk=build_id)
return build
except (Build.DoesNotExist, AttributeError, ValueError):
pass
return None
class BuildLineDetail(BuildLineEndpoint, RetrieveUpdateDestroyAPI):
"""API endpoint for detail view of a BuildLine object."""
pass
def get_source_build(self) -> Build:
"""Return the target source location for the BuildLine queryset."""
return None
class BuildOrderContextMixin:

View File

@ -1083,6 +1083,7 @@ class BuildLineSerializer(InvenTreeModelSerializer):
'available_substitute_stock',
'available_variant_stock',
'total_available_stock',
'external_stock',
]
read_only_fields = [
@ -1124,15 +1125,23 @@ class BuildLineSerializer(InvenTreeModelSerializer):
available_substitute_stock = serializers.FloatField(read_only=True)
available_variant_stock = serializers.FloatField(read_only=True)
total_available_stock = serializers.FloatField(read_only=True)
external_stock = serializers.FloatField(read_only=True)
@staticmethod
def annotate_queryset(queryset):
def annotate_queryset(queryset, build=None):
"""Add extra annotations to the queryset:
- allocated: Total stock quantity allocated against this build line
- available: Total stock available for allocation against this build line
- on_order: Total stock on order for this build line
- in_production: Total stock currently in production for this build line
Arguments:
queryset: The queryset to annotate
build: The build order to filter against (optional)
Note: If the 'build' is provided, we can use it to filter available stock, depending on the specified location for the build
"""
queryset = queryset.select_related(
'build', 'bom_item',
@ -1169,6 +1178,18 @@ class BuildLineSerializer(InvenTreeModelSerializer):
ref = 'bom_item__sub_part__'
stock_filter = None
if build is not None and build.take_from is not None:
location = build.take_from
# Filter by locations below the specified location
stock_filter = Q(
location__tree_id=location.tree_id,
location__lft__gte=location.lft,
location__rght__lte=location.rght,
location__level__gte=location.level,
)
# Annotate the "in_production" quantity
queryset = queryset.annotate(
in_production=part.filters.annotate_in_production_quantity(reference=ref)
@ -1181,10 +1202,8 @@ class BuildLineSerializer(InvenTreeModelSerializer):
)
# Annotate the "available" quantity
# TODO: In the future, this should be refactored.
# TODO: Note that part.serializers.BomItemSerializer also has a similar annotation
queryset = queryset.alias(
total_stock=part.filters.annotate_total_stock(reference=ref),
total_stock=part.filters.annotate_total_stock(reference=ref, filter=stock_filter),
allocated_to_sales_orders=part.filters.annotate_sales_order_allocations(reference=ref),
allocated_to_build_orders=part.filters.annotate_build_order_allocations(reference=ref),
)
@ -1197,11 +1216,21 @@ class BuildLineSerializer(InvenTreeModelSerializer):
)
)
external_stock_filter = Q(location__external=True)
if stock_filter:
external_stock_filter &= stock_filter
# Add 'external stock' annotations
queryset = queryset.annotate(
external_stock=part.filters.annotate_total_stock(reference=ref, filter=external_stock_filter)
)
ref = 'bom_item__substitutes__part__'
# Extract similar information for any 'substitute' parts
queryset = queryset.alias(
substitute_stock=part.filters.annotate_total_stock(reference=ref),
substitute_stock=part.filters.annotate_total_stock(reference=ref, filter=stock_filter),
substitute_build_allocations=part.filters.annotate_build_order_allocations(reference=ref),
substitute_sales_allocations=part.filters.annotate_sales_order_allocations(reference=ref)
)
@ -1215,7 +1244,7 @@ class BuildLineSerializer(InvenTreeModelSerializer):
)
# Annotate the queryset with 'available variant stock' information
variant_stock_query = part.filters.variant_stock_query(reference='bom_item__sub_part__')
variant_stock_query = part.filters.variant_stock_query(reference='bom_item__sub_part__', filter=stock_filter)
queryset = queryset.alias(
variant_stock_total=part.filters.annotate_variant_quantity(variant_stock_query, reference='quantity'),

View File

@ -200,6 +200,11 @@
<div id='build-lines-toolbar'>
{% include "filter_list.html" with id='buildlines' %}
</div>
{% if build.take_from %}
<div class='alert alert-block alert-info'>
{% trans "Available stock has been filtered based on specified source location for this build order" %}
</div>
{% endif %}
<table class='table table-striped table-condensed' id='build-lines-table' data-toolbar='#build-lines-toolbar'></table>
</div>
</div>
@ -374,6 +379,9 @@ onPanelLoad('allocate', function() {
"#build-lines-table",
{{ build.pk }},
{
{% if build.take_from %}
location: {{ build.take_from.pk }},
{% endif %}
{% if build.project_code %}
project_code: {{ build.project_code.pk }},
{% endif %}

View File

@ -675,12 +675,14 @@ class BaseInvenTreeSetting(models.Model):
}
try:
setting = cls.objects.get(**filters)
except cls.DoesNotExist:
if create:
setting = cls(key=key, **kwargs)
else:
return
setting = cls.objects.filter(**filters).first()
if not setting:
if create:
setting = cls(key=key, **kwargs)
else:
return
except (OperationalError, ProgrammingError):
if not key.startswith('_'):
logger.warning("Database is locked, cannot set setting '%s'", key)

View File

@ -215,7 +215,7 @@ login_default_protocol: http
# Remote / proxy login
# These settings can introduce security problems if configured incorrectly. Please read
# https://docs.djangoproject.com/en/4.0/howto/auth-remote-user/ for more details
# https://docs.djangoproject.com/en/4.2/howto/auth-remote-user/ for more details
# The header name should be prefixed by `HTTP`. Please read the docs for more details
# https://docs.djangoproject.com/en/stable/ref/request-response/#django.http.HttpRequest.META
remote_login_enabled: False

View File

View File

@ -0,0 +1,140 @@
"""Shared templating code."""
import logging
import os
import warnings
from pathlib import Path
from django.conf import settings
from django.core.exceptions import AppRegistryNotReady
from django.core.files.storage import default_storage
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
import InvenTree.helpers
from InvenTree.config import ensure_dir
logger = logging.getLogger('inventree')
MEDIA_STORAGE_DIR = Path(settings.MEDIA_ROOT)
class TemplatingMixin:
"""Mixin that contains shared templating code."""
name: str = ''
db: str = ''
def __init__(self, *args, **kwargs):
"""Ensure that the required properties are set."""
super().__init__(*args, **kwargs)
if self.name == '':
raise NotImplementedError('ref must be set')
if self.db == '':
raise NotImplementedError('db must be set')
def create_defaults(self):
"""Function that creates all default templates for the app."""
raise NotImplementedError('create_defaults must be implemented')
def get_src_dir(self, ref_name):
"""Get the source directory for the default templates."""
raise NotImplementedError('get_src_dir must be implemented')
def get_new_obj_data(self, data, filename):
"""Get the data for a new template db object."""
raise NotImplementedError('get_new_obj_data must be implemented')
# Standardized code
def ready(self):
"""This function is called whenever the app is loaded."""
import InvenTree.ready
# skip loading if plugin registry is not loaded or we run in a background thread
if (
not InvenTree.ready.isPluginRegistryLoaded()
or not InvenTree.ready.isInMainThread()
):
return
if not InvenTree.ready.canAppAccessDatabase(allow_test=False):
return # pragma: no cover
with maintenance_mode_on():
try:
self.create_defaults()
except (
AppRegistryNotReady,
IntegrityError,
OperationalError,
ProgrammingError,
):
# Database might not yet be ready
warnings.warn(
f'Database was not ready for creating {self.name}s', stacklevel=2
)
set_maintenance_mode(False)
def create_template_dir(self, model, data):
"""Create folder and database entries for the default templates, if they do not already exist."""
ref_name = model.getSubdir()
# Create root dir for templates
src_dir = self.get_src_dir(ref_name)
dst_dir = MEDIA_STORAGE_DIR.joinpath(self.name, 'inventree', ref_name)
ensure_dir(dst_dir, default_storage)
# Copy each template across (if required)
for entry in data:
self.create_template_file(model, src_dir, entry, ref_name)
def create_template_file(self, model, src_dir, data, ref_name):
"""Ensure a label template is in place."""
# Destination filename
filename = os.path.join(self.name, 'inventree', ref_name, data['file'])
src_file = src_dir.joinpath(data['file'])
dst_file = MEDIA_STORAGE_DIR.joinpath(filename)
do_copy = False
if not dst_file.exists():
logger.info("%s template '%s' is not present", self.name, filename)
do_copy = True
else:
# Check if the file contents are different
src_hash = InvenTree.helpers.hash_file(src_file)
dst_hash = InvenTree.helpers.hash_file(dst_file)
if src_hash != dst_hash:
logger.info("Hash differs for '%s'", filename)
do_copy = True
if do_copy:
logger.info("Copying %s template '%s'", self.name, dst_file)
# Ensure destination dir exists
dst_file.parent.mkdir(parents=True, exist_ok=True)
# Copy file
default_storage.save(filename, src_file.open('rb'))
# Check if a file matching the template already exists
try:
if model.objects.filter(**{self.db: filename}).exists():
return # pragma: no cover
except Exception:
logger.exception(
"Failed to query %s for '%s' - you should run 'invoke update' first!",
self.name,
filename,
)
logger.info("Creating entry for %s '%s'", model, data.get('name'))
try:
model.objects.create(**self.get_new_obj_data(data, filename))
except Exception:
logger.warning("Failed to create %s '%s'", self.name, data['name'])

View File

@ -1,69 +1,31 @@
"""label app specification."""
"""Config options for the label app."""
import hashlib
import logging
import os
import shutil
import warnings
from pathlib import Path
from django.apps import AppConfig
from django.conf import settings
from django.core.exceptions import AppRegistryNotReady
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
import InvenTree.helpers
import InvenTree.ready
logger = logging.getLogger('inventree')
from generic.templating.apps import TemplatingMixin
class LabelConfig(AppConfig):
"""App configuration class for the 'label' app."""
class LabelConfig(TemplatingMixin, AppConfig):
"""Configuration class for the "label" app."""
name = 'label'
db = 'label'
def ready(self):
"""This function is called whenever the label app is loaded."""
# skip loading if plugin registry is not loaded or we run in a background thread
if (
not InvenTree.ready.isPluginRegistryLoaded()
or not InvenTree.ready.isInMainThread()
):
return
if not InvenTree.ready.canAppAccessDatabase(allow_test=False):
return # pragma: no cover
with maintenance_mode_on():
try:
self.create_labels() # pragma: no cover
except (
AppRegistryNotReady,
IntegrityError,
OperationalError,
ProgrammingError,
):
# Database might not yet be ready
warnings.warn(
'Database was not ready for creating labels', stacklevel=2
)
set_maintenance_mode(False)
def create_labels(self):
def create_defaults(self):
"""Create all default templates."""
# Test if models are ready
import label.models
try:
import label.models
except Exception: # pragma: no cover
# Database is not ready yet
return
assert bool(label.models.StockLocationLabel is not None)
# Create the categories
self.create_labels_category(
self.create_template_dir(
label.models.StockItemLabel,
'stockitem',
[
{
'file': 'qr.html',
@ -75,9 +37,8 @@ class LabelConfig(AppConfig):
],
)
self.create_labels_category(
self.create_template_dir(
label.models.StockLocationLabel,
'stocklocation',
[
{
'file': 'qr.html',
@ -96,9 +57,8 @@ class LabelConfig(AppConfig):
],
)
self.create_labels_category(
self.create_template_dir(
label.models.PartLabel,
'part',
[
{
'file': 'part_label.html',
@ -117,9 +77,8 @@ class LabelConfig(AppConfig):
],
)
self.create_labels_category(
self.create_template_dir(
label.models.BuildLineLabel,
'buildline',
[
{
'file': 'buildline_label.html',
@ -131,72 +90,18 @@ class LabelConfig(AppConfig):
],
)
def create_labels_category(self, model, ref_name, labels):
"""Create folder and database entries for the default templates, if they do not already exist."""
# Create root dir for templates
src_dir = Path(__file__).parent.joinpath('templates', 'label', ref_name)
def get_src_dir(self, ref_name):
"""Get the source directory."""
return Path(__file__).parent.joinpath('templates', self.name, ref_name)
dst_dir = settings.MEDIA_ROOT.joinpath('label', 'inventree', ref_name)
if not dst_dir.exists():
logger.info("Creating required directory: '%s'", dst_dir)
dst_dir.mkdir(parents=True, exist_ok=True)
# Create labels
for label in labels:
self.create_template_label(model, src_dir, ref_name, label)
def create_template_label(self, model, src_dir, ref_name, label):
"""Ensure a label template is in place."""
filename = os.path.join('label', 'inventree', ref_name, label['file'])
src_file = src_dir.joinpath(label['file'])
dst_file = settings.MEDIA_ROOT.joinpath(filename)
to_copy = False
if dst_file.exists():
# File already exists - let's see if it is the "same"
if InvenTree.helpers.hash_file(dst_file) != InvenTree.helpers.hash_file(
src_file
): # pragma: no cover
logger.info("Hash differs for '%s'", filename)
to_copy = True
else:
logger.info("Label template '%s' is not present", filename)
to_copy = True
if to_copy:
logger.info("Copying label template '%s'", dst_file)
# Ensure destination dir exists
dst_file.parent.mkdir(parents=True, exist_ok=True)
# Copy file
shutil.copyfile(src_file, dst_file)
# Check if a label matching the template already exists
try:
if model.objects.filter(label=filename).exists():
return # pragma: no cover
except Exception:
logger.exception(
"Failed to query label for '%s' - you should run 'invoke update' first!",
filename,
)
logger.info("Creating entry for %s '%s'", model, label['name'])
try:
model.objects.create(
name=label['name'],
description=label['description'],
label=filename,
filters='',
enabled=True,
width=label['width'],
height=label['height'],
)
except Exception:
logger.warning("Failed to create label '%s'", label['name'])
def get_new_obj_data(self, data, filename):
"""Get the data for a new template db object."""
return {
'name': data['name'],
'description': data['description'],
'label': filename,
'filters': '',
'enabled': True,
'width': data['width'],
'height': data['height'],
}

View File

@ -96,8 +96,13 @@ class LabelTemplate(InvenTree.models.InvenTreeMetadataModel):
abstract = True
@classmethod
def getSubdir(cls) -> str:
"""Return the subdirectory for this label."""
return cls.SUBDIR
# Each class of label files will be stored in a separate subdirectory
SUBDIR = 'label'
SUBDIR: str = 'label'
# Object we will be printing against (will be filled out later)
object_to_print = None

View File

@ -30,7 +30,7 @@ class LabelTest(InvenTreeAPITestCase):
def setUpTestData(cls):
"""Ensure that some label instances exist as part of init routine."""
super().setUpTestData()
apps.get_app_config('label').create_labels()
apps.get_app_config('label').create_defaults()
def test_default_labels(self):
"""Test that the default label templates are copied across."""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -247,7 +247,7 @@ class TestLabelPrinterMachineType(TestMachineRegistryMixin, InvenTreeAPITestCase
plugin_ref = 'inventreelabelmachine'
# setup the label app
apps.get_app_config('label').create_labels() # type: ignore
apps.get_app_config('label').create_defaults() # type: ignore
plg_registry.reload_plugins()
config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config()) # type: ignore
config.active = True

View File

@ -1767,6 +1767,7 @@ class BomFilter(rest_filters.FilterSet):
part_active = rest_filters.BooleanFilter(
label='Master part is active', field_name='part__active'
)
part_trackable = rest_filters.BooleanFilter(
label='Master part is trackable', field_name='part__trackable'
)
@ -1775,6 +1776,7 @@ class BomFilter(rest_filters.FilterSet):
sub_part_trackable = rest_filters.BooleanFilter(
label='Sub part is trackable', field_name='sub_part__trackable'
)
sub_part_assembly = rest_filters.BooleanFilter(
label='Sub part is an assembly', field_name='sub_part__assembly'
)
@ -1814,6 +1816,22 @@ class BomFilter(rest_filters.FilterSet):
return queryset.filter(q_a | q_b).distinct()
part = rest_filters.ModelChoiceFilter(
queryset=Part.objects.all(), method='filter_part', label=_('Part')
)
def filter_part(self, queryset, name, part):
"""Filter the queryset based on the specified part."""
return queryset.filter(part.get_bom_item_filter())
uses = rest_filters.ModelChoiceFilter(
queryset=Part.objects.all(), method='filter_uses', label=_('Uses')
)
def filter_uses(self, queryset, name, part):
"""Filter the queryset based on the specified part."""
return queryset.filter(part.get_used_in_bom_item_filter())
class BomMixin:
"""Mixin class for BomItem API endpoints."""
@ -1889,62 +1907,6 @@ class BomList(BomMixin, ListCreateDestroyAPIView):
return JsonResponse(data, safe=False)
return Response(data)
def filter_queryset(self, queryset):
"""Custom query filtering for the BomItem list API."""
queryset = super().filter_queryset(queryset)
params = self.request.query_params
# Filter by part?
part = params.get('part', None)
if part is not None:
"""
If we are filtering by "part", there are two cases to consider:
a) Bom items which are defined for *this* part
b) Inherited parts which are defined for a *parent* part
So we need to construct two queries!
"""
# First, check that the part is actually valid!
try:
part = Part.objects.get(pk=part)
queryset = queryset.filter(part.get_bom_item_filter())
except (ValueError, Part.DoesNotExist):
pass
"""
Filter by 'uses'?
Here we pass a part ID and return BOM items for any assemblies which "use" (or "require") that part.
There are multiple ways that an assembly can "use" a sub-part:
A) Directly specifying the sub_part in a BomItem field
B) Specifying a "template" part with inherited=True
C) Allowing variant parts to be substituted
D) Allowing direct substitute parts to be specified
- BOM items which are "inherited" by parts which are variants of the master BomItem
"""
uses = params.get('uses', None)
if uses is not None:
try:
# Extract the part we are interested in
uses_part = Part.objects.get(pk=uses)
queryset = queryset.filter(uses_part.get_used_in_bom_item_filter())
except (ValueError, Part.DoesNotExist):
pass
return queryset
filter_backends = SEARCH_ORDER_FILTER_ALIAS
search_fields = [

View File

@ -107,7 +107,7 @@ def annotate_on_order_quantity(reference: str = ''):
)
def annotate_total_stock(reference: str = ''):
def annotate_total_stock(reference: str = '', filter: Q = None):
"""Annotate 'total stock' quantity against a queryset.
- This function calculates the 'total stock' for a given part
@ -121,6 +121,9 @@ def annotate_total_stock(reference: str = ''):
# Stock filter only returns 'in stock' items
stock_filter = stock.models.StockItem.IN_STOCK_FILTER
if filter is not None:
stock_filter &= filter
return Coalesce(
SubquerySum(f'{reference}stock_items__quantity', filter=stock_filter),
Decimal(0),
@ -216,9 +219,7 @@ def annotate_sales_order_allocations(reference: str = ''):
)
def variant_stock_query(
reference: str = '', filter: Q = stock.models.StockItem.IN_STOCK_FILTER
):
def variant_stock_query(reference: str = '', filter: Q = None):
"""Create a queryset to retrieve all stock items for variant parts under the specified part.
- Useful for annotating a queryset with aggregated information about variant parts
@ -227,11 +228,16 @@ def variant_stock_query(
reference: The relationship reference of the part from the current model
filter: Q object which defines how to filter the returned StockItem instances
"""
stock_filter = stock.models.StockItem.IN_STOCK_FILTER
if filter:
stock_filter &= filter
return stock.models.StockItem.objects.filter(
part__tree_id=OuterRef(f'{reference}tree_id'),
part__lft__gt=OuterRef(f'{reference}lft'),
part__rght__lt=OuterRef(f'{reference}rght'),
).filter(filter)
).filter(stock_filter)
def annotate_variant_quantity(subquery: Q, reference: str = 'quantity'):

View File

@ -610,6 +610,7 @@ class PartSerializer(
'stock_item_count',
'suppliers',
'total_in_stock',
'external_stock',
'unallocated_stock',
'variant_stock',
# Fields only used for Part creation
@ -734,6 +735,12 @@ class PartSerializer(
)
)
queryset = queryset.annotate(
external_stock=part.filters.annotate_total_stock(
filter=Q(location__external=True)
)
)
# Annotate with the total 'available stock' quantity
# This is the current stock, minus any allocations
queryset = queryset.annotate(
@ -780,14 +787,17 @@ class PartSerializer(
allocated_to_sales_orders = serializers.FloatField(read_only=True)
building = serializers.FloatField(read_only=True)
in_stock = serializers.FloatField(read_only=True)
ordering = serializers.FloatField(read_only=True)
ordering = serializers.FloatField(read_only=True, label=_('On Order'))
required_for_build_orders = serializers.IntegerField(read_only=True)
required_for_sales_orders = serializers.IntegerField(read_only=True)
stock_item_count = serializers.IntegerField(read_only=True)
suppliers = serializers.IntegerField(read_only=True)
total_in_stock = serializers.FloatField(read_only=True)
unallocated_stock = serializers.FloatField(read_only=True)
variant_stock = serializers.FloatField(read_only=True)
stock_item_count = serializers.IntegerField(read_only=True, label=_('Stock Items'))
suppliers = serializers.IntegerField(read_only=True, label=_('Suppliers'))
total_in_stock = serializers.FloatField(read_only=True, label=_('Total Stock'))
external_stock = serializers.FloatField(read_only=True, label=_('External Stock'))
unallocated_stock = serializers.FloatField(
read_only=True, label=_('Unallocated Stock')
)
variant_stock = serializers.FloatField(read_only=True, label=_('Variant Stock'))
minimum_stock = serializers.FloatField()
@ -1387,6 +1397,7 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
'available_stock',
'available_substitute_stock',
'available_variant_stock',
'external_stock',
# Annotated field describing quantity on order
'on_order',
# Annotated field describing quantity being built
@ -1456,6 +1467,8 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
available_substitute_stock = serializers.FloatField(read_only=True)
available_variant_stock = serializers.FloatField(read_only=True)
external_stock = serializers.FloatField(read_only=True)
@staticmethod
def setup_eager_loading(queryset):
"""Prefetch against the provided queryset to speed up database access."""
@ -1534,6 +1547,13 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
)
)
# Calculate 'external_stock'
queryset = queryset.annotate(
external_stock=part.filters.annotate_total_stock(
reference=ref, filter=Q(location__external=True)
)
)
ref = 'substitutes__part__'
# Extract similar information for any 'substitute' parts

View File

@ -289,13 +289,13 @@ def check_plugin(plugin_slug: str, plugin_pk: int) -> InvenTreePlugin:
# Check that the 'plugin' specified is valid
try:
plugin_cgf = PluginConfig.objects.get(**filter)
plugin_cgf = PluginConfig.objects.filter(**filter).first()
except PluginConfig.DoesNotExist:
raise NotFound(detail=f"Plugin '{ref}' not installed")
if plugin_cgf is None:
# This only occurs if the plugin mechanism broke
raise NotFound(detail=f"Plugin '{ref}' not found") # pragma: no cover
raise NotFound(detail=f"Plugin '{ref}' not installed") # pragma: no cover
# Check that the plugin is activated
if not plugin_cgf.active:

View File

@ -121,7 +121,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
def test_printing_process(self):
"""Test that a label can be printed."""
# Ensure the labels were created
apps.get_app_config('label').create_labels()
apps.get_app_config('label').create_defaults()
# Lookup references
part = Part.objects.first()
@ -183,7 +183,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
def test_printing_options(self):
"""Test printing options."""
# Ensure the labels were created
apps.get_app_config('label').create_labels()
apps.get_app_config('label').create_defaults()
# Lookup references
parts = Part.objects.all()[:2]
@ -224,7 +224,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
plugin_ref = 'samplelabelprinter'
# Activate the label components
apps.get_app_config('label').create_labels()
apps.get_app_config('label').create_defaults()
self.do_activate_plugin()
def run_print_test(label, qs, url_name, url_single):

View File

@ -115,7 +115,11 @@ class PluginsRegistry:
return None
try:
cfg, _created = PluginConfig.objects.get_or_create(key=slug)
cfg = PluginConfig.objects.filter(key=slug).first()
if not cfg:
cfg = PluginConfig.objects.create(key=slug)
except PluginConfig.DoesNotExist:
return None
except (IntegrityError, OperationalError, ProgrammingError): # pragma: no cover

View File

@ -1,256 +1,124 @@
"""Config options for the 'report' app."""
"""Config options for the report app."""
import logging
import os
import shutil
import warnings
from pathlib import Path
from django.apps import AppConfig
from django.conf import settings
from django.core.exceptions import AppRegistryNotReady
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
import InvenTree.helpers
logger = logging.getLogger('inventree')
from generic.templating.apps import TemplatingMixin
class ReportConfig(AppConfig):
"""Configuration class for the 'report' app."""
class ReportConfig(TemplatingMixin, AppConfig):
"""Configuration class for the "report" app."""
name = 'report'
db = 'template'
def ready(self):
"""This function is called whenever the report app is loaded."""
import InvenTree.ready
# skip loading if plugin registry is not loaded or we run in a background thread
if (
not InvenTree.ready.isPluginRegistryLoaded()
or not InvenTree.ready.isInMainThread()
):
return
if not InvenTree.ready.canAppAccessDatabase(allow_test=False):
return # pragma: no cover
"""This function is called whenever the app is loaded."""
# Configure logging for PDF generation (disable "info" messages)
logging.getLogger('fontTools').setLevel(logging.WARNING)
logging.getLogger('weasyprint').setLevel(logging.WARNING)
with maintenance_mode_on():
self.create_reports()
super().ready()
set_maintenance_mode(False)
def create_reports(self):
"""Create default report templates."""
def create_defaults(self):
"""Create all default templates."""
# Test if models are ready
try:
self.create_default_test_reports()
self.create_default_build_reports()
self.create_default_bill_of_materials_reports()
self.create_default_purchase_order_reports()
self.create_default_sales_order_reports()
self.create_default_return_order_reports()
self.create_default_stock_location_reports()
except (
AppRegistryNotReady,
IntegrityError,
OperationalError,
ProgrammingError,
):
# Database might not yet be ready
warnings.warn('Database was not ready for creating reports', stacklevel=2)
def create_default_reports(self, model, reports):
"""Copy default report files across to the media directory."""
# Source directory for report templates
src_dir = Path(__file__).parent.joinpath('templates', 'report')
# Destination directory
dst_dir = settings.MEDIA_ROOT.joinpath('report', 'inventree', model.getSubdir())
if not dst_dir.exists():
logger.info("Creating missing directory: '%s'", dst_dir)
dst_dir.mkdir(parents=True, exist_ok=True)
# Copy each report template across (if required)
for report in reports:
# Destination filename
filename = os.path.join(
'report', 'inventree', model.getSubdir(), report['file']
)
src_file = src_dir.joinpath(report['file'])
dst_file = settings.MEDIA_ROOT.joinpath(filename)
do_copy = False
if not dst_file.exists():
logger.info("Report template '%s' is not present", filename)
do_copy = True
else:
# Check if the file contents are different
src_hash = InvenTree.helpers.hash_file(src_file)
dst_hash = InvenTree.helpers.hash_file(dst_file)
if src_hash != dst_hash:
logger.info("Hash differs for '%s'", filename)
do_copy = True
if do_copy:
logger.info("Copying test report template '%s'", dst_file)
shutil.copyfile(src_file, dst_file)
try:
# Check if a report matching the template already exists
if model.objects.filter(template=filename).exists():
continue
logger.info("Creating new TestReport for '%s'", report.get('name'))
model.objects.create(
name=report['name'],
description=report['description'],
template=filename,
enabled=True,
)
except Exception:
pass
def create_default_test_reports(self):
"""Create database entries for the default TestReport templates, if they do not already exist."""
try:
from .models import TestReport
import report.models
except Exception: # pragma: no cover
# Database is not ready yet
return
assert bool(report.models.TestReport is not None)
# List of test reports to copy across
reports = [
{
'file': 'inventree_test_report.html',
'name': 'InvenTree Test Report',
'description': 'Stock item test report',
}
]
# Create the categories
self.create_template_dir(
report.models.TestReport,
[
{
'file': 'inventree_test_report.html',
'name': 'InvenTree Test Report',
'description': 'Stock item test report',
}
],
)
self.create_default_reports(TestReport, reports)
self.create_template_dir(
report.models.BuildReport,
[
{
'file': 'inventree_build_order.html',
'name': 'InvenTree Build Order',
'description': 'Build Order job sheet',
}
],
)
def create_default_bill_of_materials_reports(self):
"""Create database entries for the default Bill of Material templates (if they do not already exist)."""
try:
from .models import BillOfMaterialsReport
except Exception: # pragma: no cover
# Database is not ready yet
return
self.create_template_dir(
report.models.BillOfMaterialsReport,
[
{
'file': 'inventree_bill_of_materials_report.html',
'name': 'Bill of Materials',
'description': 'Bill of Materials report',
}
],
)
# List of Build reports to copy across
reports = [
{
'file': 'inventree_bill_of_materials_report.html',
'name': 'Bill of Materials',
'description': 'Bill of Materials report',
}
]
self.create_template_dir(
report.models.PurchaseOrderReport,
[
{
'file': 'inventree_po_report.html',
'name': 'InvenTree Purchase Order',
'description': 'Purchase Order example report',
}
],
)
self.create_default_reports(BillOfMaterialsReport, reports)
self.create_template_dir(
report.models.SalesOrderReport,
[
{
'file': 'inventree_so_report.html',
'name': 'InvenTree Sales Order',
'description': 'Sales Order example report',
}
],
)
def create_default_build_reports(self):
"""Create database entries for the default BuildReport templates (if they do not already exist)."""
try:
from .models import BuildReport
except Exception: # pragma: no cover
# Database is not ready yet
return
self.create_template_dir(
report.models.ReturnOrderReport,
[
{
'file': 'inventree_return_order_report.html',
'name': 'InvenTree Return Order',
'description': 'Return Order example report',
}
],
)
# List of Build reports to copy across
reports = [
{
'file': 'inventree_build_order.html',
'name': 'InvenTree Build Order',
'description': 'Build Order job sheet',
}
]
self.create_template_dir(
report.models.StockLocationReport,
[
{
'file': 'inventree_slr_report.html',
'name': 'InvenTree Stock Location',
'description': 'Stock Location example report',
}
],
)
self.create_default_reports(BuildReport, reports)
def get_src_dir(self, ref_name):
"""Get the source directory."""
return Path(__file__).parent.joinpath('templates', self.name)
def create_default_purchase_order_reports(self):
"""Create database entries for the default SalesOrderReport templates (if they do not already exist)."""
try:
from .models import PurchaseOrderReport
except Exception: # pragma: no cover
# Database is not ready yet
return
# List of Build reports to copy across
reports = [
{
'file': 'inventree_po_report.html',
'name': 'InvenTree Purchase Order',
'description': 'Purchase Order example report',
}
]
self.create_default_reports(PurchaseOrderReport, reports)
def create_default_sales_order_reports(self):
"""Create database entries for the default Sales Order report templates (if they do not already exist)."""
try:
from .models import SalesOrderReport
except Exception: # pragma: no cover
# Database is not ready yet
return
# List of Build reports to copy across
reports = [
{
'file': 'inventree_so_report.html',
'name': 'InvenTree Sales Order',
'description': 'Sales Order example report',
}
]
self.create_default_reports(SalesOrderReport, reports)
def create_default_return_order_reports(self):
"""Create database entries for the default ReturnOrderReport templates."""
try:
from report.models import ReturnOrderReport
except Exception: # pragma: no cover
# Database not yet ready
return
# List of templates to copy across
reports = [
{
'file': 'inventree_return_order_report.html',
'name': 'InvenTree Return Order',
'description': 'Return Order example report',
}
]
self.create_default_reports(ReturnOrderReport, reports)
def create_default_stock_location_reports(self):
"""Create database entries for the default StockLocationReport templates."""
try:
from report.models import StockLocationReport
except Exception: # pragma: no cover
# Database not yet ready
return
# List of templates to copy across
reports = [
{
'file': 'inventree_slr_report.html',
'name': 'InvenTree Stock Location',
'description': 'Stock Location example report',
}
]
self.create_default_reports(StockLocationReport, reports)
def get_new_obj_data(self, data, filename):
"""Get the data for a new template db object."""
return {
'name': data['name'],
'description': data['description'],
'template': filename,
'enabled': True,
}

View File

@ -1172,12 +1172,18 @@ function loadBomTable(table, options={}) {
var available_stock = availableQuantity(row);
var external_stock = row.external_stock ?? 0;
var text = renderLink(`${available_stock}`, url);
if (row.sub_part_detail && row.sub_part_detail.units) {
text += ` <small>${row.sub_part_detail.units}</small>`;
}
if (external_stock > 0) {
text += makeIconBadge('fa-sitemap', `{% trans "External stock" %}: ${external_stock}`);
}
if (available_stock <= 0) {
text += makeIconBadge('fa-times-circle icon-red', '{% trans "No Stock Available" %}');
} else {

View File

@ -2618,6 +2618,10 @@ function loadBuildLineTable(table, build_id, options={}) {
icons += makeIconBadge('fa-tools icon-blue', `{% trans "In Production" %}: ${formatDecimal(row.in_production)}`);
}
if (row.external_stock > 0) {
icons += makeIconBadge('fa-sitemap', `{% trans "External stock" %}: ${row.external_stock}`);
}
return renderLink(text, url) + icons;
}
},
@ -2730,6 +2734,7 @@ function loadBuildLineTable(table, build_id, options={}) {
allocateStockToBuild(build_id, [row], {
output: options.output,
source_location: options.location,
success: function() {
$(table).bootstrapTable('refresh');
}

View File

@ -2804,6 +2804,15 @@ function loadPartCategoryTable(table, options) {
title: '{% trans "Parts" %}',
switchable: true,
sortable: true,
},
{
field: 'structural',
title: '{% trans "Structural" %}',
switchable: true,
sortable: true,
formatter: function(value) {
return yesNoLabel(value);
}
}
]
});

View File

@ -8,9 +8,7 @@
<h3>{% trans "Sign Up" %}</h3>
<p>
{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}
You are about to use your {{provider_name}} account to login to {{site_name}}.
{% endblocktrans %}
{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {{provider_name}} account to login to {{site_name}}.{% endblocktrans %}
<br>
{% trans "As a final step, please complete the following form" %}:
</p>

View File

@ -1,16 +1,8 @@
# InvenTree environment variables for a postgresql production setup
# InvenTree environment variables for docker compose deployment
# Location of persistent database data (stored external to the docker containers)
# Note: You *must* un-comment this line, and point it to a path on your local machine
# e.g. Linux
#INVENTREE_EXT_VOLUME=/home/inventree/data
# e.g. Windows (docker desktop)
#INVENTREE_EXT_VOLUME=c:/Users/inventree/data
# Default web port for the InvenTree server
INVENTREE_WEB_PORT=1337
# Specify the location of the external data volume
# By default, placed in local directory 'inventree-data'
INVENTREE_EXT_VOLUME=./inventree-data
# Ensure debug is false for a production setup
INVENTREE_DEBUG=False
@ -23,16 +15,14 @@ INVENTREE_LOG_LEVEL=WARNING
#INVENTREE_ADMIN_EMAIL=
# Database configuration options
# Note: The example setup is for a PostgreSQL database
INVENTREE_DB_ENGINE=postgresql
INVENTREE_DB_NAME=inventree
INVENTREE_DB_HOST=inventree-db
INVENTREE_DB_PORT=5432
# Database credentials - These must be configured before running
# Uncomment the lines below, and change from the default values!
#INVENTREE_DB_USER=pguser
#INVENTREE_DB_PASSWORD=pgpassword
# Database credentials - These should be changed from the default values!
INVENTREE_DB_USER=pguser
INVENTREE_DB_PASSWORD=pgpassword
# Redis cache setup (disabled by default)
# Un-comment the following lines to enable Redis cache
@ -45,12 +35,15 @@ INVENTREE_DB_PORT=5432
INVENTREE_GUNICORN_TIMEOUT=90
# Enable custom plugins?
INVENTREE_PLUGINS_ENABLED=False
INVENTREE_PLUGINS_ENABLED=True
# Run migrations automatically?
INVENTREE_AUTO_UPDATE=False
INVENTREE_AUTO_UPDATE=True
# Image tag that should be used
INVENTREE_TAG=stable
COMPOSE_PROJECT_NAME=inventree-production
# Site URL - update this to match your host (and update the Caddyfile too!)
INVENTREE_SITE_URL="http://inventree.localhost"
COMPOSE_PROJECT_NAME=inventree

36
docker/Caddyfile Normal file
View File

@ -0,0 +1,36 @@
# Caddyfile for Inventree
# The following environment variables may be used:
# - INVENTREE_SITE_URL: The upstream URL of the Inventree site (default: inventree.localhost)
# - INVENTREE_SERVER: The internal URL of the Inventree container (default: http://inventree-server:8000)
(log_common) {
log {
output file /var/log/caddy/{args.0}.access.log
}
}
# Change the host to your domain (this will serve at inventree.localhost)
{$INVENTREE_SITE_URL:inventree.localhost} {
import log_common inventree
encode gzip
request_body {
max_size 100MB
}
handle_path /static/* {
root * /var/www/static
file_server
}
handle_path /media/* {
forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} {
uri /auth/
}
root * /var/www/media
file_server
}
reverse_proxy {$INVENTREE_SERVER:"http://inventree-server:8000"}
}

View File

@ -1,64 +0,0 @@
version: "3.8"
# Docker compose recipe for InvenTree development server
# - Runs sqlite database
# - Uses built-in django webserver
# - Runs the InvenTree background worker process
# - Serves media and static content directly from Django webserver
# IMPORTANT NOTE:
# The InvenTree docker image does not clone source code from git.
# Instead, you must specify *where* the source code is located,
# (on your local machine).
# The django server will auto-detect any code changes and reload the server.
services:
# InvenTree web server services
# Uses gunicorn as the web server
inventree-dev-server:
container_name: inventree-dev-server
build:
context: .
target: dev
# Cache the built image to be used by the inventree-dev-worker process
image: inventree-dev-image
ports:
# Expose web server on port 8000
- 8000:8000
volumes:
# Ensure you specify the location of the 'src' directory at the end of this file
- src:/home/inventree
environment:
- INVENTREE_DEBUG=True
- INVENTREE_DB_ENGINE=sqlite
- INVENTREE_DB_NAME=/home/inventree/db.sqlite3
restart: unless-stopped
# Background worker process handles long-running or periodic tasks
inventree-dev-worker:
container_name: inventree-dev-worker
image: inventree-dev-image
command: invoke worker
depends_on:
- inventree-dev-server
volumes:
# Ensure you specify the location of the 'src' directory at the end of this file
- src:/home/inventree
environment:
- INVENTREE_DEBUG=True
- INVENTREE_DB_ENGINE=sqlite
- INVENTREE_DB_NAME=/home/inventree/db.sqlite3
restart: unless-stopped
volumes:
# NOTE: Change "../" to a directory on your local machine, where the InvenTree source code is located
# Persistent data, stored external to the container(s)
src:
driver: local
driver_opts:
type: none
o: bind
# This directory specified where InvenTree source code is stored "outside" the docker containers
# By default, this directory is one level above the "docker" directory
device: ${INVENTREE_EXT_VOLUME:-../}

View File

@ -39,6 +39,7 @@ services:
# Use PostgreSQL as the database backend
inventree-db:
image: postgres:13
container_name: inventree-db
expose:
- ${INVENTREE_DB_PORT:-5432}/tcp
environment:
@ -48,13 +49,14 @@ services:
- POSTGRES_DB=${INVENTREE_DB_NAME:?You must provide the 'INVENTREE_DB_NAME' variable in the .env file}
volumes:
# Map 'data' volume such that postgres database is stored externally
- inventree_data:/var/lib/postgresql/data/:z
- ${INVENTREE_EXT_VOLUME:?You must specify the 'INVENTREE_EXT_VOLUME' variable in the .env file!}:/var/lib/postgresql/data/:z
restart: unless-stopped
# redis acts as database cache manager
# only runs under the "redis" profile : https://docs.docker.com/compose/profiles/
inventree-cache:
image: redis:7.0
container_name: inventree-cache
depends_on:
- inventree-db
profiles:
@ -70,10 +72,8 @@ services:
inventree-server:
# If you wish to specify a particular InvenTree version, do so here
image: inventree/inventree:${INVENTREE_TAG:-stable}
container_name: inventree-server
# Only change this port if you understand the stack.
# If you change this you have to change:
# - the proxy settings (on two lines)
# - only change the exposed port - eg `1338:8000` if you want to expose the server on port 1338
expose:
- 8000
depends_on:
@ -82,13 +82,14 @@ services:
- .env
volumes:
# Data volume must map to /home/inventree/data
- inventree_data:/home/inventree/data:z
- ${INVENTREE_EXT_VOLUME}:/home/inventree/data:z
restart: unless-stopped
# Background worker process handles long-running or periodic tasks
inventree-worker:
# If you wish to specify a particular InvenTree version, do so here
image: inventree/inventree:${INVENTREE_TAG:-stable}
container_name: inventree-worker
command: invoke worker
depends_on:
- inventree-server
@ -96,37 +97,26 @@ services:
- .env
volumes:
# Data volume must map to /home/inventree/data
- inventree_data:/home/inventree/data:z
- ${INVENTREE_EXT_VOLUME}:/home/inventree/data:z
restart: unless-stopped
# nginx acts as a reverse proxy
# static files are served directly by nginx
# media files are served by nginx, although authentication is redirected to inventree-server
# web requests are redirected to gunicorn
# NOTE: You will need to provide a working nginx.conf file!
# caddy acts as reverse proxy and static file server
# https://hub.docker.com/_/caddy
inventree-proxy:
image: nginx:stable
container_name: inventree-proxy
image: caddy:alpine
restart: always
depends_on:
- inventree-server
ports:
- ${INVENTREE_WEB_PORT:-80}:80
- 443:443
env_file:
- .env
ports:
# Default web port is 1337 (can be changed in the .env file)
- ${INVENTREE_WEB_PORT:-1337}:80
volumes:
# Provide nginx configuration file to the container
# Refer to the provided example file as a starting point
- ./nginx.prod.conf:/etc/nginx/conf.d/default.conf:ro,z
# nginx proxy needs access to static and media files
- inventree_data:/var/www:z
restart: unless-stopped
volumes:
# Persistent data, stored external to the container(s)
inventree_data:
driver: local
driver_opts:
type: none
o: bind
# This directory specified where InvenTree data are stored "outside" the docker containers
device: ${INVENTREE_EXT_VOLUME:?You must specify the 'INVENTREE_EXT_VOLUME' variable in the .env file!}
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ${INVENTREE_EXT_VOLUME}/static:/var/www/static:z
- ${INVENTREE_EXT_VOLUME}/media:/var/www/media:z
- ${INVENTREE_EXT_VOLUME}:/var/log:z
- ${INVENTREE_EXT_VOLUME}:/data:z
- ${INVENTREE_EXT_VOLUME}:/config:z

View File

@ -7,5 +7,5 @@ apk add gcc g++ musl-dev openssl-dev libffi-dev cargo python3-dev openldap-dev \
jpeg-dev openjpeg-dev libwebp-dev zlib-dev \
sqlite sqlite-dev \
mariadb-connector-c-dev mariadb-client mariadb-dev \
postgresql13-dev postgresql-libs postgresql13-client \
postgresql13-dev postgresql-libs \
$@

View File

@ -1,66 +0,0 @@
server {
# Listen for connection on (internal) port 80
listen 80;
real_ip_header proxy_protocol;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header CLIENT_IP $remote_addr;
proxy_pass_request_headers on;
proxy_redirect off;
client_max_body_size 100M;
proxy_buffering off;
proxy_request_buffering off;
# Change 'inventree-dev-server' to the name of the inventree server container,
# and '8000' to the INVENTREE_WEB_PORT (if not default)
proxy_pass http://inventree-dev-server:8000;
}
# Redirect any requests for static files
location /static/ {
alias /var/www/dev/static/;
autoindex on;
# Caching settings
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
}
# Redirect any requests for media files
location /media/ {
alias /var/www/dev/media/;
# Media files require user authentication
auth_request /auth;
# Content header to force download
add_header Content-disposition "attachment";
}
# Use the 'user' API endpoint for auth
location /auth {
internal;
proxy_pass http://inventree-dev-server:8000/auth/;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
}

View File

@ -1,67 +0,0 @@
server {
# Listen for connection on (internal) port 80
# If you are exposing this server to the internet, you should use HTTPS!
# In which case, you should also set up a redirect from HTTP to HTTPS, and listen on port 443
# See the Nginx documentation for more details
listen 80;
real_ip_header proxy_protocol;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header CLIENT_IP $remote_addr;
proxy_pass_request_headers on;
proxy_redirect off;
client_max_body_size 100M;
proxy_buffering off;
proxy_request_buffering off;
# Do not touch this unless you have a specific reason - this and the docker-compose need to match
proxy_pass http://inventree-server:8000;
}
# Redirect any requests for static files
location /static/ {
alias /var/www/static/;
autoindex on;
# Caching settings
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
}
# Redirect any requests for media files
location /media/ {
alias /var/www/media/;
# Media files require user authentication
auth_request /auth;
# Content header to force download
add_header Content-disposition "attachment";
}
# Use the 'user' API endpoint for auth
location /auth {
internal;
proxy_pass http://inventree-server:8000/auth/;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
}

View File

@ -17,3 +17,6 @@ gunicorn>=21.2.0
# LDAP required packages
django-auth-ldap # Django integration for ldap auth
python-ldap # LDAP auth support
# Upgraded python package installer
uv

View File

@ -0,0 +1 @@
https://docs.djangoproject.com/en/{{ config.extra.django_version }}

View File

@ -1,3 +1,285 @@
{!
include-markdown "../../../CONTRIBUTING.md"
!}
---
title: Contribution Guide
---
Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase.
## Quickstart
The following commands will get you quickly configure and run a development server, complete with a demo dataset to work with:
### Devcontainer
The recommended method for getting up and running with an InvenTree development environment is to use our [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) setup in [vscode](https://code.visualstudio.com/).
!!! success "Devcontainer Guide"
Refer to the [devcontainer guide](./devcontainer.md) for more information!
### Docker
To setup a development environment using [docker](../start/docker.md), run the following instructions:
```bash
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
docker compose run inventree-dev-server invoke install
docker compose run inventree-dev-server invoke setup-test --dev
docker compose up -d
```
### Bare Metal
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
```
Read the [InvenTree setup documentation](../start/intro.md) for a complete installation reference guide.
### Setup Devtools
Run the following command to set up all toolsets for development.
```bash
invoke 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.
### Version Numbering
InvenTree version numbering follows the [semantic versioning](https://semver.org/) specification.
### Master Branch
The HEAD of the "main" or "master" branch of InvenTree represents the current "latest" state of code development.
- All feature branches are merged into master
- All bug fixes are merged into master
**No pushing to master:** New features must be submitted as a pull request from a separate branch (one branch per feature).
### Feature Branches
Feature branches should be branched *from* the *master* branch.
- One major feature per branch / pull request
- Feature pull requests are merged back *into* the master branch
- Features *may* also be merged into a release candidate branch
### Stable Branch
The HEAD of the "stable" branch represents the latest stable release code.
- Versioned releases are merged into the "stable" branch
- Bug fix branches are made *from* the "stable" branch
#### Release Candidate Branches
- Release candidate branches are made from master, and merged into stable.
- RC branches are targeted at a major/minor version e.g. "0.5"
- When a release candidate branch is merged into *stable*, the release is tagged
#### Bugfix Branches
- If a bug is discovered in a tagged release version of InvenTree, a "bugfix" or "hotfix" branch should be made *from* that tagged release
- When approved, the branch is merged back *into* stable, with an incremented PATCH number (e.g. 0.4.1 -> 0.4.2)
- The bugfix *must* also be cherry picked into the *master* branch.
## API versioning
The [API version](https://github.com/inventree/InvenTree/blob/master/InvenTree/InvenTree/api_version.py) needs to be bumped every time when the API is changed.
## Environment
### Software Versions
The core software modules are targeting the following versions:
| Name | Minimum version | Note |
|---|---| --- |
| Python | {{ config.extra.min_python_version }} | Minimum required version |
| Invoke | {{ config.extra.min_invoke_version }} | Minimum required version |
| Django | {{ config.extra.django_version }} | Pinned version |
| Node | 18 | Only needed for frontend development |
Any other software dependencies are handled by the project package config.
### Auto creating updates
The following tools can be used to auto-upgrade syntax that was depreciated in new versions:
```bash
pip install pyupgrade
pip install django-upgrade
```
To update the codebase run the following script.
```bash
pyupgrade `find . -name "*.py"`
django-upgrade --target-version {{ config.extra.django_version }} `find . -name "*.py"`
```
## Credits
If you add any new dependencies / libraries, they should be added to [the credits page](../credits.md).
## Migration Files
Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `invoke migrate` and commit the migration files before submitting the PR.
*Note: A github action checks for unstaged migration files and will reject the PR if it finds any!*
## Unit Testing
Any new code should be covered by unit tests - a submitted PR may not be accepted if the code coverage for any new features is insufficient, or the overall code coverage is decreased.
The InvenTree code base makes use of [GitHub actions](https://github.com/features/actions) to run a suite of automated tests against the code base every time a new pull request is received. These actions include (but are not limited to):
- Checking Python and Javascript code against standard style guides
- Running unit test suite
- Automated building and pushing of docker images
- Generating translation files
The various github actions can be found in the `./github/workflows` directory
### Run tests locally
To run test locally, use:
```
invoke test
```
To run only partial tests, for example for a module use:
```
invoke test --runtest order
```
To see all the available options:
```
invoke test --help
```
## Code Style
Code style is automatically checked as part of the project's CI pipeline on GitHub. This means that any pull requests which do not conform to the style guidelines will fail CI checks.
### Backend Code
Backend code (Python) is checked against the [PEP style guidelines](https://peps.python.org/pep-0008/). Please write docstrings for each function and class - we follow the [google doc-style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for python.
### Frontend Code
Frontend code (Javascript) is checked using [eslint](https://eslint.org/). While docstrings are not enforced for front-end code, good code documentation is encouraged!
### Running Checks Locally
If you have followed the setup devtools procedure, then code style checking is performend automatically whenever you commit changes to the code.
### Django templates
Django are checked by [djlint](https://github.com/Riverside-Healthcare/djlint) through pre-commit.
The following rules out of the [default set](https://djlint.com/docs/linter/) are not applied:
```bash
D018: (Django) Internal links should use the { % url ... % } pattern
H006: Img tag should have height and width attributes
H008: Attributes should be double quoted
H021: Inline styles should be avoided
H023: Do not use entity references
H025: Tag seems to be an orphan
H030: Consider adding a meta description
H031: Consider adding meta keywords
T002: Double quotes should be used in tags
```
## Documentation
New features or updates to existing features should be accompanied by user documentation.
## Translations
Any user-facing strings *must* be passed through the translation engine.
- InvenTree code is written in English
- User translatable strings are provided in English as the primary language
- Secondary language translations are provided [via Crowdin](https://crowdin.com/project/inventree)
*Note: Translation files are updated via GitHub actions - you do not need to compile translations files before submitting a pull request!*
### Python Code
For strings exposed via Python code, use the following format:
```python
from django.utils.translation import gettext_lazy as _
user_facing_string = _('This string will be exposed to the translation engine!')
```
### Templated Strings
HTML and javascript files are passed through the django templating engine. Translatable strings are implemented as follows:
```html
{ % load i18n % }
<span>{ % trans "This string will be translated" % } - this string will not!</span>
```
## Github use
### Tags
The tags describe issues and PRs in multiple areas:
| Area | Name | Description |
| --- | --- | --- |
| Triage Labels | | |
| | triage:not-checked | Item was not checked by the core team |
| | triage:not-approved | Item is not green-light by maintainer |
| Type Labels | | |
| | breaking | Indicates a major update or change which breaks compatibility |
| | bug | Identifies a bug which needs to be addressed |
| | dependency | Relates to a project dependency |
| | duplicate | Duplicate of another issue or PR |
| | enhancement | This is an suggested enhancement, extending the functionality of an existing feature |
| | experimental | This is a new *experimental* feature which needs to be enabled manually |
| | feature | This is a new feature, introducing novel functionality |
| | help wanted | Assistance required |
| | invalid | This issue or PR is considered invalid |
| | inactive | Indicates lack of activity |
| | migration | Database migration, requires special attention |
| | question | This is a question |
| | roadmap | This is a roadmap feature with no immediate plans for implementation |
| | security | Relates to a security issue |
| | starter | Good issue for a developer new to the project |
| | wontfix | No work will be done against this issue or PR |
| Feature Labels | | |
| | API | Relates to the API |
| | barcode | Barcode scanning and integration |
| | build | Build orders |
| | importer | Data importing and processing |
| | order | Purchase order and sales orders |
| | part | Parts |
| | plugin | Plugin ecosystem |
| | pricing | Pricing functionality |
| | report | Report generation |
| | stock | Stock item management |
| | user interface | User interface |
| Ecosystem Labels | | |
| | backport | Tags that the issue will be backported to a stable branch as a bug-fix |
| | demo | Relates to the InvenTree demo server or dataset |
| | docker | Docker / docker-compose |
| | CI | CI / unit testing ecosystem |
| | refactor | Refactoring existing code |
| | setup | Relates to the InvenTree setup / installation process |

View File

@ -16,22 +16,29 @@ You need to make sure that you have the following tools installed before continu
- [docker](https://www.docker.com/products/docker-desktop/) is needed to run the devcontainer
- [vscode](https://code.visualstudio.com/Download) is needed to edit and debug code
#### Docker Containers
The InvenTree devcontainer setup will install two docker containers:
| Container | Description |
| --- | --- |
| db | InvenTree database (postgresql) |
| inventree | InvenTree server |
#### Setup/Installation
1. Clone the repository (If you want to submit changes fork it and use the url to your fork in the next step)
```bash
git clone https://github.com/inventree/InvenTree.git
```
2. open vscode, navigate to the extensions sidebar and search for `ms-vscode-remote.remote-containers`. Click on install.
3. open the cloned folder from above by clicking on `file > open folder`
2. Open vscode, navigate to the extensions sidebar and search for `ms-vscode-remote.remote-containers`. Click on install.
3. Open the cloned folder from above by clicking on `file > open folder`
4. vscode should now ask you if you'd like to reopen this folder in a devcontainer. Click `Reopen in Container`. If it does not ask you, open the command palette (<kbd>CTRL/CMD</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>) and search for `Reopen in Container`. This can take a few minutes until the image is downloaded, build and setup with all dependencies.
5. Open a new terminal from the top menu by clicking `Terminal > New Terminal`
6. The last line in your terminal should now show the text `(venv)` at the start of the line
7. From here' we need to setup the InvenTree specific development environment
8. From the newly opened terminal, run: `invoke install`
9. If you want test data on your server, run: `invoke setup-test --dev`. If not, run `invoke setup-dev`
7. You are done! You now should have a functioning InvenTree development installation
### Setup in codespaces
### Setup in Codespaces
Open [inventree/InvenTree](https://github.com/inventree/InvenTree) with your browser and click on `Code`, select the `codespaces` tab and click on create codespace on current branch. This may can take a few minutes until your inventree development environment is setup.
@ -52,14 +59,14 @@ If you only need a superuser, run the `superuser` task. It should prompt you for
#### Run background workers
If you need to process your queue with background workers, run the `worker` task.
If you need to process your queue with background workers, run the `worker` task. This is a foreground task which will execute in the terminal.
### Running InvenTree
You can either only run InvenTree or use the integrated debugger for debugging. Goto the `Run and debug` side panel make sure `InvenTree Server` is selected. Click on the play button on the left.
!!! tip "Debug with 3rd party"
Sometimes you need to debug also some 3rd party packages. Just select `python: Django - 3rd party`
Sometimes you need to debug also some 3rd party packages. Just select `InvenTree Servre - 3rd party`
You can now set breakpoints and vscode will automatically pause execution if that point is hit. You can see all variables available in that context and evaluate some code with the debugger console at the bottom. Use the play or step buttons to continue execution.
@ -94,12 +101,15 @@ Your plugin should now be activateable from the InvenTree settings. You can also
### Troubleshooting
#### Your ssh-keys are not available in the devcontainer but are loaded to the active `ssh-agent` on macOS
Make sure you enabled full disk access on macOS for vscode, otherwise your ssh-keys are not available inside the container (Ref: [Automatically add SSH keys to ssh-agent [comment]](https://github.com/microsoft/vscode-remote-release/issues/4024#issuecomment-831671081)).
#### You're not able to use your gpg-keys inside the devcontainer to sign commits on macOS
Make sure you have `gnupg` and `pinentry-mac` installed and set up correctly. Read this [medium post](https://medium.com/@jma/setup-gpg-for-git-on-macos-4ad69e8d3733) for more info on how to set it up correctly.
#### Where are the database, media files, ... stored?
Backups, Commandhistory, media/static files, venv, plugin.txt, secret_key.txt, ... are stored in the `dev` folder. If you want to start with a clean setup, you can remove that folder, but be aware that this will delete everything you already setup in InvenTree.
### Performance Improvements

View File

@ -1,27 +0,0 @@
---
title: Getting started
---
InvenTree consists of a Django-based backend server, and a HTML / vanilla JS based frontend that uses Bootstrap. The main languages used are Python and Javascript.
As part of the larger project other languages/techniques are used, such as docker (dev/deployment), bash (installer) and markdown (documentation).
### Getting started
#### Getting to know the basics
The Django framework is a powerful tool for creating web applications. It is well documented and has a large community. The [Django documentation](https://docs.djangoproject.com/en/stable/) is a good place to start.
In particular the [tutorial](https://docs.djangoproject.com/en/stable/intro/tutorial01/) is a good way to get to know the basics of Django.
InvenTree follows the best practies for Django so most of the contents should be applicable to InvenTree as well. The REST API is based on the [Django REST framework](https://www.django-rest-framework.org/).
#### Setting up a development environment
The recommended way to set up a development environment is to use VSCode devcontainers. The required files are provided with the repo, the docs are on a [dedicated page](./devcontainer.md).
It is also possible to use [docker development](../start/docker_dev.md) or [bare metal development](../start/bare_dev.md). With these you need to install the required dependencies manually with a dedicated task.
```bash
invoke setup-dev
```
#### Following standards
Before contributing to the project, please read the [contributing guidelines](contributing.md). Pull requests that do not follow the guidelines, do not pass QC checks or lower the test coverage will not be accepted.

View File

@ -85,7 +85,7 @@ The configuration entries must be enabled via the [InvenTree admin interface](..
### Plugin Mixins
Common use cases are covered by pre-supplied modules in the form of *mixins* (similar to how [Django](https://docs.djangoproject.com/en/stable/topics/class-based-views/mixins/) does it). Each mixin enables the integration into a specific area of InvenTree. Sometimes it also enhances the plugin with helper functions to supply often used functions out-of-the-box.
Common use cases are covered by pre-supplied modules in the form of *mixins* (similar to how [Django]({% include "django.html" %}/topics/class-based-views/mixins/) does it). Each mixin enables the integration into a specific area of InvenTree. Sometimes it also enhances the plugin with helper functions to supply often used functions out-of-the-box.
Supported mixin classes are:

View File

@ -7,4 +7,4 @@ title: App Mixin
If this mixin is added to a plugin the directory the plugin class is defined in is added to the list of `INSTALLED_APPS` in the InvenTree server configuration.
!!! warning "Danger Zone"
Only use this mixin if you have an understanding of djangos [app system](https://docs.djangoproject.com/en/stable/ref/applications). Plugins with this mixin are deeply integrated into InvenTree and can cause difficult to reproduce or long-running errors. Use the built-in testing functions of django to make sure your code does not cause unwanted behaviour in InvenTree before releasing.
Only use this mixin if you have an understanding of djangos [app system]({% include "django.html" %}/ref/applications). Plugins with this mixin are deeply integrated into InvenTree and can cause difficult to reproduce or long-running errors. Use the built-in testing functions of django to make sure your code does not cause unwanted behaviour in InvenTree before releasing.

View File

@ -12,7 +12,7 @@ A much simpler and more accessible method of recording custom information agains
### MetadataMixin Class
*Most* of the models in the InvenTree database inherit from the `MetadataMixin` class, which adds the `metadata` field to each inheriting model. The `metadata` field is a [JSONField](https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.JSONField) which allows for storing arbitrary JSON data against the model instance.
*Most* of the models in the InvenTree database inherit from the `MetadataMixin` class, which adds the `metadata` field to each inheriting model. The `metadata` field is a [JSONField]({% include "django.html" %}/ref/models/fields/#django.db.models.JSONField) which allows for storing arbitrary JSON data against the model instance.
This field is provided to allow any plugins to store and retrieve arbitrary data against any item in the database.

View File

@ -6,7 +6,7 @@ title: URLs Mixin
Use the class constant `URLS` for a array of URLs that should be added to InvenTrees URL paths or override the `plugin.setup_urls` function.
The array has to contain valid URL patterns as defined in the [django documentation](https://docs.djangoproject.com/en/stable/topics/http/urls/).
The array has to contain valid URL patterns as defined in the [django documentation]({% include "django.html" %}/topics/http/urls/).
``` python
class MyUrlsPlugin(UrlsMixin, InvenTreePlugin):
@ -19,14 +19,14 @@ class MyUrlsPlugin(UrlsMixin, InvenTreePlugin):
```
The URLs get exposed under `/plugin/{plugin.slug}/*` and get exposed to the template engine with the prefix `plugin:{plugin.slug}:` (for usage with the [url tag](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#url)).
The URLs get exposed under `/plugin/{plugin.slug}/*` and get exposed to the template engine with the prefix `plugin:{plugin.slug}:` (for usage with the [url tag]({% include "django.html" %}/ref/templates/builtins/#url)).
!!! info "Note"
In this example, when an HTTP request is made to `/plugin/{plugin.slug}/increase/.../...` the function `self.view_increase` is called and returns the view to be displayed (step 4 in the Django documentation)
### Views
If your plugin will implement and host another webpage, familiarize yourself with Django views. Implementation is exactly the same.
A good place to start is the [django documentation](https://docs.djangoproject.com/en/4.2/topics/http/views/). Additional InvenTree-specific information is below.
A good place to start is the [django documentation]({% include "django.html" %}/topics/http/views/). Additional InvenTree-specific information is below.
### Rendering Views
Rendering templated views is also supported. Templated HTML files should be placed inside your plugin folder in a sub folder called `templates`.

View File

@ -29,7 +29,7 @@ If the installed version of invoke is too old, users may see error messages duri
- *'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 `2.0.0`.
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:
@ -60,7 +60,7 @@ Always activate the virtual environment before running server commands!
### 'str' object has no attribute 'removeSuffix'
This error occurs because your installed python version is not up to date. We [require Python v3.9 or newer](./start/intro.md#python-requirements)
This error occurs because your installed python version is not up to date. We [require Python {{ config.extra.min_python_version }} or newer](./start/intro.md#python-requirements)
You (or your system administrator) needs to update python to meet the minimum requirements for InvenTree.
@ -77,7 +77,7 @@ The most common problem here is that the correct sequenct of steps has not been
For more information, refer to the installation guides:
- [Docker Installation](./start/docker_prod.md#updating-inventree)
- [Docker Installation](./start/docker_install.md#updating-inventree)
- [Bare Metal Installation](./start/install.md#updating-inventree)
!!! warning "Invoke Update"
@ -117,7 +117,7 @@ invoke worker
### File Sync Issues - Docker
When installing under [Docker](./start/docker.md), sometimes issues may arise keeping [persistent data](./start/docker.md#persistent-data) in sync. Refer to the [common issues](./start/docker_prod.md#common-issues) section in the docker setup guide for further details.
When installing under [Docker](./start/docker.md), sometimes issues may arise keeping [persistent data](./start/docker.md#persistent-data) in sync. Refer to the [common issues](./start/docker.md#common-issues) section in the docker setup guide for further details.
### Permission denied for mkdir: /home/inventree

View File

@ -25,7 +25,7 @@ Templates are rendered using standard HTML / CSS - if you are familiar with web
### Template Language
Uploaded report template files are passed through the [django template rendering framework](https://docs.djangoproject.com/en/dev/topics/templates/), and as such accept the same variable template strings as any other django template file. Different variables are passed to the report template (based on the context of the report) and can be used to customize the contents of the generated PDF.
Uploaded report template files are passed through the [django template rendering framework]({% include "django.html" %}/topics/templates/), and as such accept the same variable template strings as any other django template file. Different variables are passed to the report template (based on the context of the report) and can be used to customize the contents of the generated PDF.
### Variables
@ -77,7 +77,7 @@ Item: {{ item }}
```
!!! info "Conditionals"
Refer to the [django template language documentation](https://docs.djangoproject.com/en/dev/ref/templates/language/) for more information.
Refer to the [django template language documentation]({% include "django.html" %}/ref/templates/language/) for more information.
### Localization Issues
@ -278,7 +278,7 @@ Asset files can be rendered directly into the template as follows
If the requested asset name does not match the name of an uploaded asset, the template will continue without loading the image.
!!! info "Assets location"
You need to ensure your asset images to the report/assets directory in the [data directory](../start/docker_dev.md/#data-directory). Upload new assets via the [admin interface](../settings/admin.md) to ensure they are uploaded to the correct location on the server.
You need to ensure your asset images to the report/assets directory in the [data directory](../start/intro.md#file-storage). Upload new assets via the [admin interface](../settings/admin.md) to ensure they are uploaded to the correct location on the server.
## Report Snippets

View File

@ -94,7 +94,7 @@ This can be used to track usage and performance of the InvenTree backend and con
If your InvenTree instance is used in a multi-site environment, you can enable multi-site support. Note that supporting multiple sites is well outside the scope of most InvenTree installations. If you know what you are doing, and have a good reason to enable multi-site support, you can do so by setting the `INVENTREE_SITE_MULTI` environment variable to `True`.
!!! tip "Django Documentation"
For more information on multi-site support, refer to the [Django documentation](https://docs.djangoproject.com/en/3.2/ref/contrib/sites/).
For more information on multi-site support, refer to the [Django documentation]({% include "django.html" %}/ref/contrib/sites/).
| Environment Variable | Config Key | Description | Default |
| --- | --- | --- | --- |

View File

@ -9,7 +9,7 @@ title: Bare Metal Production Server
The following instructions provide a reasonably performant server, using [gunicorn](https://gunicorn.org/) as a webserver, and [supervisor](http://supervisord.org/) as a process manager.
For alternative deployment methods, django apps provide multiple deployment methods - see the [Django documentation](https://docs.djangoproject.com/en/2.2/howto/deployment/).
For alternative deployment methods, django apps provide multiple deployment methods - see the [Django documentation]({% include "django.html" %}/howto/deployment/).
There are also numerous online tutorials describing how to deploy a Django application either locally or on an online platform.
@ -102,10 +102,9 @@ In addition to the InvenTree server, you will need a method of delivering static
### 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 [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 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!
As a starting point, you can refer to the [docker production example](./docker_prod.md) for a demonstration of running InvenTree behind a nginx proxy.
As a starting point, you can refer to the [docker guide](./docker.md) for a demonstration of running InvenTree behind a Caddy proxy.

View File

@ -1,28 +1,13 @@
---
title: Database Configuration
title: InvenTree Configuration
---
## Database Configuration
## InvenTree Configuration
While many InvenTree options can be configured at "run time", there are a number of system configuration parameters which need to be set *before* running InvenTree. Admin users will need to adjust the InvenTree installation to meet the particular needs of their setup. For example, pointing to the correct database backend, or specifying a list of allowed hosts.
InvenTree system settings can be specified either via environment variables, or in a configuration file.
!!! info "Environment Variables"
Settings specified using environment variables take priority. Values provided in the configuration file are ignored if a matching environment variable is present.
### Environment Variables
In addition to specifying InvenTree options via the `config.yaml` file, these options can also be specified via environment variables. This can be usful for system administrators who want the flexibility of altering settings without editing the configuration file.
Environment variable settings generally use the `INVENTREE_` prefix, and are all uppercase.
!!! info "Configuration Priority"
Configuration options set via environment variables will take priority over the values set in the `config.yaml` file.
!!! warning "Available Variables"
Some configuration options cannot be set via environment variables. Refer to the documentation below.
### Configuration File
To support install specific settings, a simple configuration file `config.yaml` is provided. This configuration file is loaded by the InvenTree server at runtime. Settings specific to a given install should be adjusted in `config.yaml`.
@ -43,7 +28,19 @@ The configuration file *template* can be found on [GitHub](https://github.com/in
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.
!!! tip "Restart Server"
The contents of the configuration file are read when the InevnTree server first launches. If any changes are made to the configuration file, ensure that the server is restarted, so that the changes can be made operational.
The contents of the configuration file are read when the InvenTree server first launches. If any changes are made to the configuration file, ensure that the server is restarted, so that the changes can be made operational.
### Environment Variables
In addition to specifying InvenTree options via the `config.yaml` file, these options can also be specified via environment variables. This can be useful for system administrators who want the flexibility of altering settings without editing the configuration file.
Environment variable settings generally use the `INVENTREE_` prefix, and are all uppercase.
!!! info "Configuration Priority"
Configuration options set via environment variables will take priority over the values set in the `config.yaml` file. This can be useful for overriding specific settings without needing to edit the configuration file.
!!! warning "Available Variables"
Some configuration options cannot be set via environment variables. Refer to the documentation below.
## Basic Options
@ -56,31 +53,52 @@ The following basic options are available:
| INVENTREE_DB_LOGGING | db_logging | Enable logging of database messages | False |
| INVENTREE_TIMEZONE | timezone | Server timezone | UTC |
| INVENTREE_SITE_URL | site_url | Specify a fixed site URL | *Not specified* |
| INVENTREE_ADMIN_ENABLED | admin_enabled | Enable the [django administrator interface](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/) | True |
| INVENTREE_ADMIN_ENABLED | admin_enabled | Enable the [django administrator interface]({% include "django.html" %}/ref/contrib/admin/) | True |
| INVENTREE_ADMIN_URL | admin_url | URL for accessing [admin interface](../settings/admin.md) | admin |
| INVENTREE_LANGUAGE | language | Default language | en-us |
| INVENTREE_BASE_URL | base_url | Server base URL | *Not specified* |
| INVENTREE_AUTO_UPDATE | auto_update | Database migrations will be run automatically | False |
### Admin Site
## Server Access
Django provides a powerful [administrator interface](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/) which can be used to manage the InvenTree database. This interface is enabled by default, but can be disabled by setting `INVENTREE_ADMIN_ENABLED` to `False`.
Depending on how your InvenTree installation is configured, you will need to pay careful attention to the following settings. If you are running your server behind a proxy, or want to adjust support for CORS requests, one or more of the following settings may need to be adjusted.
#### Custom Admin URL
!!! warning "Advanced Users"
The following settings require a certain assumed level of knowledge. You should also refer to the [django documentation]({% include "django.html" %}/ref/settings/) for more information.
By default, the admin interface is available at the `/admin/` URL. This can be changed by setting the `INVENTREE_ADMIN_URL` environment variable.
!!! danger "Not Secure"
Allowing access from any host is not secure, and should be adjusted for your installation.
!!! info "Environment Variables"
Note that a provided environment variable will override the value provided in the configuration file.
!!! success "INVENTREE_SITE_URL"
If you have specified the `INVENTREE_SITE_URL`, this will automatically be used as a trusted CSRF and CORS host (see below).
| Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- |
| INVENTREE_ALLOWED_HOSTS | allowed_hosts | List of allowed hosts | `*` |
| INVENTREE_TRUSTED_ORIGINS | trusted_origins | List of trusted origins. Refer to the [django documentation]({% include "django.html" %}/ref/settings/#csrf-trusted-origins) | Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list. |
| INVENTREE_CORS_ORIGIN_ALLOW_ALL | cors.allow_all | Allow all remote URLS for CORS checks | False |
| INVENTREE_CORS_ORIGIN_WHITELIST | cors.whitelist | List of whitelisted CORS URLs. Refer to the [django-cors-headers documentation](https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr) | Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list. |
| 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_CORS_ALLOW_CREDENTIALS | cors.allow_credentials | Allow cookies in cross-site requests | True |
## Admin Site
Django provides a powerful [administrator interface]({% include "django.html" %}/ref/contrib/admin/) which can be used to manage the InvenTree database. This interface is enabled by default, and available at the `/admin/` URL.
The following admin site configuration options are available:
| Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- |
| INVENTREE_ADMIN_ENABLED | admin_enabled | Enable the django administrator interface | True |
| INVENTREE_ADMIN_URL | admin_url | URL for accessing the admin interface | admin |
!!! warning "Security"
Changing the admin URL is a simple way to improve security, but it is not a substitute for proper security practices.
### Base URL Configuration
The base URL of the InvenTree site is required for constructing absolute URLs in a number of circumstances. To construct a URL, the InvenTree iterates through the following options in decreasing order of importance:
1. Static configuration (i.e. set using environment variable or configuration file as above)
2. Global settings (i.e. configured at run-time in the [global settings](../settings/global.md))
3. Using the hostname supplied by the user request
## Administrator Account
An administrator account can be specified using the following environment variables:
@ -196,32 +214,6 @@ A list of currency codes (e.g. *AUD*, *CAD*, *JPY*, *USD*) can be specified usin
!!! tip "More Info"
Read the [currencies documentation](../settings/currency.md) for more information on currency support in InvenTree
## Server Access
Depending on how your InvenTree installation is configured, you will need to pay careful attention to the following settings. If you are running your server behind a proxy, or want to adjust support for CORS requests, one or more of the following settings may need to be adjusted.
!!! warning "Advanced Users"
The following settings require a certain assumed level of knowledge. You should also refer to the [django documentation](https://docs.djangoproject.com/en/4.2/ref/settings/) for more information.
!!! danger "Not Secure"
Allowing access from any host is not secure, and should be adjusted for your installation.
!!! info "Environment Variables"
Note that a provided environment variable will override the value provided in the configuration file.
!!! success "INVENTREE_SITE_URL"
If you have specified the `INVENTREE_SITE_URL`, this will automatically be used as a trusted CSRF and CORS host (see below).
| Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- |
| INVENTREE_ALLOWED_HOSTS | allowed_hosts | List of allowed hosts | `*` |
| INVENTREE_TRUSTED_ORIGINS | trusted_origins | List of trusted origins. Refer to the [django documentation](https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins) | Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list. |
| INVENTREE_CORS_ORIGIN_ALLOW_ALL | cors.allow_all | Allow all remote URLS for CORS checks | False |
| INVENTREE_CORS_ORIGIN_WHITELIST | cors.whitelist | List of whitelisted CORS URLs. Refer to the [django-cors-headers documentation](https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr) | Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list. |
| 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_CORS_ALLOW_CREDENTIALS | cors.allow_credentials | Allow cookies in cross-site requests | True |
## File Storage Locations
InvenTree requires some external directories for storing files:
@ -230,6 +222,7 @@ InvenTree requires some external directories for storing files:
| --- | --- | --- | --- |
| 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_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
@ -298,7 +291,7 @@ The InvenTree server can be integrated with the [sentry.io](https://sentry.io) m
!!! info "Default DSN"
If enabled with the default DSN, server errors will be logged to a sentry.io account monitored by the InvenTree developers.
### Customisation Options
### Customization Options
The logo and custom messages can be changed/set:
@ -327,9 +320,3 @@ The following [plugin](../extend/plugins.md) configuration options are available
| INVENTREE_PLUGIN_NOINSTALL | plugin_noinstall | Disable Plugin installation via API - only use plugins.txt file | False |
| INVENTREE_PLUGIN_FILE | plugins_plugin_file | Location of plugin installation file | *Not specified* |
| INVENTREE_PLUGIN_DIR | plugins_plugin_dir | Location of external plugin directory | *Not specified* |
## Other Options
### Middleware
Custom middleware layers can be specified here.

View File

@ -2,18 +2,24 @@
title: Docker Setup
---
## Docker Image
## Docker Installation Guide
The information on this page serves as useful theory and background information for setting up InvenTree using docker.
!!! tip "Docker Install"
To jump right into the installation process, refer to the [docker installation guide](./docker_install.md)
## Docker Theory
The most convenient method of installing and running InvenTree is to use the official [docker image](https://hub.docker.com/r/inventree/inventree), available from docker-hub.
The InvenTree docker image contains all the required system packages, python modules, and configuration files for running a containerised InvenTree web server.
!!! tip "Compose Yourself"
The InvenTree container requires linking with other docker containers (such as a database backend) for complete operation. Sample [docker compose](#docker-compose) scripts are provided to get you up and running
The InvenTree docker image contains all the required system packages, python modules, and configuration files for running a containerized InvenTree production installation.
!!! tip "Docker Compose"
The InvenTree container requires linking with other docker containers (such as a database backend, and a file server) for complete operation. Refer to the [docker compose](#docker-compose) instructions to get up and running
!!! warning "Check the version"
Please make sure you are reading the [STABLE](https://docs.inventree.org/en/stable/start/docker_prod/) documentation when using the stable docker image tags.
Please make sure you are reading the [STABLE](https://docs.inventree.org/en/stable/start/docker/) documentation when using the stable docker image tags.
!!! warning "Assumed Knowledge"
A very basic understanding of [Docker](https://www.docker.com/) and [docker compose](https://docs.docker.com/compose/) is assumed, for the following setup guides.
@ -26,16 +32,11 @@ Pre-built Docker images are available from [dockerhub](https://hub.docker.com/r/
| --- | --- | --- |
| **inventree:stable** | The most recent *stable* release version of InvenTree | [stable docs](https://docs.inventree.org/en/stable/start/docker/) |
| **inventree:latest** | The most up-to-date *development* version of InvenTree. | [latest docs](https://docs.inventree.org/en/latest/start/docker/) |
| **inventree:_tag_** | Specific tagged images are built for each tagged release of InvenTree, e.g. `inventree:0.7.3`| https://docs.inventree.org/en/INSERT_YOUR_TAG_HERE/start/docker/ |
| **inventree:_tag_** | Specific tagged images are built for each tagged release of InvenTree, e.g. `inventree:0.7.3`| *Refer to specific InvenTree version* |
### Docker Compose
The InvenTree docker image provides a containerized webserver, however it *must* be connected with other containers (at the very least, a database backend).
InvenTree provides sample docker compose files to get you up and running:
- A [development](#development-server) compose file provides a simple way to spin up a development environment
- A [production](#production-server) compose file is intended to be used in a production environment, running the web server behind a nginx proxy.
The InvenTree docker image provides a containerized webserver, however it *must* be connected with other containers to function.
### Environment Variables
@ -77,14 +78,82 @@ Plugins are supported natively when running under docker. There are two ways to
- Install via the `plugins.txt` file provided in the external data directory
- Install into the `plugins/` subdirectory in the external data directory
## Docker Setup Guides
## Docker Compose
With these basics in mind, refer to the following installation guides:
[docker compose](https://docs.docker.com/compose/) is used to sequence all the required containerized processes.
### Production Server
### Static and Media Files
Refer to the [docker production server setup guide](./docker_prod.md) for instructions on configuring a production server using docker.
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.
### Development Server
!!! info "Read More"
Refer to the [Serving Files](./serving_files.md) section for more details
Refer to the [docker development server setup guide](./docker_dev.md) for instructions on configuring a development server using docker.
### SSL Certificates
The provided `Caddyfile` configuration file is setup to enable [Automatic HTTPS](https://caddyserver.com/docs/automatic-https) by default! All you have to do is specify a `https://` URL in the `INVENTREE_SITE_URL` variable.
### Containers
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.
#### File 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).
#### 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).
!!! warning "Disabled by default"
The *redis* container is not enabled in the default configuration. This is provided as an example for users wishing to use redis.
To enable the *redis* container, run any `docker compose` commands with the `--profile redis` flag.
You will also need to un-comment the `INVENTREE_CACHE_<...>` variables in the `.env` file.
### Data Volume
InvenTree stores any persistent data (e.g. uploaded media files, database data, etc) in a [volume](https://docs.docker.com/storage/volumes/) which is mapped to a local system directory. The location of this directory must be configured in the `.env` file, specified using the `INVENTREE_EXT_VOLUME` variable.
!!! info "Data Directory"
Make sure you change the path to the local directory where you want persistent data to be stored.
## Common Issues
### Volume Mapping
When configuring a docker install, sometimes a misconfiguration can cause peculiar issues where it seems that the installation is functioning correctly, but uploaded files and plugins do not "persist" across sessions. In such cases, the "mounted" volume has not mapped to a directory on your local filesystem. This may occur if you have tried multiple setup options without clearing existing volume bindings.
!!! tip "Start with a clean slate"
To prevent such issues, it is recommended that you start with a "clean slate" if you have previously configured an InvenTree installation under docker.
If you have previously setup InvenTree, remove existing volume bindings using the following command:
```docker volume rm -f inventree-production_inventree_data```

View File

@ -1,241 +0,0 @@
---
title: Docker Development Server
---
## Docker Development Server
You can use docker to launch and manage a development server, in a similar fashion to managing a production server.
The InvenTree dockerfile (`./Dockerfile`) uses a [multi-stage build](https://docs.docker.com/develop/develop-images/multistage-build/) process to allow both production and development setups from the same image.
There are some key differences compared to the [docker production setup](./docker_prod.md):
- The docker image is built locally, rather than being downloaded from DockerHub
- The docker image points to the source code on your local machine (mounted as a 'volume' in the docker container)
- The django webserver is used, instead of running behind Gunicorn
- The server will automatically reload when code changes are detected
!!! info "Static and Media Files"
The development server runs in DEBUG mode, and serves static and media files natively.
!!! info "Hacker Mode"
The following setup guide starts a development server which will reload "on the fly" as changes are made to the source code. This is designed for programmers and developers who wish to add and test new InvenTree features.
### Data Directory
Persistent data (such as the stored database, media files, configuration files, etc) will be stored in the `./data` directory (relative to the InvenTree source code directory).
- This directory is automatically created when you launch InvenTree via docker
- This directory is excluded from git version tracking
## Quickstart Guide
To get "up and running" with a development environment, complete with a set of [demo data](https://github.com/inventree/demo-dataset) to work with, run the following commands:
```bash
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
docker compose run inventree-dev-server invoke update
docker compose run inventree-dev-server invoke setup-test --dev
docker compose up -d
```
!!! tip "Development Server"
You can then view the development server at [http://localhost:8000](http://localhost:8000)
!!! info "Details, details"
For a more in-depth setup guide, continue reading below!
## Development Setup Guide
To get started with an InvenTree development setup, follow the simple steps outlined below. Before continuing, ensure that you have completed the following steps:
- Downloaded the InvenTree source code to your local machine
- Installed docker on your local machine (install *Docker Desktop* on Windows)
- Have a terminal open to the root directory of the InvenTree source code
### Edit Environment Variables (Optional)
If desired, the user may edit the environment variables, located in the `.env` file.
!!! success "This step is optional"
This step can be skipped, as the default variables will work just fine!
!!! info "Database Credentials"
You may also wish to change the database username (`INVENTREE_DB_USER`) and password (`INVENTREE_DB_PASSWORD`) from their default values
### Perform Initial Setup
Perform the initial database setup by running the following command:
```bash
docker compose run inventree-dev-server invoke update
```
If this is the first time you are configuring the development server, this command will build a development version of the inventree docker image.
This command also performs the following steps:
- Ensure required python packages are installed
- Perform the required schema updates to create the required database tables
- Update translation files
- Collect all required static files into a directory where they can be served by nginx
!!! info "Grab a coffee"
This initial build process may take a few minutes!
### Import Demo Data
To fill the database with a demo dataset, run the following command:
```bash
docker compose run inventree-dev-server invoke setup-test --dev
```
### Start Docker Containers
Now that the database has been created, and migrations applied, we are ready to launch the InvenTree containers:
```bash
docker compose up -d
```
### Create Admin Account
If you are creating the initial database, you need to create an admin (superuser) account for the database. Run the command below, and follow the prompts:
!!! info "Containers must be running"
For the `invoke superuser` command to execute properly, ensure you have run the `docker compose up -d` command.
```bash
docker compose run inventree-dev-server invoke superuser
```
This command launches the remaining containers:
- `inventree-dev-server` - InvenTree web server
- `inventree-dev-worker` - Background worker
!!! success "Check Connection"
Check that the server is running at [http://localhost:8000](http://localhost:8000). The server may take a few minutes to be ready.
## Running commands in the container
Using `docker compose run [...]` commands creates a new container to run this specific command.
This will eventually clutter your docker with many dead containers that take up space on the system.
You can access the running containers directly with the following:
```bash
docker exec -it inventree-dev-server /bin/bash
```
You then run the following to access the virtualenv:
```bash
source data/env/bin/activate
```
This sets up a bash terminal where you can run `invoke` commands directly.
!!! warning "Tests"
Running `invoke test` in your currently active inventree-dev-server container may result in tests taking longer than usual.
### Cleaning up old containers
If you have Docker Desktop installed, you will be able to remove containers directly in the GUI.
Your active containers are grouped under "inventree" in Docker Desktop.
The main dev-server, dev-db, and dev-worker containers are all listed without the "inventree" prefix.
One time run containers, like those executed via `docker compose run [...]` are suffixed with `run-1a2b3c4d5e6f` where the hex string varies.
To remove such containers, either click the garbage bin on the end of the line, or mark the containers, and click the delete button that shows up.
This is the recommended procedure for container cleanup.
#### Advanced cleanup
!!! warning "Advanced users only"
This section requires good knowledge of Docker and how it operates.
Never perform these commands if you do not understand what they do
If you're running a container with the general boilerplate commands used with invoke (invoke test, invoke update, etc) and no custom parameters or execution, you can add the `--rm` flag to `docker compose run`, and the container will delete itself when it goes down.
Do note that any data not stored in a volume, i.e. only in the container, will be lost when the container stops.
To clean out old containers using the command line, follow this guide:
Run the following command:
```bash
docker ps -a --filter status=exited
```
This gives you a list of all stopped containers.
Find the containers you wish to delete, copy the container IDs and add them to this command:
```bash
docker rm [ID1] [ID2] [IDn]
```
When executed, this removes all containers whose IDs were pasted.
!!! warning "Execute at own risk"
The command below does not forgive errors.
Execute this only if you know what you're doing
Running this command will remove **all** stopped one-time run InvenTree containers matching parameters:
```bash
docker container prune --filter label="com.docker.compose.oneoff=True" --filter label="com.docker.compose.service=inventree-dev-server"
```
The following output will appear:
```
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
[IDs of any container that was deleted, one per line]
```
## Restarting Services
Once initial setup is complete, stopping and restarting the services is much simpler:
### Stop InvenTree Services
To stop the InvenTree development server, simply run the following command:
```
docker compose down
```
### Start InvenTree Services
To start the InvenTree development server, simply run the following command:
```
docker compose up -d
```
### Restart InvenTree Services
A restart cycle is as simple as:
```
docker compose restart
```
## Editing InvenTree Source
Any changes made to the InvenTree source code are automatically detected by the services running under docker.
Thus, you can freely edit the InvenTree source files in your editor of choice.
### Database Updates
Any updates which require a database schema change must be reflected in the database itself.
To run database migrations inside the docker container, run the following command:
```
docker compose run inventree-dev-server invoke update
```
### Docker Image Updates
Occasionally, the docker image itself may receive some updates. In these cases, it may be required that the image is rebuilt. To perform a complete rebuild of the InvenTree development image from local source, run the following command:
```
docker compose build --no-cache
```

View File

@ -0,0 +1,211 @@
---
title: Docker Production Server
---
## Docker Production Server
The following guide provides a streamlined production InvenTree installation, with minimal configuration required.
!!! tip "Docker Installation"
This guide assumes that you have already installed [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/). If you have not yet installed Docker, please refer to the [official installation guide](https://docs.docker.com/get-docker/).
!!! info "Starting Point"
This setup guide should be considered a *starting point* - your particular production requirements vary from the example shown here.
!!! tip "Docker Theory"
Refer to the [docker theory section](./docker.md) for more information about how our docker installation works under the hood.
### Before You Start
!!! warning "Check the version"
Please make sure you are reading the [STABLE](https://docs.inventree.org/en/stable/start/docker/) documentation when using the stable docker image tags.
!!! warning "Docker Knowledge Required"
This guide assumes that you are reasonably comfortable with the basic concepts of docker and docker compose.
## Docker Installation
### Required Files
The following files required for this setup are provided with the InvenTree source, located in the `./docker/` directory of the [InvenTree source code](https://github.com/inventree/InvenTree/tree/master/docker/production):
| Filename | Description |
| --- | --- |
| [docker-compose.yml](https://github.com/inventree/InvenTree/blob/master/docker/docker-compose.yml) | The docker compose script |
| [.env](https://github.com/inventree/InvenTree/blob/master/docker/.env) | Environment variables |
| [Caddyfile](https://github.com/inventree/InvenTree/blob/master/docker/Caddyfile) | Caddy configuration file |
Download these files to a directory on your local machine.
!!! success "Working Directory"
This tutorial assumes you are working from a direction 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!
### Edit Environment Variables
The first step is to edit the environment variables, located in the `.env` file.
!!! warning "External Volume"
You must define the `INVENTREE_EXT_VOLUME` variable - this must point to a directory *on your local machine* where persistent data is to be stored.
!!! warning "Database Credentials"
You must also define the database username (`INVENTREE_DB_USER`) and password (`INVENTREE_DB_PASSWORD`). You should ensure they are changed from the default values for added security
!!! info "Other Variables"
There are a number of other environment variables which can be set to customize the InvenTree installation. Refer to the [environment variables](./config.md) documentation for more information.
### Initial Database Setup
Perform the initial database setup by running the following command:
```bash
docker compose run --rm inventree-server invoke update
```
This command performs the following steps:
- Ensure required python packages are installed
- Create a new (empty) database
- Perform the required schema updates to create the required database tables
- Update translation files
- Update required static files
### Create Administrator Account
If you are creating the initial database, you need to create an admin (superuser) account for the database. Run the command below, and follow the prompts:
```
docker compose run inventree-server invoke superuser
```
Alternatively, admin account details can be specified in the `.env` file, removing the need for this manual step:
| Variable | Description |
| --- | --- |
| INVENTREE_ADMIN_USER | Admin account username |
| INVENTREE_ADMIN_PASSWORD | Admin account password |
| INVENTREE_ADMIN_EMAIL | Admin account email address |
!!! warning "Scrub Account Data"
Ensure that the admin account credentials are removed from the `.env` file after the first run, for security.
### Start Docker Containers
Now that the database has been created, migrations applied, and you have created an admin account, we are ready to launch the InvenTree containers:
```
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
!!! 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).
## Updating InvenTree
To update your InvenTree installation to the latest version, follow these steps:
### Stop Containers
Stop all running containers as below:
```
docker compose down
```
### Update Images
Pull down the latest version of the InvenTree docker image
```
docker compose pull
```
This ensures that the InvenTree containers will be running the latest version of the InvenTree source code.
!!! tip "Docker Directory"
All `docker compose` commands must be performed in the same directory as the [docker-compose.yml file](#required-files)
!!! info "Tagged Version"
If you are targeting a particular "tagged" version of InvenTree, you may wish to edit the `INVENTREE_TAG` variable in the `.env` file before issuing the `docker compose pull` command
### Update Database
Run the following command to ensure that the InvenTree database is updated:
```
docker compose run --rm inventree-server invoke update
```
!!! info "Skip Backup"
By default, the `invoke update` command performs a database backup. To skip this step, add the `--skip-backup` flag
### Start Containers
Now restart the docker containers:
```
docker compose up -d
```
## Data Backup
Database and media files are stored external to the container, in the volume location specified in the `docker-compose.yml` file. It is strongly recommended that a backup of the files in this volume is performed on a regular basis.
Read more about [data backup](./backup.md).
### Exporting Database as JSON
To export the database to an agnostic JSON file, perform the following command:
```
docker compose run --rm inventree-server invoke export-records -f /home/inventree/data/data.json
```
This will export database records to the file `data.json` in your mounted volume directory.
## Further Configuration
### Custom Domain
By default, the InvenTree server is accessible at [http://inventree.localhost](http://inventree.localhost). If you wish to use a custom domain, you can edit the `.env` environment file to specify the domain name.
Look for the `INVENTREE_SITE_URL` variable, and set it to the desired domain name.
!!! tip "Configuration Options"
There are a number of other environment variables which can be set to customize the InvenTree installation. Refer to the [configuration documentation](./config.md) for more information.
### SSL Configuration
The provided `Caddyfile` configuration file is setup to enable [Automatic HTTPS](https://caddyserver.com/docs/automatic-https) "out of the box". All you have to do is specify a `https://` URL in the `INVENTREE_SITE_URL` variable.
The [Caddy](./docker.md#ssl-certificates) container will automatically generate SSL certificates for your domain.
#### Persistent Files
Any persistent files generated by the Caddy container (such as certificates, etc) will be stored in the `caddy` directory within the external volume.
### Demo Dataset
To quickly get started with a demo dataset, you can run the following command:
```
docker compose run --rm inventree-server invoke setup-test -i
```
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
```

View File

@ -1,247 +0,0 @@
---
title: Docker Production Server
---
## Docker Production Server
The following guide provides a streamlined production InvenTree installation, with minimal configuration required.
!!! info "Starting Point"
This setup guide should be considered a *starting point*. It is likely that your particular production requirements will vary from the example shown here.
### Before You Start
!!! warning "Check the version"
Please make sure you are reading the [STABLE](https://docs.inventree.org/en/stable/start/docker_prod/) documentation when using the stable docker image tags.
!!! warning "Docker Skills Required"
This guide assumes that you are reasonably comfortable with the basic concepts of docker and docker compose.
#### Docker Image
This production setup guide uses the official InvenTree docker image, available from dockerhub.
!!! info "Stable Version"
The provided docker compose file targets `inventree:stable` by default.
#### Docker Compose
A sample [docker compose file](https://github.com/inventree/InvenTree/blob/master/docker/production/docker-compose.yml) is provided to sequence all the required processes.
!!! tip "Starting Point"
If you require a different configuration, use this docker compose file as a starting point.
#### Static and Media Files
The sample docker compose configuration outlined on this page uses nginx 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
#### Required Files
The following files required for this setup are provided with the InvenTree source, located in the `./docker/production` directory of the [InvenTree source code](https://github.com/inventree/InvenTree/tree/master/docker/production):
| Filename | Description |
| --- | --- |
| [docker-compose.yml](https://github.com/inventree/InvenTree/blob/master/docker/production/docker-compose.yml) | The docker compose script |
| [.env](https://github.com/inventree/InvenTree/blob/master/docker/production/.env) | Environment variables |
| [nginx.prod.conf](https://github.com/inventree/InvenTree/blob/master/docker/production/nginx.prod.conf) | nginx proxy configuration file |
This tutorial assumes you are working from the `./docker/production` directory. If this is not the case, ensure that these required files are all located in your working directory.
!!! 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!
### Containers
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 | nginx proxy server |
| *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.
#### Nginx Proxy
Nginx working as a reverse proxy, separating requests for static and media files, and directing everything else to Gunicorn.
This container uses the official [nginx image](https://hub.docker.com/_/nginx).
#### 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).
!!! warning "Disabled by default"
The *redis* container is not enabled in the default configuration. This is provided as an example for users wishing to use redis.
To enable the *redis* container, run any `docker compose` commands with the `--profile redis` flag.
You will also need to un-comment the `INVENTREE_CACHE_<...>` variables in the `.env` file.
### Data Volume
InvenTree stores any persistent data (e.g. uploaded media files, database data, etc) in a [volume](https://docs.docker.com/storage/volumes/) which is mapped to a local system directory. The location of this directory must be configured in the `.env` file, specified using the `INVENTREE_EXT_VOLUME` variable.
!!! info "Data Directory"
Make sure you change the path to the local directory where you want persistent data to be stored.
#### Common Issues
When configuring a docker install, sometimes a misconfiguration can cause peculiar issues where it seems that the installation is functioning correctly, but uploaded files and plugins do not "persist" across sessions. In such cases, the "mounted" volume has not mapped to a directory on your local filesystem. This may occur if you have tried multiple setup options without clearing existing volume bindings.
!!! tip "Start with a clean slate"
To prevent such issues, it is recommended that you start with a "clean slate" if you have previously configured an InvenTree installation under docker.
If you have previously setup InvenTree, remove existing volume bindings using the following command:
```docker volume rm -f inventree-production_inventree_data```
## Production Setup Guide
### Edit Environment Variables
The first step is to edit the environment variables, located in the `.env` file.
!!! warning "External Volume"
You must define the `INVENTREE_EXT_VOLUME` variable - this must point to a directory *on your local machine* where persistent data is to be stored.
!!! warning "Database Credentials"
You must also define the database username (`INVENTREE_DB_USER`) and password (`INVENTREE_DB_PASSWORD`). You should ensure they are changed from the default values for added security
### Initial Database Setup
Perform the initial database setup by running the following command:
```bash
docker compose run inventree-server invoke update
```
This command performs the following steps:
- Ensure required python packages are installed
- Create a new (empty) database
- Perform the required schema updates to create the required database tables
- Update translation files
- Collect all required static files into a directory where they can be served by nginx
### Create Administrator Account
If you are creating the initial database, you need to create an admin (superuser) account for the database. Run the command below, and follow the prompts:
```
docker compose run inventree-server invoke superuser
```
Alternatively, admin account details can be specified in the `.env` file, removing the need for this manual step:
| Variable | Description |
| --- | --- |
| INVENTREE_ADMIN_USER | Admin account username |
| INVENTREE_ADMIN_PASSWORD | Admin account password |
| INVENTREE_ADMIN_EMAIL | Admin account email address |
!!! warning "Scrub Account Data"
Ensure that the admin account credentials are removed from the `.env` file after the first run, for security.
### Start Docker Containers
Now that the database has been created, migrations applied, and you have created an admin account, we are ready to launch the InvenTree containers:
```
docker compose up -d
```
This command launches the following containers:
- `inventree-db` - PostgreSQL database
- `inventree-server` - InvenTree web server
- `inventree-worker` - Background worker
- `inventree-nginx` - Nginx reverse proxy
!!! success "Up and Running!"
You should now be able to view the InvenTree login screen at [http://localhost:1337](http://localhost:1337)
## Updating InvenTree
To update your InvenTree installation to the latest version, follow these steps:
### Stop Containers
Stop all running containers as below:
```
docker compose down
```
### Update Images
Pull down the latest version of the InvenTree docker image
```
docker compose pull
```
This ensures that the InvenTree containers will be running the latest version of the InvenTree source code.
!!! tip "Docker Directory"
All `docker compose` commands must be performed in the same directory as the [docker-compose.yml file](#required-files)
!!! info "Tagged Version"
If you are targeting a particular "tagged" version of InvenTree, you may wish to edit the `INVENTREE_TAG` variable in the `.env` file before issuing the `docker compose pull` command
### Update Database
Run the following command to ensure that the InvenTree database is updated:
```
docker compose run inventree-server invoke update
```
!!! info "Skip Backup"
By default, the `invoke update` command performs a database backup. To skip this step, add the `--skip-backup` flag
### Start Containers
Now restart the docker containers:
```
docker compose up -d
```
## Data Backup
Database and media files are stored external to the container, in the volume location specified in the `docker-compose.yml` file. It is strongly recommended that a backup of the files in this volume is performed on a regular basis.
Read more about [data backup](./backup.md).
### Exporting Database as JSON
To export the database to an agnostic JSON file, perform the following command:
```
docker compose run inventree-server invoke export-records -f /home/inventree/data/data.json
```
This will export database records to the file `data.json` in your mounted volume directory.

View File

@ -2,71 +2,78 @@
title: Setup Introduction
---
!!! info "Fast install"
A quick-and-easy install can be done done with the following one-liner.
```bash
wget -qO install.sh https://get.inventree.org && bash install.sh
```
Read more about the [installer](./installer.md).
## Introduction
InvenTree can be self-hosted with minimal system requirements. Multiple database back-ends are supported, allowing for flexibility where required.
A functional InvenTree server can be hosted with minimal setup requirements. Multiple installation methods and database back-ends are supported, allowing for flexibility where required.
!!! info "Production Ready"
InvenTree is designed to be a production-ready application, and can be deployed in a variety of environments. The following instructions are designed to help you get started with a *production* setup. For a development setup, refer to the [devcontainer setup guide](../develop/devcontainer.md).
## Installation Methods
To quickly jump to a specific installation method, refer to the following links:
- [**Docker**](./docker.md)
- [**Package Installer**](./installer.md)
- [**Bare Metal**](./install.md)
!!! success "Docker Recommended"
The recommended method of installing InvenTree is to follow our [docker setup guide](./docker.md). InvenTree provides out-of-the-box support for docker and docker compose, which provides a simple, reliable and repeatable pipeline for integration into your production environment.
!!! info "Further Reading"
For more information on the InvenTree tech stack, continue reading below!
### Configuration Options
Independent of the preferred installation method, InvenTree provides a number of [configuration options](./config.md) which can be used to customize the server environment.
## System Components
The InvenTree server ecosystem consists of the following components:
### Database
A persistent database is required for data storage. InvenTree can be used with any of the following database backends:
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/)
* PostgreSQL
* MySQL / MariaDB
* SQLite
!!! warning "SQLite"
While SQLite provides a simpler setup and is useful for a development environment, we strongly recommend against using it for a production environment. Use PostgreSQL or MySQL instead
Database selection should be determined by your particular installation requirements.
### Media Files
Uploaded media files (images, attachments, reports, etc) are stored to a persistent storage volume.
### 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 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.
The webserver code also provides a first-party API for performing database query actions.
Once a database is setup, you need a way of accessing the data. InvenTree provides a "server" application out of the box, but this may not scale particularly well with multiple users. Instead, InvenTree can be served using a webserver such as [Gunicorn](https://gunicorn.org/). For more information see the [deployment documentation](./bare_prod.md).
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.
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.
## OS Requirements
The InvenTree documentation assumes that the operating system is a debian based Linux OS. Some installation steps may differ for different systems.
The InvenTree documentation *assumes* that the operating system is a debian based Linux OS. Some installation steps may differ for different systems.
!!! warning "Installing on Windows"
Installation on Windows is *not guaranteed* to work (at all). To install on a Windows system, it is highly recommended that you [install WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10#manual-installation-steps), and then follow installation procedure from within the WSL environment.
To install on a Windows system, you should [install WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10#manual-installation-steps), and then follow installation procedure from within the WSL environment.
!!! success "Docker"
Installation on any OS is simplified by following the [docker setup guide](../docker).
Installation on any OS is simplified by following the [docker setup guide](./docker.md).
## Python Requirements
InvenTree runs on [Python](https://python.org).
!!! warning "Python Version"
InvenTree requires Python 3.9 (or newer). If your system has an older version of Python installed, you will need to follow the update instructions for your OS.
InvenTree requires a minimum Python version of {{ config.extra.min_python_version}}. If your system has an older version of Python installed, you will need to follow the update instructions for your OS.
### 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 2.0.0 or newer. Some platforms may be shipped with older versions of invoke!
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`
@ -102,15 +109,11 @@ To configure Inventree inside a virtual environment, ``cd`` into the inventree b
source env/bin/activate
```
!!! info "Activate Virtual Environment"
if
```
source env/bin/activate
```
is not working try
```
. env/bin/activate
```
Or, if that does not work, try:
```
. env/bin/activate
```
This will place the current shell session inside a virtual environment - the terminal should display the ``(env)`` prefix.
@ -127,31 +130,10 @@ Alternatively, the source can be downloaded as a [.zip archive](https://github.c
!!! info "Updating via Git"
Downloading the source code using Git is recommended, as it allows for simple updates when a new version of InvenTree is released.
## Installation Guides
There are multiple ways to get an InvenTree server up and running, of various complexity (and robustness)!
### Docker
The recommended method of installing InvenTree is to use [docker](https://www.docker.com). InvenTree provides out-of-the-box support for docker and docker compose, which provides a simple, reliable and repeatable pipeline for integration into your production environment.
Refer to the following guides for further instructions:
- [**Docker development server setup guide**](./docker_dev.md)
- [**Docker production server setup guide**](./docker.md)
### Bare Metal
If you do not wish to use the docker container, you will need to manually install the required packages and follow through the installation guide.
Refer to the following guides for further instructions:
- [**Bare metal development server setup guide**](./bare_dev.md)
- [**Bare metal production server setup guide**](./install.md)
## Debug Mode
By default, the InvenTree web server is configured to run in [DEBUG mode](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DEBUG).
By default, a production InvenTree installation is configured to run with [DEBUG mode]({% include "django.html" %}/ref/settings/#std:setting-DEBUG) *disabled*.
Running in DEBUG mode provides many handy development features, however it is strongly recommended *NOT* to run in DEBUG mode in a production environment. This recommendation is made because DEBUG mode leaks a lot of information about your installation and may pose a security risk.

View File

@ -4,25 +4,26 @@ title: Serving Static and Media Files
## Serving Files
In production, the InvenTree web server software *does not* provide hosting of static files, or user-uploaded (media) 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/).
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.
!!! 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.
!!! into "Read More"
You can find further information in the [django documentation](https://docs.djangoproject.com/en/dev/howto/static-files/deployment/).
!!! 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.
The [docker production example](./docker_prod.md) provides an example using [Nginx](https://www.nginx.com/) to serve *static* and *media* files, and redirecting other requests to the InvenTree web server itself.
You may use this as a jumping off point, or use an entirely different server setup.
#### Static Files
### 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
### Media Files
It is highly recommended that the *media* files are served in such a way that user authentication is required.
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.
Refer to the [docker production example](./docker_prod.md) for a demonstration of using nginx to serve media files only to authenticated users, and forward authentication requests to the InvenTree web server.
### 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.

View File

@ -8,6 +8,7 @@ site_author: InvenTree
repo_url: https://github.com/inventree/inventree
repo_name: inventree/inventree
# Theme
theme:
name: material
@ -34,6 +35,7 @@ theme:
icon:
repo: fontawesome/brands/github
features:
- content.code.copy
- header.autohide
- navigation.expand
- navigation.footer
@ -75,10 +77,9 @@ nav:
- Terminology: concepts/terminology.md
- Physical Units: concepts/units.md
- Development:
- Getting started: develop/starting.md
- Contributing: develop/contributing.md
- Devcontainer: develop/devcontainer.md
- Platform UI: develop/react-frontend.md
- React Frontend: develop/react-frontend.md
- Credits: credits.md
- Privacy: privacy.md
- Release Notes: releases/release_notes.md
@ -87,8 +88,7 @@ nav:
- Configuration: start/config.md
- Docker:
- Introduction: start/docker.md
- Production: start/docker_prod.md
- Development: start/docker_dev.md
- Installation: start/docker_install.md
- Bare Metal:
- Introduction: start/install.md
- Installer: start/installer.md
@ -274,6 +274,10 @@ extra:
# provider: google
# property: UA-143467500-1
min_python_version: 3.9
min_invoke_version: 2.0.0
django_version: 4.2
version:
default: stable
provider: mike

13
docs/mlc_config.json Normal file
View File

@ -0,0 +1,13 @@
{
"ignorePatterns": [
{
"pattern": "http://inventree.localhost"
},
{
"pattern": "http://localhost"
},
{
"pattern": "http://127.0.0.1"
}
]
}

View File

@ -45,7 +45,7 @@ python-barcode[images] # Barcode generator
python-dotenv # Environment variable management
pyyaml>=6.0.1 # YAML parsing
qrcode[pil] # QR code generator
rapidfuzz==0.7.6 # Fuzzy string matching
rapidfuzz # Fuzzy string matching
regex # Advanced regular expressions
sentry-sdk # Error reporting (optional)
setuptools # Standard dependency

View File

@ -274,7 +274,7 @@ pyyaml==6.0.1
# tablib
qrcode==7.4.2
# via django-allauth-2fa
rapidfuzz==0.7.6
rapidfuzz==3.6.1
redis==5.0.1
# via django-redis
referencing==0.33.0

View File

@ -1,9 +1,11 @@
import { Trans, t } from '@lingui/macro';
import {
AspectRatio,
Button,
Group,
Image,
Modal,
Overlay,
Paper,
Text,
rem,
@ -20,6 +22,7 @@ import { InvenTreeIcon } from '../../functions/icons';
import { useUserState } from '../../states/UserState';
import { PartThumbTable } from '../../tables/part/PartThumbTable';
import { ActionButton } from '../buttons/ActionButton';
import { StylishText } from '../items/StylishText';
import { ApiImage } from './ApiImage';
/**
@ -58,9 +61,9 @@ const backup_image = '/static/img/blank_image.png';
*/
const removeModal = (apiPath: string, setImage: (image: string) => void) =>
modals.openConfirmModal({
title: t`Remove Image`,
title: <StylishText size="xl">{t`Remove Image`}</StylishText>,
children: (
<Text size="sm">
<Text>
<Trans>Remove the associated image from this item?</Trans>
</Text>
),
@ -245,13 +248,8 @@ function ImageActionButtons({
pk: string;
setImage: (image: string) => void;
}) {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title={t`Select image`} size="70%">
<PartThumbTable pk={pk} close={close} setImage={setImage} />
</Modal>
{visible && (
<Group
spacing="xs"
@ -259,24 +257,37 @@ function ImageActionButtons({
>
{actions.selectExisting && (
<ActionButton
icon={<InvenTreeIcon icon="select_image" />}
icon={
<InvenTreeIcon
icon="select_image"
iconProps={{ color: 'white' }}
/>
}
tooltip={t`Select from existing images`}
variant="outline"
size="lg"
tooltipAlignment="top"
onClick={open}
onClick={() => {
modals.open({
title: <StylishText size="xl">{t`Select Image`}</StylishText>,
size: 'xxl',
children: <PartThumbTable pk={pk} setImage={setImage} />
});
}}
/>
)}
{actions.uploadFile && (
<ActionButton
icon={<InvenTreeIcon icon="upload" />}
icon={
<InvenTreeIcon icon="upload" iconProps={{ color: 'white' }} />
}
tooltip={t`Upload new image`}
variant="outline"
size="lg"
tooltipAlignment="top"
onClick={() => {
modals.open({
title: t`Upload Image`,
title: <StylishText size="xl">{t`Upload Image`}</StylishText>,
children: (
<UploadModal apiPath={apiPath} setImage={setImage} />
)
@ -320,39 +331,33 @@ export function DetailsImage(props: DetailImageProps) {
return (
<>
<Paper
ref={ref}
style={{
position: 'relative',
width: `${IMAGE_DIMENSION}px`,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<ApiImage
src={img}
style={{ zIndex: 1 }}
height={IMAGE_DIMENSION}
width={IMAGE_DIMENSION}
onClick={() => {
modals.open({
children: <ApiImage src={img} />,
withCloseButton: false
});
}}
/>
{permissions.hasChangeRole(props.appRole) && (
<ImageActionButtons
visible={hovered}
actions={props.imageActions}
apiPath={props.apiPath}
hasImage={props.src ? true : false}
pk={props.pk}
setImage={setAndRefresh}
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1}>
<>
<ApiImage
src={img}
height={IMAGE_DIMENSION}
width={IMAGE_DIMENSION}
onClick={() => {
modals.open({
children: <ApiImage src={img} />,
withCloseButton: false
});
}}
/>
)}
</Paper>
{permissions.hasChangeRole(props.appRole) && hovered && (
<Overlay color="black" opacity={0.8}>
<ImageActionButtons
visible={hovered}
actions={props.imageActions}
apiPath={props.apiPath}
hasImage={props.src ? true : false}
pk={props.pk}
setImage={setAndRefresh}
/>
</Overlay>
)}
</>
</AspectRatio>
</>
);
}

View File

@ -10,7 +10,7 @@ export enum UserRoles {
return_order = 'return_order',
sales_order = 'sales_order',
stock = 'stock',
stock_location = 'stocklocation',
stock_location = 'stock_location',
stocktake = 'stocktake'
}

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