2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

Merge remote-tracking branch 'inventree/master' into shipment-assign-fix

This commit is contained in:
Oliver Walters 2022-05-17 18:48:57 +10:00
commit f4b470c396
105 changed files with 474 additions and 357 deletions

View File

@ -2,9 +2,6 @@
Main JSON interface views Main JSON interface views
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.conf import settings from django.conf import settings
from django.http import JsonResponse from django.http import JsonResponse

View File

@ -1,7 +1,5 @@
""" Custom fields used in InvenTree """ """ Custom fields used in InvenTree """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys import sys
from .validators import allowable_url_schemes from .validators import allowable_url_schemes

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from rest_framework.filters import OrderingFilter from rest_framework.filters import OrderingFilter

View File

@ -2,8 +2,6 @@
Helper forms which subclass Django forms to provide additional functionality Helper forms which subclass Django forms to provide additional functionality
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from urllib.parse import urlencode from urllib.parse import urlencode
import logging import logging

View File

@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging import logging
from rest_framework import serializers from rest_framework import serializers

View File

@ -2,8 +2,6 @@
Generic models which provide extra functionality over base Django model types. Generic models which provide extra functionality over base Django model types.
""" """
from __future__ import unicode_literals
import re import re
import os import os
import logging import logging

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from rest_framework import permissions from rest_framework import permissions
import users.models import users.models

View File

@ -2,9 +2,6 @@
Serializers used in various InvenTree apps Serializers used in various InvenTree apps
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import tablib import tablib

View File

@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re import re
import json import json
import warnings
import requests import requests
import logging import logging
@ -11,6 +9,8 @@ from django.utils import timezone
from django.core.exceptions import AppRegistryNotReady from django.core.exceptions import AppRegistryNotReady
from django.db.utils import OperationalError, ProgrammingError from django.db.utils import OperationalError, ProgrammingError
from django.core import mail as django_mail
from django.conf import settings
logger = logging.getLogger("inventree") logger = logging.getLogger("inventree")
@ -52,6 +52,15 @@ def schedule_task(taskname, **kwargs):
pass pass
def raise_warning(msg):
"""Log and raise a warning"""
logger.warning(msg)
# If testing is running raise a warning that can be asserted
if settings.TESTING:
warnings.warn(msg)
def offload_task(taskname, *args, force_sync=False, **kwargs): def offload_task(taskname, *args, force_sync=False, **kwargs):
""" """
Create an AsyncTask if workers are running. Create an AsyncTask if workers are running.
@ -67,28 +76,38 @@ def offload_task(taskname, *args, force_sync=False, **kwargs):
import importlib import importlib
from InvenTree.status import is_worker_running from InvenTree.status import is_worker_running
except AppRegistryNotReady: # pragma: no cover
logger.warning(f"Could not offload task '{taskname}' - app registry not ready")
return
except (OperationalError, ProgrammingError): # pragma: no cover
raise_warning(f"Could not offload task '{taskname}' - database not ready")
if is_worker_running() and not force_sync: # pragma: no cover if is_worker_running() and not force_sync: # pragma: no cover
# Running as asynchronous task # Running as asynchronous task
try: try:
task = AsyncTask(taskname, *args, **kwargs) task = AsyncTask(taskname, *args, **kwargs)
task.run() task.run()
except ImportError: except ImportError:
logger.warning(f"WARNING: '{taskname}' not started - Function not found") raise_warning(f"WARNING: '{taskname}' not started - Function not found")
else:
if callable(taskname):
# function was passed - use that
_func = taskname
else: else:
# Split path # Split path
try: try:
app, mod, func = taskname.split('.') app, mod, func = taskname.split('.')
app_mod = app + '.' + mod app_mod = app + '.' + mod
except ValueError: except ValueError:
logger.warning(f"WARNING: '{taskname}' not started - Malformed function path") raise_warning(f"WARNING: '{taskname}' not started - Malformed function path")
return return
# Import module from app # Import module from app
try: try:
_mod = importlib.import_module(app_mod) _mod = importlib.import_module(app_mod)
except ModuleNotFoundError: except ModuleNotFoundError:
logger.warning(f"WARNING: '{taskname}' not started - No module named '{app_mod}'") raise_warning(f"WARNING: '{taskname}' not started - No module named '{app_mod}'")
return return
# Retrieve function # Retrieve function
@ -102,17 +121,11 @@ def offload_task(taskname, *args, force_sync=False, **kwargs):
if not _func: if not _func:
_func = eval(func) # pragma: no cover _func = eval(func) # pragma: no cover
except NameError: except NameError:
logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'") raise_warning(f"WARNING: '{taskname}' not started - No function named '{func}'")
return return
# Workers are not running: run it as synchronous task # Workers are not running: run it as synchronous task
_func(*args, **kwargs) _func(*args, **kwargs)
except AppRegistryNotReady: # pragma: no cover
logger.warning(f"Could not offload task '{taskname}' - app registry not ready")
return
except (OperationalError, ProgrammingError): # pragma: no cover
logger.warning(f"Could not offload task '{taskname}' - database not ready")
def heartbeat(): def heartbeat():
@ -126,8 +139,8 @@ def heartbeat():
try: try:
from django_q.models import Success from django_q.models import Success
logger.info("Could not perform heartbeat task - App registry not ready")
except AppRegistryNotReady: # pragma: no cover except AppRegistryNotReady: # pragma: no cover
logger.info("Could not perform heartbeat task - App registry not ready")
return return
threshold = timezone.now() - timedelta(minutes=30) threshold = timezone.now() - timedelta(minutes=30)
@ -205,25 +218,25 @@ def check_for_updates():
response = requests.get('https://api.github.com/repos/inventree/inventree/releases/latest') response = requests.get('https://api.github.com/repos/inventree/inventree/releases/latest')
if response.status_code != 200: if response.status_code != 200:
raise ValueError(f'Unexpected status code from GitHub API: {response.status_code}') raise ValueError(f'Unexpected status code from GitHub API: {response.status_code}') # pragma: no cover
data = json.loads(response.text) data = json.loads(response.text)
tag = data.get('tag_name', None) tag = data.get('tag_name', None)
if not tag: if not tag:
raise ValueError("'tag_name' missing from GitHub response") raise ValueError("'tag_name' missing from GitHub response") # pragma: no cover
match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag) match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag)
if len(match.groups()) != 3: if len(match.groups()) != 3: # pragma: no cover
logger.warning(f"Version '{tag}' did not match expected pattern") logger.warning(f"Version '{tag}' did not match expected pattern")
return return
latest_version = [int(x) for x in match.groups()] latest_version = [int(x) for x in match.groups()]
if len(latest_version) != 3: if len(latest_version) != 3:
raise ValueError(f"Version '{tag}' is not correct format") raise ValueError(f"Version '{tag}' is not correct format") # pragma: no cover
logger.info(f"Latest InvenTree version: '{tag}'") logger.info(f"Latest InvenTree version: '{tag}'")
@ -288,7 +301,7 @@ def send_email(subject, body, recipients, from_email=None, html_message=None):
recipients = [recipients] recipients = [recipients]
offload_task( offload_task(
'django.core.mail.send_mail', django_mail.send_mail,
subject, subject,
body, body,
from_email, from_email,

View File

@ -2,10 +2,20 @@
Unit tests for task management Unit tests for task management
""" """
from datetime import timedelta
from django.utils import timezone
from django.test import TestCase from django.test import TestCase
from django_q.models import Schedule from django_q.models import Schedule
from error_report.models import Error
import InvenTree.tasks import InvenTree.tasks
from common.models import InvenTreeSetting
threshold = timezone.now() - timedelta(days=30)
threshold_low = threshold - timedelta(days=1)
class ScheduledTaskTests(TestCase): class ScheduledTaskTests(TestCase):
@ -41,3 +51,79 @@ class ScheduledTaskTests(TestCase):
# But the 'minutes' should have been updated # But the 'minutes' should have been updated
t = Schedule.objects.get(func=task) t = Schedule.objects.get(func=task)
self.assertEqual(t.minutes, 5) self.assertEqual(t.minutes, 5)
def get_result():
"""Demo function for test_offloading"""
return 'abc'
class InvenTreeTaskTests(TestCase):
"""Unit tests for tasks"""
def test_offloading(self):
"""Test task offloading"""
# Run with function ref
InvenTree.tasks.offload_task(get_result)
# Run with string ref
InvenTree.tasks.offload_task('InvenTree.test_tasks.get_result')
# Error runs
# Malformed taskname
with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTree' not started - Malformed function path"):
InvenTree.tasks.offload_task('InvenTree')
# Non exsistent app
with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC.test_tasks'"):
InvenTree.tasks.offload_task('InvenTreeABC.test_tasks.doesnotmatter')
# Non exsistent function
with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTree.test_tasks.doesnotexsist' not started - No function named 'doesnotexsist'"):
InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexsist')
def test_task_hearbeat(self):
"""Test the task heartbeat"""
InvenTree.tasks.offload_task(InvenTree.tasks.heartbeat)
def test_task_delete_successful_tasks(self):
"""Test the task delete_successful_tasks"""
from django_q.models import Success
Success.objects.create(name='abc', func='abc', stopped=threshold, started=threshold_low)
InvenTree.tasks.offload_task(InvenTree.tasks.delete_successful_tasks)
results = Success.objects.filter(started__lte=threshold)
self.assertEqual(len(results), 0)
def test_task_delete_old_error_logs(self):
"""Test the task delete_old_error_logs"""
# Create error
error_obj = Error.objects.create()
error_obj.when = threshold_low
error_obj.save()
# Check that it is not empty
errors = Error.objects.filter(when__lte=threshold,)
self.assertNotEqual(len(errors), 0)
# Run action
InvenTree.tasks.offload_task(InvenTree.tasks.delete_old_error_logs)
# Check that it is empty again
errors = Error.objects.filter(when__lte=threshold,)
self.assertEqual(len(errors), 0)
def test_task_check_for_updates(self):
"""Test the task check_for_updates"""
# Check that setting should be empty
self.assertEqual(InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION'), '')
# Get new version
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_updates)
# Check that setting is not empty
response = InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION')
self.assertNotEqual(response, '')
self.assertTrue(bool(response))

View File

@ -5,8 +5,6 @@ In particular these views provide base functionality for rendering Django forms
as JSON objects and passing them to modal forms (using jQuery / bootstrap). as JSON objects and passing them to modal forms (using jQuery / bootstrap).
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import json import json
@ -797,13 +795,9 @@ class CurrencyRefreshView(RedirectView):
On a POST request we will attempt to refresh the exchange rates On a POST request we will attempt to refresh the exchange rates
""" """
from InvenTree.tasks import offload_task from InvenTree.tasks import offload_task, update_exchange_rates
# Define associated task from InvenTree.tasks list of methods offload_task(update_exchange_rates, force_sync=True)
taskname = 'InvenTree.tasks.update_exchange_rates'
# Run it
offload_task(taskname, force_sync=True)
return redirect(reverse_lazy('settings')) return redirect(reverse_lazy('settings'))

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin

View File

@ -2,9 +2,6 @@
JSON API for the Build app JSON API for the Build app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.urls import include, re_path from django.urls import include, re_path
from rest_framework import filters, generics from rest_framework import filters, generics
@ -285,6 +282,13 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView):
API endpoint for deleting multiple build outputs API endpoint for deleting multiple build outputs
""" """
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['to_complete'] = False
return ctx
queryset = Build.objects.none() queryset = Build.objects.none()
serializer_class = build.serializers.BuildOutputDeleteSerializer serializer_class = build.serializers.BuildOutputDeleteSerializer

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,7 +0,0 @@
"""
Django Forms for interacting with Build objects
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

View File

@ -2,8 +2,6 @@
Build database model definitions Build database model definitions
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import decimal import decimal
import os import os
@ -1141,12 +1139,13 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs):
""" """
Callback function to be executed after a Build instance is saved Callback function to be executed after a Build instance is saved
""" """
from . import tasks as build_tasks
if created: if created:
# A new Build has just been created # A new Build has just been created
# Run checks on required parts # Run checks on required parts
InvenTree.tasks.offload_task('build.tasks.check_build_stock', instance) InvenTree.tasks.offload_task(build_tasks.check_build_stock, instance)
class BuildOrderAttachment(InvenTreeAttachment): class BuildOrderAttachment(InvenTreeAttachment):

View File

@ -2,9 +2,6 @@
JSON serializers for Build API JSON serializers for Build API
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import transaction from django.db import transaction
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -202,7 +199,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
def validate_quantity(self, quantity): def validate_quantity(self, quantity):
if quantity < 0: if quantity <= 0:
raise ValidationError(_("Quantity must be greater than zero")) raise ValidationError(_("Quantity must be greater than zero"))
part = self.get_part() part = self.get_part()
@ -212,7 +209,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
if part.trackable: if part.trackable:
raise ValidationError(_("Integer quantity required for trackable parts")) raise ValidationError(_("Integer quantity required for trackable parts"))
if part.has_trackable_parts(): if part.has_trackable_parts:
raise ValidationError(_("Integer quantity required, as the bill of materials contains trackable parts")) raise ValidationError(_("Integer quantity required, as the bill of materials contains trackable parts"))
return quantity return quantity
@ -235,7 +232,6 @@ class BuildOutputCreateSerializer(serializers.Serializer):
serial_numbers = serial_numbers.strip() serial_numbers = serial_numbers.strip()
# TODO: Field level validation necessary here?
return serial_numbers return serial_numbers
auto_allocate = serializers.BooleanField( auto_allocate = serializers.BooleanField(

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from decimal import Decimal from decimal import Decimal
import logging import logging

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.urls import reverse from django.urls import reverse
@ -305,6 +302,215 @@ class BuildTest(BuildAPITest):
self.assertEqual(bo.status, BuildStatus.CANCELLED) self.assertEqual(bo.status, BuildStatus.CANCELLED)
def test_create_delete_output(self):
"""
Test that we can create and delete build outputs via the API
"""
bo = Build.objects.get(pk=1)
n_outputs = bo.output_count
create_url = reverse('api-build-output-create', kwargs={'pk': 1})
# Attempt to create outputs with invalid data
response = self.post(
create_url,
{
'quantity': 'not a number',
},
expected_code=400
)
self.assertIn('A valid number is required', str(response.data))
for q in [-100, -10.3, 0]:
response = self.post(
create_url,
{
'quantity': q,
},
expected_code=400
)
if q == 0:
self.assertIn('Quantity must be greater than zero', str(response.data))
else:
self.assertIn('Ensure this value is greater than or equal to 0', str(response.data))
# Mark the part being built as 'trackable' (requires integer quantity)
bo.part.trackable = True
bo.part.save()
response = self.post(
create_url,
{
'quantity': 12.3,
},
expected_code=400
)
self.assertIn('Integer quantity required for trackable parts', str(response.data))
# Erroneous serial numbers
response = self.post(
create_url,
{
'quantity': 5,
'serial_numbers': '1, 2, 3, 4, 5, 6',
'batch': 'my-batch',
},
expected_code=400
)
self.assertIn('Number of unique serial numbers (6) must match quantity (5)', str(response.data))
# At this point, no new build outputs should have been created
self.assertEqual(n_outputs, bo.output_count)
# Now, create with *good* data
response = self.post(
create_url,
{
'quantity': 5,
'serial_numbers': '1, 2, 3, 4, 5',
'batch': 'my-batch',
},
expected_code=201,
)
# 5 new outputs have been created
self.assertEqual(n_outputs + 5, bo.output_count)
# Attempt to create with identical serial numbers
response = self.post(
create_url,
{
'quantity': 3,
'serial_numbers': '1-3',
},
expected_code=400,
)
self.assertIn('The following serial numbers already exist : 1,2,3', str(response.data))
# Double check no new outputs have been created
self.assertEqual(n_outputs + 5, bo.output_count)
# Now, let's delete each build output individually via the API
outputs = bo.build_outputs.all()
delete_url = reverse('api-build-output-delete', kwargs={'pk': 1})
response = self.post(
delete_url,
{
'outputs': [],
},
expected_code=400
)
self.assertIn('A list of build outputs must be provided', str(response.data))
# Mark 1 build output as complete
bo.complete_build_output(outputs[0], self.user)
self.assertEqual(n_outputs + 5, bo.output_count)
self.assertEqual(1, bo.complete_count)
# Delete all outputs at once
# Note: One has been completed, so this should fail!
response = self.post(
delete_url,
{
'outputs': [
{
'output': output.pk,
} for output in outputs
]
},
expected_code=400
)
self.assertIn('This build output has already been completed', str(response.data))
# No change to the build outputs
self.assertEqual(n_outputs + 5, bo.output_count)
self.assertEqual(1, bo.complete_count)
# Let's delete 2 build outputs
response = self.post(
delete_url,
{
'outputs': [
{
'output': output.pk,
} for output in outputs[1:3]
]
},
expected_code=201
)
# Two build outputs have been removed
self.assertEqual(n_outputs + 3, bo.output_count)
self.assertEqual(1, bo.complete_count)
# Tests for BuildOutputComplete serializer
complete_url = reverse('api-build-output-complete', kwargs={'pk': 1})
# Let's mark the remaining outputs as complete
response = self.post(
complete_url,
{
'outputs': [],
'location': 4,
},
expected_code=400,
)
self.assertIn('A list of build outputs must be provided', str(response.data))
for output in outputs[3:]:
output.refresh_from_db()
self.assertTrue(output.is_building)
response = self.post(
complete_url,
{
'outputs': [
{
'output': output.pk
} for output in outputs[3:]
],
'location': 4,
},
expected_code=201,
)
# Check that the outputs have been completed
self.assertEqual(3, bo.complete_count)
for output in outputs[3:]:
output.refresh_from_db()
self.assertEqual(output.location.pk, 4)
self.assertFalse(output.is_building)
# Try again, with an output which has already been completed
response = self.post(
complete_url,
{
'outputs': [
{
'output': outputs.last().pk,
}
]
},
expected_code=400,
)
self.assertIn('This build output has already been completed', str(response.data))
class BuildAllocationTest(BuildAPITest): class BuildAllocationTest(BuildAPITest):
""" """

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse

View File

@ -2,9 +2,6 @@
Django views for interacting with Build objects Django views for interacting with Build objects
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin

View File

@ -2,9 +2,6 @@
Provides a JSON API for common components. Provides a JSON API for common components.
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json import json
from django.http.response import HttpResponse from django.http.response import HttpResponse

View File

@ -2,9 +2,6 @@
Django forms for interacting with common objects Django forms for interacting with common objects
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _

View File

@ -3,9 +3,6 @@ Common database model definitions.
These models are 'generic' and do not fit a particular business logic object. These models are 'generic' and do not fit a particular business logic object.
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import decimal import decimal
import math import math

View File

@ -2,9 +2,6 @@
JSON serializers for common components JSON serializers for common components
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.helpers import get_objectreference from InvenTree.helpers import get_objectreference

View File

@ -2,9 +2,6 @@
User-configurable settings for the common app User-configurable settings for the common app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from moneyed import CURRENCIES from moneyed import CURRENCIES
from django.conf import settings from django.conf import settings

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from common.notifications import NotificationMethod, SingleNotificationMethod, BulkNotificationMethod, storage from common.notifications import NotificationMethod, SingleNotificationMethod, BulkNotificationMethod, storage
from plugin.models import NotificationUserSetting from plugin.models import NotificationUserSetting
from part.test_part import BaseNotificationIntegrationTest from part.test_part import BaseNotificationIntegrationTest

View File

@ -2,6 +2,7 @@
from django.test import TestCase from django.test import TestCase
from common.models import NotificationEntry from common.models import NotificationEntry
from . import tasks as common_tasks
from InvenTree.tasks import offload_task from InvenTree.tasks import offload_task
@ -14,4 +15,4 @@ class TaskTest(TestCase):
# check empty run # check empty run
self.assertEqual(NotificationEntry.objects.all().count(), 0) self.assertEqual(NotificationEntry.objects.all().count(), 0)
offload_task('common.tasks.delete_old_notifications',) offload_task(common_tasks.delete_old_notifications,)

View File

@ -1,6 +1,3 @@
""" """
Unit tests for the views associated with the 'common' app Unit tests for the views associated with the 'common' app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from http import HTTPStatus from http import HTTPStatus
import json import json
from datetime import timedelta from datetime import timedelta

View File

@ -2,9 +2,6 @@
Django views for interacting with common models Django views for interacting with common models
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin

View File

@ -2,9 +2,6 @@
Provides a JSON API for the Company app Provides a JSON API for the Company app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as rest_filters from django_filters import rest_framework as rest_filters

View File

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -2,9 +2,6 @@
Django Forms for interacting with Company app Django Forms for interacting with Company app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from InvenTree.forms import HelperForm from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField from InvenTree.fields import RoundingDecimalFormField

View File

@ -2,9 +2,6 @@
Company database model definitions Company database model definitions
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -1,8 +1,5 @@
""" Unit tests for Company views (see views.py) """ """ Unit tests for Company views (see views.py) """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError

View File

@ -2,10 +2,6 @@
Django views for interacting with Company app Django views for interacting with Company app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import StockItemLabel, StockLocationLabel, PartLabel from .models import StockItemLabel, StockLocationLabel, PartLabel

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
@ -24,6 +21,7 @@ from plugin.registry import registry
from stock.models import StockItem, StockLocation from stock.models import StockItem, StockLocation
from part.models import Part from part.models import Part
from plugin.base.label import label as plugin_label
from .models import StockItemLabel, StockLocationLabel, PartLabel from .models import StockItemLabel, StockLocationLabel, PartLabel
from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer, PartLabelSerializer from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer, PartLabelSerializer
@ -156,7 +154,7 @@ class LabelPrintMixin:
# Offload a background task to print the provided label # Offload a background task to print the provided label
offload_task( offload_task(
'plugin.base.label.label.print_label', plugin_label.print_label,
plugin.plugin_slug(), plugin.plugin_slug(),
image, image,
label_instance=label_instance, label_instance=label_instance,

View File

@ -2,9 +2,6 @@
Label printing models Label printing models
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys import sys
import os import os
import logging import logging

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField from InvenTree.serializers import InvenTreeAttachmentSerializerField

View File

@ -1,8 +1,5 @@
# Tests for labels # Tests for labels
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.urls import reverse from django.urls import reverse
from InvenTree.api_tester import InvenTreeAPITestCase from InvenTree.api_tester import InvenTreeAPITestCase

View File

@ -1,8 +1,5 @@
# Tests for labels # Tests for labels
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
from django.conf import settings from django.conf import settings

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin

View File

@ -2,9 +2,6 @@
JSON API for the Order app JSON API for the Order app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.urls import include, path, re_path from django.urls import include, path, re_path
from django.db.models import Q, F from django.db.models import Q, F

View File

@ -2,9 +2,6 @@
Django Forms for interacting with Order objects Django Forms for interacting with Order objects
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -2,9 +2,6 @@
JSON serializers for the Order API JSON serializers for the Order API
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from decimal import Decimal from decimal import Decimal
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -1,8 +1,5 @@
""" Unit tests for Order views (see views.py) """ """ Unit tests for Order views (see views.py) """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model

View File

@ -2,9 +2,6 @@
Django views for interacting with Order app Django views for interacting with Order app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.http.response import JsonResponse from django.http.response import JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin

View File

@ -2,9 +2,6 @@
Provides a JSON API for the Part app Provides a JSON API for the Part app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime import datetime
from django.urls import include, path, re_path from django.urls import include, path, re_path

View File

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import logging import logging
from django.db.utils import OperationalError, ProgrammingError from django.db.utils import OperationalError, ProgrammingError

View File

@ -2,9 +2,6 @@
Django Forms for interacting with Part objects Django Forms for interacting with Part objects
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -2,8 +2,6 @@
Part database model definitions Part database model definitions
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import decimal import decimal
import os import os
@ -61,7 +59,6 @@ from order import models as OrderModels
from company.models import SupplierPart from company.models import SupplierPart
import part.settings as part_settings import part.settings as part_settings
from stock import models as StockModels from stock import models as StockModels
from plugin.models import MetadataMixin from plugin.models import MetadataMixin
@ -2293,12 +2290,13 @@ def after_save_part(sender, instance: Part, created, **kwargs):
""" """
Function to be executed after a Part is saved Function to be executed after a Part is saved
""" """
from part import tasks as part_tasks
if not created and not InvenTree.ready.isImportingData(): if not created and not InvenTree.ready.isImportingData():
# Check part stock only if we are *updating* the part (not creating it) # Check part stock only if we are *updating* the part (not creating it)
# Run this check in the background # Run this check in the background
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance) InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance)
class PartAttachment(InvenTreeAttachment): class PartAttachment(InvenTreeAttachment):

View File

@ -2,9 +2,6 @@
User-configurable settings for the Part app User-configurable settings for the Part app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from common.models import InvenTreeSetting from common.models import InvenTreeSetting

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging import logging
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -49,6 +46,6 @@ def notify_low_stock_if_required(part: part.models.Part):
for p in parts: for p in parts:
if p.is_part_low_on_stock(): if p.is_part_low_on_stock():
InvenTree.tasks.offload_task( InvenTree.tasks.offload_task(
'part.tasks.notify_low_stock', notify_low_stock,
p p
) )

View File

@ -589,32 +589,15 @@
// Get a list of the selected BOM items // Get a list of the selected BOM items
var rows = $("#bom-table").bootstrapTable('getSelections'); var rows = $("#bom-table").bootstrapTable('getSelections');
// TODO - In the future, display (in the dialog) which items are going to be deleted if (rows.length == 0) {
rows = $('#bom-table').bootstrapTable('getData');
}
showQuestionDialog( deleteBomItems(rows, {
'{% trans "Delete selected BOM items?" %}', success: function() {
'{% trans "All selected BOM items will be deleted" %}', $('#bom-table').bootstrapTable('refresh');
{
accept: function() {
// Keep track of each DELETE request
var requests = [];
rows.forEach(function(row) {
requests.push(
inventreeDelete(
`/api/bom/${row.pk}/`,
)
);
});
// Wait for *all* the requests to complete
$.when.apply($, requests).done(function() {
location.reload();
});
}
} }
); });
}); });
$('#bom-upload').click(function() { $('#bom-upload').click(function() {

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import PIL import PIL
from django.urls import reverse from django.urls import reverse

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import transaction from django.db import transaction
from django.test import TestCase from django.test import TestCase

View File

@ -1,8 +1,5 @@
# Tests for Part Parameters # Tests for Part Parameters
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
import django.core.exceptions as django_exceptions import django.core.exceptions as django_exceptions

View File

@ -1,8 +1,5 @@
# Tests for the Part model # Tests for the Part model
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from allauth.account.models import EmailAddress from allauth.account.models import EmailAddress
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model

View File

@ -2,9 +2,6 @@
Django views for interacting with Part app Django views for interacting with Part app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import transaction from django.db import transaction

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin

View File

@ -2,9 +2,6 @@
JSON API for the plugin app JSON API for the plugin app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.urls import include, re_path from django.urls import include, re_path

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging import logging

View File

@ -2,9 +2,6 @@
Functions for triggering and responding to server side events Functions for triggering and responding to server side events
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging import logging
from django.conf import settings from django.conf import settings
@ -40,7 +37,7 @@ def trigger_event(event, *args, **kwargs):
logger.debug(f"Event triggered: '{event}'") logger.debug(f"Event triggered: '{event}'")
offload_task( offload_task(
'plugin.events.register_event', register_event,
event, event,
*args, *args,
**kwargs **kwargs
@ -75,7 +72,7 @@ def register_event(event, *args, **kwargs):
# Offload a separate task for each plugin # Offload a separate task for each plugin
offload_task( offload_task(
'plugin.events.process_event', process_event,
slug, slug,
event, event,
*args, *args,

View File

@ -1,8 +1,5 @@
"""API for location plugins""" """API for location plugins"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from rest_framework import permissions from rest_framework import permissions
from rest_framework.exceptions import ParseError, NotFound from rest_framework.exceptions import ParseError, NotFound
from rest_framework.response import Response from rest_framework.response import Response
@ -56,7 +53,7 @@ class LocatePluginView(APIView):
try: try:
StockItem.objects.get(pk=item_pk) StockItem.objects.get(pk=item_pk)
offload_task('plugin.registry.call_function', plugin, 'locate_stock_item', item_pk) offload_task(registry.call_function, plugin, 'locate_stock_item', item_pk)
data['item'] = item_pk data['item'] = item_pk
@ -69,7 +66,7 @@ class LocatePluginView(APIView):
try: try:
StockLocation.objects.get(pk=location_pk) StockLocation.objects.get(pk=location_pk)
offload_task('plugin.registry.call_function', plugin, 'locate_stock_location', location_pk) offload_task(registry.call_function, plugin, 'locate_stock_location', location_pk)
data['location'] = location_pk data['location'] = location_pk

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from plugin.models import NotificationUserSetting from plugin.models import NotificationUserSetting
from part.test_part import BaseNotificationIntegrationTest from part.test_part import BaseNotificationIntegrationTest
from plugin.builtin.integration.core_notifications import CoreNotificationsPlugin from plugin.builtin.integration.core_notifications import CoreNotificationsPlugin

View File

@ -2,8 +2,10 @@
Import helper for events Import helper for events
""" """
from plugin.base.event.events import trigger_event from plugin.base.event.events import process_event, register_event, trigger_event
__all__ = [ __all__ = [
'process_event',
'register_event',
'trigger_event', 'trigger_event',
] ]

View File

@ -2,8 +2,6 @@
Plugin model definitions Plugin model definitions
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import warnings import warnings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -2,9 +2,6 @@
JSON serializers for plugin app JSON serializers for plugin app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import subprocess import subprocess

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.urls import reverse from django.urls import reverse

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings from django.conf import settings

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import ReportSnippet, ReportAsset from .models import ReportSnippet, ReportAsset

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.urls import include, path, re_path from django.urls import include, path, re_path

View File

@ -2,9 +2,6 @@
Report template model definitions Report template model definitions
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import sys import sys
import logging import logging

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField from InvenTree.serializers import InvenTreeAttachmentSerializerField

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import shutil import shutil

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin

View File

@ -2,9 +2,6 @@
JSON API for the Stock app JSON API for the Stock app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timedelta from datetime import datetime, timedelta

View File

@ -1,4 +1,3 @@
from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -2,9 +2,6 @@
Django Forms for interacting with Stock app Django Forms for interacting with Stock app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from InvenTree.forms import HelperForm from InvenTree.forms import HelperForm
from .models import StockItem, StockItemTracking from .models import StockItem, StockItemTracking

View File

@ -2,10 +2,6 @@
Stock database model definitions Stock database model definitions
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
from jinja2 import Template from jinja2 import Template
@ -2024,10 +2020,11 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs):
""" """
Function to be executed after a StockItem object is deleted Function to be executed after a StockItem object is deleted
""" """
from part import tasks as part_tasks
if not InvenTree.ready.isImportingData(): if not InvenTree.ready.isImportingData():
# Run this check in the background # Run this check in the background
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part) InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance.part)
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log') @receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
@ -2035,10 +2032,11 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs):
""" """
Hook function to be executed after StockItem object is saved/updated Hook function to be executed after StockItem object is saved/updated
""" """
from part import tasks as part_tasks
if not InvenTree.ready.isImportingData(): if not InvenTree.ready.isImportingData():
# Run this check in the background # Run this check in the background
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part) InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance.part)
class StockItemAttachment(InvenTreeAttachment): class StockItemAttachment(InvenTreeAttachment):

View File

@ -2,9 +2,6 @@
JSON serializers for Stock app JSON serializers for Stock app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from decimal import Decimal from decimal import Decimal
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.db import transaction from django.db import transaction

View File

@ -2,9 +2,6 @@
Unit testing for the Stock API Unit testing for the Stock API
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
import io import io
import tablib import tablib

View File

@ -2,9 +2,6 @@
Django views for interacting with Stock app Django views for interacting with Stock app
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime from datetime import datetime
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView

View File

@ -16,6 +16,7 @@
/* exported /* exported
constructBomUploadTable, constructBomUploadTable,
deleteBomItems,
downloadBomTemplate, downloadBomTemplate,
exportBom, exportBom,
newPartFromBomWizard, newPartFromBomWizard,
@ -647,7 +648,88 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) {
reloadParentTable(); reloadParentTable();
} }
}); });
}
function deleteBomItems(items, options={}) {
/* Delete the selected BOM items from the database
*/
function renderItem(item, opts={}) {
var sub_part = item.sub_part_detail;
var thumb = thumbnailImage(sub_part.thumbnail || sub_part.image);
var html = `
<tr>
<td>${thumb} ${sub_part.full_name}</td>
<td>${item.reference}</td>
<td>${item.quantity}
</tr>
`;
return html;
}
var rows = '';
items.forEach(function(item) {
rows += renderItem(item);
});
var html = `
<div class='alert alert-block alert-danger'>
{% trans "All selected BOM items will be deleted" %}
</div>
<table class='table table-striped table-condensed'>
<tr>
<th>{% trans "Part" %}</th>
<th>{% trans "Reference" %}</th>
<th>{% trans "Quantity" %}</th>
</tr>
${rows}
</table>
`;
constructFormBody({}, {
title: '{% trans "Delete selected BOM items?" %}',
fields: {},
preFormContent: html,
submitText: '{% trans "Delete" %}',
submitClass: 'danger',
confirm: true,
onSubmit: function(fields, opts) {
// Individually send DELETE requests for each BOM item
// We do *not* send these all at once, to prevent overloading the server
// Show the progress spinner
$(opts.modal).find('#modal-progress-spinner').show();
function deleteNextBomItem() {
if (items.length > 0) {
var item = items.shift();
inventreeDelete(`/api/bom/${item.pk}/`,
{
complete: deleteNextBomItem,
}
);
} else {
// Destroy this modal once all items are deleted
$(opts.modal).modal('hide');
if (options.success) {
options.success();
}
}
}
deleteNextBomItem();
},
});
} }
@ -1146,16 +1228,10 @@ function loadBomTable(table, options={}) {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
var html = ` var item = table.bootstrapTable('getRowByUniqueId', pk);
<div class='alert alert-block alert-danger'>
{% trans "Are you sure you want to delete this BOM item?" %}
</div>`;
constructForm(`/api/bom/${pk}/`, { deleteBomItems([item], {
method: 'DELETE', success: function() {
title: '{% trans "Delete BOM Item" %}',
preFormContent: html,
onSuccess: function() {
reloadBomTable(table); reloadBomTable(table);
} }
}); });

View File

@ -288,6 +288,7 @@ function constructDeleteForm(fields, options) {
* - method: The HTTP method e.g. 'PUT', 'POST', 'DELETE' (default='PATCH') * - method: The HTTP method e.g. 'PUT', 'POST', 'DELETE' (default='PATCH')
* - title: The form title * - title: The form title
* - submitText: Text for the "submit" button * - submitText: Text for the "submit" button
* - submitClass: CSS class for the "submit" button (default = ')
* - closeText: Text for the "close" button * - closeText: Text for the "close" button
* - fields: list of fields to display, with the following options * - fields: list of fields to display, with the following options
* - filters: API query filters * - filters: API query filters

View File

@ -42,6 +42,8 @@ function createNewModal(options={}) {
} }
}); });
var submitClass = options.submitClass || 'primary';
var html = ` var html = `
<div class='modal fade modal-fixed-footer modal-primary inventree-modal' role='dialog' id='modal-form-${id}' tabindex='-1'> <div class='modal fade modal-fixed-footer modal-primary inventree-modal' role='dialog' id='modal-form-${id}' tabindex='-1'>
<div class='modal-dialog'> <div class='modal-dialog'>
@ -74,7 +76,7 @@ function createNewModal(options={}) {
<span class='flex-item' style='flex-grow: 1;'></span> <span class='flex-item' style='flex-grow: 1;'></span>
<h4><span id='modal-progress-spinner' class='fas fa-circle-notch fa-spin' style='display: none;'></span></h4> <h4><span id='modal-progress-spinner' class='fas fa-circle-notch fa-spin' style='display: none;'></span></h4>
<button type='button' class='btn btn-secondary' id='modal-form-close' data-bs-dismiss='modal'>{% trans "Cancel" %}</button> <button type='button' class='btn btn-secondary' id='modal-form-close' data-bs-dismiss='modal'>{% trans "Cancel" %}</button>
<button type='button' class='btn btn-primary' id='modal-form-submit'>{% trans "Submit" %}</button> <button type='button' class='btn btn-${submitClass}' id='modal-form-submit'>{% trans "Submit" %}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.utils import OperationalError, ProgrammingError from django.db.utils import OperationalError, ProgrammingError
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.apps import apps from django.apps import apps
from django.urls import reverse from django.urls import reverse

View File

@ -2,9 +2,6 @@
Test that the root API endpoint is available. Test that the root API endpoint is available.
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json import json
import requests import requests

View File

@ -7,9 +7,6 @@ This is because the "translated" javascript files are compiled into the "static"
They should only contain template tags that render static information. They should only contain template tags that render static information.
""" """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys import sys
import re import re
import os import os

View File

@ -1,8 +1,5 @@
""" Check that there are no database migration files which have not been committed. """ """ Check that there are no database migration files which have not been committed. """
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys import sys
import subprocess import subprocess

Some files were not shown because too many files have changed in this diff Show More