2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-15 11:35:41 +00:00

Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters
2022-07-20 22:46:07 +10:00
15 changed files with 142 additions and 67 deletions

View File

@ -26,7 +26,8 @@ jobs:
# Build the docker image
build:
runs-on: ubuntu-latest
permissions:
id-token: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -56,13 +57,22 @@ jobs:
- name: Set up Docker Buildx
if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@v1
- name: Set up cosign
uses: sigstore/cosign-installer@48866aa521d8bf870604709cd43ec2f602d03ff2
- name: Login to Dockerhub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@69f6fc9d46f2f8bf0d5491e4aabe0bb8c6a4678a
with:
images: |
inventree/inventree
- name: Build and Push
id: build-and-push
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v2
with:
@ -74,6 +84,10 @@ jobs:
build-args: |
commit_hash=${{ env.git_commit_hash }}
commit_date=${{ env.git_commit_date }}
- name: Sign the published image
env:
COSIGN_EXPERIMENTAL: "true"
run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }}
- name: Push to Stable Branch
uses: ad-m/github-push-action@master
if: env.stable_release == 'true'

View File

@ -127,20 +127,9 @@ FROM base as dev
ENV INVENTREE_DEBUG=True
ENV INVENTREE_DEV_DIR="${INVENTREE_HOME}/dev"
# Location for python virtual environment
# If the INVENTREE_PY_ENV variable is set, the entrypoint script will use it!
ENV INVENTREE_PY_ENV="${INVENTREE_DEV_DIR}/env"
# Override default path settings
ENV INVENTREE_STATIC_ROOT="${INVENTREE_DEV_DIR}/static"
ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DEV_DIR}/media"
ENV INVENTREE_PLUGIN_DIR="${INVENTREE_DEV_DIR}/plugins"
ENV INVENTREE_CONFIG_FILE="${INVENTREE_DEV_DIR}/config.yaml"
ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DEV_DIR}/secret_key.txt"
ENV INVENTREE_PLUGIN_FILE="${INVENTREE_DEV_DIR}/plugins.txt"
ENV INVENTREE_PY_ENV="${INVENTREE_DATA_DIR}/env"
WORKDIR ${INVENTREE_HOME}

View File

@ -114,9 +114,9 @@ C) Look for default key file "secret_key.txt"
d) Create "secret_key.txt" if it does not exist
"""
if os.getenv("INVENTREE_SECRET_KEY"):
if secret_key := os.getenv("INVENTREE_SECRET_KEY"):
# Secret key passed in directly
SECRET_KEY = os.getenv("INVENTREE_SECRET_KEY").strip() # pragma: no cover
SECRET_KEY = secret_key.strip() # pragma: no cover
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover
else:
# Secret key passed in by file location

View File

@ -691,7 +691,7 @@ class TestSettings(helpers.InvenTreeTestCase):
valid = [
'inventree/config.yaml',
'inventree/dev/config.yaml',
'inventree/data/config.yaml',
]
self.assertTrue(any([opt in config.get_config_file().lower() for opt in valid]))
@ -706,7 +706,7 @@ class TestSettings(helpers.InvenTreeTestCase):
valid = [
'inventree/plugins.txt',
'inventree/dev/plugins.txt',
'inventree/data/plugins.txt',
]
self.assertTrue(any([opt in config.get_plugin_file().lower() for opt in valid]))

View File

@ -813,6 +813,14 @@ class Build(MPTTModel, ReferenceIndexingMixin):
interchangeable = kwargs.get('interchangeable', False)
substitutes = kwargs.get('substitutes', True)
def stock_sort(item, bom_item, variant_parts):
if item.part == bom_item.sub_part:
return 1
elif item.part in variant_parts:
return 2
else:
return 3
# Get a list of all 'untracked' BOM items
for bom_item in self.untracked_bom_items:
@ -859,15 +867,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
This ensures that allocation priority is first given to "direct" parts
"""
def stock_sort(item):
if item.part == bom_item.sub_part:
return 1
elif item.part in variant_parts:
return 2
else:
return 3
available_stock = sorted(available_stock, key=stock_sort)
available_stock = sorted(available_stock, key=lambda item, b=bom_item, v=variant_parts: stock_sort(item, b, v))
if len(available_stock) == 0:
# No stock items are available

View File

@ -495,7 +495,7 @@ class PurchaseOrderLineItemList(APIDownloadMixin, ListCreateAPI):
search_fields = [
'part__part__name',
'part__part__description',
'part__MPN',
'part__manufacturer_part__MPN',
'part__SKU',
'reference',
]

View File

@ -168,23 +168,16 @@
{% if order.status == PurchaseOrderStatus.PENDING %}
$('#new-po-line').click(function() {
var fields = poLineItemFields({
order: {{ order.pk }},
createPurchaseOrderLineItem({{ order.pk }}, {
{% if order.supplier %}
supplier: {{ order.supplier.pk }},
{% if order.supplier.currency %}
currency: '{{ order.supplier.currency }}',
{% endif %}
{% endif %}
});
constructForm('{% url "api-po-line-list" %}', {
fields: fields,
method: 'POST',
title: '{% trans "Add Line Item" %}',
onSuccess: function() {
$('#po-line-table').bootstrapTable('refresh');
},
}
});
});

View File

@ -252,18 +252,19 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
self.assertEqual(result.reason, 'OK')
# api_call with full url
result = self.mixin.api_call('https://api.github.com/orgs/inventree', endpoint_is_url=True)
result = self.mixin.api_call('orgs/inventree')
self.assertTrue(result)
# api_call with post and data
result = self.mixin.api_call(
'repos/inventree/InvenTree',
method='GET'
'https://reqres.in/api/users/',
data={"name": "morpheus", "job": "leader"},
method='POST',
endpoint_is_url=True,
)
self.assertTrue(result)
self.assertEqual(result['name'], 'InvenTree')
self.assertEqual(result['html_url'], 'https://github.com/inventree/InvenTree')
self.assertEqual(result['name'], 'morpheus')
# api_call with filter
result = self.mixin.api_call('repos/inventree/InvenTree/stargazers', url_args={'page': '2'})

View File

@ -102,10 +102,14 @@ function editManufacturerPart(part, options={}) {
}
function supplierPartFields() {
function supplierPartFields(options={}) {
return {
part: {},
var fields = {
part: {
filters: {
purchaseable: true,
}
},
manufacturer_part: {
filters: {
part_detail: true,
@ -128,6 +132,12 @@ function supplierPartFields() {
icon: 'fa-box',
}
};
if (options.part) {
fields.manufacturer_part.filters.part = options.part;
}
return fields;
}
/*
@ -135,10 +145,11 @@ function supplierPartFields() {
*/
function createSupplierPart(options={}) {
var fields = supplierPartFields();
var fields = supplierPartFields({
part: options.part,
});
if (options.part) {
fields.manufacturer_part.filters.part = options.part;
fields.part.hidden = true;
fields.part.value = options.part;
}

View File

@ -255,15 +255,20 @@ function renderOwner(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderPurchaseOrder(name, data, parameters={}, options={}) {
var html = `<span>${data.reference}</span>`;
var thumbnail = null;
var html = '';
if (data.supplier_detail) {
thumbnail = data.supplier_detail.thumbnail || data.supplier_detail.image;
html += ' - ' + select2Thumbnail(thumbnail);
html += `<span>${data.supplier_detail.name}</span>`;
html += select2Thumbnail(thumbnail);
}
html += `<span>${data.reference}</span>`;
var thumbnail = null;
if (data.supplier_detail) {
html += ` - <span>${data.supplier_detail.name}</span>`;
}
if (data.description) {

View File

@ -16,6 +16,7 @@
renderLink,
salesOrderStatusDisplay,
setupFilterList,
supplierPartFields,
*/
/* exported
@ -25,6 +26,8 @@
completePurchaseOrder,
completeShipment,
completePendingShipments,
createPurchaseOrder,
createPurchaseOrderLineItem,
createSalesOrder,
createSalesOrderShipment,
editPurchaseOrderLineItem,
@ -539,6 +542,26 @@ function createPurchaseOrder(options={}) {
}
// Create a new PurchaseOrderLineItem
function createPurchaseOrderLineItem(order, options={}) {
var fields = poLineItemFields({
order: order,
supplier: options.supplier,
currency: options.currency,
});
constructForm('{% url "api-po-line-list" %}', {
fields: fields,
method: 'POST',
title: '{% trans "Add Line Item" %}',
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/* Construct a set of fields for the SalesOrderLineItem form */
function soLineItemFields(options={}) {
@ -590,13 +613,40 @@ function poLineItemFields(options={}) {
var fields = {
order: {
hidden: true,
filters: {
supplier_detail: true,
}
},
part: {
filters: {
part_detail: true,
supplier_detail: true,
supplier: options.supplier,
},
secondary: {
method: 'POST',
title: '{% trans "Add Supplier Part" %}',
fields: function(data) {
var fields = supplierPartFields({
part: data.part,
});
fields.supplier.value = options.supplier;
// Adjust manufacturer part query based on selected part
fields.manufacturer_part.adjustFilters = function(query, opts) {
var part = getFormFieldValue('part', {}, opts);
if (part) {
query.part = part;
}
return query;
};
return fields;
}
}
},
quantity: {},
@ -610,6 +660,7 @@ function poLineItemFields(options={}) {
if (options.order) {
fields.order.value = options.order;
fields.order.hidden = true;
}
if (options.currency) {

View File

@ -22,7 +22,7 @@ services:
inventree-dev-db:
container_name: inventree-dev-db
image: postgres:13
ports:
expose:
- ${INVENTREE_DB_PORT:-5432}/tcp
environment:
- PGDATA=/var/lib/postgresql/data/dev/pgdb

View File

@ -42,4 +42,7 @@ INVENTREE_CACHE_PORT=6379
# Enable plugins?
INVENTREE_PLUGINS_ENABLED=False
# Image tag that should be used
INVENTREE_TAG=stable
COMPOSE_PROJECT_NAME=inventree-production

View File

@ -45,7 +45,7 @@ services:
inventree-db:
container_name: inventree-db
image: postgres:13
ports:
expose:
- ${INVENTREE_DB_PORT:-5432}/tcp
environment:
- PGDATA=/var/lib/postgresql/data/pgdb
@ -65,16 +65,16 @@ services:
- inventree-db
env_file:
- .env
ports:
- ${INVENTREE_CACHE_PORT:-6379}:6379
restart: unless-stopped
expose:
- ${INVENTREE_CACHE_PORT:-6379}
restart: always
# InvenTree web server services
# Uses gunicorn as the web server
inventree-server:
container_name: inventree-server
# If you wish to specify a particular InvenTree version, do so here
image: inventree/inventree:stable
image: inventree/inventree:${INVENTREE_TAG:-stable}
expose:
- 8000
depends_on:
@ -85,13 +85,14 @@ services:
volumes:
# Data volume must map to /home/inventree/data
- inventree_data:/home/inventree/data
- inventree_plugins:/home/inventree/InvenTree/plugins
restart: unless-stopped
# Background worker process handles long-running or periodic tasks
inventree-worker:
container_name: inventree-worker
# If you wish to specify a particular InvenTree version, do so here
image: inventree/inventree:stable
image: inventree/inventree:${INVENTREE_TAG:-stable}
command: invoke worker
depends_on:
- inventree-server
@ -100,6 +101,7 @@ services:
volumes:
# Data volume must map to /home/inventree/data
- inventree_data:/home/inventree/data
- inventree_plugins:/home/inventree/InvenTree/plugins
restart: unless-stopped
# nginx acts as a reverse proxy
@ -126,7 +128,6 @@ services:
restart: unless-stopped
volumes:
# NOTE: Change /path/to/data to a directory on your local machine
# Persistent data, stored external to the container(s)
inventree_data:
driver: local
@ -135,3 +136,10 @@ volumes:
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!}
inventree_plugins:
driver: local
driver_opts:
type: none
o: bind
# This directory specified where the optional local plugin directory is stored "outside" the docker containers
device: ${INVENTREE_EXT_PLUGINS:-./}

View File

@ -406,7 +406,13 @@ def import_fixtures(c):
# Execution tasks
@task(help={'address': 'Server address:port (default=127.0.0.1:8000)'})
@task
def wait(c):
"""Wait until the database connection is ready."""
return manage(c, "wait_for_db")
@task(pre=[wait], help={'address': 'Server address:port (default=127.0.0.1:8000)'})
def server(c, address="127.0.0.1:8000"):
"""Launch a (deveopment) server using Django's in-built webserver.
@ -415,12 +421,6 @@ def server(c, address="127.0.0.1:8000"):
manage(c, "runserver {address}".format(address=address), pty=True)
@task
def wait(c):
"""Wait until the database connection is ready."""
return manage(c, "wait_for_db")
@task(pre=[wait])
def worker(c):
"""Run the InvenTree background worker process."""