Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/inventree/InvenTree into …
Browse files Browse the repository at this point in the history
…settings-ui
  • Loading branch information
matmair committed Sep 8, 2023
2 parents 2851552 + 1e55fc8 commit 99e12fc
Show file tree
Hide file tree
Showing 207 changed files with 78,580 additions and 26,765 deletions.
1 change: 1 addition & 0 deletions .github/workflows/qc_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ jobs:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
npm: true
- name: Download Python Code For `${{ env.wrapper_name }}`
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }}
./${{ env.wrapper_name }}
Expand Down
5 changes: 3 additions & 2 deletions .pkgr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ before:
- contrib/packager.io/before.sh
dependencies:
- curl
- python3
- python3-venv
- python3.9
- python3.9-venv
- python3.9-dev
- python3-pip
- python3-cffi
- python3-brotli
Expand Down
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
exclude: |
(?x)^(
InvenTree/InvenTree/static/.*|
InvenTree/locale/.*
InvenTree/locale/.*|
src/frontend/src/locales/.*
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ ENTRYPOINT ["/bin/sh", "./init.sh"]
FROM inventree_base as frontend

RUN apk add --no-cache --update nodejs npm && npm install -g yarn
RUN yarn config set network-timeout 600000 -g
COPY InvenTree ${INVENTREE_HOME}/InvenTree
COPY src ${INVENTREE_HOME}/src
COPY tasks.py ${INVENTREE_HOME}/tasks.py
Expand Down Expand Up @@ -131,7 +132,9 @@ CMD gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ./Inve
FROM inventree_base as dev

# Install nodejs / npm / yarn

RUN apk add --no-cache --update nodejs npm && npm install -g yarn
RUN yarn config set network-timeout 600000 -g

# The development image requires the source code to be mounted to /home/inventree/
# So from here, we don't actually "do" anything, apart from some file management
Expand Down
29 changes: 29 additions & 0 deletions InvenTree/InvenTree/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Admin classes"""

from django.contrib import admin
from django.http.request import HttpRequest

from djmoney.contrib.exchange.admin import RateAdmin
from djmoney.contrib.exchange.models import Rate
from import_export.resources import ModelResource


Expand Down Expand Up @@ -31,3 +36,27 @@ def export_resource(self, obj):
row[idx] = val

return row

def get_fields(self, **kwargs):
"""Return fields, with some common exclusions"""

fields = super().get_fields(**kwargs)

fields_to_exclude = [
'metadata',
'lft', 'rght', 'tree_id', 'level',
]

return [f for f in fields if f.column_name not in fields_to_exclude]


class CustomRateAdmin(RateAdmin):
"""Admin interface for the Rate class"""

def has_add_permission(self, request: HttpRequest) -> bool:
"""Disable the 'add' permission for Rate objects"""
return False


admin.site.unregister(Rate)
admin.site.register(Rate, CustomRateAdmin)
42 changes: 36 additions & 6 deletions InvenTree/InvenTree/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,39 @@ class NotFoundView(AjaxView):

permission_classes = [permissions.AllowAny]

def not_found(self, request):
"""Return a 404 error"""
return JsonResponse(
{
'detail': _('API endpoint not found'),
'url': request.build_absolute_uri(),
},
status=404
)

def options(self, request, *args, **kwargs):
"""Return 404"""
return self.not_found(request)

def get(self, request, *args, **kwargs):
"""Process an `not found` event on the API."""
data = {
'details': _('API endpoint not found'),
'url': request.build_absolute_uri(),
}
"""Return 404"""
return self.not_found(request)

return JsonResponse(data, status=404)
def post(self, request, *args, **kwargs):
"""Return 404"""
return self.not_found(request)

def patch(self, request, *args, **kwargs):
"""Return 404"""
return self.not_found(request)

def put(self, request, *args, **kwargs):
"""Return 404"""
return self.not_found(request)

def delete(self, request, *args, **kwargs):
"""Return 404"""
return self.not_found(request)


class BulkDeleteMixin:
Expand Down Expand Up @@ -262,6 +287,11 @@ def post(self, request, *args, **kwargs):
'offset': 0,
}

if 'search' not in data:
raise ValidationError({
'search': 'Search term must be provided',
})

for key, cls in self.get_result_types().items():
# Only return results which are specifically requested
if key in data:
Expand Down
8 changes: 7 additions & 1 deletion InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@


# InvenTree API version
INVENTREE_API_VERSION = 130
INVENTREE_API_VERSION = 132

"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v132 -> 2023-09-07 : https://github.com/inventree/InvenTree/pull/5515
- Add 'issued_by' filter to BuildOrder API list endpoint
v131 -> 2023-08-09 : https://github.com/inventree/InvenTree/pull/5415
- Annotate 'available_variant_stock' to the SalesOrderLine serializer
v130 -> 2023-07-14 : https://github.com/inventree/InvenTree/pull/5251
- Refactor label printing interface
Expand Down
11 changes: 9 additions & 2 deletions InvenTree/InvenTree/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
from django.contrib.auth import get_user_model
from django.core.exceptions import AppRegistryNotReady
from django.db import transaction
from django.db.utils import IntegrityError
from django.db.utils import IntegrityError, OperationalError

import InvenTree.conversion
import InvenTree.tasks
from InvenTree.config import get_setting
from InvenTree.ready import canAppAccessDatabase, isInTestMode
from InvenTree.ready import (canAppAccessDatabase, isInMainThread,
isInTestMode, isPluginRegistryLoaded)

logger = logging.getLogger("inventree")

Expand All @@ -34,6 +35,10 @@ def ready(self):
- Collecting notification methods
- Adding users set in the current environment
"""
# skip loading if plugin registry is not loaded or we run in a background thread
if not isPluginRegistryLoaded() or not isInMainThread():
return

if canAppAccessDatabase() or settings.TESTING_ENV:
InvenTree.tasks.check_for_migrations(worker=False)

Expand Down Expand Up @@ -154,6 +159,8 @@ def update_exchange_rates(self): # pragma: no cover
if update:
try:
update_exchange_rates()
except OperationalError:
logger.warning("Could not update exchange rates - database not ready")
except Exception as e:
logger.error(f"Error updating exchange rates: {e} ({type(e)})")

Expand Down
2 changes: 1 addition & 1 deletion InvenTree/InvenTree/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def to_dict(value):
if value is None:
return {}

if type(value) == dict:
if isinstance(value, dict):
return value

try:
Expand Down
61 changes: 54 additions & 7 deletions InvenTree/InvenTree/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,13 @@ def reload_unit_registry():
return reg


def convert_physical_value(value: str, unit: str = None):
def convert_physical_value(value: str, unit: str = None, strip_units=True):
"""Validate that the provided value is a valid physical quantity.
Arguments:
value: Value to validate (str)
unit: Optional unit to convert to, and validate against
strip_units: If True, strip units from the returned value, and return only the dimension
Raises:
ValidationError: If the value is invalid or cannot be converted to the specified unit
Expand All @@ -100,7 +101,7 @@ def convert_physical_value(value: str, unit: str = None):

if unit:

if val.units == ureg.dimensionless:
if is_dimensionless(val):
# If the provided value is dimensionless, assume that the unit is correct
val = ureg.Quantity(value, unit)
else:
Expand All @@ -109,19 +110,65 @@ def convert_physical_value(value: str, unit: str = None):

# At this point we *should* have a valid pint value
# To double check, look at the maginitude
float(val.magnitude)
float(ureg.Quantity(val.magnitude).magnitude)
except (TypeError, ValueError, AttributeError):
error = _('Provided value is not a valid number')
except (pint.errors.UndefinedUnitError, pint.errors.DefinitionSyntaxError):
error = _('Provided value has an invalid unit')
if unit:
error += f' ({unit})'

except pint.errors.DimensionalityError:
error = _('Provided value could not be converted to the specified unit')

if error:
if unit:
error += f' ({unit})'

except Exception as e:
error = _('Error') + ': ' + str(e)

if error:
raise ValidationError(error)

# Return the converted value
return val
# Calculate the "magnitude" of the value, as a float
# If the value is specified strangely (e.g. as a fraction or a dozen), this can cause isuses
# So, we ensure that it is converted to a floating point value
# If we wish to return a "raw" value, some trickery is required
if unit:
magnitude = ureg.Quantity(val.to(unit)).magnitude
else:
magnitude = ureg.Quantity(val.to_base_units()).magnitude

magnitude = float(ureg.Quantity(magnitude).to_base_units().magnitude)

if strip_units:
return magnitude
elif unit or val.units:
return ureg.Quantity(magnitude, unit or val.units)
else:
return ureg.Quantity(magnitude)


def is_dimensionless(value):
"""Determine if the provided value is 'dimensionless'
A dimensionless value might look like:
0.1
1/2 dozen
three thousand
1.2 dozen
(etc)
"""
ureg = get_unit_registry()

# Ensure the provided value is in the right format
value = ureg.Quantity(value)

if value.units == ureg.dimensionless:
return True

if value.to_base_units().units == ureg.dimensionless:
return True

# At this point, the value is not dimensionless
return False
16 changes: 12 additions & 4 deletions InvenTree/InvenTree/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def is_email_configured():
NOTE: This does not check if the configuration is valid!
"""
configured = True
testing = settings.TESTING

if InvenTree.ready.isInTestMode():
return False
Expand All @@ -28,24 +29,31 @@ def is_email_configured():
configured = False

# Display warning unless in test mode
if not settings.TESTING: # pragma: no cover
if not testing: # pragma: no cover
logger.debug("EMAIL_HOST is not configured")

# Display warning unless in test mode
if not settings.EMAIL_HOST_USER and not settings.TESTING: # pragma: no cover
if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover
logger.debug("EMAIL_HOST_USER is not configured")

# Display warning unless in test mode
if not settings.EMAIL_HOST_PASSWORD and not settings.TESTING: # pragma: no cover
if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover
logger.debug("EMAIL_HOST_PASSWORD is not configured")

# Email sender must be configured
if not settings.DEFAULT_FROM_EMAIL:
configured = False

if not testing: # pragma: no cover
logger.warning("DEFAULT_FROM_EMAIL is not configured")

return configured


def send_email(subject, body, recipients, from_email=None, html_message=None):
"""Send an email with the specified subject and body, to the specified recipients list."""

if type(recipients) == str:
if isinstance(recipients, str):
recipients = [recipients]

import InvenTree.ready
Expand Down
29 changes: 28 additions & 1 deletion InvenTree/InvenTree/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.utils.translation import gettext_lazy as _

from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.forms import SignupForm, set_form_field_order
from allauth.account.forms import LoginForm, SignupForm, set_form_field_order
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth_2fa.adapter import OTPAdapter
Expand Down Expand Up @@ -166,6 +166,24 @@ class Meta:


# override allauth
class CustomLoginForm(LoginForm):
"""Custom login form to override default allauth behaviour"""

def login(self, request, redirect_url=None):
"""Perform login action.
First check that:
- A valid user has been supplied
"""

if not self.user:
# No user supplied - redirect to the login page
return HttpResponseRedirect(reverse('account_login'))

# Now perform default login action
return super().login(request, redirect_url)


class CustomSignupForm(SignupForm):
"""Override to use dynamic settings."""

Expand Down Expand Up @@ -292,6 +310,15 @@ def send_mail(self, template_prefix, email, context):

return False

def get_email_confirmation_url(self, request, emailconfirmation):
"""Construct the email confirmation url"""

from InvenTree.helpers_model import construct_absolute_url

url = super().get_email_confirmation_url(request, emailconfirmation)
url = construct_absolute_url(url)
return url


class CustomSocialAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultSocialAccountAdapter):
"""Override of adapter to use dynamic settings."""
Expand Down
Loading

0 comments on commit 99e12fc

Please sign in to comment.