mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master' into 0.4.x
This commit is contained in:
commit
e8d4e2a7e6
3
.github/workflows/docker_publish.yaml
vendored
3
.github/workflows/docker_publish.yaml
vendored
@ -13,6 +13,9 @@ jobs:
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Check Release tag
|
||||
run: |
|
||||
python3 ci/check_version_number.py ${{ github.event.release.tag_name }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
|
@ -344,13 +344,15 @@ def GetExportFormats():
|
||||
]
|
||||
|
||||
|
||||
def DownloadFile(data, filename, content_type='application/text'):
|
||||
""" Create a dynamic file for the user to download.
|
||||
def DownloadFile(data, filename, content_type='application/text', inline=False):
|
||||
"""
|
||||
Create a dynamic file for the user to download.
|
||||
|
||||
Args:
|
||||
data: Raw file data (string or bytes)
|
||||
filename: Filename for the file download
|
||||
content_type: Content type for the download
|
||||
inline: Download "inline" or as attachment? (Default = attachment)
|
||||
|
||||
Return:
|
||||
A StreamingHttpResponse object wrapping the supplied data
|
||||
@ -365,7 +367,10 @@ def DownloadFile(data, filename, content_type='application/text'):
|
||||
|
||||
response = StreamingHttpResponse(wrapper, content_type=content_type)
|
||||
response['Content-Length'] = len(data)
|
||||
response['Content-Disposition'] = 'attachment; filename={f}'.format(f=filename)
|
||||
|
||||
disposition = "inline" if inline else "attachment"
|
||||
|
||||
response['Content-Disposition'] = f'{disposition}; filename={filename}'
|
||||
|
||||
return response
|
||||
|
||||
|
@ -926,6 +926,20 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
"LABEL_INLINE": {
|
||||
'name': _('Inline label display'),
|
||||
'description': _('Display PDF labels in the browser, instead of downloading as a file'),
|
||||
'default': True,
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
"REPORT_INLINE": {
|
||||
'name': _('Inline report display'),
|
||||
'description': _('Display PDF reports in the browser, instead of downloading as a file'),
|
||||
'default': False,
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
'SEARCH_PREVIEW_RESULTS': {
|
||||
'name': _('Search Preview Results'),
|
||||
'description': _('Number of results to show in search preview window'),
|
||||
@ -965,7 +979,10 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
|
||||
|
||||
@classmethod
|
||||
def get_filters(cls, key, **kwargs):
|
||||
return {'key__iexact': key, 'user__id': kwargs['user'].id}
|
||||
return {
|
||||
'key__iexact': key,
|
||||
'user__id': kwargs['user'].id
|
||||
}
|
||||
|
||||
|
||||
class PriceBreak(models.Model):
|
||||
|
@ -109,10 +109,13 @@ class LabelPrintMixin:
|
||||
else:
|
||||
pdf = outputs[0].get_document().write_pdf()
|
||||
|
||||
inline = common.models.InvenTreeUserSetting.get_setting('LABEL_INLINE', user=request.user)
|
||||
|
||||
return InvenTree.helpers.DownloadFile(
|
||||
pdf,
|
||||
label_name,
|
||||
content_type='application/pdf'
|
||||
content_type='application/pdf',
|
||||
inline=inline
|
||||
)
|
||||
|
||||
|
||||
|
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
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.4 on 2021-08-07 11:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import part.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0070_alter_part_variant_of'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='partparametertemplate',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Parameter Name', max_length=100, unique=True, validators=[part.models.validate_template_name], verbose_name='Name'),
|
||||
),
|
||||
]
|
@ -2143,6 +2143,16 @@ class PartTestTemplate(models.Model):
|
||||
)
|
||||
|
||||
|
||||
def validate_template_name(name):
|
||||
"""
|
||||
Prevent illegal characters in "name" field for PartParameterTemplate
|
||||
"""
|
||||
|
||||
for c in "!@#$%^&*()<>{}[].,?/\|~`_+-=\'\"":
|
||||
if c in str(name):
|
||||
raise ValidationError(_(f"Illegal character in template name ({c})"))
|
||||
|
||||
|
||||
class PartParameterTemplate(models.Model):
|
||||
"""
|
||||
A PartParameterTemplate provides a template for key:value pairs for extra
|
||||
@ -2181,7 +2191,15 @@ class PartParameterTemplate(models.Model):
|
||||
except PartParameterTemplate.DoesNotExist:
|
||||
pass
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name=_('Name'), help_text=_('Parameter Name'), unique=True)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name=_('Name'),
|
||||
help_text=_('Parameter Name'),
|
||||
unique=True,
|
||||
validators=[
|
||||
validate_template_name,
|
||||
]
|
||||
)
|
||||
|
||||
units = models.CharField(max_length=25, verbose_name=_('Units'), help_text=_('Parameter Units'), blank=True)
|
||||
|
||||
|
@ -204,6 +204,7 @@ def settings_value(key, *args, **kwargs):
|
||||
|
||||
if 'user' in kwargs:
|
||||
return InvenTreeUserSetting.get_setting(key, user=kwargs['user'])
|
||||
|
||||
return InvenTreeSetting.get_setting(key)
|
||||
|
||||
|
||||
|
@ -254,10 +254,13 @@ class ReportPrintMixin:
|
||||
else:
|
||||
pdf = outputs[0].get_document().write_pdf()
|
||||
|
||||
inline = common.models.InvenTreeUserSetting.get_setting('REPORT_INLINE', user=request.user)
|
||||
|
||||
return InvenTree.helpers.DownloadFile(
|
||||
pdf,
|
||||
report_name,
|
||||
content_type='application/pdf'
|
||||
content_type='application/pdf',
|
||||
inline=inline,
|
||||
)
|
||||
|
||||
|
||||
|
@ -30,6 +30,18 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class='list-group-item' title='{% trans "Labels" %}'>
|
||||
<a href='#' class='nav-toggle' id='select-user-labels'>
|
||||
<span class='fas fa-tag'></span> {% trans "Labels" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class='list-group-item' title='{% trans "Reports" %}'>
|
||||
<a href='#' class='nav-toggle' id='select-user-reports'>
|
||||
<span class='fas fa-file-pdf'></span> {% trans "Reports" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!--
|
||||
<li class='list-group-item' title='{% trans "Settings" %}'>
|
||||
<a href='#' class='nav-toggle' id='select-user-settings'>
|
||||
|
@ -68,80 +68,3 @@
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
$("#param-table").inventreeTable({
|
||||
url: "{% url 'api-part-parameter-template-list' %}",
|
||||
queryParams: {
|
||||
ordering: 'name',
|
||||
},
|
||||
formatNoMatches: function() { return '{% trans "No part parameter templates found" %}'; },
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
title: 'ID',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: 'Name',
|
||||
sortable: 'true',
|
||||
},
|
||||
{
|
||||
field: 'units',
|
||||
title: 'Units',
|
||||
sortable: 'true',
|
||||
},
|
||||
{
|
||||
formatter: function(value, row, index, field) {
|
||||
var bEdit = "<button title='{% trans "Edit Template" %}' class='template-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-edit'></span></button>";
|
||||
var bDel = "<button title='{% trans "Delete Template" %}' class='template-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
|
||||
|
||||
var html = "<div class='btn-group float-right' role='group'>" + bEdit + bDel + "</div>";
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$("#new-param").click(function() {
|
||||
launchModalForm("{% url 'part-param-template-create' %}", {
|
||||
success: function() {
|
||||
$("#param-table").bootstrapTable('refresh');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$("#param-table").on('click', '.template-edit', function() {
|
||||
var button = $(this);
|
||||
|
||||
var url = "/part/parameter/template/" + button.attr('pk') + "/edit/";
|
||||
|
||||
launchModalForm(url, {
|
||||
success: function() {
|
||||
$("#param-table").bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#param-table").on('click', '.template-delete', function() {
|
||||
var button = $(this);
|
||||
|
||||
var url = "/part/parameter/template/" + button.attr('pk') + "/delete/";
|
||||
|
||||
launchModalForm(url, {
|
||||
success: function() {
|
||||
$("#param-table").bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#import-part").click(function() {
|
||||
launchModalForm("{% url 'api-part-import' %}?reset", {});
|
||||
});
|
||||
|
||||
{% endblock %}
|
@ -18,6 +18,8 @@
|
||||
{% include "InvenTree/settings/user_settings.html" %}
|
||||
{% include "InvenTree/settings/user_homepage.html" %}
|
||||
{% include "InvenTree/settings/user_search.html" %}
|
||||
{% include "InvenTree/settings/user_labels.html" %}
|
||||
{% include "InvenTree/settings/user_reports.html" %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
|
||||
@ -241,6 +243,79 @@ $("#cat-param-table").on('click', '.template-delete', function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("#param-table").inventreeTable({
|
||||
url: "{% url 'api-part-parameter-template-list' %}",
|
||||
queryParams: {
|
||||
ordering: 'name',
|
||||
},
|
||||
formatNoMatches: function() { return '{% trans "No part parameter templates found" %}'; },
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
title: 'ID',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: 'Name',
|
||||
sortable: 'true',
|
||||
},
|
||||
{
|
||||
field: 'units',
|
||||
title: 'Units',
|
||||
sortable: 'true',
|
||||
},
|
||||
{
|
||||
formatter: function(value, row, index, field) {
|
||||
var bEdit = "<button title='{% trans "Edit Template" %}' class='template-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-edit'></span></button>";
|
||||
var bDel = "<button title='{% trans "Delete Template" %}' class='template-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
|
||||
|
||||
var html = "<div class='btn-group float-right' role='group'>" + bEdit + bDel + "</div>";
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$("#new-param").click(function() {
|
||||
launchModalForm("{% url 'part-param-template-create' %}", {
|
||||
success: function() {
|
||||
$("#param-table").bootstrapTable('refresh');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$("#param-table").on('click', '.template-edit', function() {
|
||||
var button = $(this);
|
||||
|
||||
var url = "/part/parameter/template/" + button.attr('pk') + "/edit/";
|
||||
|
||||
launchModalForm(url, {
|
||||
success: function() {
|
||||
$("#param-table").bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#param-table").on('click', '.template-delete', function() {
|
||||
var button = $(this);
|
||||
|
||||
var url = "/part/parameter/template/" + button.attr('pk') + "/delete/";
|
||||
|
||||
launchModalForm(url, {
|
||||
success: function() {
|
||||
$("#param-table").bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#import-part").click(function() {
|
||||
launchModalForm("{% url 'api-part-import' %}?reset", {});
|
||||
});
|
||||
|
||||
|
||||
enableNavbar({
|
||||
label: 'settings',
|
||||
toggleId: '#item-menu-toggle',
|
||||
|
23
InvenTree/templates/InvenTree/settings/user_labels.html
Normal file
23
InvenTree/templates/InvenTree/settings/user_labels.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "panel.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block label %}user-labels{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
{% trans "Label Settings" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
<table class='table table-striped table-condensed'>
|
||||
{% include "InvenTree/settings/header.html" %}
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="LABEL_INLINE" icon='fa-tag' user_setting=True %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
23
InvenTree/templates/InvenTree/settings/user_reports.html
Normal file
23
InvenTree/templates/InvenTree/settings/user_reports.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "panel.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block label %}user-reports{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
{% trans "Report Settings" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
<table class='table table-striped table-condensed'>
|
||||
{% include "InvenTree/settings/header.html" %}
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="REPORT_INLINE" icon='fa-file-pdf' user_setting=True %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -30,6 +30,17 @@ function createManufacturerPart(options={}) {
|
||||
fields.manufacturer.value = options.manufacturer;
|
||||
}
|
||||
|
||||
fields.manufacturer.secondary = {
|
||||
title: '{% trans "Add Manufacturer" %}',
|
||||
fields: function(data) {
|
||||
var company_fields = companyFormFields();
|
||||
|
||||
company_fields.is_manufacturer.value = true;
|
||||
|
||||
return company_fields;
|
||||
}
|
||||
}
|
||||
|
||||
constructForm('{% url "api-manufacturer-part-list" %}', {
|
||||
fields: fields,
|
||||
method: 'POST',
|
||||
@ -72,7 +83,7 @@ function supplierPartFields() {
|
||||
filters: {
|
||||
part_detail: true,
|
||||
manufacturer_detail: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
description: {},
|
||||
link: {
|
||||
@ -108,6 +119,33 @@ function createSupplierPart(options={}) {
|
||||
fields.manufacturer_part.value = options.manufacturer_part;
|
||||
}
|
||||
|
||||
// Add a secondary modal for the supplier
|
||||
fields.supplier.secondary = {
|
||||
title: '{% trans "Add Supplier" %}',
|
||||
fields: function(data) {
|
||||
var company_fields = companyFormFields();
|
||||
|
||||
company_fields.is_supplier.value = true;
|
||||
|
||||
return company_fields;
|
||||
}
|
||||
};
|
||||
|
||||
// Add a secondary modal for the manufacturer part
|
||||
fields.manufacturer_part.secondary = {
|
||||
title: '{% trans "Add Manufacturer Part" %}',
|
||||
fields: function(data) {
|
||||
var mp_fields = manufacturerPartFields();
|
||||
|
||||
if (data.part) {
|
||||
mp_fields.part.value = data.part;
|
||||
mp_fields.part.hidden = true;
|
||||
}
|
||||
|
||||
return mp_fields;
|
||||
}
|
||||
};
|
||||
|
||||
constructForm('{% url "api-supplier-part-list" %}', {
|
||||
fields: fields,
|
||||
method: 'POST',
|
||||
|
@ -564,6 +564,30 @@ function insertConfirmButton(options) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Extract all specified form values as a single object
|
||||
*/
|
||||
function extractFormData(fields, options) {
|
||||
|
||||
var data = {};
|
||||
|
||||
for (var idx = 0; idx < options.field_names.length; idx++) {
|
||||
|
||||
var name = options.field_names[idx];
|
||||
|
||||
var field = fields[name] || null;
|
||||
|
||||
if (!field) continue;
|
||||
|
||||
if (field.type == 'candy') continue;
|
||||
|
||||
data[name] = getFormFieldValue(name, field, options);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Submit form data to the server.
|
||||
*
|
||||
@ -950,10 +974,10 @@ function initializeRelatedFields(fields, options) {
|
||||
|
||||
switch (field.type) {
|
||||
case 'related field':
|
||||
initializeRelatedField(name, field, options);
|
||||
initializeRelatedField(field, fields, options);
|
||||
break;
|
||||
case 'choice':
|
||||
initializeChoiceField(name, field, options);
|
||||
initializeChoiceField(field, fields, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -968,7 +992,9 @@ function initializeRelatedFields(fields, options) {
|
||||
* - field: The field data object
|
||||
* - options: The options object provided by the client
|
||||
*/
|
||||
function addSecondaryModal(name, field, options) {
|
||||
function addSecondaryModal(field, fields, options) {
|
||||
|
||||
var name = field.name;
|
||||
|
||||
var secondary = field.secondary;
|
||||
|
||||
@ -981,22 +1007,42 @@ function addSecondaryModal(name, field, options) {
|
||||
|
||||
$(options.modal).find(`label[for="id_${name}"]`).append(html);
|
||||
|
||||
// TODO: Launch a callback
|
||||
// Callback function when the secondary button is pressed
|
||||
$(options.modal).find(`#btn-new-${name}`).click(function() {
|
||||
|
||||
if (secondary.callback) {
|
||||
// A "custom" callback can be specified for the button
|
||||
secondary.callback(field, options);
|
||||
} else if (secondary.api_url) {
|
||||
// By default, a new modal form is created, with the parameters specified
|
||||
// The parameters match the "normal" form creation parameters
|
||||
// Determine the API query URL
|
||||
var url = secondary.api_url || field.api_url;
|
||||
|
||||
secondary.onSuccess = function(data, opts) {
|
||||
setRelatedFieldData(name, data, options);
|
||||
};
|
||||
// If the "fields" attribute is a function, call it with data
|
||||
if (secondary.fields instanceof Function) {
|
||||
|
||||
constructForm(secondary.api_url, secondary);
|
||||
// Extract form values at time of button press
|
||||
var data = extractFormData(fields, options)
|
||||
|
||||
secondary.fields = secondary.fields(data);
|
||||
}
|
||||
|
||||
// If no onSuccess function is defined, provide a default one
|
||||
if (!secondary.onSuccess) {
|
||||
secondary.onSuccess = function(data, opts) {
|
||||
|
||||
// Force refresh from the API, to get full detail
|
||||
inventreeGet(`${url}${data.pk}/`, {}, {
|
||||
success: function(responseData) {
|
||||
|
||||
setRelatedFieldData(name, responseData, options);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Method should be "POST" for creation
|
||||
secondary.method = secondary.method || 'POST';
|
||||
|
||||
constructForm(
|
||||
url,
|
||||
secondary
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1010,7 +1056,9 @@ function addSecondaryModal(name, field, options) {
|
||||
* - field: Field definition from the OPTIONS request
|
||||
* - options: Original options object provided by the client
|
||||
*/
|
||||
function initializeRelatedField(name, field, options) {
|
||||
function initializeRelatedField(field, fields, options) {
|
||||
|
||||
var name = field.name;
|
||||
|
||||
if (!field.api_url) {
|
||||
// TODO: Provide manual api_url option?
|
||||
@ -1023,7 +1071,7 @@ function initializeRelatedField(name, field, options) {
|
||||
|
||||
// Add a button to launch a 'secondary' modal
|
||||
if (field.secondary != null) {
|
||||
addSecondaryModal(name, field, options);
|
||||
addSecondaryModal(field, fields, options);
|
||||
}
|
||||
|
||||
// TODO: Add 'placeholder' support for entry select2 fields
|
||||
@ -1192,7 +1240,9 @@ function setRelatedFieldData(name, data, options) {
|
||||
}
|
||||
|
||||
|
||||
function initializeChoiceField(name, field, options) {
|
||||
function initializeChoiceField(field, fields, options) {
|
||||
|
||||
var name = field.name;
|
||||
|
||||
var select = $(options.modal).find(`#id_${name}`);
|
||||
|
||||
|
@ -13,6 +13,16 @@ function createSalesOrder(options={}) {
|
||||
},
|
||||
customer: {
|
||||
value: options.customer,
|
||||
secondary: {
|
||||
title: '{% trans "Add Customer" %}',
|
||||
fields: function(data) {
|
||||
var fields = companyFormFields();
|
||||
|
||||
fields.is_customer.value = true;
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
},
|
||||
customer_reference: {},
|
||||
description: {},
|
||||
@ -44,6 +54,16 @@ function createPurchaseOrder(options={}) {
|
||||
},
|
||||
supplier: {
|
||||
value: options.supplier,
|
||||
secondary: {
|
||||
title: '{% trans "Add Supplier" %}',
|
||||
fields: function(data) {
|
||||
var fields = companyFormFields();
|
||||
|
||||
fields.is_supplier.value = true;
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
},
|
||||
supplier_reference: {},
|
||||
description: {},
|
||||
|
@ -17,7 +17,16 @@ function yesNoLabel(value) {
|
||||
function partFields(options={}) {
|
||||
|
||||
var fields = {
|
||||
category: {},
|
||||
category: {
|
||||
secondary: {
|
||||
title: '{% trans "Add Part Category" %}',
|
||||
fields: function(data) {
|
||||
var fields = categoryFields();
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
},
|
||||
name: {},
|
||||
IPN: {},
|
||||
revision: {},
|
||||
@ -30,7 +39,8 @@ function partFields(options={}) {
|
||||
link: {
|
||||
icon: 'fa-link',
|
||||
},
|
||||
default_location: {},
|
||||
default_location: {
|
||||
},
|
||||
default_supplier: {},
|
||||
default_expiry: {
|
||||
icon: 'fa-calendar-alt',
|
||||
|
@ -2,6 +2,18 @@
|
||||
{% load inventree_extras %}
|
||||
{% load status_codes %}
|
||||
|
||||
|
||||
function locationFields() {
|
||||
return {
|
||||
parent: {
|
||||
help_text: '{% trans "Parent stock location" %}',
|
||||
},
|
||||
name: {},
|
||||
description: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* Stock API functions
|
||||
* Requires api.js to be loaded first
|
||||
*/
|
||||
|
@ -24,7 +24,7 @@ However, powerful business logic works in the background to ensure that stock tr
|
||||
|
||||
InvenTree is [available via Docker](https://hub.docker.com/r/inventree/inventree). Read the [docker guide](https://inventree.readthedocs.io/en/latest/start/docker/) for full details.
|
||||
|
||||
# Companion App
|
||||
# Mobile App
|
||||
|
||||
InvenTree is supported by a [companion mobile app](https://inventree.readthedocs.io/en/latest/app/app/) which allows users access to stock control information and functionality.
|
||||
|
||||
|
38
ci/check_version_number.py
Normal file
38
ci/check_version_number.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""
|
||||
On release, ensure that the release tag matches the InvenTree version number!
|
||||
"""
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import argparse
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
version_file = os.path.join(here, '..', 'InvenTree', 'InvenTree', 'version.py')
|
||||
|
||||
with open(version_file, 'r') as f:
|
||||
|
||||
results = re.findall(r'INVENTREE_SW_VERSION = "(.*)"', f.read())
|
||||
|
||||
if not len(results) == 1:
|
||||
print(f"Could not find INVENTREE_SW_VERSION in {version_file}")
|
||||
sys.exit(1)
|
||||
|
||||
version = results[0]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('tag', help='Version tag', action='store')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.tag == version:
|
||||
print(f"Release tag '{args.tag}' does not match INVENTREE_SW_VERSION '{version}'")
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
Loading…
Reference in New Issue
Block a user