Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2022-11-21 20:35:57 +11:00
commit 669f27011e
57 changed files with 14688 additions and 13703 deletions

View File

@ -4,6 +4,8 @@ labels: ["question", "triage:not-checked", "setup"]
body:
- type: checkboxes
id: deployment
validations:
required: true
attributes:
label: "Deployment Method"
options:
@ -12,6 +14,8 @@ body:
- label: "Docker Production"
- label: "Bare metal Development"
- label: "Bare metal Production"
- label: "Digital Ocean image"
- label: "Other (please provide a link `Steps to Reproduce`"
- type: textarea
id: description
validations:

View File

@ -15,33 +15,20 @@ env:
- INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
- INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
after_install: contrib/packager.io/postinstall.sh
dependencies:
- curl
- python3
- python3-venv
- python3-pip
- python3-cffi
- python3-brotli
- python3-wheel
- libpango-1.0-0
- libharfbuzz0b
- libpangoft2-1.0-0
- gettext
- nginx
- jq
targets:
ubuntu-20.04:
dependencies:
- curl
- python3
- python3-venv
- python3-pip
- python3-cffi
- python3-brotli
- python3-wheel
- libpango-1.0-0
- libharfbuzz0b
- libpangoft2-1.0-0
- gettext
- nginx
- jq
debian-11:
dependencies:
- curl
- python3
- python3-venv
- python3-pip
- python3-cffi
- python3-brotli
- python3-wheel
- libpango-1.0-0
- libpangoft2-1.0-0
- gettext
- nginx
- jq
ubuntu-20.04: true
debian-11: true

View File

@ -2,11 +2,14 @@
# InvenTree API version
INVENTREE_API_VERSION = 82
INVENTREE_API_VERSION = 83
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v83 -> 2022-11-19 : https://github.com/inventree/InvenTree/pull/3949
- Add support for structural Stock locations
v82 -> 2022-11-16 : https://github.com/inventree/InvenTree/pull/3931
- Add support for structural Part categories

View File

@ -744,6 +744,7 @@ SOCIALACCOUNT_STORE_TOKENS = True
# settings for allauth
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', 'login_confirm_days', 3, typecast=int)
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', 'login_attempts', 5, typecast=int)
ACCOUNT_DEFAULT_HTTP_PROTOCOL = get_setting('INVENTREE_LOGIN_DEFAULT_HTTP_PROTOCOL', 'login_default_protocol', 'http')
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_PREVENT_ENUMERATION = True

View File

@ -458,7 +458,7 @@ main {
}
.part-thumb-container:hover .part-thumb-overlay {
opacity: 1;
opacity: 0.75;
}
.part-thumb-overlay {
@ -467,8 +467,6 @@ main {
left: 0;
opacity: 0;
transition: .25s ease;
padding-top: 10px;
padding-left: 25px;
margin: 5px;
}

View File

@ -6,7 +6,7 @@ import re
import warnings
from dataclasses import dataclass
from datetime import timedelta
from typing import Callable
from typing import Callable, List
from django.conf import settings
from django.core import mail as django_mail
@ -153,7 +153,7 @@ class ScheduledTask:
class TaskRegister:
"""Registery for periodicall tasks."""
task_list: list[ScheduledTask] = []
task_list: List[ScheduledTask] = []
def register(self, task, schedule, minutes: int = None):
"""Register a task with the que."""

View File

@ -3,23 +3,7 @@
from django import forms
from django.utils.translation import gettext as _
from InvenTree.forms import HelperForm
from .files import FileManager
from .models import InvenTreeSetting
class SettingEditForm(HelperForm):
"""Form for creating / editing a settings object."""
class Meta:
"""Metaclassoptions for SettingEditForm."""
model = InvenTreeSetting
fields = [
'value'
]
class UploadFileForm(forms.Form):

View File

@ -58,6 +58,9 @@
{% if allow_download %}
<button type='button' class='btn btn-outline-secondary' title="{% trans 'Download image from URL' %}" id='company-image-url'><span class='fas fa-cloud-download-alt'></span></button>
{% endif %}
{% if company.image %}
<button type='button' class='btn btn-outline-secondary' title='{% trans "Delete image" %}' id='company-image-delete'><span class='fas fa-trash-alt icon-red'></span></button>
{% endif %}
</div>
</div>
</div>
@ -194,10 +197,37 @@
$('#company-image').click(function() {
showModalImage('{{ company.image.url }}');
});
$('#company-image-delete').click(function(event) {
event.stopPropagation();
showQuestionDialog(
'{% trans "Remove Image" %}',
'{% trans "Remove associated image from this company" %}',
{
accept_text: '{% trans "Remove" %}',
submitClass: 'danger',
accept: function() {
inventreePut(
'{% url "api-company-detail" company.pk %}',
{
'image': null,
},
{
method: 'PATCH',
success: function() {
location.reload();
}
}
);
}
}
);
});
{% endif %}
$("#company-image-upload").click(function() {
$("#company-image-upload").click(function(event) {
event.stopPropagation();
constructForm(
'{% url "api-company-detail" company.pk %}',
{

View File

@ -161,6 +161,7 @@ background:
# Login configuration
login_confirm_days: 3
login_attempts: 5
login_default_protocol: http
# Remote / proxy login
# These settings can introduce security problems if configured incorrectly. Please read

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

@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='partcategory',
name='structural',
field=models.BooleanField(default=False, help_text="Parts may not be directly assigned to a structural category, but may be assigned to it's child categories.", verbose_name='Structural'),
field=models.BooleanField(default=False, help_text="Parts may not be directly assigned to a structural category, but may be assigned to child categories.", verbose_name='Structural'),
),
]

View File

@ -122,7 +122,7 @@ class PartCategory(MetadataMixin, InvenTreeTree):
verbose_name=_('Structural'),
help_text=_(
'Parts may not be directly assigned to a structural category, '
'but may be assigned to it\'s child categories.'),
'but may be assigned to child categories.'),
)
default_keywords = models.CharField(null=True, blank=True, max_length=250, verbose_name=_('Default keywords'), help_text=_('Default keywords for parts in this category'))

View File

@ -502,8 +502,34 @@
});
});
$("#part-image-upload").click(function() {
$('#part-image-delete').click(function(event) {
event.stopPropagation();
showQuestionDialog(
'{% trans "Remove Image" %}',
'{% trans "Remove associated image from this part" %}',
{
accept_text: '{% trans "Remove" %}',
submitClass: 'danger',
accept: function() {
inventreePut(
'{% url "api-part-detail" part.pk %}',
{
'image': null,
},
{
method: 'PATCH',
success: function(data) {
location.reload();
}
}
);
}
}
);
});
$("#part-image-upload").click(function(event) {
event.stopPropagation();
constructForm(
'{% url "api-part-detail" part.pk %}',
{
@ -576,12 +602,12 @@
});
}
$("#part-image-select").click(function() {
launchModalForm("{% url 'part-image-select' part.id %}",
{
reload: true,
after_render: onSelectImage
});
$("#part-image-select").click(function(event) {
event.stopPropagation();
launchModalForm("{% url 'part-image-select' part.id %}", {
reload: true,
after_render: onSelectImage
});
});
$("#part-edit").click(function() {

View File

@ -13,6 +13,9 @@
{% if allow_download %}
<button type='button' class='btn btn-outline-secondary' title="{% trans 'Download image from URL' %}" id='part-image-url'><span class='fas fa-cloud-download-alt'></span></button>
{% endif %}
{% if part.image %}
<button type='button' class='btn btn-outline-secondary' title='{% trans "Delete image" %}' id='part-image-delete'><span class='fas fa-trash-alt icon-red'></span></button>
{% endif %}
</div>
</div>
{% endif %}

View File

@ -309,6 +309,8 @@ class StockLocationList(APIDownloadMixin, ListCreateAPI):
]
filterset_fields = [
'name',
'structural'
]
search_fields = [

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2022-11-18 15:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0089_alter_stockitem_purchase_price'),
]
operations = [
migrations.AddField(
model_name='stocklocation',
name='structural',
field=models.BooleanField(default=False, help_text="Stock items may not be directly located into a structural stock locations, but may be located to child locations.", verbose_name='Structural'),
),
]

View File

@ -107,6 +107,14 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree):
help_text=_('Select Owner'),
related_name='stock_locations')
structural = models.BooleanField(
default=False,
verbose_name=_('Structural'),
help_text=_(
'Stock items may not be directly located into a structural stock locations, '
'but may be located to child locations.'),
)
def get_location_owner(self):
"""Get the closest "owner" for this location.
@ -139,6 +147,17 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree):
return user in owner.get_related_owners(include_group=True)
def clean(self):
"""Custom clean action for the StockLocation model:
- Ensure stock location can't be made structural if stock items already located to them
"""
if self.pk and self.structural and self.item_count > 0:
raise ValidationError(
_("You cannot make this stock location structural because some stock items "
"are already located into it!"))
super().clean()
def get_absolute_url(self):
"""Return url for instance."""
return reverse('stock-location-detail', kwargs={'pk': self.id})
@ -496,8 +515,14 @@ class StockItem(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
- The 'part' and 'supplier_part.part' fields cannot point to the same Part object
- The 'part' is not virtual
- The 'part' does not belong to itself
- The location is not structural
- Quantity must be 1 if the StockItem has a serial number
"""
if self.location is not None and self.location.structural:
raise ValidationError(
{'location': _("Stock items cannot be located into structural stock locations!")})
super().clean()
# Strip serial number field

View File

@ -606,6 +606,7 @@ class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
'items',
'owner',
'icon',
'structural',
]
read_only_fields = [

View File

@ -6,6 +6,7 @@ from datetime import datetime, timedelta
from enum import IntEnum
import django.http
from django.core.exceptions import ValidationError
from django.urls import reverse
import tablib
@ -225,6 +226,71 @@ class StockLocationTest(StockAPITestCase):
child.refresh_from_db()
self.assertEqual(child.parent, parent_stock_location)
def test_stock_location_structural(self):
"""Test the effectiveness of structural stock locations
Make sure:
- Stock items cannot be created in structural locations
- Stock items cannot be located to structural locations
- Check that stock location change to structural fails if items located into it
"""
# Create our structural stock location
structural_location = StockLocation.objects.create(
name='Structural stock location',
description='This is the structural stock location',
parent=None,
structural=True
)
stock_item_count_before = StockItem.objects.count()
# Make sure that we get an error if we try to create a stock item in the structural location
with self.assertRaises(ValidationError):
item = StockItem.objects.create(
batch="Stock item which shall not be created",
location=structural_location
)
# Ensure that the stock item really did not get created in the structural location
self.assertEqual(stock_item_count_before, StockItem.objects.count())
# Create a non-structural location for test stock location change
non_structural_location = StockLocation.objects.create(
name='Non-structural category',
description='This is a non-structural category',
parent=None,
structural=False
)
# Construct a part for stock item creation
part = Part.objects.create(
name='Part for stock item creation', description='Part for stock item creation',
category=None,
is_template=False,
)
# Create the test stock item located to a non-structural category
item = StockItem.objects.create(
batch="Item which will be tried to relocated to a structural location",
location=non_structural_location,
part=part
)
# Try to relocate it to a structural location
item.location = structural_location
with self.assertRaises(ValidationError):
item.save()
# Ensure that the item did not get saved to the DB
item.refresh_from_db()
self.assertEqual(item.location.pk, non_structural_location.pk)
# Try to change the non-structural location to structural while items located into it
non_structural_location.structural = True
with self.assertRaises(ValidationError):
non_structural_location.full_clean()
class StockItemListTest(StockAPITestCase):
"""Tests for the StockItem API LIST endpoint."""

View File

@ -60,9 +60,15 @@ function buildFormFields() {
},
take_from: {
icon: 'fa-sitemap',
filters: {
structural: false,
}
},
destination: {
icon: 'fa-sitemap',
filters: {
structural: false,
}
},
link: {
icon: 'fa-link',
@ -524,7 +530,11 @@ function completeBuildOutputs(build_id, outputs, options={}) {
preFormContent: html,
fields: {
status: {},
location: {},
location: {
filters: {
structural: false,
},
},
notes: {},
accept_incomplete_allocation: {},
},
@ -2391,7 +2401,7 @@ function autoAllocateStockToBuild(build_id, bom_items=[], options={}) {
<strong>{% trans "Automatic Stock Allocation" %}</strong><br>
{% trans "Stock items will be automatically allocated to this build order, according to the provided guidelines" %}:
<ul>
<li>{% trans "If a location is specifed, stock will only be allocated from that location" %}</li>
<li>{% trans "If a location is specified, stock will only be allocated from that location" %}</li>
<li>{% trans "If stock is considered interchangeable, it will be allocated from the first location it is found" %}</li>
<li>{% trans "If substitute stock is allowed, it will be used where stock of the primary part cannot be found" %}</li>
</ul>
@ -2401,6 +2411,9 @@ function autoAllocateStockToBuild(build_id, bom_items=[], options={}) {
var fields = {
location: {
value: options.location,
filters: {
structural: false,
}
},
exclude_location: {},
interchangeable: {

View File

@ -621,11 +621,11 @@ function showQuestionDialog(title, content, options={}) {
* cancel - Functino to run if the user presses 'Cancel'
*/
var modal = createNewModal({
title: title,
submitText: options.accept_text || '{% trans "Accept" %}',
closeText: options.cancel_text || '{% trans "Cancel" %}',
});
options.title = title;
options.submitText = options.accept_text || '{% trans "Accept" %}';
options.closeText = options.cancel_text || '{% trans "Cancel" %}';
var modal = createNewModal(options);
modalSetContent(modal, content);

View File

@ -888,7 +888,11 @@ function poLineItemFields(options={}) {
purchase_price: {},
purchase_price_currency: {},
target_date: {},
destination: {},
destination: {
filters: {
structural: false,
}
},
notes: {},
};
@ -1688,7 +1692,11 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
constructForm(`/api/order/po/${order_id}/receive/`, {
method: 'POST',
fields: {
location: {},
location: {
filters: {
structural: false,
}
},
},
preFormContent: html,
confirm: true,

View File

@ -99,6 +99,9 @@ function partFields(options={}) {
icon: 'fa-link',
},
default_location: {
filters: {
structural: false,
}
},
default_supplier: {
filters: {
@ -297,7 +300,11 @@ function categoryFields() {
},
name: {},
description: {},
default_location: {},
default_location: {
filters: {
structural: false,
}
},
default_keywords: {
icon: 'fa-key',
},

View File

@ -80,6 +80,9 @@ function serializeStockItem(pk, options={}) {
},
destination: {
icon: 'fa-sitemap',
filters: {
structural: false,
}
},
notes: {},
};
@ -114,6 +117,7 @@ function stockLocationFields(options={}) {
name: {},
description: {},
owner: {},
structural: {},
icon: {
help_text: `{% trans "Icon (optional) - Explore all available icons on" %} <a href="https://fontawesome.com/v5/search?s=solid" target="_blank" rel="noopener noreferrer">Font Awesome</a>.`,
placeholder: 'fas fa-box',
@ -280,6 +284,9 @@ function stockItemFields(options={}) {
},
location: {
icon: 'fa-sitemap',
filters: {
structural: false,
},
},
quantity: {
help_text: '{% trans "Enter initial quantity for this stock item" %}',
@ -838,6 +845,9 @@ function mergeStockItems(items, options={}) {
location: {
value: location,
icon: 'fa-sitemap',
filters: {
structural: false,
}
},
notes: {},
allow_mismatched_suppliers: {},
@ -1106,7 +1116,11 @@ function adjustStock(action, items, options={}) {
var extraFields = {};
if (specifyLocation) {
extraFields.location = {};
extraFields.location = {
filters: {
structural: false,
},
};
}
if (action != 'delete') {
@ -2810,6 +2824,9 @@ function uninstallStockItem(installed_item_id, options={}) {
fields: {
location: {
icon: 'fa-sitemap',
filters: {
structural: false,
}
},
note: {},
},

View File

@ -132,8 +132,6 @@ There are several options to deploy InvenTree.
<div align="center"><h4>
<a href="https://inventree.readthedocs.io/en/latest/start/docker/">Docker</a>
<span> · </span>
<a href="https://marketplace.digitalocean.com/apps/inventree?refcode=d6172576d014"><img src="https://www.deploytodo.com/do-btn-blue-ghost.svg" alt="Deploy to DO" width="auto" height="40" /></a>
<span> · </span>
<a href="https://inventree.readthedocs.io/en/latest/start/install/">Bare Metal</a>
</h4></div>

View File

@ -2,16 +2,12 @@
# This script was generated by bashly 0.8.9 (https://bashly.dannyb.co)
# Modifying it manually is not recommended
# :wrapper.bash3_bouncer
if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
printf "bash version 4 or higher is required\n" >&2
exit 1
fi
# :command.master_script
# :command.root_command
root_command() {
# src/root_command.sh
# Settings
source_url=${args[source]}
publisher=${args[publisher]}
@ -59,25 +55,26 @@ root_command() {
# Check if os and version is supported
get_distribution
echo "### Detected distribution: $OS $VER"
NOT_SUPPORTED=false
SUPPORTED=true
case "$OS" in
Ubuntu)
if [[ $VER != "20.04" ]]; then
NOT_SUPPORTED=true
SUPPORTED=false
fi
;;
Debian | Raspbian)
"Debian GNU/Linux" | Raspbian)
if [[ $VER != "11" ]]; then
NOT_SUPPORTED=true
SUPPORTED=false
fi
OS=Debian
;;
*)
echo "### Distribution not supported"
NOT_SUPPORTED=true
SUPPORTED=false
;;
esac
if [[ $NOT_SUPPORTED ]]; then
if [[ $SUPPORTED != "true" ]]; then
echo "This OS is currently not supported"
echo "please install manually using https://inventree.readthedocs.io/en/stable/start/install/"
echo "or check https://github.com/inventree/InvenTree/issues/3836 for packaging for your OS."
@ -96,11 +93,10 @@ root_command() {
fi
done
echo "### Adding key and package source"
# Add key
do_call "wget -qO- https://dl.packager.io/srv/$publisher/InvenTree/key | sudo apt-key add -"
# Add packagelist
do_call "sudo wget -O /etc/apt/sources.list.d/inventree.list https://dl.packager.io/srv/$publisher/InvenTree/$source_url/installer/${lsb_dist}/${dist_version}.repo"
echo "### Getting and adding key"
wget -qO- https://dl.packager.io/srv/$publisher/InvenTree/key | sudo apt-key add -
echo "### Adding package source"
do_call "sudo wget -O /etc/apt/sources.list.d/inventree.list https://dl.packager.io/srv/$publisher/InvenTree/$source_url/installer/${OS,,}/${VER}.repo"
echo "### Updateing package lists"
do_call "sudo apt-get update"
@ -118,34 +114,30 @@ root_command() {
}
# :command.version_command
version_command() {
echo "$version"
}
# :command.usage
install_usage() {
install.sh_usage() {
if [[ -n $long_usage ]]; then
printf "install - Interactive installer for InvenTree\n"
printf "install.sh - Interactive installer for InvenTree\n"
echo
else
printf "install - Interactive installer for InvenTree\n"
printf "install.sh - Interactive installer for InvenTree\n"
echo
fi
printf "Usage:\n"
printf " install [SOURCE] [PUBLISHER] [OPTIONS]\n"
printf " install --help | -h\n"
printf " install --version | -v\n"
printf " install.sh [SOURCE] [PUBLISHER] [OPTIONS]\n"
printf " install.sh --help | -h\n"
printf " install.sh --version | -v\n"
echo
# :command.long_usage
if [[ -n $long_usage ]]; then
printf "Options:\n"
# :command.usage_fixed_flags
echo " --help, -h"
printf " Show this help\n"
echo
@ -153,34 +145,27 @@ install_usage() {
printf " Show version number\n"
echo
# :command.usage_flags
# :flag.usage
echo " --no-call, -n"
printf " Do not call outside APIs (only functionally needed)\n"
echo
# :flag.usage
echo " --dry-run, -d"
printf " Dry run (do not install anything)\n"
echo
# :command.usage_args
printf "Arguments:\n"
# :argument.usage
echo " SOURCE"
printf " Package source that should be used\n"
printf " Allowed: stable, master, main\n"
printf " Default: stable\n"
echo
# :argument.usage
echo " PUBLISHER"
printf " Publisher that should be used\n"
printf " Default: inventree\n"
echo
# :command.usage_examples
printf "Examples:\n"
printf " install\n"
printf " install master --no-call\n"
@ -190,7 +175,6 @@ install_usage() {
fi
}
# :command.normalize_input
normalize_input() {
local arg flags
@ -214,7 +198,7 @@ normalize_input() {
shift
done
}
# :command.inspect_args
inspect_args() {
readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort)
if (( ${#args[@]} )); then
@ -234,11 +218,8 @@ inspect_args() {
fi
}
# :command.command_functions
# :command.parse_requirements
parse_requirements() {
# :command.fixed_flags_filter
case "${1:-}" in
--version | -v )
version_command
@ -247,31 +228,26 @@ parse_requirements() {
--help | -h )
long_usage=yes
install_usage
install.sh_usage
exit
;;
esac
# :command.command_filter
action="root"
# :command.parse_requirements_while
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
# :flag.case
--no-call | -n )
# :flag.case_no_arg
args[--no-call]=1
shift
;;
# :flag.case
--dry-run | -d )
# :flag.case_no_arg
args[--dry-run]=1
shift
;;
@ -282,8 +258,7 @@ parse_requirements() {
;;
* )
# :command.parse_requirements_case
# :command.parse_requirements_case_simple
if [[ -z ${args[source]+x} ]]; then
args[source]=$1
@ -302,11 +277,9 @@ parse_requirements() {
esac
done
# :command.default_assignments
[[ -n ${args[source]:-} ]] || args[source]="stable"
[[ -n ${args[publisher]:-} ]] || args[publisher]="inventree"
# :command.whitelist_filter
if [[ ! ${args[source]} =~ ^(stable|master|main)$ ]]; then
printf "%s\n" "source must be one of: stable, master, main" >&2
exit 1
@ -314,17 +287,14 @@ parse_requirements() {
}
# :command.initialize
initialize() {
version="2.0"
long_usage=''
set -e
# src/initialize.sh
}
# :command.run
run() {
declare -A args=()
declare -a other_args=()

View File

@ -1,341 +0,0 @@
#!/usr/bin/env bash
# This script was generated by bashly 0.8.9 (https://bashly.dannyb.co)
# Modifying it manually is not recommended
# :wrapper.bash3_bouncer
if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
printf "bash version 4 or higher is required\n" >&2
exit 1
fi
# :command.master_script
# :command.root_command
root_command() {
# src/root_command.sh
# Settings
source_url=${args[source]}
publisher=${args[publisher]}
# Flags
no_call=${args[--no-call]}
dry_run=${args[--dry-run]}
REQS="wget apt-transport-https"
function do_call() {
if [[ $dry_run ]]; then
echo -e "### DRY RUN: \n$1"
else
$1
fi
}
function get_distribution {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$NAME
VER=$VERSION_ID
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si)
VER=$(lsb_release -sr)
elif [ -f /etc/lsb-release ]; then
. /etc/lsb-release
OS=$DISTRIB_ID
VER=$DISTRIB_RELEASE
elif [ -f /etc/debian_version ]; then
OS=Debian
VER=$(cat /etc/debian_version)
elif [ -f /etc/SuSe-release ]; then
OS=SEL
elif [ -f /etc/redhat-release ]; then
OS=RedHat
else
OS=$(uname -s)
VER=$(uname -r)
fi
}
echo "### Installer for InvenTree - source: $publisher/$source_url"
# Check if os and version is supported
get_distribution
echo "### Detected distribution: $OS $VER"
NOT_SUPPORTED=false
case "$OS" in
Ubuntu)
if [[ $VER != "20.04" ]]; then
NOT_SUPPORTED=true
fi
;;
Debian | Raspbian)
if [[ $VER != "11" ]]; then
NOT_SUPPORTED=true
fi
;;
*)
echo "### Distribution not supported"
NOT_SUPPORTED=true
;;
esac
if [[ $NOT_SUPPORTED ]]; then
echo "This OS is currently not supported"
echo "please install manually using https://inventree.readthedocs.io/en/stable/start/install/"
echo "or check https://github.com/inventree/InvenTree/issues/3836 for packaging for your OS."
echo "If you think this is a bug please file an issue at"
echo "https://github.com/inventree/InvenTree/issues/new?template=install.yaml"
exit 1
fi
echo "### Installing required packages for download"
for pkg in $REQS; do
if dpkg-query -W -f'${Status}' "$pkg" 2>/dev/null | grep -q "ok installed"; then
true
else
do_call "sudo apt-get -yqq install $pkg"
fi
done
echo "### Adding key and package source"
# Add key
do_call "wget -qO- https://dl.packager.io/srv/$publisher/InvenTree/key | sudo apt-key add -"
# Add packagelist
do_call "sudo wget -O /etc/apt/sources.list.d/inventree.list https://dl.packager.io/srv/$publisher/InvenTree/$source_url/installer/${lsb_dist}/${dist_version}.repo"
echo "### Updateing package lists"
do_call "sudo apt-get update"
# Set up environment for install
echo "### Setting installer args"
if [[ $no_call ]]; then
do_call "export NO_CALL=true"
fi
echo "### Installing InvenTree"
do_call "sudo apt-get install inventree -y"
echo "### Install done!"
}
# :command.version_command
version_command() {
echo "$version"
}
# :command.usage
install_usage() {
if [[ -n $long_usage ]]; then
printf "install - Interactive installer for InvenTree\n"
echo
else
printf "install - Interactive installer for InvenTree\n"
echo
fi
printf "Usage:\n"
printf " install [SOURCE] [PUBLISHER] [OPTIONS]\n"
printf " install --help | -h\n"
printf " install --version | -v\n"
echo
# :command.long_usage
if [[ -n $long_usage ]]; then
printf "Options:\n"
# :command.usage_fixed_flags
echo " --help, -h"
printf " Show this help\n"
echo
echo " --version, -v"
printf " Show version number\n"
echo
# :command.usage_flags
# :flag.usage
echo " --no-call, -n"
printf " Do not call outside APIs (only functionally needed)\n"
echo
# :flag.usage
echo " --dry-run, -d"
printf " Dry run (do not install anything)\n"
echo
# :command.usage_args
printf "Arguments:\n"
# :argument.usage
echo " SOURCE"
printf " Package source that should be used\n"
printf " Allowed: stable, master, main\n"
printf " Default: stable\n"
echo
# :argument.usage
echo " PUBLISHER"
printf " Publisher that should be used\n"
printf " Default: inventree\n"
echo
# :command.usage_examples
printf "Examples:\n"
printf " install\n"
printf " install master --no-call\n"
printf " install master matmair --dry-run\n"
echo
fi
}
# :command.normalize_input
normalize_input() {
local arg flags
while [[ $# -gt 0 ]]; do
arg="$1"
if [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
input+=("${BASH_REMATCH[1]}")
input+=("${BASH_REMATCH[2]}")
elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then
input+=("${BASH_REMATCH[1]}")
input+=("${BASH_REMATCH[2]}")
elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then
flags="${BASH_REMATCH[1]}"
for (( i=0 ; i < ${#flags} ; i++ )); do
input+=("-${flags:i:1}")
done
else
input+=("$arg")
fi
shift
done
}
# :command.inspect_args
inspect_args() {
readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort)
if (( ${#args[@]} )); then
echo args:
for k in "${sorted_keys[@]}"; do echo "- \${args[$k]} = ${args[$k]}"; done
else
echo args: none
fi
if (( ${#other_args[@]} )); then
echo
echo other_args:
echo "- \${other_args[*]} = ${other_args[*]}"
for i in "${!other_args[@]}"; do
echo "- \${other_args[$i]} = ${other_args[$i]}"
done
fi
}
# :command.command_functions
# :command.parse_requirements
parse_requirements() {
# :command.fixed_flags_filter
case "${1:-}" in
--version | -v )
version_command
exit
;;
--help | -h )
long_usage=yes
install_usage
exit
;;
esac
# :command.command_filter
action="root"
# :command.parse_requirements_while
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
# :flag.case
--no-call | -n )
# :flag.case_no_arg
args[--no-call]=1
shift
;;
# :flag.case
--dry-run | -d )
# :flag.case_no_arg
args[--dry-run]=1
shift
;;
-?* )
printf "invalid option: %s\n" "$key" >&2
exit 1
;;
* )
# :command.parse_requirements_case
# :command.parse_requirements_case_simple
if [[ -z ${args[source]+x} ]]; then
args[source]=$1
shift
elif [[ -z ${args[publisher]+x} ]]; then
args[publisher]=$1
shift
else
printf "invalid argument: %s\n" "$key" >&2
exit 1
fi
;;
esac
done
# :command.default_assignments
[[ -n ${args[source]:-} ]] || args[source]="stable"
[[ -n ${args[publisher]:-} ]] || args[publisher]="inventree"
# :command.whitelist_filter
if [[ ! ${args[source]} =~ ^(stable|master|main)$ ]]; then
printf "%s\n" "source must be one of: stable, master, main" >&2
exit 1
fi
}
# :command.initialize
initialize() {
version="2.0"
long_usage=''
set -e
# src/initialize.sh
}
# :command.run
run() {
declare -A args=()
declare -a other_args=()
declare -a input=()
normalize_input "$@"
parse_requirements "${input[@]}"
if [[ $action == "root" ]]; then
root_command
fi
}
initialize
run "$@"

View File

@ -0,0 +1,32 @@
# All settings are optional (with their default values provided below), and
# can also be set with an environment variable with the same name, capitalized
# and prefixed by `BASHLY_` - for example: BASHLY_SOURCE_DIR
#
# When setting environment variables, you can use:
# - "0", "false" or "no" to represent false
# - "1", "true" or "yes" to represent true
# The path containing the bashly configuration and source files
source_dir: src
# The path to use for creating the bash script
target_dir: ..
# The path to use for upgrading library files, relative to the source dir
lib_dir: lib
# When true, enable bash strict mode (set -euo pipefail)
strict: false
# When true, the generated script will use tab indentation instead of spaces
# (every 2 leading spaces will be converted to a tab character)
tab_indent: false
# When true, the generated script will consider any argument in the form of
# `-abc` as if it is `-a -b -c`.
compact_short_flags: true
# Set to 'production' or 'development':
# - production generate a smaller script, without file markers
# - development generate with file markers
env: production

View File

@ -1,4 +1,4 @@
name: install
name: install.sh
help: Interactive installer for InvenTree
version: 2.0

View File

@ -45,25 +45,26 @@ echo "### Installer for InvenTree - source: $publisher/$source_url"
# Check if os and version is supported
get_distribution
echo "### Detected distribution: $OS $VER"
NOT_SUPPORTED=false
SUPPORTED=true
case "$OS" in
Ubuntu)
if [[ $VER != "20.04" ]]; then
NOT_SUPPORTED=true
SUPPORTED=false
fi
;;
Debian | Raspbian)
"Debian GNU/Linux" | Raspbian)
if [[ $VER != "11" ]]; then
NOT_SUPPORTED=true
SUPPORTED=false
fi
OS=Debian
;;
*)
echo "### Distribution not supported"
NOT_SUPPORTED=true
SUPPORTED=false
;;
esac
if [[ $NOT_SUPPORTED ]]; then
if [[ $SUPPORTED != "true" ]]; then
echo "This OS is currently not supported"
echo "please install manually using https://inventree.readthedocs.io/en/stable/start/install/"
echo "or check https://github.com/inventree/InvenTree/issues/3836 for packaging for your OS."
@ -82,11 +83,10 @@ for pkg in $REQS; do
fi
done
echo "### Adding key and package source"
# Add key
do_call "wget -qO- https://dl.packager.io/srv/$publisher/InvenTree/key | sudo apt-key add -"
# Add packagelist
do_call "sudo wget -O /etc/apt/sources.list.d/inventree.list https://dl.packager.io/srv/$publisher/InvenTree/$source_url/installer/${lsb_dist}/${dist_version}.repo"
echo "### Getting and adding key"
wget -qO- https://dl.packager.io/srv/$publisher/InvenTree/key | sudo apt-key add -
echo "### Adding package source"
do_call "sudo wget -O /etc/apt/sources.list.d/inventree.list https://dl.packager.io/srv/$publisher/InvenTree/$source_url/installer/${OS,,}/${VER}.repo"
echo "### Updateing package lists"
do_call "sudo apt-get update"

View File

@ -84,7 +84,7 @@ function detect_envs() {
echo "# Setting base environment variables"
export INVENTREE_CONFIG_FILE=${CONF_DIR}/config.yaml
export INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE:-${CONF_DIR}/config.yaml}
if test -f "${INVENTREE_CONFIG_FILE}"; then
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
@ -93,22 +93,22 @@ function detect_envs() {
pip install jc -q
# Load config
local conf=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
# Parse the config file
export INVENTREE_MEDIA_ROOT=$conf | jq '.[].media_root'
export INVENTREE_STATIC_ROOT=$conf | jq '.[].static_root'
export INVENTREE_BACKUP_DIR=$conf | jq '.[].backup_dir'
export INVENTREE_PLUGINS_ENABLED=$conf | jq '.[].plugins_enabled'
export INVENTREE_PLUGIN_FILE=$conf | jq '.[].plugin_file'
export INVENTREE_SECRET_KEY_FILE=$conf | jq '.[].secret_key_file'
export INVENTREE_MEDIA_ROOT=$(jq -r '.[].media_root' <<< ${CONF})
export INVENTREE_STATIC_ROOT=$(jq -r '.[].static_root' <<< ${CONF})
export INVENTREE_BACKUP_DIR=$(jq -r '.[].backup_dir' <<< ${CONF})
export INVENTREE_PLUGINS_ENABLED=$(jq -r '.[].plugins_enabled' <<< ${CONF})
export INVENTREE_PLUGIN_FILE=$(jq -r '.[].plugin_file' <<< ${CONF})
export INVENTREE_SECRET_KEY_FILE=$(jq -r '.[].secret_key_file' <<< ${CONF})
export INVENTREE_DB_ENGINE=$conf | jq '.[].database.ENGINE'
export INVENTREE_DB_NAME=$conf | jq '.[].database.NAME'
export INVENTREE_DB_USER=$conf | jq '.[].database.USER'
export INVENTREE_DB_PASSWORD=$conf | jq '.[].database.PASSWORD'
export INVENTREE_DB_HOST=$conf | jq '.[].database.HOST'
export INVENTREE_DB_PORT=$conf | jq '.[].database.PORT'
export INVENTREE_DB_ENGINE=$(jq -r '.[].database.ENGINE' <<< ${CONF})
export INVENTREE_DB_NAME=$(jq -r '.[].database.NAME' <<< ${CONF})
export INVENTREE_DB_USER=$(jq -r '.[].database.USER' <<< ${CONF})
export INVENTREE_DB_PASSWORD=$(jq -r '.[].database.PASSWORD' <<< ${CONF})
export INVENTREE_DB_HOST=$(jq -r '.[].database.HOST' <<< ${CONF})
export INVENTREE_DB_PORT=$(jq -r '.[].database.PORT' <<< ${CONF})
else
echo "# No config file found: ${INVENTREE_CONFIG_FILE}, using envs or defaults"
@ -160,7 +160,8 @@ function create_initscripts() {
echo "# python enviroment already present - skipping"
else
echo "# Setting up python enviroment"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && python3 -m venv env && pip install invoke"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && ${SETUP_PYTHON} -m venv env"
sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install invoke wheel"
if [ -n "${SETUP_EXTRA_PIP}" ]; then
echo "# Installing extra pip packages"

View File

@ -11,7 +11,7 @@ PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
. ${APP_HOME}/contrib/packager.io/functions.sh
# Envs that should be passed to setup commands
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP
export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_BACKUP_DIR,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP,SETUP_PYTHON
# Get the envs
detect_local_env
@ -23,8 +23,10 @@ export DATA_DIR=${APP_HOME}/data
export SETUP_NGINX_FILE=${SETUP_NGINX_FILE:-/etc/nginx/sites-enabled/inventree.conf}
export SETUP_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
export SETUP_PYTHON=${SETUP_PYTHON:-python3}
# SETUP_DEBUG can be set to get debug info
# SETUP_EXTRA_PIP can be set to install extra pip packages
# SETUP_PYTHON can be set to use a different python version
# get base info
detect_envs