mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 12:06:44 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into plugin-2037
This commit is contained in:
commit
83d0693013
@ -69,6 +69,35 @@ def getStaticUrl(filename):
|
|||||||
return os.path.join(STATIC_URL, str(filename))
|
return os.path.join(STATIC_URL, str(filename))
|
||||||
|
|
||||||
|
|
||||||
|
def construct_absolute_url(*arg):
|
||||||
|
"""
|
||||||
|
Construct (or attempt to construct) an absolute URL from a relative URL.
|
||||||
|
|
||||||
|
This is useful when (for example) sending an email to a user with a link
|
||||||
|
to something in the InvenTree web framework.
|
||||||
|
|
||||||
|
This requires the BASE_URL configuration option to be set!
|
||||||
|
"""
|
||||||
|
|
||||||
|
base = str(InvenTreeSetting.get_setting('INVENTREE_BASE_URL'))
|
||||||
|
|
||||||
|
url = '/'.join(arg)
|
||||||
|
|
||||||
|
if not base:
|
||||||
|
return url
|
||||||
|
|
||||||
|
# Strip trailing slash from base url
|
||||||
|
if base.endswith('/'):
|
||||||
|
base = base[:-1]
|
||||||
|
|
||||||
|
if url.startswith('/'):
|
||||||
|
url = url[1:]
|
||||||
|
|
||||||
|
url = f"{base}/{url}"
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
def getBlankImage():
|
def getBlankImage():
|
||||||
"""
|
"""
|
||||||
Return the qualified path for the 'blank image' placeholder.
|
Return the qualified path for the 'blank image' placeholder.
|
||||||
|
@ -141,6 +141,10 @@
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-spacer {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
#navbar-barcode-li {
|
#navbar-barcode-li {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@ -545,7 +549,6 @@
|
|||||||
.inventree-body {
|
.inventree-body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inventree-pre-content {
|
.inventree-pre-content {
|
||||||
@ -562,10 +565,6 @@
|
|||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
|
||||||
padding-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
@ -52,7 +52,7 @@ def schedule_task(taskname, **kwargs):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def offload_task(taskname, force_sync=False, *args, **kwargs):
|
def offload_task(taskname, *args, force_sync=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create an AsyncTask if workers are running.
|
Create an AsyncTask if workers are running.
|
||||||
This is different to a 'scheduled' task,
|
This is different to a 'scheduled' task,
|
||||||
@ -108,7 +108,7 @@ def offload_task(taskname, force_sync=False, *args, **kwargs):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Workers are not running: run it as synchronous task
|
# Workers are not running: run it as synchronous task
|
||||||
_func()
|
_func(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def heartbeat():
|
def heartbeat():
|
||||||
@ -290,7 +290,7 @@ def update_exchange_rates():
|
|||||||
Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete()
|
Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete()
|
||||||
|
|
||||||
|
|
||||||
def send_email(subject, body, recipients, from_email=None):
|
def send_email(subject, body, recipients, from_email=None, html_message=None):
|
||||||
"""
|
"""
|
||||||
Send an email with the specified subject and body,
|
Send an email with the specified subject and body,
|
||||||
to the specified recipients list.
|
to the specified recipients list.
|
||||||
@ -306,4 +306,5 @@ def send_email(subject, body, recipients, from_email=None):
|
|||||||
from_email,
|
from_email,
|
||||||
recipients,
|
recipients,
|
||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
|
html_message=html_message
|
||||||
)
|
)
|
||||||
|
@ -102,9 +102,9 @@ class APITests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
fixtures = [
|
fixtures = [
|
||||||
'location',
|
'location',
|
||||||
'stock',
|
|
||||||
'part',
|
|
||||||
'category',
|
'category',
|
||||||
|
'part',
|
||||||
|
'stock'
|
||||||
]
|
]
|
||||||
|
|
||||||
token = None
|
token = None
|
||||||
|
@ -142,7 +142,7 @@
|
|||||||
<td><span class='fas fa-calendar-alt'></span></td>
|
<td><span class='fas fa-calendar-alt'></span></td>
|
||||||
<td>{% trans "Completed" %}</td>
|
<td>{% trans "Completed" %}</td>
|
||||||
{% if build.completion_date %}
|
{% if build.completion_date %}
|
||||||
<td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
|
<td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge badge-right rounded-pill bg-dark'>{{ build.completed_by }}</span>{% endif %}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td><em>{% trans "Build not complete" %}</em></td>
|
<td><em>{% trans "Build not complete" %}</em></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -830,19 +830,19 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
# login / SSO
|
# login / SSO
|
||||||
'LOGIN_ENABLE_PWD_FORGOT': {
|
'LOGIN_ENABLE_PWD_FORGOT': {
|
||||||
'name': _('Enable password forgot'),
|
'name': _('Enable password forgot'),
|
||||||
'description': _('Enable password forgot function on the login-pages'),
|
'description': _('Enable password forgot function on the login pages'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
'LOGIN_ENABLE_REG': {
|
'LOGIN_ENABLE_REG': {
|
||||||
'name': _('Enable registration'),
|
'name': _('Enable registration'),
|
||||||
'description': _('Enable self-registration for users on the login-pages'),
|
'description': _('Enable self-registration for users on the login pages'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
'LOGIN_ENABLE_SSO': {
|
'LOGIN_ENABLE_SSO': {
|
||||||
'name': _('Enable SSO'),
|
'name': _('Enable SSO'),
|
||||||
'description': _('Enable SSO on the login-pages'),
|
'description': _('Enable SSO on the login pages'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
@ -123,7 +123,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-calendar-alt'></span></td>
|
<td><span class='fas fa-calendar-alt'></span></td>
|
||||||
<td>{% trans "Created" %}</td>
|
<td>{% trans "Created" %}</td>
|
||||||
<td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td>
|
<td>{{ order.creation_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.created_by }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if order.issue_date %}
|
{% if order.issue_date %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -143,7 +143,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-calendar-alt'></span></td>
|
<td><span class='fas fa-calendar-alt'></span></td>
|
||||||
<td>{% trans "Received" %}</td>
|
<td>{% trans "Received" %}</td>
|
||||||
<td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td>
|
<td>{{ order.complete_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.received_by }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if order.responsible %}
|
{% if order.responsible %}
|
||||||
|
@ -128,7 +128,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-calendar-alt'></span></td>
|
<td><span class='fas fa-calendar-alt'></span></td>
|
||||||
<td>{% trans "Created" %}</td>
|
<td>{% trans "Created" %}</td>
|
||||||
<td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td>
|
<td>{{ order.creation_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.created_by }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if order.target_date %}
|
{% if order.target_date %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -141,14 +141,14 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-truck'></span></td>
|
<td><span class='fas fa-truck'></span></td>
|
||||||
<td>{% trans "Shipped" %}</td>
|
<td>{% trans "Shipped" %}</td>
|
||||||
<td>{{ order.shipment_date }}<span class='badge'>{{ order.shipped_by }}</span></td>
|
<td>{{ order.shipment_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.shipped_by }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if order.status == PurchaseOrderStatus.COMPLETE %}
|
{% if order.status == PurchaseOrderStatus.COMPLETE %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-calendar-alt'></span></td>
|
<td><span class='fas fa-calendar-alt'></span></td>
|
||||||
<td>{% trans "Received" %}</td>
|
<td>{% trans "Received" %}</td>
|
||||||
<td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td>
|
<td>{{ order.complete_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.received_by }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if order.responsible %}
|
{% if order.responsible %}
|
||||||
|
@ -1988,6 +1988,9 @@ class Part(MPTTModel):
|
|||||||
def related_count(self):
|
def related_count(self):
|
||||||
return len(self.get_related_parts())
|
return len(self.get_related_parts())
|
||||||
|
|
||||||
|
def is_part_low_on_stock(self):
|
||||||
|
return self.total_stock <= self.minimum_stock
|
||||||
|
|
||||||
|
|
||||||
def attach_file(instance, filename):
|
def attach_file(instance, filename):
|
||||||
""" Function for storing a file for a PartAttachment
|
""" Function for storing a file for a PartAttachment
|
||||||
|
58
InvenTree/part/tasks.py
Normal file
58
InvenTree/part/tasks.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from allauth.account.models import EmailAddress
|
||||||
|
|
||||||
|
from common.models import InvenTree
|
||||||
|
|
||||||
|
import InvenTree.helpers
|
||||||
|
import InvenTree.tasks
|
||||||
|
|
||||||
|
from part.models import Part
|
||||||
|
|
||||||
|
logger = logging.getLogger("inventree")
|
||||||
|
|
||||||
|
|
||||||
|
def notify_low_stock(part: Part):
|
||||||
|
"""
|
||||||
|
Notify users who have starred a part when its stock quantity falls below the minimum threshold
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(f"Sending low stock notification email for {part.full_name}")
|
||||||
|
|
||||||
|
starred_users_email = EmailAddress.objects.filter(user__starred_parts__part=part)
|
||||||
|
|
||||||
|
# TODO: In the future, include the part image in the email template
|
||||||
|
|
||||||
|
if len(starred_users_email) > 0:
|
||||||
|
logger.info(f"Notify users regarding low stock of {part.name}")
|
||||||
|
context = {
|
||||||
|
# Pass the "Part" object through to the template context
|
||||||
|
'part': part,
|
||||||
|
'link': InvenTree.helpers.construct_absolute_url(part.get_absolute_url()),
|
||||||
|
}
|
||||||
|
|
||||||
|
subject = _(f'[InvenTree] {part.name} is low on stock')
|
||||||
|
html_message = render_to_string('email/low_stock_notification.html', context)
|
||||||
|
recipients = starred_users_email.values_list('email', flat=True)
|
||||||
|
|
||||||
|
InvenTree.tasks.send_email(subject, '', recipients, html_message=html_message)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_low_stock_if_required(part: Part):
|
||||||
|
"""
|
||||||
|
Check if the stock quantity has fallen below the minimum threshold of part.
|
||||||
|
|
||||||
|
If true, notify the users who have subscribed to the part
|
||||||
|
"""
|
||||||
|
|
||||||
|
if part.is_part_low_on_stock():
|
||||||
|
InvenTree.tasks.offload_task(
|
||||||
|
'part.tasks.notify_low_stock',
|
||||||
|
part
|
||||||
|
)
|
@ -8,58 +8,55 @@
|
|||||||
{% include "sidebar_link.html" with url=url text="Return to BOM" icon="fa-undo" %}
|
{% include "sidebar_link.html" with url=url text="Return to BOM" icon="fa-undo" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block heading %}
|
||||||
|
{% trans "Upload Bill of Materials" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<div class='panel' id='panel-upload-file'>
|
{% block actions %}
|
||||||
<div class='panel-heading'>
|
{% endblock %}
|
||||||
{% block heading %}
|
|
||||||
<h4>{% trans "Upload Bill of Materials" %}</h4>
|
{% block page_info %}
|
||||||
{{ wizard.form.media }}
|
<div class='panel-content'>
|
||||||
{% endblock %}
|
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
|
||||||
|
{% if description %}- {{ description }}{% endif %}</p>
|
||||||
|
|
||||||
|
<form action="" method="post" class='js-modal-form' enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block form_buttons_top %}
|
||||||
|
{% endblock form_buttons_top %}
|
||||||
|
|
||||||
|
{% block form_alert %}
|
||||||
|
<div class='alert alert-info alert-block'>
|
||||||
|
<strong>{% trans "Requirements for BOM upload" %}:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>{% trans "The BOM file must contain the required named columns as provided in the " %} <strong><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></strong></li>
|
||||||
|
<li>{% trans "Each part must already exist in the database" %}</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class='panel-content'>
|
{% endblock %}
|
||||||
{% block details %}
|
|
||||||
|
|
||||||
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
|
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
|
||||||
{% if description %}- {{ description }}{% endif %}</p>
|
{{ wizard.management_form }}
|
||||||
|
{% block form_content %}
|
||||||
|
{% crispy wizard.form %}
|
||||||
|
{% endblock form_content %}
|
||||||
|
</table>
|
||||||
|
|
||||||
<form action="" method="post" class='js-modal-form' enctype="multipart/form-data">
|
{% block form_buttons_bottom %}
|
||||||
{% csrf_token %}
|
{% if wizard.steps.prev %}
|
||||||
{% load crispy_forms_tags %}
|
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-outline-secondary">{% trans "Previous Step" %}</button>
|
||||||
|
{% endif %}
|
||||||
{% block form_buttons_top %}
|
<button type="submit" class="save btn btn-outline-secondary">{% trans "Upload File" %}</button>
|
||||||
{% endblock form_buttons_top %}
|
</form>
|
||||||
|
{% endblock form_buttons_bottom %}
|
||||||
{% block form_alert %}
|
</div>
|
||||||
<div class='alert alert-info alert-block'>
|
{% endblock page_info %}
|
||||||
<strong>{% trans "Requirements for BOM upload" %}:</strong>
|
|
||||||
<ul>
|
|
||||||
<li>{% trans "The BOM file must contain the required named columns as provided in the " %} <strong><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></strong></li>
|
|
||||||
<li>{% trans "Each part must already exist in the database" %}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
|
|
||||||
{{ wizard.management_form }}
|
|
||||||
{% block form_content %}
|
|
||||||
{% crispy wizard.form %}
|
|
||||||
{% endblock form_content %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% block form_buttons_bottom %}
|
|
||||||
{% if wizard.steps.prev %}
|
|
||||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-outline-secondary">{% trans "Previous Step" %}</button>
|
|
||||||
{% endif %}
|
|
||||||
<button type="submit" class="save btn btn-outline-secondary">{% trans "Upload File" %}</button>
|
|
||||||
</form>
|
|
||||||
{% endblock form_buttons_bottom %}
|
|
||||||
|
|
||||||
{% endblock details %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock page_content %}
|
|
||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
enableSidebar('bom-upload');
|
||||||
|
|
||||||
{% endblock js_ready %}
|
{% endblock js_ready %}
|
||||||
|
@ -210,7 +210,8 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
parent: null,
|
parent: null,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
}
|
},
|
||||||
|
allowTreeView: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -20,13 +20,6 @@
|
|||||||
<!-- Details Table -->
|
<!-- Details Table -->
|
||||||
<table class="table table-striped table-condensed">
|
<table class="table table-striped table-condensed">
|
||||||
<col width='25'>
|
<col width='25'>
|
||||||
{% if part.IPN %}
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-tag'></span></td>
|
|
||||||
<td>{% trans "IPN" %}</td>
|
|
||||||
<td>{{ part.IPN }}{% include "clip.html"%}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-shapes'></span></td>
|
<td><span class='fas fa-shapes'></span></td>
|
||||||
<td>{% trans "Name" %}</td>
|
<td>{% trans "Name" %}</td>
|
||||||
@ -37,6 +30,22 @@
|
|||||||
<td>{% trans "Description" %}</td>
|
<td>{% trans "Description" %}</td>
|
||||||
<td>{{ part.description }}{% include "clip.html"%}</td>
|
<td>{{ part.description }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if part.category %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-sitemap'></span></td>
|
||||||
|
<td>{% trans "Category" %}</td>
|
||||||
|
<td>
|
||||||
|
<a href='{% url "category-detail" part.category.pk %}'>{{ part.category }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.IPN %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-tag'></span></td>
|
||||||
|
<td>{% trans "IPN" %}</td>
|
||||||
|
<td>{{ part.IPN }}{% include "clip.html"%}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% if part.revision %}
|
{% if part.revision %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-code-branch'></span></td>
|
<td><span class='fas fa-code-branch'></span></td>
|
||||||
@ -44,6 +53,20 @@
|
|||||||
<td>{{ part.revision }}{% include "clip.html"%}</td>
|
<td>{{ part.revision }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if part.units %}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>{% trans "Units" %}</td>
|
||||||
|
<td>{{ part.units }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.minimum_stock %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-less-than-equal'></span></td>
|
||||||
|
<td>{% trans "Minimum stock level" %}</td>
|
||||||
|
<td>{{ part.minimum_stock }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% if part.keywords %}
|
{% if part.keywords %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-key'></span></td>
|
<td><span class='fas fa-key'></span></td>
|
||||||
@ -64,7 +87,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ part.creation_date }}
|
{{ part.creation_date }}
|
||||||
{% if part.creation_user %}
|
{% if part.creation_user %}
|
||||||
<span class='badge'>{{ part.creation_user }}</span>
|
<span class='badge badge-right rounded-pill bg-dark'>{{ part.creation_user }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -79,7 +102,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-search-location'></span></td>
|
<td><span class='fas fa-search-location'></span></td>
|
||||||
<td>{% trans "Default Location" %}</td>
|
<td>{% trans "Default Location" %}</td>
|
||||||
<td>{{ part.default_location }}</td>
|
<td>
|
||||||
|
<a href='{% url "stock-location-detail" part.default_location.pk %}'>{{ part.default_location }}</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.default_supplier %}
|
{% if part.default_supplier %}
|
||||||
|
@ -122,6 +122,12 @@ def inventree_title(*args, **kwargs):
|
|||||||
return version.inventreeInstanceTitle()
|
return version.inventreeInstanceTitle()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def inventree_base_url(*args, **kwargs):
|
||||||
|
""" Return the INVENTREE_BASE_URL setting """
|
||||||
|
return InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def python_version(*args, **kwargs):
|
def python_version(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -14,6 +14,8 @@ from stock.models import StockItem
|
|||||||
|
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
|
|
||||||
|
import InvenTree.helpers
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@ -119,18 +121,10 @@ def internal_link(link, text):
|
|||||||
|
|
||||||
text = str(text)
|
text = str(text)
|
||||||
|
|
||||||
base_url = InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
|
url = InvenTree.helpers.construct_absolute_url(link)
|
||||||
|
|
||||||
# If the base URL is not set, just return the text
|
# If the base URL is not set, just return the text
|
||||||
if not base_url:
|
if not url:
|
||||||
return text
|
return text
|
||||||
|
|
||||||
if not base_url.endswith('/'):
|
|
||||||
base_url += '/'
|
|
||||||
|
|
||||||
if base_url.endswith('/') and link.startswith('/'):
|
|
||||||
link = link[1:]
|
|
||||||
|
|
||||||
url = f"{base_url}{link}"
|
|
||||||
|
|
||||||
return mark_safe(f'<a href="{url}">{text}</a>')
|
return mark_safe(f'<a href="{url}">{text}</a>')
|
||||||
|
@ -17,7 +17,7 @@ from django.db.models import Sum, Q
|
|||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models.signals import pre_delete
|
from django.db.models.signals import pre_delete, post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from markdownx.models import MarkdownxField
|
from markdownx.models import MarkdownxField
|
||||||
@ -41,6 +41,7 @@ from users.models import Owner
|
|||||||
|
|
||||||
from company import models as CompanyModels
|
from company import models as CompanyModels
|
||||||
from part import models as PartModels
|
from part import models as PartModels
|
||||||
|
from part import tasks as part_tasks
|
||||||
|
|
||||||
|
|
||||||
class StockLocation(InvenTreeTree):
|
class StockLocation(InvenTreeTree):
|
||||||
@ -1651,6 +1652,24 @@ def before_delete_stock_item(sender, instance, using, **kwargs):
|
|||||||
child.save()
|
child.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=StockItem, dispatch_uid='stock_item_post_delete_log')
|
||||||
|
def after_delete_stock_item(sender, instance: StockItem, **kwargs):
|
||||||
|
"""
|
||||||
|
Function to be executed after a StockItem object is deleted
|
||||||
|
"""
|
||||||
|
|
||||||
|
part_tasks.notify_low_stock_if_required(instance.part)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
|
||||||
|
def after_save_stock_item(sender, instance: StockItem, **kwargs):
|
||||||
|
"""
|
||||||
|
Hook function to be executed after StockItem object is saved/updated
|
||||||
|
"""
|
||||||
|
|
||||||
|
part_tasks.notify_low_stock_if_required(instance.part)
|
||||||
|
|
||||||
|
|
||||||
class StockItemAttachment(InvenTreeAttachment):
|
class StockItemAttachment(InvenTreeAttachment):
|
||||||
"""
|
"""
|
||||||
Model for storing file attachments against a StockItem object.
|
Model for storing file attachments against a StockItem object.
|
||||||
|
@ -393,7 +393,7 @@
|
|||||||
<td><span class='fas fa-calendar-alt'></span></td>
|
<td><span class='fas fa-calendar-alt'></span></td>
|
||||||
<td>{% trans "Last Stocktake" %}</td>
|
<td>{% trans "Last Stocktake" %}</td>
|
||||||
{% if item.stocktake_date %}
|
{% if item.stocktake_date %}
|
||||||
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
|
<td>{{ item.stocktake_date }} <span class='badge badge-right rounded-pill bg-dark'>{{ item.stocktake_user }}</span></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td><em>{% trans "No stocktake performed" %}</em></td>
|
<td><em>{% trans "No stocktake performed" %}</em></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -183,7 +183,8 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
parent: 'null',
|
parent: 'null',
|
||||||
{% endif %}
|
{% endif %}
|
||||||
}
|
},
|
||||||
|
allowTreeView: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
linkButtonsToSelection(
|
linkButtonsToSelection(
|
||||||
|
@ -36,7 +36,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
|
|||||||
|
|
||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
{% for item in location.stock_items.all %}
|
{% for item in location.stock_items.all %}
|
||||||
<li class='list-group-item'><strong>{{ item.part.full_name }}</strong> - <em>{{ item.part.description }}</em><span class='badge'>{% decimal item.quantity %}</span></li>
|
<li class='list-group-item'><strong>{{ item.part.full_name }}</strong> - <em>{{ item.part.description }}</em><span class='badge badge-right rounded-pill bg-dark'>{% decimal item.quantity %}</span></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
{% inventree_title %} | {% trans "Index" %}
|
{% inventree_title %} | {% trans "Index" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumb_list %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
<!-- Sidebar data is filled dynamically for the index page-->
|
<!-- Sidebar data is filled dynamically for the index page-->
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
{% inventree_title %} | {% trans "Search Results" %}
|
{% inventree_title %} | {% trans "Search Results" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumb_list %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class='panel panel-inventree'>
|
<div class='panel panel-inventree'>
|
||||||
|
@ -7,6 +7,12 @@
|
|||||||
{% trans "Category Settings" %}
|
{% trans "Category Settings" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block actions %}
|
||||||
|
<button class='btn btn-success' id='new-cat-param' disabled=''>
|
||||||
|
<div class='fas fa-plus-circle'></div> {% trans "New Parameter" %}
|
||||||
|
</button>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
@ -21,12 +27,6 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='cat-param-buttons'>
|
|
||||||
<button class='btn btn-success' id='new-cat-param' disabled=''>
|
|
||||||
<div class='fas fa-plus-circle'></div> {% trans "New Parameter" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='cat-param-table' data-toolbar='#cat-param-buttons'>
|
<table class='table table-striped table-condensed' id='cat-param-table' data-toolbar='#cat-param-buttons'>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -13,29 +13,31 @@
|
|||||||
<table class='table table-striped table-condensed'>
|
<table class='table table-striped table-condensed'>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %}
|
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %}
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<table class='table table-striped table-condensed'>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
|
<td></td>
|
||||||
<th>{% trans "Base Currency" %}</th>
|
<th>{% trans "Base Currency" %}</th>
|
||||||
<th>{{ base_currency }}</th>
|
<th>{{ base_currency }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan='2'>{% trans "Exchange Rates" %}</th>
|
<td></td>
|
||||||
|
<th colspan='4'>{% trans "Exchange Rates" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for rate in rates %}
|
{% for rate in rates %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ rate.currency }}</td>
|
<td></td>
|
||||||
<td>{{ rate.value }}</td>
|
<td>{{ rate.value }}</td>
|
||||||
|
<td>{{ rate.currency }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Last Update" %}
|
{% trans "Last Update" %}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td colspan="3">
|
||||||
{% if rates_updated %}
|
{% if rates_updated %}
|
||||||
{{ rates_updated }}
|
{{ rates_updated }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -44,7 +46,7 @@
|
|||||||
<form action='{% url "settings-currencies-refresh" %}' method='post'>
|
<form action='{% url "settings-currencies-refresh" %}' method='post'>
|
||||||
<div id='refresh-rates-form'>
|
<div id='refresh-rates-form'>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type='submit' id='update-rates' class='btn btn-outline-secondary float-right'>{% trans "Update Now" %}</button>
|
<button type='submit' id='update-rates' class='btn btn-primary float-right'>{% trans "Update Now" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_PWD_FORGOT" icon="fa-info-circle" %}
|
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_PWD_FORGOT" icon="fa-info-circle" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="LOGIN_MAIL_REQUIRED" icon="fa-info-circle" %}
|
{% include "InvenTree/settings/setting.html" with key="LOGIN_MAIL_REQUIRED" icon="fa-info-circle" %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Signup' %}</td>
|
<th><h5>{% trans 'Signup' %}</h5></th>
|
||||||
<td colspan='4'></td>
|
<td colspan='4'></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_REG" icon="fa-info-circle" %}
|
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_REG" icon="fa-info-circle" %}
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h4>{% trans "Part Options" %}</h4>
|
|
||||||
|
|
||||||
<table class='table table-striped table-condensed'>
|
<table class='table table-striped table-condensed'>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
|
||||||
@ -40,12 +38,17 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h4>{% trans "Part Import" %}</h4>
|
<div class='panel-heading'>
|
||||||
|
<div class='d-flex flex-span'>
|
||||||
<button class='btn btn-success' id='import-part'>
|
<h4>{% trans "Part Import" %}</h4>
|
||||||
<span class='fas fa-plus-circle'></span> {% trans "Import Part" %}
|
{% include "spacer.html" %}
|
||||||
</button>
|
<div class='btn-group' role='group'>
|
||||||
|
<button class='btn btn-success' id='import-part'>
|
||||||
|
<span class='fas fa-plus-circle'></span> {% trans "Import Part" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class='table table-striped table-condensed'>
|
<table class='table table-striped table-condensed'>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -53,14 +56,16 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class='panel-heading'>
|
||||||
|
<span class='d-flex flex-span'>
|
||||||
<h4>{% trans "Part Parameter Templates" %}</h4>
|
<h4>{% trans "Part Parameter Templates" %}</h4>
|
||||||
|
{% include "spacer.html" %}
|
||||||
<div id='param-buttons'>
|
<div class='btn-group' role='group'>
|
||||||
<button class='btn btn-success' id='new-param'>
|
<button class='btn btn-success' id='new-param'>
|
||||||
<span class='fas fa-plus-circle'></span> {% trans "New Parameter" %}
|
<span class='fas fa-plus-circle'></span> {% trans "New Parameter" %}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='param-table' data-toolbar='#param-buttons'>
|
<table class='table table-striped table-condensed' id='param-table' data-toolbar='#param-buttons'>
|
||||||
|
@ -21,15 +21,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div id='setting-{{ setting.pk }}'>
|
<div id='setting-{{ setting.pk }}'>
|
||||||
<strong>
|
|
||||||
<span id='setting-value-{{ setting.key.upper }}' fieldname='{{ setting.key.upper }}'>
|
<span id='setting-value-{{ setting.key.upper }}' fieldname='{{ setting.key.upper }}'>
|
||||||
{% if setting.value %}
|
{% if setting.value %}
|
||||||
{{ setting.value }}
|
<strong>{{ setting.value }}</strong>
|
||||||
{% else %}
|
{% else %}
|
||||||
<em>{% trans "No value set" %}</em>
|
<em style='color: #855;'>{% trans "No value set" %}</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</strong>
|
|
||||||
{{ setting.units }}
|
{{ setting.units }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -11,18 +11,18 @@
|
|||||||
{% trans "Account Settings" %}
|
{% trans "Account Settings" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block actions %}
|
||||||
|
<div class='btn btn-primary' type='button' id='edit-user' title='{% trans "Edit User Information" %}'>
|
||||||
|
<span class='fas fa-user-cog'></span> {% trans "Edit" %}
|
||||||
|
</div>
|
||||||
|
<div class='btn btn-primary' type='button' id='edit-password' title='{% trans "Change Password" %}'>
|
||||||
|
<span class='fas fa-key'></span> {% trans "Set Password" %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% mail_configured as mail_conf %}
|
{% mail_configured as mail_conf %}
|
||||||
|
|
||||||
<div class='btn-group' style='float: right;'>
|
|
||||||
<div class='btn btn-primary' type='button' id='edit-user' title='{% trans "Edit User Information" %}'>
|
|
||||||
<span class='fas fa-user-cog'></span> {% trans "Edit" %}
|
|
||||||
</div>
|
|
||||||
<div class='btn btn-primary' type='button' id='edit-password' title='{% trans "Change Password" %}'>
|
|
||||||
<span class='fas fa-key'></span> {% trans "Set Password" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class='table table-striped table-condensed'>
|
<table class='table table-striped table-condensed'>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans "Username" %}</td>
|
<td>{% trans "Username" %}</td>
|
||||||
@ -39,61 +39,81 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class='panel-heading'>
|
<div class='panel-heading'>
|
||||||
<h4>{% trans "Email" %}</h4>
|
<div class='d-flex flex-span'>
|
||||||
|
<h4>{% trans "Email" %}</h4>
|
||||||
|
{% include "spacer.html" %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class='row'>
|
||||||
{% if user.emailaddress_set.all %}
|
<div class='col-sm-6'>
|
||||||
<p>{% trans 'The following email addresses are associated with your account:' %}</p>
|
{% if user.emailaddress_set.all %}
|
||||||
|
<p>{% trans 'The following email addresses are associated with your account:' %}</p>
|
||||||
|
|
||||||
<form action="{% url 'account_email' %}" class="email_list" method="post">
|
<form action="{% url 'account_email' %}" class="email_list" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset class="blockLabels">
|
<fieldset class="blockLabels">
|
||||||
|
|
||||||
{% for emailaddress in user.emailaddress_set.all %}
|
{% for emailaddress in user.emailaddress_set.all %}
|
||||||
<div class="ctrlHolder">
|
<div>
|
||||||
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
|
<div class="ctrlHolder">
|
||||||
|
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
|
||||||
|
|
||||||
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
|
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
|
||||||
|
|
||||||
{{ emailaddress.email }}
|
{% if emailaddress.primary %}
|
||||||
{% if emailaddress.verified %}
|
<b>{{ emailaddress.email }}</b>
|
||||||
<span class="verified">{% trans "Verified" %}</span>
|
{% else %}
|
||||||
{% else %}
|
{{ emailaddress.email }}
|
||||||
<span class="unverified">{% trans "Unverified" %}</span>
|
{% endif %}
|
||||||
{% endif %}
|
</label>
|
||||||
{% if emailaddress.primary %}<span class="primary">{% trans "Primary" %}</span>{% endif %}
|
{% if emailaddress.verified %}
|
||||||
</label>
|
<span class='badge badge-right rounded-pill bg-success'>{% trans "Verified" %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class='badge badge-right rounded-pill bg-warning'>{% trans "Unverified" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if emailaddress.primary %}<span class='badge badge-right rounded-pill bg-primary'>{% trans "Primary" %}</span>{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="buttonHolder">
|
<div class="buttonHolder">
|
||||||
<button class="btn btn-primary secondaryAction" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
|
<button class="btn btn-primary secondaryAction" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
|
||||||
<button class="btn btn-primary secondaryAction" type="submit" name="action_send" {% if not mail_conf %}disabled{% endif %}>{% trans 'Re-send Verification' %}</button>
|
<button class="btn btn-primary secondaryAction" type="submit" name="action_send" {% if not mail_conf %}disabled{% endif %}>{% trans 'Re-send Verification' %}</button>
|
||||||
<button class="btn btn-primary primaryAction" type="submit" name="action_remove" >{% trans 'Remove' %}</button>
|
<button class="btn btn-primary primaryAction" type="submit" name="action_remove" >{% trans 'Remove' %}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p><strong>{% trans 'Warning:'%}</strong>
|
<p><strong>{% trans 'Warning:'%}</strong>
|
||||||
{% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
|
{% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% if can_add_email %}
|
<div class='col-sm-6'>
|
||||||
<br>
|
{% if can_add_email %}
|
||||||
<h4>{% trans "Add Email Address" %}</h4>
|
<h5>{% trans "Add Email Address" %}</h5>
|
||||||
|
|
||||||
<form method="post" action="{% url 'account_email' %}" class="add_email">
|
<form method="post" action="{% url 'account_email' %}" class="add_email">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ add_email_form|crispy }}
|
|
||||||
<button class="btn btn-primary" name="action_add" type="submit">{% trans "Add Email" %}</button>
|
<label for="id_email" class=" requiredField">
|
||||||
|
E-mail<span class="asteriskField">*</span>
|
||||||
|
</label>
|
||||||
|
<div id="div_id_email" class="form-group input-group mb-3">
|
||||||
|
<div class='input-group-prepend'><span class='input-group-text'>@</span></div>
|
||||||
|
<input type="email" name="email" placeholder='{% trans "Enter e-mail address" %}' class="textinput textInput form-control" required="" id="id_email">
|
||||||
|
<div class='input-group-append'>
|
||||||
|
<button class="btn btn-primary" name="action_add" type="submit">{% trans "Add Email" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='panel-heading'>
|
<div class='panel-heading'>
|
||||||
@ -135,7 +155,9 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans 'You currently have no social network accounts connected to this account.' %}</p>
|
<div class='alert alert-block alert-warning'>
|
||||||
|
{% trans "There are no social network accounts connected to your InvenTree account" %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
@ -155,26 +177,26 @@
|
|||||||
|
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
|
|
||||||
<form action='{% url "settings-appearance" %}' method='post'>
|
<div class='col-sm-6'>
|
||||||
{% csrf_token %}
|
<form action='{% url "settings-appearance" %}' method='post'>
|
||||||
<input name='next' type='hidden' value='{% url "settings" %}'>
|
{% csrf_token %}
|
||||||
<div class="col-sm-6" style="width: 200px;">
|
<input name='next' type='hidden' value='{% url "settings" %}'>
|
||||||
<div id="div_id_themes" class="form-group">
|
<label for='theme' class=' requiredField'>
|
||||||
<div class="controls ">
|
{% trans "Select theme" %}
|
||||||
<select name='theme' class='select form-control'>
|
</label>
|
||||||
{% get_available_themes as themes %}
|
<div class='form-group input-group mb-3'>
|
||||||
{% for theme in themes %}
|
<select id='theme' name='theme' class='select form-control'>
|
||||||
<option value='{{ theme.key }}'>{{ theme.name }}</option>
|
{% get_available_themes as themes %}
|
||||||
{% endfor %}
|
{% for theme in themes %}
|
||||||
</select>
|
<option value='{{ theme.key }}'>{{ theme.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<div class='input-group-append'>
|
||||||
|
<input type="submit" value="{% trans 'Set Theme' %}" class="btn btn-primary">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<div class="col-sm-6" style="width: auto;">
|
</div>
|
||||||
<input type="submit" value="{% trans 'Set Theme' %}" class="btn btn btn-primary">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='panel-heading'>
|
<div class='panel-heading'>
|
||||||
@ -186,7 +208,10 @@
|
|||||||
<form action="{% url 'set_language' %}" method="post">
|
<form action="{% url 'set_language' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{% url 'settings' %}">
|
<input name="next" type="hidden" value="{% url 'settings' %}">
|
||||||
<div class="col-sm-6" style="width: 200px;"><div id="div_id_language" class="form-group"><div class="controls ">
|
<label for='language' class=' requiredField'>
|
||||||
|
{% trans "Select language" %}
|
||||||
|
</label>
|
||||||
|
<div class='form-group input-group mb-3'>
|
||||||
<select name="language" class="select form-control">
|
<select name="language" class="select form-control">
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
{% get_available_languages as LANGUAGES %}
|
{% get_available_languages as LANGUAGES %}
|
||||||
@ -204,11 +229,11 @@
|
|||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div></div></div>
|
<div class='input-group-append'>
|
||||||
<div class="col-sm-6" style="width: auto;">
|
<input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary">
|
||||||
<input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary">
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<h4>{% trans "Help the translation efforts!" %}</h4>
|
<h4>{% trans "Help the translation efforts!" %}</h4>
|
||||||
|
43
InvenTree/templates/email/email.html
Normal file
43
InvenTree/templates/email/email.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load inventree_extras %}
|
||||||
|
|
||||||
|
<table style='border-collapse: collapse; width: 85%; margin-left: 10%; font-size: 1rem; border: 1px solid #68686a; border-radius: 2px;'>
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<tr style='background: #eef3f7; height: 4rem; text-align: center;'>
|
||||||
|
<th colspan="100%" style="padding-bottom: 1rem; color: #68686a;">
|
||||||
|
{% block header_row %}
|
||||||
|
<p style='font-size: 1.25rem;'>{% block title %}<!-- email title goes here -->{% endblock %}</p>
|
||||||
|
{% block subtitle %}
|
||||||
|
<!-- email subtitle goes here -->
|
||||||
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<tr style="height: 3rem; border-bottom: 1px solid #68686a;">
|
||||||
|
{% block body_row %}
|
||||||
|
<!-- email body goes here -->
|
||||||
|
{% endblock %}
|
||||||
|
</tr>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<tr style='background: #eef3f7; height: 2rem;'>
|
||||||
|
<td colspan="100%" style="padding-top:1rem; text-align: center">
|
||||||
|
{% block footer_prefix %}
|
||||||
|
<!-- Custom footer information goes here -->
|
||||||
|
{% endblock %}
|
||||||
|
<p><em><small>{% trans "InvenTree version" %}: {% inventree_version %} - <a href='https://inventree.readthedocs.io'>inventree.readthedocs.io</a></small></em></p>
|
||||||
|
{% block footer_suffix %}
|
||||||
|
<!-- Custom footer information goes here -->
|
||||||
|
{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</table>
|
29
InvenTree/templates/email/low_stock_notification.html
Normal file
29
InvenTree/templates/email/low_stock_notification.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends "email/email.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load inventree_extras %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% blocktrans with part=part.name %} The available stock for {{ part }} has fallen below the configured minimum level{% endblocktrans %}
|
||||||
|
{% if link %}
|
||||||
|
<p>{% trans "Click on the following link to view this part" %}: <a href="{{ link }}">{{ link }}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
<p><em>{% blocktrans with part=part.name %}You are receiving this email because you are subscribed to notifications for this part {% endblocktrans %}.</em></p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<tr style="height: 3rem; border-bottom: 1px solid">
|
||||||
|
<th>{% trans "Part Name" %}</th>
|
||||||
|
<th>{% trans "Available Quantity" %}</th>
|
||||||
|
<th>{% trans "Minimum Quantity" %}</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 3rem">
|
||||||
|
<td style="text-align: center;">{{ part.full_name }}</td>
|
||||||
|
<td style="text-align: center;">{{ part.total_stock }}</td>
|
||||||
|
<td style="text-align: center;">{{ part.minimum_stock }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endblock %}
|
@ -1133,8 +1133,10 @@ function loadPartTable(table, url, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display a table of part categories
|
||||||
|
*/
|
||||||
function loadPartCategoryTable(table, options) {
|
function loadPartCategoryTable(table, options) {
|
||||||
/* Display a table of part categories */
|
|
||||||
|
|
||||||
var params = options.params || {};
|
var params = options.params || {};
|
||||||
|
|
||||||
@ -1157,15 +1159,15 @@ function loadPartCategoryTable(table, options) {
|
|||||||
|
|
||||||
setupFilterList(filterKey, table, filterListElement);
|
setupFilterList(filterKey, table, filterListElement);
|
||||||
|
|
||||||
var tree_view = inventreeLoad('category-tree-view') == 1;
|
var tree_view = options.allowTreeView && inventreeLoad('category-tree-view') == 1;
|
||||||
|
|
||||||
table.inventreeTable({
|
table.inventreeTable({
|
||||||
treeEnable: tree_view,
|
treeEnable: tree_view,
|
||||||
rootParentId: options.params.parent,
|
rootParentId: tree_view ? options.params.parent : null,
|
||||||
uniqueId: 'pk',
|
uniqueId: 'pk',
|
||||||
idField: 'pk',
|
idField: 'pk',
|
||||||
treeShowField: 'name',
|
treeShowField: 'name',
|
||||||
parentIdField: 'parent',
|
parentIdField: tree_view ? 'parent' : null,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: options.url || '{% url "api-part-category-list" %}',
|
url: options.url || '{% url "api-part-category-list" %}',
|
||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
@ -1176,7 +1178,7 @@ function loadPartCategoryTable(table, options) {
|
|||||||
name: 'category',
|
name: 'category',
|
||||||
original: original,
|
original: original,
|
||||||
showColumns: true,
|
showColumns: true,
|
||||||
buttons: [
|
buttons: options.allowTreeView ? [
|
||||||
{
|
{
|
||||||
icon: 'fas fa-bars',
|
icon: 'fas fa-bars',
|
||||||
attributes: {
|
attributes: {
|
||||||
@ -1215,28 +1217,31 @@ function loadPartCategoryTable(table, options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
] : [],
|
||||||
onPostBody: function() {
|
onPostBody: function() {
|
||||||
|
|
||||||
tree_view = inventreeLoad('category-tree-view') == 1;
|
if (options.allowTreeView) {
|
||||||
|
|
||||||
if (tree_view) {
|
tree_view = inventreeLoad('category-tree-view') == 1;
|
||||||
|
|
||||||
$('#view-category-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
if (tree_view) {
|
||||||
$('#view-category-tree').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
|
||||||
|
|
||||||
table.treegrid({
|
$('#view-category-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
||||||
treeColumn: 0,
|
$('#view-category-tree').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
||||||
onChange: function() {
|
|
||||||
table.bootstrapTable('resetView');
|
|
||||||
},
|
|
||||||
onExpand: function() {
|
|
||||||
|
|
||||||
}
|
table.treegrid({
|
||||||
});
|
treeColumn: 0,
|
||||||
} else {
|
onChange: function() {
|
||||||
$('#view-category-tree').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
table.bootstrapTable('resetView');
|
||||||
$('#view-category-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
},
|
||||||
|
onExpand: function() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$('#view-category-tree').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
||||||
|
$('#view-category-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -1416,8 +1416,11 @@ function loadStockTable(table, options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display a table of stock locations
|
||||||
|
*/
|
||||||
function loadStockLocationTable(table, options) {
|
function loadStockLocationTable(table, options) {
|
||||||
/* Display a table of stock locations */
|
|
||||||
|
|
||||||
var params = options.params || {};
|
var params = options.params || {};
|
||||||
|
|
||||||
@ -1443,15 +1446,15 @@ function loadStockLocationTable(table, options) {
|
|||||||
filters[key] = params[key];
|
filters[key] = params[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
var tree_view = inventreeLoad('location-tree-view') == 1;
|
var tree_view = options.allowTreeView && inventreeLoad('location-tree-view') == 1;
|
||||||
|
|
||||||
table.inventreeTable({
|
table.inventreeTable({
|
||||||
treeEnable: tree_view,
|
treeEnable: tree_view,
|
||||||
rootParentId: options.params.parent,
|
rootParentId: tree_view ? options.params.parent : null,
|
||||||
uniqueId: 'pk',
|
uniqueId: 'pk',
|
||||||
idField: 'pk',
|
idField: 'pk',
|
||||||
treeShowField: 'name',
|
treeShowField: 'name',
|
||||||
parentIdField: 'parent',
|
parentIdField: tree_view ? 'parent' : null,
|
||||||
disablePagination: tree_view,
|
disablePagination: tree_view,
|
||||||
sidePagination: tree_view ? 'client' : 'server',
|
sidePagination: tree_view ? 'client' : 'server',
|
||||||
serverSort: !tree_view,
|
serverSort: !tree_view,
|
||||||
@ -1465,28 +1468,31 @@ function loadStockLocationTable(table, options) {
|
|||||||
showColumns: true,
|
showColumns: true,
|
||||||
onPostBody: function() {
|
onPostBody: function() {
|
||||||
|
|
||||||
tree_view = inventreeLoad('location-tree-view') == 1;
|
if (options.allowTreeView) {
|
||||||
|
|
||||||
if (tree_view) {
|
tree_view = inventreeLoad('location-tree-view') == 1;
|
||||||
|
|
||||||
$('#view-location-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
if (tree_view) {
|
||||||
$('#view-location-tree').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
|
||||||
|
|
||||||
table.treegrid({
|
$('#view-location-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
||||||
treeColumn: 1,
|
$('#view-location-tree').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
||||||
onChange: function() {
|
|
||||||
table.bootstrapTable('resetView');
|
|
||||||
},
|
|
||||||
onExpand: function() {
|
|
||||||
|
|
||||||
}
|
table.treegrid({
|
||||||
});
|
treeColumn: 1,
|
||||||
} else {
|
onChange: function() {
|
||||||
$('#view-location-tree').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
table.bootstrapTable('resetView');
|
||||||
$('#view-location-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
},
|
||||||
|
onExpand: function() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$('#view-location-tree').removeClass('btn-secondary').addClass('btn-outline-secondary');
|
||||||
|
$('#view-location-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
buttons: [
|
buttons: options.allowTreeView ? [
|
||||||
{
|
{
|
||||||
icon: 'fas fa-bars',
|
icon: 'fas fa-bars',
|
||||||
attributes: {
|
attributes: {
|
||||||
@ -1525,7 +1531,7 @@ function loadStockLocationTable(table, options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
] : [],
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
|
@ -12,7 +12,10 @@
|
|||||||
<div class="navbar-header clearfix content-heading">
|
<div class="navbar-header clearfix content-heading">
|
||||||
<a class="navbar-brand" id='logo' href="{% url 'index' %}" style="padding-top: 7px; padding-bottom: 5px;"><img src="{% static 'img/inventree.png' %}" width="32" height="32" style="display:block; margin: auto;"/></a>
|
<a class="navbar-brand" id='logo' href="{% url 'index' %}" style="padding-top: 7px; padding-bottom: 5px;"><img src="{% static 'img/inventree.png' %}" width="32" height="32" style="display:block; margin: auto;"/></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-collapse collapse">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-objects" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="navbar-collapse collapse" id="navbar-objects">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
{% if roles.part.view %}
|
{% if roles.part.view %}
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
@ -130,3 +133,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{% if sticky %}
|
||||||
|
<div class='navbar-spacer'></div>
|
||||||
|
{% endif %}
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
<div class='panel panel-hidden' id='panel-{% block label %}name{% endblock %}'>
|
<div class='panel panel-hidden' id='panel-{% block label %}name{% endblock %}'>
|
||||||
{% block panel_heading %}
|
{% block panel_heading %}
|
||||||
<div class='panel-heading'>
|
<div class='panel-heading'>
|
||||||
<h4>{% block heading %}HEADING{% endblock %}</h4>
|
<div class='d-flex flex-wrap'>
|
||||||
|
<h4>{% block heading %}HEADING{% endblock %}</h4>
|
||||||
|
{% include "spacer.html" %}
|
||||||
|
<div class='btn-group' role='group'>
|
||||||
|
{% block actions %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block panel_content %}
|
{% block panel_content %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user