diff --git a/InvenTree/InvenTree/metadata.py b/InvenTree/InvenTree/metadata.py index 4294c943ba..46b3acfc21 100644 --- a/InvenTree/InvenTree/metadata.py +++ b/InvenTree/InvenTree/metadata.py @@ -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(): diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index d2d00a932c..ab5a27594f 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -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)) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index c5a90ac7f3..cda30b0a27 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -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}'") diff --git a/InvenTree/InvenTree/utils.py b/InvenTree/InvenTree/utils.py deleted file mode 100644 index dc28da81a0..0000000000 --- a/InvenTree/InvenTree/utils.py +++ /dev/null @@ -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 diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 29257341b2..55576fd328 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -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): diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 719d94b0a6..a6303cc561 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -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, diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index d2fcaf7009..9fe0945168 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -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,8 +550,8 @@ 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) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 47ce3f66c8..9deece4863 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -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)) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 42c66cf78e..e31fb9e398 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -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 """ diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 2ffc2e8d69..0c045f1cf5 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -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. diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 8e07074a76..70e557a901 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -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, diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 850ebcea3b..31f21605a2 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -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) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 2b2637330c..d22c89954f 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -12,12 +12,15 @@ {% endblock %} {% block actions %} +{% inventree_demo_mode as demo %} +{% if not demo %}
{% trans "Edit" %}
{% trans "Set Password" %}
+{% endif %} {% endblock %} {% block content %} diff --git a/InvenTree/templates/account/login.html b/InvenTree/templates/account/login.html index fbe48224b4..6e62560bfa 100644 --- a/InvenTree/templates/account/login.html +++ b/InvenTree/templates/account/login.html @@ -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 %}

{% trans "Sign In" %}

@@ -36,9 +38,16 @@ for a account and sign in below:{% endblocktrans %}

- {% if mail_conf and enable_pwd_forgot %} + {% if mail_conf and enable_pwd_forgot and not demo %} {% trans "Forgot Password?" %} {% endif %} + {% if demo %} +

+

+ {% trans "InvenTree demo instance" %} - {% trans "Click here for login details" %} +
+

+ {% endif %} {% if enable_sso %} diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index 6dc0d7d78a..262a749bfa 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -86,25 +86,20 @@
- - {% if server_restart_required %} -
+ + {% block alerts %} +
+ + {% if server_restart_required %} -
- {% endif %} - - {% block alerts %} -
- + {% endif %}
{% endblock %} diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index eeb049d320..c339d7b4e1 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -4,6 +4,7 @@ {% settings_value 'BARCODE_ENABLE' as barcodes %} {% settings_value 'STICKY_HEADER' user=request.user as sticky %} +{% inventree_demo_mode as demo %}