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:
25
.github/workflows/welcome.yml
vendored
25
.github/workflows/welcome.yml
vendored
@ -1,25 +0,0 @@
|
||||
# welcome new contributers
|
||||
name: Welcome
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened ]
|
||||
issues:
|
||||
types: [ opened ]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/first-interaction@bd33205aa5c96838e10fd65df0d01efd613677c1 # pin@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: |
|
||||
Welcome to InvenTree! Please check the [contributing docs](https://inventree.readthedocs.io/en/latest/contribute/) on how to help.
|
||||
If you experience setup / install issues please read all [install docs]( https://inventree.readthedocs.io/en/latest/start/intro/).
|
||||
pr-message: |
|
||||
This is your first PR, welcome!
|
||||
Please check [Contributing](https://github.com/inventree/InvenTree/blob/master/CONTRIBUTING.md) to make sure your submission fits our general code-style and workflow.
|
||||
Make sure to document why this PR is needed and to link connected issues so we can review it faster.
|
@ -15,7 +15,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
- id: mixed-line-ending
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: '5.0.0'
|
||||
rev: '5.0.4'
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [
|
||||
|
@ -13,7 +13,7 @@ def isImportingData():
|
||||
return 'loaddata' in sys.argv
|
||||
|
||||
|
||||
def canAppAccessDatabase(allow_test=False):
|
||||
def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False):
|
||||
"""Returns True if the apps.py file can access database records.
|
||||
|
||||
There are some circumstances where we don't want the ready function in apps.py
|
||||
@ -25,8 +25,6 @@ def canAppAccessDatabase(allow_test=False):
|
||||
'flush',
|
||||
'loaddata',
|
||||
'dumpdata',
|
||||
'makemigrations',
|
||||
'migrate',
|
||||
'check',
|
||||
'shell',
|
||||
'createsuperuser',
|
||||
@ -43,6 +41,12 @@ def canAppAccessDatabase(allow_test=False):
|
||||
# Override for testing mode?
|
||||
excluded_commands.append('test')
|
||||
|
||||
if not allow_plugins:
|
||||
excluded_commands.extend([
|
||||
'makemigrations',
|
||||
'migrate',
|
||||
])
|
||||
|
||||
for cmd in excluded_commands:
|
||||
if cmd in sys.argv:
|
||||
return False
|
||||
|
@ -236,6 +236,11 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
||||
help_text=_('Build status code')
|
||||
)
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""Return the text representation of the status field"""
|
||||
return BuildStatus.text(self.status)
|
||||
|
||||
batch = models.CharField(
|
||||
verbose_name=_('Batch Code'),
|
||||
max_length=100,
|
||||
|
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
@ -248,6 +248,11 @@ class PurchaseOrder(Order):
|
||||
status = models.PositiveIntegerField(default=PurchaseOrderStatus.PENDING, choices=PurchaseOrderStatus.items(),
|
||||
help_text=_('Purchase order status'))
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""Return the text representation of the status field"""
|
||||
return PurchaseOrderStatus.text(self.status)
|
||||
|
||||
supplier = models.ForeignKey(
|
||||
Company, on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
@ -645,6 +650,11 @@ class SalesOrder(Order):
|
||||
status = models.PositiveIntegerField(default=SalesOrderStatus.PENDING, choices=SalesOrderStatus.items(),
|
||||
verbose_name=_('Status'), help_text=_('Purchase order status'))
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""Return the text representation of the status field"""
|
||||
return SalesOrderStatus.text(self.status)
|
||||
|
||||
customer_reference = models.CharField(max_length=64, blank=True, verbose_name=_('Customer Reference '), help_text=_("Customer order reference code"))
|
||||
|
||||
target_date = models.DateField(
|
||||
|
@ -286,6 +286,12 @@
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
{% include "part/bom.html" with part=part %}
|
||||
{% if roles.part.change %}
|
||||
<button class='btn btn-success' type='button' title='{% trans "New BOM Item" %}' id='bom-item-new-footer'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "Add BOM Item" %}
|
||||
</button>
|
||||
<br/>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -611,7 +617,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
$("#bom-item-new").click(function () {
|
||||
$("[id^=bom-item-new]").click(function () {
|
||||
|
||||
var fields = bomItemFields();
|
||||
|
||||
|
@ -27,7 +27,7 @@ class PluginAppConfig(AppConfig):
|
||||
def ready(self):
|
||||
"""The ready method is extended to initialize plugins."""
|
||||
if settings.PLUGINS_ENABLED:
|
||||
if not canAppAccessDatabase(allow_test=True):
|
||||
if not canAppAccessDatabase(allow_test=True, allow_plugins=True):
|
||||
logger.info("Skipping plugin loading sequence") # pragma: no cover
|
||||
else:
|
||||
logger.info('Loading InvenTree plugins')
|
||||
|
@ -670,6 +670,11 @@ class StockItem(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
|
||||
choices=StockStatus.items(),
|
||||
validators=[MinValueValidator(0)])
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""Return the text representation of the status field"""
|
||||
return StockStatus.text(self.status)
|
||||
|
||||
notes = InvenTreeNotesField(help_text=_('Stock Item Notes'))
|
||||
|
||||
purchase_price = InvenTreeModelMoneyField(
|
||||
|
@ -23,9 +23,7 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if setting.is_bool %}
|
||||
<div class='form-check form-switch'>
|
||||
<input class='form-check-input boolean-setting' fieldname='{{ setting.key.upper }}' pk='{{ setting.pk }}' setting='{{ setting.key.upper }}' id='setting-value-{{ setting.pk }}-{{ setting.typ }}' type='checkbox' {% if setting.as_bool %}checked=''{% endif %}{{reference}}>
|
||||
</div>
|
||||
{% include "InvenTree/settings/setting_boolean.html" %}
|
||||
{% else %}
|
||||
<div id='setting-{{ setting.pk }}'>
|
||||
<span id='setting-value-{{ setting.pk }}-{{ setting.typ }}' fieldname='{{ setting.key.upper }}'>
|
||||
@ -41,7 +39,18 @@
|
||||
</span>
|
||||
{{ setting.units }}
|
||||
<div class='btn-group float-right'>
|
||||
<button class='btn btn-outline-secondary btn-small btn-edit-setting' pk='{{ setting.pk }}' setting='{{ setting.key.upper }}' title='{% trans "Edit setting" %}' {% if plugin %}plugin='{{ plugin.slug }}'{% endif %}{% if user_setting %}user='{{request.user.id}}'{% endif %}>
|
||||
<button
|
||||
class='btn btn-outline-secondary btn-small btn-edit-setting'
|
||||
title='{% trans "Edit setting" %}'
|
||||
pk='{{ setting.pk }}'
|
||||
setting='{{ setting.key.upper }}'
|
||||
{% if plugin %}plugin='{{ plugin.slug }}'{% endif %}
|
||||
{% if user_setting or notification_setting %}user='{{request.user.id}}'{% endif %}
|
||||
{% if notification_setting %}
|
||||
notification=true
|
||||
method='{{ setting.method }}'
|
||||
{% endif %}
|
||||
>
|
||||
<span class='fas fa-edit icon-green'></span>
|
||||
</button>
|
||||
</div>
|
||||
|
18
InvenTree/templates/InvenTree/settings/setting_boolean.html
Normal file
18
InvenTree/templates/InvenTree/settings/setting_boolean.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div class='form-check form-switch'>
|
||||
<input
|
||||
class='form-check-input boolean-setting'
|
||||
fieldname='{{ setting.key.upper }}'
|
||||
pk='{{ setting.pk }}'
|
||||
setting='{{ setting.key.upper }}'
|
||||
id='setting-value-{{setting.pk }}-{{ setting.typ }}'
|
||||
type='checkbox'
|
||||
{% if setting.as_bool %}checked=''{% endif %}
|
||||
{{ reference }}
|
||||
{% if plugin %}plugin='{{ plugin.slug }}'{% endif %}
|
||||
{% if user_setting or notification_setting %}user='{{ request.user.pk }}'{% endif %}
|
||||
{% if notification_setting %}
|
||||
notification=true
|
||||
method='{{ setting.method }}'
|
||||
{% endif %}
|
||||
>
|
||||
</div>
|
@ -78,12 +78,12 @@ $('table').find('.boolean-setting').change(function() {
|
||||
// Global setting by default
|
||||
var url = `/api/settings/global/${setting}/`;
|
||||
|
||||
if (plugin) {
|
||||
if (notification) {
|
||||
url = `/api/settings/notification/${pk}/`;
|
||||
} else if (plugin) {
|
||||
url = `/api/plugin/settings/${plugin}/${setting}/`;
|
||||
} else if (user) {
|
||||
url = `/api/settings/user/${setting}/`;
|
||||
} else if (notification) {
|
||||
url = `/api/settings/notification/${pk}/`;
|
||||
}
|
||||
|
||||
inventreePut(
|
||||
|
@ -43,7 +43,9 @@ function makeBarcodeInput(placeholderText='', hintText='') {
|
||||
<span class='fas fa-qrcode'></span>
|
||||
</span>
|
||||
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
|
||||
<button id='barcode_scan_btn' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'><span class='fas fa-camera'></span></button>
|
||||
<button id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
|
||||
<span class='fas fa-camera'></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id='hint_barcode_data' class='help-block'>${hintText}</div>
|
||||
</div>
|
||||
@ -571,33 +573,40 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
},
|
||||
onScan: function(response) {
|
||||
if ('stockitem' in response) {
|
||||
var stockitem = response.stockitem;
|
||||
var pk = response.stockitem.pk;
|
||||
|
||||
var duplicate = false;
|
||||
inventreeGet(
|
||||
`/api/stock/${pk}/`,
|
||||
{},
|
||||
{
|
||||
success: function(stockitem) {
|
||||
var duplicate = false;
|
||||
|
||||
items.forEach(function(item) {
|
||||
if (item.pk == stockitem.pk) {
|
||||
duplicate = true;
|
||||
items.forEach(function(item) {
|
||||
if (item.pk == stockitem.pk) {
|
||||
duplicate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicate) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', 'warning');
|
||||
} else {
|
||||
|
||||
if (stockitem.location == location_id) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already in this location" %}');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this stock item to the list
|
||||
items.push(stockitem);
|
||||
|
||||
showBarcodeMessage(modal, '{% trans "Added stock item" %}', 'success');
|
||||
|
||||
reloadTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicate) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', 'warning');
|
||||
} else {
|
||||
|
||||
if (stockitem.location == location_id) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already in this location" %}');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this stock item to the list
|
||||
items.push(stockitem);
|
||||
|
||||
showBarcodeMessage(modal, '{% trans "Added stock item" %}', 'success');
|
||||
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
);
|
||||
} else {
|
||||
// Barcode does not match a stock item
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', 'warning');
|
||||
@ -696,12 +705,26 @@ function scanItemsIntoLocation(item_list, options={}) {
|
||||
onScan: function(response) {
|
||||
updateLocationInfo(null);
|
||||
if ('stocklocation' in response) {
|
||||
// Barcode corresponds to a StockLocation
|
||||
stock_location = response.stocklocation;
|
||||
|
||||
updateLocationInfo(stock_location);
|
||||
modalEnable(modal, true);
|
||||
var pk = response.stocklocation.pk;
|
||||
|
||||
inventreeGet(`/api/stock/location/${pk}/`, {}, {
|
||||
success: function(response) {
|
||||
|
||||
stock_location = response;
|
||||
|
||||
updateLocationInfo(stock_location);
|
||||
modalEnable(modal, true);
|
||||
},
|
||||
error: function() {
|
||||
// Barcode does *NOT* correspond to a StockLocation
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
'{% trans "Barcode does not match a valid location" %}',
|
||||
'warning',
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Barcode does *NOT* correspond to a StockLocation
|
||||
showBarcodeMessage(
|
||||
|
@ -971,7 +971,7 @@ function adjustStock(action, items, options={}) {
|
||||
|
||||
var item = items[idx];
|
||||
|
||||
if ((item.serial != null) && !allowSerializedStock) {
|
||||
if ((item.serial != null) && (item.serial != '') && !allowSerializedStock) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1729,7 +1729,11 @@ function loadStockTable(table, options) {
|
||||
switchable: params['part_detail'],
|
||||
formatter: function(value, row) {
|
||||
var ipn = row.part_detail.IPN;
|
||||
return withTitle(shortenString(ipn), ipn);
|
||||
if (ipn) {
|
||||
return withTitle(shortenString(ipn), ipn);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -208,7 +208,7 @@ class InvenTreeUserAdmin(UserAdmin):
|
||||
|
||||
(And it's confusing!)
|
||||
"""
|
||||
|
||||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'last_login') # display last connection for each user in user admin panel.
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
|
||||
|
@ -47,6 +47,9 @@ server {
|
||||
|
||||
# Media files require user authentication
|
||||
auth_request /auth;
|
||||
|
||||
# Content header to force download
|
||||
add_header Content-disposition "attachment";
|
||||
}
|
||||
|
||||
# Use the 'user' API endpoint for auth
|
||||
|
@ -46,6 +46,9 @@ server {
|
||||
|
||||
# Media files require user authentication
|
||||
auth_request /auth;
|
||||
|
||||
# Content header to force download
|
||||
add_header Content-disposition "attachment";
|
||||
}
|
||||
|
||||
# Use the 'user' API endpoint for auth
|
||||
|
Reference in New Issue
Block a user