Merge branch 'master' into partial-shipment

# Conflicts:
#	InvenTree/order/serializers.py
This commit is contained in:
Oliver 2021-11-18 23:43:36 +11:00
commit 521ec4f1e6
17 changed files with 138 additions and 70 deletions

View File

@ -118,20 +118,31 @@ class InvenTreeMetadata(SimpleMetadata):
# Iterate through simple fields
for name, field in model_fields.fields.items():
if field.has_default() and name in serializer_info.keys():
if name in serializer_info.keys():
default = field.default
if field.has_default():
if callable(default):
try:
default = default()
except:
continue
default = field.default
serializer_info[name]['default'] = default
if callable(default):
try:
default = default()
except:
continue
elif name in model_default_values:
serializer_info[name]['default'] = model_default_values[name]
serializer_info[name]['default'] = default
elif name in model_default_values:
serializer_info[name]['default'] = model_default_values[name]
# Attributes to copy from the model to the field (if they don't exist)
attributes = ['help_text']
for attr in attributes:
if attr not in serializer_info[name]:
if hasattr(field, attr):
serializer_info[name][attr] = getattr(field, attr)
# Iterate through relations
for name, relation in model_fields.relations.items():

View File

@ -296,3 +296,17 @@ class InvenTreeImageSerializerField(serializers.ImageField):
return None
return os.path.join(str(settings.MEDIA_URL), str(value))
class InvenTreeDecimalField(serializers.FloatField):
"""
Custom serializer for decimal fields. Solves the following issues:
- The normal DRF DecimalField renders values with trailing zeros
- Using a FloatField can result in rounding issues: https://code.djangoproject.com/ticket/30290
"""
def to_internal_value(self, data):
# Convert the value to a string, and then a decimal
return Decimal(str(data))

View File

@ -92,6 +92,12 @@ DEBUG = _is_true(get_setting(
CONFIG.get('debug', True)
))
# Determine if we are running in "demo mode"
DEMO_MODE = _is_true(get_setting(
'INVENTREE_DEMO',
CONFIG.get('demo', False)
))
DOCKER = _is_true(get_setting(
'INVENTREE_DOCKER',
False
@ -234,7 +240,10 @@ STATIC_COLOR_THEMES_DIR = os.path.join(STATIC_ROOT, 'css', 'color-themes')
MEDIA_URL = '/media/'
if DEBUG:
logger.info("InvenTree running in DEBUG mode")
logger.info("InvenTree running with DEBUG enabled")
if DEMO_MODE:
logger.warning("InvenTree running in DEMO mode")
logger.debug(f"MEDIA_ROOT: '{MEDIA_ROOT}'")
logger.debug(f"STATIC_ROOT: '{STATIC_ROOT}'")

View File

@ -1,13 +0,0 @@
from rest_framework.views import exception_handler
def api_exception_handler(exc, context):
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
data = {'error': response.data}
response.data = data
return response

View File

@ -18,8 +18,9 @@ from rest_framework.serializers import ValidationError
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField, UserSerializerBrief
from InvenTree.status_codes import StockStatus
import InvenTree.helpers
from InvenTree.serializers import InvenTreeDecimalField
from InvenTree.status_codes import StockStatus
from stock.models import StockItem, StockLocation
from stock.serializers import StockItemSerializerBrief, LocationSerializer
@ -41,7 +42,7 @@ class BuildSerializer(InvenTreeModelSerializer):
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
overdue = serializers.BooleanField(required=False, read_only=True)
@ -473,7 +474,7 @@ class BuildItemSerializer(InvenTreeModelSerializer):
stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
location_detail = LocationSerializer(source='stock_item.location', read_only=True)
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
def __init__(self, *args, **kwargs):

View File

@ -8,9 +8,10 @@ from rest_framework import serializers
from sql_util.utils import SubqueryCount
from InvenTree.serializers import InvenTreeDecimalField
from InvenTree.serializers import InvenTreeImageSerializerField
from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeMoneySerializer
from InvenTree.serializers import InvenTreeImageSerializerField
from part.serializers import PartBriefSerializer
@ -255,7 +256,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPriceBreak object """
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
price = InvenTreeMoneySerializer(
allow_null=True,

View File

@ -20,10 +20,10 @@ from sql_util.utils import SubqueryCount
from common.settings import currency_code_mappings
from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializer
from InvenTree.helpers import normalize
from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializer
from InvenTree.serializers import InvenTreeDecimalField
from InvenTree.serializers import InvenTreeMoneySerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField
from InvenTree.status_codes import StockStatus
@ -550,7 +550,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True)
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
allocated = serializers.FloatField(source='allocated_quantity', read_only=True)
fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True)

View File

@ -15,6 +15,7 @@ from sql_util.utils import SubqueryCount, SubquerySum
from djmoney.contrib.django_rest_framework import MoneyField
from InvenTree.serializers import (InvenTreeAttachmentSerializerField,
InvenTreeDecimalField,
InvenTreeImageSerializerField,
InvenTreeModelSerializer,
InvenTreeAttachmentSerializer,
@ -120,7 +121,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
Serializer for sale prices for Part model.
"""
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
price = InvenTreeMoneySerializer(
allow_null=True
@ -144,7 +145,7 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
Serializer for internal prices for Part model.
"""
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
price = InvenTreeMoneySerializer(
allow_null=True
@ -428,7 +429,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
price_range = serializers.CharField(read_only=True)
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter(assembly=True))

View File

@ -90,6 +90,13 @@ def inventree_in_debug_mode(*args, **kwargs):
return djangosettings.DEBUG
@register.simple_tag()
def inventree_demo_mode(*args, **kwargs):
""" Return True if the server is running in DEMO mode """
return djangosettings.DEMO_MODE
@register.simple_tag()
def inventree_docker_mode(*args, **kwargs):
""" Return True if the server is running as a Docker image """

View File

@ -69,6 +69,13 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
return queryset
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['user'] = getattr(self.request, 'user', None)
return ctx
def get_serializer(self, *args, **kwargs):
kwargs['part_detail'] = True
@ -79,16 +86,6 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
return self.serializer_class(*args, **kwargs)
def update(self, request, *args, **kwargs):
"""
Record the user who updated the item
"""
# TODO: Record the user!
# user = request.user
return super().update(request, *args, **kwargs)
def perform_destroy(self, instance):
"""
Instead of "deleting" the StockItem
@ -392,6 +389,13 @@ class StockList(generics.ListCreateAPIView):
queryset = StockItem.objects.all()
filterset_class = StockFilter
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['user'] = getattr(self.request, 'user', None)
return ctx
def create(self, request, *args, **kwargs):
"""
Create a new StockItem object via the API.

View File

@ -265,15 +265,15 @@ class StockItem(MPTTModel):
user = kwargs.pop('user', None)
if user is None:
user = getattr(self, '_user', None)
# If 'add_note = False' specified, then no tracking note will be added for item creation
add_note = kwargs.pop('add_note', True)
notes = kwargs.pop('notes', '')
if not self.pk:
# StockItem has not yet been saved
add_note = add_note and True
else:
if self.pk:
# StockItem has already been saved
# Check if "interesting" fields have been changed
@ -301,11 +301,10 @@ class StockItem(MPTTModel):
except (ValueError, StockItem.DoesNotExist):
pass
add_note = False
super(StockItem, self).save(*args, **kwargs)
if add_note:
# If user information is provided, and no existing note exists, create one!
if user and self.tracking_info.count() == 0:
tracking_info = {
'status': self.status,

View File

@ -32,6 +32,7 @@ from company.serializers import SupplierPartSerializer
import InvenTree.helpers
import InvenTree.serializers
from InvenTree.serializers import InvenTreeDecimalField
from part.serializers import PartBriefSerializer
@ -55,7 +56,8 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
location_name = serializers.CharField(source='location', read_only=True)
part_name = serializers.CharField(source='part.full_name', read_only=True)
quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
class Meta:
model = StockItem
@ -79,6 +81,15 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
- Includes serialization for the item location
"""
def update(self, instance, validated_data):
"""
Custom update method to pass the user information through to the instance
"""
instance._user = self.context['user']
return super().update(instance, validated_data)
@staticmethod
def annotate_queryset(queryset):
"""
@ -136,7 +147,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
tracking_items = serializers.IntegerField(source='tracking_info_count', read_only=True, required=False)
# quantity = serializers.FloatField()
quantity = InvenTreeDecimalField()
allocated = serializers.FloatField(source='allocation_count', required=False)

View File

@ -12,12 +12,15 @@
{% endblock %}
{% block actions %}
{% inventree_demo_mode as demo %}
{% if not demo %}
<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>
{% endif %}
{% endblock %}
{% block content %}

View File

@ -1,5 +1,6 @@
{% extends "account/base.html" %}
{% load inventree_extras %}
{% load i18n account socialaccount crispy_forms_tags inventree_extras %}
{% block head_title %}{% trans "Sign In" %}{% endblock %}
@ -10,6 +11,7 @@
{% settings_value 'LOGIN_ENABLE_PWD_FORGOT' as enable_pwd_forgot %}
{% settings_value 'LOGIN_ENABLE_SSO' as enable_sso %}
{% mail_configured as mail_conf %}
{% inventree_demo_mode as demo %}
<h1>{% trans "Sign In" %}</h1>
@ -36,9 +38,16 @@ for a account and sign in below:{% endblocktrans %}</p>
<div class="btn-group float-right" role="group">
<button class="btn btn-success" type="submit">{% trans "Sign In" %}</button>
</div>
{% if mail_conf and enable_pwd_forgot %}
{% if mail_conf and enable_pwd_forgot and not demo %}
<a class="" href="{% url 'account_reset_password' %}"><small>{% trans "Forgot Password?" %}</small></a>
{% endif %}
{% if demo %}
<p>
<h6>
{% trans "InvenTree demo instance" %} - <a href='https://inventree.readthedocs.io/en/latest/demo/'>{% trans "Click here for login details" %}</a>
</h6>
</p>
{% endif %}
</form>
{% if enable_sso %}

View File

@ -87,24 +87,19 @@
</div>
<main class='col ps-md-2 pt-2 pe-2'>
{% if server_restart_required %}
<div class='notification-area' id='restart-required'>
{% block alerts %}
<div class='notification-area' id='alerts'>
<!-- Div for displayed alerts -->
{% if server_restart_required %}
<div id='alert-restart-server' class='alert alert-danger' role='alert'>
<span class='fas fa-server'></span>
<b>{% trans "Server Restart Required" %}</b>
<small>
<br>
{% trans "A configuration option has been changed which requires a server restart" %}.
<br>
{% trans "Contact your system administrator for further information" %}
{% trans "A configuration option has been changed which requires a server restart" %}. {% trans "Contact your system administrator for further information" %}
</small>
</div>
</div>
{% endif %}
{% block alerts %}
<div class='notification-area' id='alerts'>
<!-- Div for displayed alerts -->
{% endif %}
</div>
{% endblock %}

View File

@ -4,6 +4,7 @@
{% settings_value 'BARCODE_ENABLE' as barcodes %}
{% settings_value 'STICKY_HEADER' user=request.user as sticky %}
{% inventree_demo_mode as demo %}
<nav class="navbar {% if sticky %}fixed-top{% endif %} navbar-expand-lg navbar-light">
<div class="container-fluid">
@ -58,6 +59,9 @@
{% endif %}
</ul>
</div>
{% if demo %}
{% include "navbar_demo.html" %}
{% endif %}
{% include "search_form.html" %}
<ul class='navbar-nav flex-row'>
{% if barcodes %}
@ -78,7 +82,7 @@
</a>
<ul class='dropdown-menu dropdown-menu-end inventree-navbar-menu'>
{% if user.is_authenticated %}
{% if user.is_staff %}
{% if user.is_staff and not demo %}
<li><a class='dropdown-item' href="/admin/"><span class="fas fa-user"></span> {% trans "Admin" %}</a></li>
{% endif %}
<li><a class='dropdown-item' href="{% url 'account_logout' %}"><span class="fas fa-sign-out-alt"></span> {% trans "Logout" %}</a></li>

View File

@ -0,0 +1,12 @@
{% load i18n %}
{% include "spacer.html" %}
<div class='flex'>
<h6>
{% trans "InvenTree demo mode" %}
<a href='https://inventree.readthedocs.io/en/latest/demo/'>
<span class='fas fa-info-circle'></span>
</a>
</h6>
</div>
{% include "spacer.html" %}
{% include "spacer.html" %}