Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2022-06-01 20:19:14 +10:00
commit 9c0d060bf2
43 changed files with 20473 additions and 19718 deletions

26
.github/release.yml vendored Normal file
View File

@ -0,0 +1,26 @@
# .github/release.yml
changelog:
categories:
- title: Breaking Changes
labels:
- Semver-Major
- breaking
- title: New Features
labels:
- Semver-Minor
- enhancement
- title: Bug Fixes
labels:
- Semver-Patch
- bug
- title: Devops / Setup Changes
labels:
- docker
- setup
- demo
- CI
- security
- title: Other Changes
labels:
- "*"

View File

@ -2,7 +2,6 @@
# This workflow runs under any of the following conditions:
#
# - Push to the master branch
# - Push to the stable branch
# - Publish release
#
# The following actions are performed:
@ -21,7 +20,6 @@ on:
push:
branches:
- 'master'
- 'stable'
jobs:
@ -29,12 +27,15 @@ jobs:
build:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Check out repo
uses: actions/checkout@v2
- name: Version Check
run: |
python3 ci/check_version_number.py
python3 ci/version_check.py
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Run Unit Tests
@ -65,5 +66,14 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
target: production
tags: inventree/inventree:${{ env.docker_tag }}
build-args: commit_hash=${{ env.git_commit_hash }},commit_date=${{ env.git_commit_date }},commit_tag=${{ env.docker_tag }}
tags: ${{ env.docker_tags }}
build-args: |
commit_hash=${{ env.git_commit_hash }}
commit_date=${{ env.git_commit_date }}
- name: Push to Stable Branch
uses: ad-m/github-push-action@master
if: env.stable_release == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: stable
force: true

View File

@ -91,9 +91,6 @@ jobs:
cache: 'pip'
- name: Run pre-commit Checks
uses: pre-commit/action@v2.0.3
- name: Check version number
run: |
python3 ci/check_version_number.py
python:
name: Tests - inventree-python

View File

@ -137,6 +137,7 @@ The tags describe issues and PRs in multiple areas:
| Area | Name | Description |
|---|---|---|
| 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 |

View File

@ -105,10 +105,9 @@ COPY docker/init.sh ${INVENTREE_MNG_DIR}/init.sh
WORKDIR ${INVENTREE_MNG_DIR}
# Drop to the inventree user for the production image
RUN adduser inventree
RUN chown -R inventree:inventree ${INVENTREE_HOME}
USER inventree
#RUN adduser inventree
#RUN chown -R inventree:inventree ${INVENTREE_HOME}
#USER inventree
# Install InvenTree packages
RUN pip3 install --user --disable-pip-version-check -r ${INVENTREE_HOME}/requirements.txt

View File

@ -4,11 +4,17 @@ InvenTree API version information
# InvenTree API version
INVENTREE_API_VERSION = 51
INVENTREE_API_VERSION = 53
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v52 -> 2022-06-01 : https://github.com/inventree/InvenTree/pull/3110
- Adds extra search fields to the BuildOrder list API endpoint
v52 -> 2022-05-31 : https://github.com/inventree/InvenTree/pull/3103
- Allow part list API to be searched by supplier SKU
v51 -> 2022-05-24 : https://github.com/inventree/InvenTree/pull/3058
- Adds new fields to the SalesOrderShipment model

View File

@ -106,8 +106,10 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView):
search_fields = [
'reference',
'part__name',
'title',
'part__name',
'part__IPN',
'part__description',
]
def get_queryset(self):

View File

@ -375,7 +375,7 @@ onPanelLoad('attachments', function() {
},
label: 'attachment',
success: function(data, status, xhr) {
location.reload();
$('#attachment-table').bootstrapTable('refresh');
}
}
);

View File

@ -3,7 +3,6 @@
from django.test import TestCase
from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from InvenTree import status_codes as status
@ -194,14 +193,13 @@ class BuildTest(BuildTestBase):
b.save()
def test_duplicate_bom_line(self):
# Try to add a duplicate BOM item - it should fail!
# Try to add a duplicate BOM item - it should be allowed
with self.assertRaises(IntegrityError):
BomItem.objects.create(
part=self.assembly,
sub_part=self.sub_part_1,
quantity=99
)
BomItem.objects.create(
part=self.assembly,
sub_part=self.sub_part_1,
quantity=99
)
def allocate_stock(self, output, allocations):
"""

View File

@ -1425,6 +1425,20 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': bool,
},
'SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS': {
'name': _('Seach Supplier Parts'),
'description': _('Display supplier parts in search preview window'),
'default': True,
'validator': bool,
},
'SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS': {
'name': _('Search Manufacturer Parts'),
'description': _('Display manufacturer parts in search preview window'),
'default': True,
'validator': bool,
},
'SEARCH_HIDE_INACTIVE_PARTS': {
'name': _("Hide Inactive Parts"),
'description': _('Excluded inactive parts from search preview window'),

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

@ -1387,6 +1387,7 @@ class PartList(APIDownloadMixin, generics.ListCreateAPIView):
'keywords',
'category__name',
'manufacturer_parts__MPN',
'supplier_parts__SKU',
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.13 on 2022-05-31 01:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('part', '0076_auto_20220516_0819'),
]
operations = [
migrations.AlterUniqueTogether(
name='bomitem',
unique_together=set(),
),
]

View File

@ -2899,9 +2899,6 @@ class BomItem(models.Model, DataImportMixin):
class Meta:
verbose_name = _("BOM Item")
# Prevent duplication of parent/child rows
unique_together = ('part', 'sub_part')
def __str__(self):
return "{n} x {child} to make {parent}".format(
parent=self.part.full_name,

View File

@ -16,6 +16,8 @@
<tbody>
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_PARTS" user_setting=True icon='fa-shapes' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_HIDE_INACTIVE_PARTS" user_setting=True icon='fa-eye-slash' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS" user_setting=True icon='fa-building' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS" user_setting=True icon='fa-industry' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_CATEGORIES" user_setting=True icon='fa-sitemap' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_STOCK" user_setting=True icon='fa-boxes' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK" user_setting=True icon='fa-eye-slash' %}

View File

@ -2,6 +2,18 @@
<div id='attachment-buttons'>
<div class='btn-group' role='group'>
<div class='btn-group'>
<button class='btn btn-primary dropdown-toggle' type='buton' data-bs-toggle='dropdown' title='{% trans "Actions" %}'>
<span class='fas fa-tools'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu'>
<li>
<a class='dropdown-item' href='#' id='multi-attachment-delete' title='{% trans "Delete selected attachments" %}'>
<span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Attachments" %}
</a>
</li>
</ul>
</div>
{% include "filter_list.html" with id="attachments" %}
</div>
</div>

View File

@ -57,6 +57,75 @@ function addAttachmentButtonCallbacks(url, fields={}) {
}
/*
* Construct a form to delete attachment files
*/
function deleteAttachments(attachments, url, options={}) {
if (attachments.length == 0) {
console.warn('deleteAttachments function called with zero attachments provided');
return;
}
function renderAttachment(attachment, opts={}) {
var icon = '';
if (attachment.filename) {
icon = `<span class='fas fa-file-alt'></span>`;
} else if (attachment.link) {
icon = `<span class='fas fa-link'></span>`;
}
return `
<tr>
<td>${icon}</td>
<td>${attachment.filename || attachment.link}</td>
<td>${attachment.comment}</td>
</tr>`;
}
var rows = '';
attachments.forEach(function(att) {
rows += renderAttachment(att);
});
var html = `
<div class='alert alert-block alert-danger'>
{% trans "All selected attachments will be deleted" %}
</div>
<table class='table table-striped table-condensed'>
<tr>
<th></th>
<th>{% trans "Attachment" %}</th>
<th>{% trans "Comment" %}</th>
</tr>
${rows}
</table>
`;
constructFormBody({}, {
method: 'DELETE',
title: '{% trans "Delete Attachments" %}',
preFormContent: html,
onSubmit: function(fields, opts) {
inventreeMultiDelete(
url,
attachments,
{
modal: opts.modal,
success: function() {
// Refresh the table once all attachments are deleted
$('#attachment-table').bootstrapTable('refresh');
}
}
);
}
});
}
function reloadAttachmentTable() {
$('#attachment-table').bootstrapTable('refresh');
@ -71,6 +140,15 @@ function loadAttachmentTable(url, options) {
addAttachmentButtonCallbacks(url, options.fields || {});
// Add callback for the 'multi delete' button
$('#multi-attachment-delete').click(function() {
var attachments = getTableData(table);
if (attachments.length > 0) {
deleteAttachments(attachments, url);
}
});
$(table).inventreeTable({
url: url,
name: options.name || 'attachments',
@ -80,7 +158,9 @@ function loadAttachmentTable(url, options) {
sortable: true,
search: true,
queryParams: options.filters || {},
uniqueId: 'pk',
onPostBody: function() {
// Add callback for 'edit' button
$(table).find('.button-attachment-edit').click(function() {
var pk = $(this).attr('pk');
@ -105,15 +185,14 @@ function loadAttachmentTable(url, options) {
$(table).find('.button-attachment-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`${url}${pk}/`, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
var attachment = $(table).bootstrapTable('getRowByUniqueId', pk);
deleteAttachments([attachment], url);
});
},
columns: [
{
checkbox: true,
},
{
field: 'attachment',
title: '{% trans "Attachment" %}',

View File

@ -151,6 +151,46 @@ function updateSearch() {
);
}
if (checkPermission('part') && checkPermission('purchase_order')) {
var params = {
part_detail: true,
supplier_detail: true,
manufacturer_detail: true,
};
if (user_settings.SEARCH_HIDE_INACTIVE_PARTS) {
// Return *only* active parts
params.active = true;
}
if (user_settings.SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS) {
addSearchQuery(
'supplierpart',
'{% trans "Supplier Parts" %}',
'{% url "api-supplier-part-list" %}',
params,
renderSupplierPart,
{
url: '/supplier-part',
}
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS) {
addSearchQuery(
'manufacturerpart',
'{% trans "Manufacturer Parts" %}',
'{% url "api-manufacturer-part-list" %}',
params,
renderManufacturerPart,
{
url: '/manufacturer-part',
}
);
}
}
if (checkPermission('part_category') && user_settings.SEARCH_PREVIEW_SHOW_CATEGORIES) {
// Search for matching part categories
addSearchQuery(

View File

@ -4,20 +4,79 @@ Ensure that the release tag matches the InvenTree version number:
master / main branch:
- version number must end with 'dev'
stable branch:
- version number must *not* end with 'dev'
- version number cannot already exist as a release tag
tagged branch:
- version number must match tag being built
- version number cannot already exist as a release tag
"""
import json
import os
import re
import sys
import requests
def get_existing_release_tags():
"""Request information on existing releases via the GitHub API"""
response = requests.get('https://api.github.com/repos/inventree/inventree/releases')
if response.status_code != 200:
raise ValueError(f'Unexpected status code from GitHub API: {response.status_code}')
data = json.loads(response.text)
# Return a list of all tags
tags = []
for release in data:
tag = release['tag_name'].strip()
match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag)
if len(match.groups()) != 3:
print(f"Version '{tag}' did not match expected pattern")
continue
tags.append([int(x) for x in match.groups()])
return tags
def check_version_number(version_string):
"""Check the provided version number.
Returns True if the provided version is the 'newest' InvenTree release
"""
print(f"Checking version '{version_string}'")
# Check that the version string matches the required format
match = re.match(r"^(\d+)\.(\d+)\.(\d+)(?: dev)?$", version_string)
if not match or len(match.groups()) != 3:
raise ValueError(f"Version string '{version_string}' did not match required pattern")
version_tuple = [int(x) for x in match.groups()]
# Look through the existing releases
existing = get_existing_release_tags()
# Assume that this is the highest release, unless told otherwise
highest_release = True
for release in existing:
if release == version_tuple:
raise ValueError(f"Duplicate release '{version_string}' exists!")
if release > version_tuple:
highest_release = False
print(f"Found newer release: {str(release)}")
return highest_release
if __name__ == '__main__':
here = os.path.abspath(os.path.dirname(__file__))
@ -49,24 +108,12 @@ if __name__ == '__main__':
print(f"InvenTree Version: '{version}'")
highest_release = check_version_number(version)
# Determine which docker tag we are going to use
docker_tag = None
docker_tags = None
if GITHUB_REF_TYPE == 'branch' and ('stable' in GITHUB_REF or 'stable' in GITHUB_BASE_REF):
print("Checking requirements for 'stable' release branch:")
pattern = r"^\d+(\.\d+)+$"
result = re.match(pattern, version)
if result is None:
print(f"Version number '{version}' does not match required pattern for stable branch")
sys.exit(1)
else:
print(f"Version number '{version}' matches stable branch")
docker_tag = 'stable'
elif GITHUB_REF_TYPE == 'tag':
if GITHUB_REF_TYPE == 'tag':
# GITHUB_REF should be of th eform /refs/heads/<tag>
version_tag = GITHUB_REF.split('/')[-1]
print(f"Checking requirements for tagged release - '{version_tag}':")
@ -77,7 +124,10 @@ if __name__ == '__main__':
# TODO: Check if there is already a release with this tag!
docker_tag = version_tag
if highest_release:
docker_tags = [version_tag, 'stable']
else:
docker_tags = [version_tag]
elif GITHUB_REF_TYPE == 'branch':
# Otherwise we know we are targetting the 'master' branch
@ -92,7 +142,7 @@ if __name__ == '__main__':
else:
print(f"Version number '{version}' matches development branch")
docker_tag = 'latest'
docker_tags = ['latest']
else:
print("Unsupported branch / version combination:")
@ -102,13 +152,20 @@ if __name__ == '__main__':
print("GITHUB_REF:", GITHUB_REF)
sys.exit(1)
if docker_tag is None:
if docker_tags is None:
print("Docker tag could not be determined")
sys.exit(1)
print(f"Version check passed for '{version}'!")
print(f"Docker tag: '{docker_tag}'")
print(f"Docker tags: '{docker_tags}'")
# Ref: https://getridbug.com/python/how-to-set-environment-variables-in-github-actions-using-python/
with open(os.getenv('GITHUB_ENV'), 'a') as env_file:
env_file.write(f"docker_tag={docker_tag}\n")
# Construct tag string
tags = ",".join([f"inventree/inventree:{tag}" for tag in docker_tags])
env_file.write(f"docker_tags={tags}\n")
if GITHUB_REF_TYPE == 'tag' and highest_release:
env_file.write("stable_release=true\n")