diff --git a/.gitignore b/.gitignore
index 7c360a8231..5dd3580ef6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,4 +61,7 @@ secret_key.txt
# Coverage reports
.coverage
-htmlcov/
\ No newline at end of file
+htmlcov/
+
+# Development files
+dev/
\ No newline at end of file
diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py
index 618c9f730f..208472ff2e 100644
--- a/InvenTree/InvenTree/settings.py
+++ b/InvenTree/InvenTree/settings.py
@@ -191,7 +191,7 @@ STATIC_URL = '/static/'
STATIC_ROOT = os.path.abspath(
get_setting(
'INVENTREE_STATIC_ROOT',
- CONFIG.get('static_root', '/home/inventree/static')
+ CONFIG.get('static_root', '/home/inventree/data/static')
)
)
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
index bce493fb23..0108418517 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -37,6 +37,7 @@ from django.conf.urls.static import static
from django.views.generic.base import RedirectView
from rest_framework.documentation import include_docs_urls
+from .views import auth_request
from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView
from .views import CurrencySettingsView, CurrencyRefreshView
@@ -155,24 +156,28 @@ urlpatterns = [
url(r'^search/', SearchView.as_view(), name='search'),
url(r'^stats/', DatabaseStatsView.as_view(), name='stats'),
+ url(r'^auth/?', auth_request),
+
url(r'^api/', include(apipatterns)),
url(r'^api-doc/', include_docs_urls(title='InvenTree API')),
url(r'^markdownx/', include('markdownx.urls')),
]
-# Static file access
-urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+# Server running in "DEBUG" mode?
+if settings.DEBUG:
+ # Static file access
+ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
-# Media file access
-urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ # Media file access
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
-# Debug toolbar access (if in DEBUG mode)
-if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS:
- import debug_toolbar
- urlpatterns = [
- path('__debug/', include(debug_toolbar.urls)),
- ] + urlpatterns
+ # Debug toolbar access (only allowed in DEBUG mode)
+ if 'debug_toolbar' in settings.INSTALLED_APPS:
+ import debug_toolbar
+ urlpatterns = [
+ path('__debug/', include(debug_toolbar.urls)),
+ ] + urlpatterns
# Send any unknown URLs to the parts page
urlpatterns += [url(r'^.*$', RedirectView.as_view(url='/index/', permanent=False), name='index')]
diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py
index 7b8546b1c2..5161bee6a1 100644
--- a/InvenTree/InvenTree/version.py
+++ b/InvenTree/InvenTree/version.py
@@ -8,7 +8,7 @@ import re
import common.models
-INVENTREE_SW_VERSION = "0.2.3 pre"
+INVENTREE_SW_VERSION = "0.2.4 pre"
"""
Increment thi API version number whenever there is a significant change to the API that any clients need to know about
diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index 108908c571..06aec54c18 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -10,7 +10,7 @@ from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
-from django.http import JsonResponse, HttpResponseRedirect
+from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.urls import reverse_lazy
from django.conf import settings
@@ -36,6 +36,19 @@ from .helpers import str2bool
from rest_framework import views
+def auth_request(request):
+ """
+ Simple 'auth' endpoint used to determine if the user is authenticated.
+ Useful for (for example) redirecting authentication requests through
+ django's permission framework.
+ """
+
+ if request.user.is_authenticated:
+ return HttpResponse(status=200)
+ else:
+ return HttpResponse(status=403)
+
+
class TreeSerializer(views.APIView):
""" JSON View for serializing a Tree object.
diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml
index 1333b876b8..0e6232d270 100644
--- a/InvenTree/config_template.yaml
+++ b/InvenTree/config_template.yaml
@@ -129,9 +129,9 @@ cors:
media_root: '/home/inventree/data/media'
# STATIC_ROOT is the local filesystem location for storing static files
-# By default, it is stored under /home/inventree
+# By default, it is stored under /home/inventree/data/static
# Use environment variable INVENTREE_STATIC_ROOT
-static_root: '/home/inventree/static'
+static_root: '/home/inventree/data/static'
# Optional URL schemes to allow in URL fields
# By default, only the following schemes are allowed: ['http', 'https', 'ftp', 'ftps']
diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html
index ec296d4174..2e1cb2e71f 100644
--- a/InvenTree/part/templates/part/part_base.html
+++ b/InvenTree/part/templates/part/part_base.html
@@ -181,6 +181,14 @@
{% endif %}
{% endif %}
{% endif %}
+ {% if part.trackable and part.getLatestSerialNumber %}
+
|
+
+ |
+ {% trans "Latest Serial Number" %} |
+ {{ part.getLatestSerialNumber }}{% include "clip.html"%} |
+
+ {% endif %}
diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py
index 9bcdc5182e..d60689ccef 100644
--- a/InvenTree/stock/serializers.py
+++ b/InvenTree/stock/serializers.py
@@ -161,6 +161,13 @@ class StockItemSerializer(InvenTreeModelSerializer):
required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False)
+ purchase_price = serializers.SerializerMethodField()
+
+ def get_purchase_price(self, obj):
+ """ Return purchase_price (Money field) as string (includes currency) """
+
+ return str(obj.purchase_price) if obj.purchase_price else '-'
+
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
@@ -215,6 +222,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
'tracking_items',
'uid',
'updated',
+ 'purchase_price',
]
""" These fields are read-only in this context.
diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js
index 0ce10b28a7..4e992df67f 100644
--- a/InvenTree/templates/js/stock.js
+++ b/InvenTree/templates/js/stock.js
@@ -660,6 +660,11 @@ function loadStockTable(table, options) {
title: '{% trans "Last Updated" %}',
sortable: true,
},
+ {
+ field: 'purchase_price',
+ title: '{% trans "Purchase Price" %}',
+ sortable: true,
+ },
{
field: 'packaging',
title: '{% trans "Packaging" %}',
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 5c5d1dc32a..e27bd5591a 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -7,6 +7,8 @@ ARG branch="master"
ENV PYTHONUNBUFFERED 1
# InvenTree key settings
+
+# The INVENTREE_HOME directory is where the InvenTree source repository will be located
ENV INVENTREE_HOME="/home/inventree"
# GitHub settings
@@ -17,10 +19,9 @@ ENV INVENTREE_LOG_LEVEL="INFO"
ENV INVENTREE_DOCKER="true"
# InvenTree paths
-ENV INVENTREE_SRC_DIR="${INVENTREE_HOME}/src"
-ENV INVENTREE_MNG_DIR="${INVENTREE_SRC_DIR}/InvenTree"
+ENV INVENTREE_MNG_DIR="${INVENTREE_HOME}/InvenTree"
ENV INVENTREE_DATA_DIR="${INVENTREE_HOME}/data"
-ENV INVENTREE_STATIC_ROOT="${INVENTREE_HOME}/static"
+ENV INVENTREE_STATIC_ROOT="${INVENTREE_DATA_DIR}/static"
ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DATA_DIR}/media"
ENV INVENTREE_CONFIG_FILE="${INVENTREE_DATA_DIR}/config.yaml"
@@ -44,8 +45,6 @@ RUN addgroup -S inventreegroup && adduser -S inventree -G inventreegroup
WORKDIR ${INVENTREE_HOME}
-RUN mkdir -p ${INVENTREE_STATIC_ROOT}
-
# Install required system packages
RUN apk add --no-cache git make bash \
gcc libgcc g++ libstdc++ \
@@ -78,37 +77,40 @@ RUN pip install --no-cache-dir -U gunicorn
FROM base as production
# Clone source code
RUN echo "Downloading InvenTree from ${INVENTREE_REPO}"
-RUN git clone --branch ${INVENTREE_BRANCH} --depth 1 ${INVENTREE_REPO} ${INVENTREE_SRC_DIR}
+RUN git clone --branch ${INVENTREE_BRANCH} --depth 1 ${INVENTREE_REPO} ${INVENTREE_HOME}
# Install InvenTree packages
-RUN pip install --no-cache-dir -U -r ${INVENTREE_SRC_DIR}/requirements.txt
+RUN pip install --no-cache-dir -U -r ${INVENTREE_HOME}/requirements.txt
# Copy gunicorn config file
COPY gunicorn.conf.py ${INVENTREE_HOME}/gunicorn.conf.py
# Copy startup scripts
-COPY start_prod_server.sh ${INVENTREE_SRC_DIR}/start_prod_server.sh
-COPY start_worker.sh ${INVENTREE_SRC_DIR}/start_worker.sh
+COPY start_prod_server.sh ${INVENTREE_HOME}/start_prod_server.sh
+COPY start_prod_worker.sh ${INVENTREE_HOME}/start_prod_worker.sh
-RUN chmod 755 ${INVENTREE_SRC_DIR}/start_prod_server.sh
-RUN chmod 755 ${INVENTREE_SRC_DIR}/start_worker.sh
+RUN chmod 755 ${INVENTREE_HOME}/start_prod_server.sh
+RUN chmod 755 ${INVENTREE_HOME}/start_prod_worker.sh
-# exec commands should be executed from the "src" directory
-WORKDIR ${INVENTREE_SRC_DIR}
+WORKDIR ${INVENTREE_HOME}
# Let us begin
CMD ["bash", "./start_prod_server.sh"]
FROM base as dev
-# The development image requires the source code to be mounted to /home/inventree/src/
-# So from here, we don't actually "do" anything
+# The development image requires the source code to be mounted to /home/inventree/
+# So from here, we don't actually "do" anything, apart from some file management
-WORKDIR ${INVENTREE_SRC_DIR}
+ENV INVENTREE_DEV_DIR = "${INVENTREE_HOME}/dev"
-COPY start_dev_server.sh ${INVENTREE_HOME}/start_dev_server.sh
-COPY start_dev_worker.sh ${INVENTREE_HOME}/start_dev_worker.sh
-RUN chmod 755 ${INVENTREE_HOME}/start_dev_server.sh
-RUN chmod 755 ${INVENTREE_HOME}/start_dev_worker.sh
+# Override default path settings
+ENV INVENTREE_STATIC_ROOT="${INVENTREE_DEV_DIR}/static"
+ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DEV_DIR}/media"
+ENV INVENTREE_CONFIG_FILE="${INVENTREE_DEV_DIR}/config.yaml"
+ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DEV_DIR}/secret_key.txt"
-CMD ["bash", "/home/inventree/start_dev_server.sh"]
+WORKDIR ${INVENTREE_HOME}
+
+# Launch the development server
+CMD ["bash", "/home/inventree/docker/start_dev_server.sh"]
diff --git a/docker/dev-config.env b/docker/dev-config.env
index 200c3db479..fe1f073633 100644
--- a/docker/dev-config.env
+++ b/docker/dev-config.env
@@ -1,7 +1,9 @@
INVENTREE_DB_ENGINE=sqlite3
-INVENTREE_DB_NAME=/home/inventree/src/inventree_docker_dev.sqlite3
-INVENTREE_MEDIA_ROOT=/home/inventree/src/inventree_media
-INVENTREE_STATIC_ROOT=/home/inventree/src/inventree_static
-INVENTREE_CONFIG_FILE=/home/inventree/src/config.yaml
-INVENTREE_SECRET_KEY_FILE=/home/inventree/src/secret_key.txt
-INVENTREE_DEBUG=true
\ No newline at end of file
+INVENTREE_DB_NAME=/home/inventree/dev/inventree_db.sqlite3
+INVENTREE_MEDIA_ROOT=/home/inventree/dev/media
+INVENTREE_STATIC_ROOT=/home/inventree/dev/static
+INVENTREE_CONFIG_FILE=/home/inventree/dev/config.yaml
+INVENTREE_SECRET_KEY_FILE=/home/inventree/dev/secret_key.txt
+INVENTREE_DEBUG=true
+INVENTREE_WEB_ADDR=0.0.0.0
+INVENTREE_WEB_PORT=8000
\ No newline at end of file
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index ddf50135c9..29eccc26c6 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -13,8 +13,8 @@ version: "3.8"
services:
# InvenTree web server services
# Uses gunicorn as the web server
- inventree-server:
- container_name: inventree-server
+ inventree-dev-server:
+ container_name: inventree-dev-server
build:
context: .
target: dev
@@ -22,7 +22,7 @@ services:
- 8000:8000
volumes:
# Ensure you specify the location of the 'src' directory at the end of this file
- - src:/home/inventree/src
+ - src:/home/inventree
env_file:
# Environment variables required for the dev server are configured in dev-config.env
- dev-config.env
@@ -30,24 +30,24 @@ services:
restart: unless-stopped
# Background worker process handles long-running or periodic tasks
- inventree-worker:
- container_name: inventree-worker
+ inventree-dev-worker:
+ container_name: inventree-dev-worker
build:
context: .
target: dev
- entrypoint: /home/inventree/start_dev_worker.sh
+ entrypoint: /home/inventree/docker/start_dev_worker.sh
depends_on:
- - inventree-server
+ - inventree-dev-server
volumes:
# Ensure you specify the location of the 'src' directory at the end of this file
- - src:/home/inventree/src
+ - src:/home/inventree
env_file:
# Environment variables required for the dev server are configured in dev-config.env
- dev-config.env
restart: unless-stopped
volumes:
- # NOTE: Change /path/to/src to a directory on your local machine, where the InvenTree source code is located
+ # 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
@@ -55,5 +55,5 @@ volumes:
type: none
o: bind
# This directory specified where InvenTree source code is stored "outside" the docker containers
- # Note: This directory must conatin the file *manage.py*
- device: /path/to/inventree/src
+ # By default, this directory is one level above the "docker" directory
+ device: ../
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 9e77dd1181..dcd35af148 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -30,6 +30,7 @@ services:
- POSTGRES_USER=pguser
- POSTGRES_PASSWORD=pgpassword
volumes:
+ # Map 'data' volume such that postgres database is stored externally
- data:/var/lib/postgresql/data/
restart: unless-stopped
@@ -43,8 +44,8 @@ services:
depends_on:
- inventree-db
volumes:
+ # Data volume must map to /home/inventree/data
- data:/home/inventree/data
- - static:/home/inventree/static
environment:
# Default environment variables are configured to match the 'db' container
# Note: If you change the database image, these will need to be adjusted
@@ -61,13 +62,13 @@ services:
inventree-worker:
container_name: inventree-worker
image: inventree/inventree:latest
- entrypoint: ./start_worker.sh
+ entrypoint: ./start_prod_worker.sh
depends_on:
- inventree-db
- inventree-server
volumes:
+ # Data volume must map to /home/inventree/data
- data:/home/inventree/data
- - static:/home/inventree/static
environment:
# Default environment variables are configured to match the 'db' container
# Note: If you change the database image, these will need to be adjusted
@@ -81,7 +82,8 @@ services:
restart: unless-stopped
# nginx acts as a reverse proxy
- # static files are served by nginx
+ # 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!
inventree-proxy:
@@ -93,11 +95,11 @@ services:
# Change "1337" to the port that you want InvenTree web server to be available on
- 1337:80
volumes:
- # Provide nginx.conf file to the container
+ # Provide ./nginx.conf file to the container
# Refer to the provided example file as a starting point
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- # Static data volume is mounted to /var/www/static
- - static:/var/www/static:ro
+ # nginx proxy needs access to static and media files
+ - data:/var/www
restart: unless-stopped
volumes:
@@ -110,6 +112,4 @@ volumes:
o: bind
# This directory specified where InvenTree data are stored "outside" the docker containers
# Change this path to a local system path where you want InvenTree data stored
- device: /path/to/data
- # Static files, shared between containers
- static:
\ No newline at end of file
+ device: /path/to/data
\ No newline at end of file
diff --git a/docker/nginx.conf b/docker/nginx.conf
index 754a7321bb..270378735e 100644
--- a/docker/nginx.conf
+++ b/docker/nginx.conf
@@ -1,3 +1,4 @@
+
server {
# Listen for connection on (internal) port 80
@@ -34,4 +35,23 @@ server {
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;
+ }
+
+ # 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;
+ }
+
}
\ No newline at end of file
diff --git a/docker/start_dev_server.sh b/docker/start_dev_server.sh
index d4e33a79a5..ad12ec023a 100644
--- a/docker/start_dev_server.sh
+++ b/docker/start_dev_server.sh
@@ -16,21 +16,22 @@ if test -f "$INVENTREE_CONFIG_FILE"; then
echo "$INVENTREE_CONFIG_FILE exists - skipping"
else
echo "Copying config file to $INVENTREE_CONFIG_FILE"
- cp $INVENTREE_SRC_DIR/InvenTree/config_template.yaml $INVENTREE_CONFIG_FILE
+ cp $INVENTREE_HOME/InvenTree/config_template.yaml $INVENTREE_CONFIG_FILE
fi
-# Setup a virtual environment
-python3 -m venv inventree-docker-dev
+# Setup a virtual environment (within the "dev" directory)
+python3 -m venv ./dev/env
-source inventree-docker-dev/bin/activate
+# Activate the virtual environment
+source ./dev/env/bin/activate
echo "Installing required packages..."
-pip install --no-cache-dir -U -r ${INVENTREE_SRC_DIR}/requirements.txt
+pip install --no-cache-dir -U -r ${INVENTREE_HOME}/requirements.txt
echo "Starting InvenTree server..."
# Wait for the database to be ready
-cd $INVENTREE_MNG_DIR
+cd ${INVENTREE_HOME}/InvenTree
python manage.py wait_for_db
sleep 10
@@ -45,4 +46,4 @@ python manage.py migrate --run-syncdb || exit 1
python manage.py clearsessions || exit 1
# Launch a development server
-python manage.py runserver 0.0.0.0:$INVENTREE_WEB_PORT
+python manage.py runserver ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}
diff --git a/docker/start_dev_worker.sh b/docker/start_dev_worker.sh
index 099f447a9c..bfadc1f49a 100644
--- a/docker/start_dev_worker.sh
+++ b/docker/start_dev_worker.sh
@@ -2,15 +2,15 @@
echo "Starting InvenTree worker..."
-cd $INVENTREE_SRC_DIR
+cd $INVENTREE_HOME
# Activate virtual environment
-source inventree-docker-dev/bin/activate
+source ./dev/env/bin/activate
sleep 5
# Wait for the database to be ready
-cd $INVENTREE_MNG_DIR
+cd InvenTree
python manage.py wait_for_db
sleep 10
diff --git a/docker/start_prod_server.sh b/docker/start_prod_server.sh
index 2e5acb5c9d..9d86b331eb 100644
--- a/docker/start_prod_server.sh
+++ b/docker/start_prod_server.sh
@@ -16,7 +16,7 @@ if test -f "$INVENTREE_CONFIG_FILE"; then
echo "$INVENTREE_CONFIG_FILE exists - skipping"
else
echo "Copying config file to $INVENTREE_CONFIG_FILE"
- cp $INVENTREE_SRC_DIR/InvenTree/config_template.yaml $INVENTREE_CONFIG_FILE
+ cp $INVENTREE_HOME/InvenTree/config_template.yaml $INVENTREE_CONFIG_FILE
fi
echo "Starting InvenTree server..."
diff --git a/docker/start_worker.sh b/docker/start_prod_worker.sh
similarity index 100%
rename from docker/start_worker.sh
rename to docker/start_prod_worker.sh
diff --git a/requirements.txt b/requirements.txt
index 3eb31a900b..abcf2cb098 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
invoke>=1.4.0 # Invoke build tool
wheel>=0.34.2 # Wheel
-Django==3.2.2 # Django package
+Django==3.2.4 # Django package
pillow==8.2.0 # Image manipulation
djangorestframework==3.12.4 # DRF framework
django-cors-headers==3.2.0 # CORS headers extension for DRF
diff --git a/tasks.py b/tasks.py
index 88bd5e42e4..5aab30651a 100644
--- a/tasks.py
+++ b/tasks.py
@@ -282,7 +282,7 @@ def export_records(c, filename='data.json'):
tmpfile = f"{filename}.tmp"
- cmd = f"dumpdata --indent 2 --output {tmpfile} {content_excludes()}"
+ cmd = f"dumpdata --indent 2 --output '{tmpfile}' {content_excludes()}"
# Dump data to temporary file
manage(c, cmd, pty=True)
@@ -348,7 +348,7 @@ def import_records(c, filename='data.json'):
with open(tmpfile, "w") as f_out:
f_out.write(json.dumps(data, indent=2))
- cmd = f"loaddata {tmpfile} -i {content_excludes()}"
+ cmd = f"loaddata '{tmpfile}' -i {content_excludes()}"
manage(c, cmd, pty=True)