From ecccfbd546997334df8292d30101c54c1c7eff08 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 May 2022 20:33:00 +0200 Subject: [PATCH 01/33] Remove encoding header Fixes #2996 --- InvenTree/InvenTree/api.py | 3 --- InvenTree/InvenTree/fields.py | 2 -- InvenTree/InvenTree/forms.py | 2 -- InvenTree/InvenTree/serializers.py | 3 --- InvenTree/InvenTree/views.py | 2 -- InvenTree/build/api.py | 3 --- InvenTree/build/models.py | 2 -- InvenTree/build/serializers.py | 3 --- InvenTree/build/views.py | 3 --- InvenTree/common/api.py | 3 --- InvenTree/common/forms.py | 3 --- InvenTree/common/models.py | 3 --- InvenTree/common/serializers.py | 3 --- InvenTree/common/settings.py | 3 --- InvenTree/common/test_views.py | 3 --- InvenTree/common/views.py | 3 --- InvenTree/company/api.py | 3 --- InvenTree/company/forms.py | 3 --- InvenTree/company/models.py | 3 --- InvenTree/company/test_views.py | 3 --- InvenTree/company/views.py | 4 ---- InvenTree/label/models.py | 3 --- InvenTree/label/test_api.py | 3 --- InvenTree/label/tests.py | 3 --- InvenTree/order/api.py | 3 --- InvenTree/order/forms.py | 3 --- InvenTree/order/serializers.py | 3 --- InvenTree/order/test_views.py | 3 --- InvenTree/order/views.py | 3 --- InvenTree/part/api.py | 3 --- InvenTree/part/forms.py | 3 --- InvenTree/part/models.py | 2 -- InvenTree/part/settings.py | 3 --- InvenTree/part/test_param.py | 3 --- InvenTree/part/views.py | 3 --- InvenTree/plugin/api.py | 3 --- InvenTree/plugin/base/event/events.py | 3 --- InvenTree/plugin/models.py | 2 -- InvenTree/plugin/serializers.py | 3 --- InvenTree/report/models.py | 3 --- InvenTree/stock/api.py | 3 --- InvenTree/stock/forms.py | 3 --- InvenTree/stock/models.py | 4 ---- InvenTree/stock/serializers.py | 3 --- InvenTree/stock/test_api.py | 3 --- InvenTree/stock/views.py | 3 --- ci/check_api_endpoint.py | 3 --- ci/check_js_templates.py | 3 --- ci/check_locale_files.py | 3 --- ci/check_migration_files.py | 3 --- ci/check_version_number.py | 3 --- 51 files changed, 149 deletions(-) diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index 9d901516d5..e58ef798fc 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -2,9 +2,6 @@ Main JSON interface views """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.http import JsonResponse diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index b3d81d75a5..894b993e50 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -1,7 +1,5 @@ """ Custom fields used in InvenTree """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import sys from .validators import allowable_url_schemes diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 8814c571dd..40e8509411 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -2,8 +2,6 @@ Helper forms which subclass Django forms to provide additional functionality """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from urllib.parse import urlencode import logging diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index d3d0038cea..e16a4f1ba7 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -2,9 +2,6 @@ Serializers used in various InvenTree apps """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import tablib diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 6291b321a8..56fd6be28f 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -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). """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import os import json diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 4453232823..c8c54b3a43 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -2,9 +2,6 @@ JSON API for the Build app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.urls import include, re_path from rest_framework import filters, generics diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index a1517d73dd..fdba0b2fd8 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -2,8 +2,6 @@ Build database model definitions """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import decimal import os diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 0f1703750c..ebd98fefc9 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -2,9 +2,6 @@ JSON serializers for Build API """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import transaction from django.core.exceptions import ValidationError as DjangoValidationError from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index c8ac4509b8..57d8f8bd37 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -2,9 +2,6 @@ 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.views.generic import DetailView, ListView diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index 1996a4bdbf..646025ab2a 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -2,9 +2,6 @@ Provides a JSON API for common components. """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json from django.http.response import HttpResponse diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index 4a2a1601aa..7fcdf8535c 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -2,9 +2,6 @@ Django forms for interacting with common objects """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django import forms from django.utils.translation import gettext as _ diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 8b50d05413..1824e9d335 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -3,9 +3,6 @@ Common database model definitions. These models are 'generic' and do not fit a particular business logic object. """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import decimal import math diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 8dd0f5bcee..7d0c8cc219 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -2,9 +2,6 @@ JSON serializers for common components """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.helpers import get_objectreference diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py index 2dd7756eba..361284d831 100644 --- a/InvenTree/common/settings.py +++ b/InvenTree/common/settings.py @@ -2,9 +2,6 @@ User-configurable settings for the common app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from moneyed import CURRENCIES from django.conf import settings diff --git a/InvenTree/common/test_views.py b/InvenTree/common/test_views.py index 7d7bfde87e..2394913c73 100644 --- a/InvenTree/common/test_views.py +++ b/InvenTree/common/test_views.py @@ -1,6 +1,3 @@ """ Unit tests for the views associated with the 'common' app """ - -# -*- coding: utf-8 -*- -from __future__ import unicode_literals diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 7fae6f3710..f9a6a4f82a 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -2,9 +2,6 @@ Django views for interacting with common models """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index b99fbd01fb..146a45f648 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -2,9 +2,6 @@ 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 import rest_framework as rest_filters diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 5549f66222..eaead82151 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -2,9 +2,6 @@ Django Forms for interacting with Company app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 9b881c227e..5e93599d24 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -2,9 +2,6 @@ Company database model definitions """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index 258090ebdb..6d28e85d23 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -1,8 +1,5 @@ """ Unit tests for Company views (see views.py) """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index 6d8279558c..5f686e805e 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -2,10 +2,6 @@ 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.views.generic import DetailView, ListView diff --git a/InvenTree/label/models.py b/InvenTree/label/models.py index 9094b957ff..7a52ed5d02 100644 --- a/InvenTree/label/models.py +++ b/InvenTree/label/models.py @@ -2,9 +2,6 @@ Label printing models """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys import os import logging diff --git a/InvenTree/label/test_api.py b/InvenTree/label/test_api.py index a444cd47dc..807de5b63e 100644 --- a/InvenTree/label/test_api.py +++ b/InvenTree/label/test_api.py @@ -1,8 +1,5 @@ # Tests for labels -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.urls import reverse from InvenTree.api_tester import InvenTreeAPITestCase diff --git a/InvenTree/label/tests.py b/InvenTree/label/tests.py index ad1aaba9c8..9e066aa91e 100644 --- a/InvenTree/label/tests.py +++ b/InvenTree/label/tests.py @@ -1,8 +1,5 @@ # Tests for labels -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from django.conf import settings diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 7c8a93125f..f1f6780825 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -2,9 +2,6 @@ JSON API for the Order app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.urls import include, path, re_path from django.db.models import Q, F diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index a08cf81ab1..120d1e0923 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -2,9 +2,6 @@ Django Forms for interacting with Order objects """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django import forms from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index c97090e4f6..a58de213ea 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -2,9 +2,6 @@ JSON serializers for the Order API """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from decimal import Decimal from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index a0445e3dd6..e38ea7ecef 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -1,8 +1,5 @@ """ Unit tests for Order views (see views.py) """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index f8102908e9..77b5741c3e 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -2,9 +2,6 @@ Django views for interacting with Order app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db.utils import IntegrityError from django.http.response import JsonResponse from django.shortcuts import get_object_or_404 diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 622ca38669..b71fdcacfe 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -2,9 +2,6 @@ Provides a JSON API for the Part app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from django.urls import include, path, re_path diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 87210901d2..9a14d8ad25 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -2,9 +2,6 @@ Django Forms for interacting with Part objects """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django import forms from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 96ffa581f4..2ff3f60a83 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2,8 +2,6 @@ Part database model definitions """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import decimal import os diff --git a/InvenTree/part/settings.py b/InvenTree/part/settings.py index e345a9d88d..41ecbb0d00 100644 --- a/InvenTree/part/settings.py +++ b/InvenTree/part/settings.py @@ -2,9 +2,6 @@ User-configurable settings for the Part app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from common.models import InvenTreeSetting diff --git a/InvenTree/part/test_param.py b/InvenTree/part/test_param.py index fe3514d807..db617f6ffb 100644 --- a/InvenTree/part/test_param.py +++ b/InvenTree/part/test_param.py @@ -1,8 +1,5 @@ # Tests for Part Parameters -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase, TransactionTestCase import django.core.exceptions as django_exceptions diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index f1d587e206..9aac54e7d4 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2,9 +2,6 @@ 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.exceptions import ValidationError from django.db import transaction diff --git a/InvenTree/plugin/api.py b/InvenTree/plugin/api.py index f3710e4835..85721a6770 100644 --- a/InvenTree/plugin/api.py +++ b/InvenTree/plugin/api.py @@ -2,9 +2,6 @@ JSON API for the plugin app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.urls import include, re_path diff --git a/InvenTree/plugin/base/event/events.py b/InvenTree/plugin/base/event/events.py index 59dfdce86a..4b9c44521d 100644 --- a/InvenTree/plugin/base/event/events.py +++ b/InvenTree/plugin/base/event/events.py @@ -2,9 +2,6 @@ Functions for triggering and responding to server side events """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import logging from django.conf import settings diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 3d2d143eea..3caa69e709 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -2,8 +2,6 @@ Plugin model definitions """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import warnings from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/plugin/serializers.py b/InvenTree/plugin/serializers.py index b3c0471635..c80115342a 100644 --- a/InvenTree/plugin/serializers.py +++ b/InvenTree/plugin/serializers.py @@ -2,9 +2,6 @@ JSON serializers for plugin app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import subprocess diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 32ba9077b1..8632189982 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -2,9 +2,6 @@ Report template model definitions """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import sys import logging diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 96a893e914..ec204354bd 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -2,9 +2,6 @@ JSON API for the Stock app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from collections import OrderedDict from datetime import datetime, timedelta diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 7d419ab478..7e615b5eae 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -2,9 +2,6 @@ Django Forms for interacting with Stock app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from InvenTree.forms import HelperForm from .models import StockItem, StockItemTracking diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index a46d43b007..e5fac16355 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -2,10 +2,6 @@ Stock database model definitions """ - -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from jinja2 import Template diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 6b27b87f93..40303fc120 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -2,9 +2,6 @@ JSON serializers for Stock app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from decimal import Decimal from datetime import datetime, timedelta from django.db import transaction diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 1f040b008d..28a8e0de0b 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -2,9 +2,6 @@ Unit testing for the Stock API """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import io import tablib diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 168403d692..15191b5fb7 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -2,9 +2,6 @@ Django views for interacting with Stock app """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from datetime import datetime from django.views.generic import DetailView, ListView diff --git a/ci/check_api_endpoint.py b/ci/check_api_endpoint.py index 2969c64792..0d110379ef 100644 --- a/ci/check_api_endpoint.py +++ b/ci/check_api_endpoint.py @@ -2,9 +2,6 @@ Test that the root API endpoint is available. """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json import requests diff --git a/ci/check_js_templates.py b/ci/check_js_templates.py index 71b9912f3a..4f35e57eb4 100644 --- a/ci/check_js_templates.py +++ b/ci/check_js_templates.py @@ -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. """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys import re import os diff --git a/ci/check_locale_files.py b/ci/check_locale_files.py index 06246cd923..d17fe27e3d 100644 --- a/ci/check_locale_files.py +++ b/ci/check_locale_files.py @@ -1,8 +1,5 @@ """ Check that there are no database migration files which have not been committed. """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys import subprocess diff --git a/ci/check_migration_files.py b/ci/check_migration_files.py index 8ef0ada13d..16bd87485d 100644 --- a/ci/check_migration_files.py +++ b/ci/check_migration_files.py @@ -1,8 +1,5 @@ """ Check that there are no database migration files which have not been committed. """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys import subprocess diff --git a/ci/check_version_number.py b/ci/check_version_number.py index 0514854407..b5ab7f65a2 100644 --- a/ci/check_version_number.py +++ b/ci/check_version_number.py @@ -2,9 +2,6 @@ On release, ensure that the release tag matches the InvenTree version number! """ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys import re import os From 3154a4ebd03d1f5da3f22635171bf0c021bae681 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 May 2022 21:01:55 +0200 Subject: [PATCH 02/33] remove old import catching --- tasks.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tasks.py b/tasks.py index 2ed0b4d35e..fc69c8ba22 100644 --- a/tasks.py +++ b/tasks.py @@ -6,11 +6,7 @@ import sys import pathlib import re - -try: - from invoke import ctask as task -except: - from invoke import task +from invoke import task def apps(): From af6eac8cc9cf9e9181744ae61510f3df25587d0d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 May 2022 01:12:19 +0200 Subject: [PATCH 03/33] Add isort --- requirements.txt | 1 + setup.cfg | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/requirements.txt b/requirements.txt index 5065b4f877..43d077104f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,6 +36,7 @@ flake8==3.8.3 # PEP checking gunicorn>=20.1.0 # Gunicorn web server importlib_metadata # Backport for importlib.metadata inventree # Install the latest version of the InvenTree API python library +isort==5.10.1 # DEV: python import sorting markdown==3.3.4 # Force particular version of markdown pep8-naming==0.11.1 # PEP naming convention extension pillow==9.0.1 # Image manipulation diff --git a/setup.cfg b/setup.cfg index b4b0af8836..a483481f5d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,3 +20,11 @@ max-complexity = 20 [coverage:run] source = ./InvenTree + +[isort] +src_paths=InvenTree +skip_glob =*/migrations/*.py +known_django=django +import_heading_firstparty=InvenTree imports +import_heading_thirdparty=Third-Party imports +sections=FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER From 73413baa59e522177574a550093b8d161408e79b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 16 May 2022 22:01:34 +1000 Subject: [PATCH 04/33] Add required imports to plugin.events --- InvenTree/plugin/events.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index bea1fafb25..66c6267d53 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -2,8 +2,10 @@ 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__ = [ + 'process_event', + 'register_event', 'trigger_event', ] From 2ed69f638a6ac3dff41a253db165d83f79298a31 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 16 May 2022 22:08:58 +1000 Subject: [PATCH 05/33] Fix error message --- InvenTree/InvenTree/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 49a4049be5..a53fd567ab 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -126,8 +126,8 @@ def heartbeat(): try: from django_q.models import Success - logger.info("Could not perform heartbeat task - App registry not ready") except AppRegistryNotReady: # pragma: no cover + logger.info("Could not perform heartbeat task - App registry not ready") return threshold = timezone.now() - timedelta(minutes=30) From 1903ac12cd17a2d7dc3a73d22d1914ef30ebec88 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 16 May 2022 22:43:29 +1000 Subject: [PATCH 06/33] Allow customization of button class in modal forms --- InvenTree/templates/js/translated/forms.js | 1 + InvenTree/templates/js/translated/modals.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 642523a60b..d45a2de78c 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -288,6 +288,7 @@ function constructDeleteForm(fields, options) { * - method: The HTTP method e.g. 'PUT', 'POST', 'DELETE' (default='PATCH') * - title: The form title * - submitText: Text for the "submit" button + * - submitClass: CSS class for the "submit" button (default = ') * - closeText: Text for the "close" button * - fields: list of fields to display, with the following options * - filters: API query filters diff --git a/InvenTree/templates/js/translated/modals.js b/InvenTree/templates/js/translated/modals.js index 85f503682e..464006ae12 100644 --- a/InvenTree/templates/js/translated/modals.js +++ b/InvenTree/templates/js/translated/modals.js @@ -42,6 +42,8 @@ function createNewModal(options={}) { } }); + var submitClass = options.submitClass || 'primary'; + var html = ` From 6658b899460f544c5c9f76858586c5c6e9489cba Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 16 May 2022 22:51:34 +1000 Subject: [PATCH 07/33] Refactor BOM item deletion - Send delete requests sequentially, rather than simultaneously - Prevents server overload - Present a much cleaner dialog to the user --- InvenTree/part/templates/part/detail.html | 31 ++------ InvenTree/templates/js/translated/bom.js | 96 ++++++++++++++++++++--- 2 files changed, 93 insertions(+), 34 deletions(-) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index bcedc95da8..1c80a88c28 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -589,32 +589,15 @@ // Get a list of the selected BOM items 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( - '{% trans "Delete selected BOM items?" %}', - '{% trans "All selected BOM items will be deleted" %}', - { - 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(); - }); - } + deleteBomItems(rows, { + success: function() { + $('#bom-table').bootstrapTable('refresh'); } - ); + }); }); $('#bom-upload').click(function() { diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 9bd66877da..0119b36664 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -16,6 +16,7 @@ /* exported constructBomUploadTable, + deleteBomItems, downloadBomTemplate, exportBom, newPartFromBomWizard, @@ -647,7 +648,88 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) { 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 = ` + + ${thumb} ${sub_part.full_name} + ${item.reference} + ${item.quantity} + + `; + + return html; + } + + var rows = ''; + + items.forEach(function(item) { + rows += renderItem(item); + }); + + var html = ` +
+ {% trans "All selected BOM items will be deleted" %} +
+ + + + + + + + ${rows} +
{% trans "Part" %}{% trans "Reference" %}{% trans "Quantity" %}
+ `; + + 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,19 +1228,13 @@ function loadBomTable(table, options={}) { var pk = $(this).attr('pk'); - var html = ` -
- {% trans "Are you sure you want to delete this BOM item?" %} -
`; + var item = table.bootstrapTable('getRowByUniqueId', pk); - constructForm(`/api/bom/${pk}/`, { - method: 'DELETE', - title: '{% trans "Delete BOM Item" %}', - preFormContent: html, - onSuccess: function() { + deleteBomItems([item], { + success: function() { reloadBomTable(table); } - }); + }); }); // Callback for "edit" button From 9ec626b650f33992d90f62aadcf5389741548594 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 17:29:14 +0200 Subject: [PATCH 08/33] Also allow non string references Fixes #3005 --- InvenTree/InvenTree/tasks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index c156d421ab..8af30264d3 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -68,6 +68,10 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): import importlib from InvenTree.status import is_worker_running + # make sure the taskname is a string + if not isinstance(taskname, str): + taskname = str(taskname) + if is_worker_running() and not force_sync: # pragma: no cover # Running as asynchronous task try: From 0f5c03e44cb8500e07f87bfd1d46ccd6cb8bc083 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 17:45:51 +0200 Subject: [PATCH 09/33] use direct import instead of text for offload --- InvenTree/InvenTree/tasks.py | 3 ++- InvenTree/InvenTree/views.py | 8 ++------ InvenTree/build/models.py | 3 ++- InvenTree/common/test_tasks.py | 3 ++- InvenTree/label/api.py | 3 ++- InvenTree/part/models.py | 3 ++- InvenTree/part/tasks.py | 3 ++- InvenTree/stock/models.py | 5 +++-- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 8af30264d3..cb52245740 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -11,6 +11,7 @@ from django.utils import timezone from django.core.exceptions import AppRegistryNotReady from django.db.utils import OperationalError, ProgrammingError +from django.core import mail as django_mail logger = logging.getLogger("inventree") @@ -292,7 +293,7 @@ def send_email(subject, body, recipients, from_email=None, html_message=None): recipients = [recipients] offload_task( - 'django.core.mail.send_mail', + django_mail.send_mail, subject, body, from_email, diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 6291b321a8..f15452c697 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -797,13 +797,9 @@ class CurrencyRefreshView(RedirectView): 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 - taskname = 'InvenTree.tasks.update_exchange_rates' - - # Run it - offload_task(taskname, force_sync=True) + offload_task(update_exchange_rates, force_sync=True) return redirect(reverse_lazy('settings')) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index a1517d73dd..e7a1bbd1b3 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -41,6 +41,7 @@ from plugin.events import trigger_event from part import models as PartModels from stock import models as StockModels from users import models as UserModels +from . import tasks as build_tasks def get_next_build_number(): @@ -1146,7 +1147,7 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs): # A new Build has just been created # 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): diff --git a/InvenTree/common/test_tasks.py b/InvenTree/common/test_tasks.py index 3f85316c41..c28a1d19fe 100644 --- a/InvenTree/common/test_tasks.py +++ b/InvenTree/common/test_tasks.py @@ -2,6 +2,7 @@ from django.test import TestCase from common.models import NotificationEntry +from . import tasks as common_tasks from InvenTree.tasks import offload_task @@ -14,4 +15,4 @@ class TaskTest(TestCase): # check empty run self.assertEqual(NotificationEntry.objects.all().count(), 0) - offload_task('common.tasks.delete_old_notifications',) + offload_task(common_tasks.delete_old_notifications,) diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index cb4b939157..34ced6a3cd 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -24,6 +24,7 @@ from plugin.registry import registry from stock.models import StockItem, StockLocation from part.models import Part +from plugin.base.label import label as plugin_label from .models import StockItemLabel, StockLocationLabel, PartLabel from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer, PartLabelSerializer @@ -156,7 +157,7 @@ class LabelPrintMixin: # Offload a background task to print the provided label offload_task( - 'plugin.base.label.label.print_label', + plugin_label.print_label, plugin.plugin_slug(), image, label_instance=label_instance, diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 96ffa581f4..229b25bf16 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -63,6 +63,7 @@ from stock import models as StockModels import common.models import part.settings as part_settings +from part import tasks as part_tasks logger = logging.getLogger("inventree") @@ -2298,7 +2299,7 @@ def after_save_part(sender, instance: Part, created, **kwargs): # Check part stock only if we are *updating* the part (not creating it) # 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): diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index b158d26ad3..c8615bd4a2 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -10,6 +10,7 @@ import InvenTree.tasks import common.notifications import part.models +from part import tasks as part_tasks logger = logging.getLogger("inventree") @@ -49,6 +50,6 @@ def notify_low_stock_if_required(part: part.models.Part): for p in parts: if p.is_part_low_on_stock(): InvenTree.tasks.offload_task( - 'part.tasks.notify_low_stock', + part_tasks.notify_low_stock, p ) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index a46d43b007..69be97cf4b 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -49,6 +49,7 @@ from users.models import Owner from company import models as CompanyModels from part import models as PartModels +from part import tasks as part_tasks class StockLocation(InvenTreeTree): @@ -2026,7 +2027,7 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs): if not InvenTree.ready.isImportingData(): # 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') @@ -2037,7 +2038,7 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs): if not InvenTree.ready.isImportingData(): # 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): From a9cfdf8fdb6853f175cdc31abc2dec91ec6dcf3a Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 17:52:36 +0200 Subject: [PATCH 10/33] fix import --- InvenTree/part/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index c8615bd4a2..d0c91ac79d 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -10,7 +10,6 @@ import InvenTree.tasks import common.notifications import part.models -from part import tasks as part_tasks logger = logging.getLogger("inventree") @@ -50,6 +49,6 @@ def notify_low_stock_if_required(part: part.models.Part): for p in parts: if p.is_part_low_on_stock(): InvenTree.tasks.offload_task( - part_tasks.notify_low_stock, + notify_low_stock, p ) From 18a263ff75e9e26fe4385d09eec3c9b58027776e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 17:55:45 +0200 Subject: [PATCH 11/33] do a local import --- InvenTree/part/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 67afef4923..fa0f1816f8 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -59,7 +59,6 @@ from order import models as OrderModels from company.models import SupplierPart import part.settings as part_settings from stock import models as StockModels -from part import tasks as part_tasks from plugin.models import MetadataMixin @@ -2291,6 +2290,7 @@ def after_save_part(sender, instance: Part, created, **kwargs): """ Function to be executed after a Part is saved """ + from part import tasks as part_tasks if not created and not InvenTree.ready.isImportingData(): # Check part stock only if we are *updating* the part (not creating it) From cce3d3a35deaae30adda964d2a3f8581e4ad0362 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:01:16 +0200 Subject: [PATCH 12/33] make imports on function level --- InvenTree/build/models.py | 2 +- InvenTree/stock/models.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index fa81111f18..0f539dc158 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -39,7 +39,6 @@ from plugin.events import trigger_event from part import models as PartModels from stock import models as StockModels from users import models as UserModels -from . import tasks as build_tasks def get_next_build_number(): @@ -1140,6 +1139,7 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs): """ Callback function to be executed after a Build instance is saved """ + from . import tasks as build_tasks if created: # A new Build has just been created diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index b3eed54c5f..6cd0469b75 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -46,7 +46,6 @@ from users.models import Owner from company import models as CompanyModels from part import models as PartModels -from part import tasks as part_tasks class StockLocation(MetadataMixin, InvenTreeTree): @@ -2021,6 +2020,7 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs): """ Function to be executed after a StockItem object is deleted """ + from part import tasks as part_tasks if not InvenTree.ready.isImportingData(): # Run this check in the background @@ -2032,6 +2032,7 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs): """ Hook function to be executed after StockItem object is saved/updated """ + from part import tasks as part_tasks if not InvenTree.ready.isImportingData(): # Run this check in the background From 30dbfa9a4fb5399d6915eafd8a5da1c710ffaa49 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:21:58 +0200 Subject: [PATCH 13/33] add tests for InvenTree.tasks --- InvenTree/InvenTree/test_tasks.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index e9c9d9f01c..c00931b237 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -41,3 +41,22 @@ class ScheduledTaskTests(TestCase): # But the 'minutes' should have been updated t = Schedule.objects.get(func=task) self.assertEqual(t.minutes, 5) + +class InvenTreeTaskTests(TestCase): + """Unit tests for tasks""" + + def test_task_hearbeat(self, name): + """Test the task heartbeat""" + InvenTree.tasks.offload_task(InvenTree.tasks.heartbeat) + + def test_task_delete_successful_tasks(self, name): + """Test the task delete_successful_tasks""" + InvenTree.tasks.offload_task(InvenTree.tasks.delete_successful_tasks) + + def test_task_delete_old_error_logs(self, name): + """Test the task delete_old_error_logs""" + InvenTree.tasks.offload_task(InvenTree.tasks.delete_old_error_logs) + + def test_task_check_for_updates(self, name): + """Test the task check_for_updates""" + InvenTree.tasks.offload_task(InvenTree.tasks.check_for_updates) From 7fc408cf607c1bbcd81403bcd9d25742bdb8e305 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:29:41 +0200 Subject: [PATCH 14/33] move exceptions up --- InvenTree/InvenTree/tasks.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index e2869c6a46..bdcf2fe413 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -68,6 +68,11 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): import importlib 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 + logger.warning(f"Could not offload task '{taskname}' - database not ready") # make sure the taskname is a string if not isinstance(taskname, str): @@ -113,11 +118,6 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): # Workers are not running: run it as synchronous task _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(): From 763cd13b7ccefbf8b1da758e0b2a497f9046db57 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:30:37 +0200 Subject: [PATCH 15/33] use a function if it was passed --- InvenTree/InvenTree/tasks.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index bdcf2fe413..cd06acfe3d 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -74,17 +74,18 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): except (OperationalError, ProgrammingError): # pragma: no cover logger.warning(f"Could not offload task '{taskname}' - database not ready") - # make sure the taskname is a string - if not isinstance(taskname, str): - taskname = str(taskname) + if is_worker_running() and not force_sync: # pragma: no cover + # Running as asynchronous task + try: + task = AsyncTask(taskname, *args, **kwargs) + task.run() + except ImportError: + logger.warning(f"WARNING: '{taskname}' not started - Function not found") + else: - if is_worker_running() and not force_sync: # pragma: no cover - # Running as asynchronous task - try: - task = AsyncTask(taskname, *args, **kwargs) - task.run() - except ImportError: - logger.warning(f"WARNING: '{taskname}' not started - Function not found") + if isinstance(taskname, function): + # function was passed - use that + _func = taskname else: # Split path try: @@ -115,8 +116,8 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'") return - # Workers are not running: run it as synchronous task - _func(*args, **kwargs) + # Workers are not running: run it as synchronous task + _func(*args, **kwargs) From 79b4b23a07ce4a7103afbcfc8d82d0a443326a0a Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:33:49 +0200 Subject: [PATCH 16/33] pep fix --- InvenTree/InvenTree/tasks.py | 1 - InvenTree/InvenTree/test_tasks.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index cd06acfe3d..97b2393df5 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -120,7 +120,6 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): _func(*args, **kwargs) - def heartbeat(): """ Simple task which runs at 5 minute intervals, diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index c00931b237..f571eee8cd 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -42,6 +42,7 @@ class ScheduledTaskTests(TestCase): t = Schedule.objects.get(func=task) self.assertEqual(t.minutes, 5) + class InvenTreeTaskTests(TestCase): """Unit tests for tasks""" From 07711b8e747110541386dbbe383d1e37b9f475ce Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:34:02 +0200 Subject: [PATCH 17/33] fix check --- InvenTree/InvenTree/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 97b2393df5..40e63108e9 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -83,7 +83,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): logger.warning(f"WARNING: '{taskname}' not started - Function not found") else: - if isinstance(taskname, function): + if callable(taskname): # function was passed - use that _func = taskname else: From fc8a63325e803f4a3843ba7f07ad88d75d246fd9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 18:57:27 +0200 Subject: [PATCH 18/33] that was a stupid misstake --- InvenTree/InvenTree/test_tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index f571eee8cd..0d043bd9f8 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -46,18 +46,18 @@ class ScheduledTaskTests(TestCase): class InvenTreeTaskTests(TestCase): """Unit tests for tasks""" - def test_task_hearbeat(self, name): + def test_task_hearbeat(self): """Test the task heartbeat""" InvenTree.tasks.offload_task(InvenTree.tasks.heartbeat) - def test_task_delete_successful_tasks(self, name): + def test_task_delete_successful_tasks(self): """Test the task delete_successful_tasks""" InvenTree.tasks.offload_task(InvenTree.tasks.delete_successful_tasks) - def test_task_delete_old_error_logs(self, name): + def test_task_delete_old_error_logs(self): """Test the task delete_old_error_logs""" InvenTree.tasks.offload_task(InvenTree.tasks.delete_old_error_logs) - def test_task_check_for_updates(self, name): + def test_task_check_for_updates(self): """Test the task check_for_updates""" InvenTree.tasks.offload_task(InvenTree.tasks.check_for_updates) From eb24bf78b8ee0ed12d7e69ea474e5711527c0cd1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 19:39:43 +0200 Subject: [PATCH 19/33] external api failures are not covered --- InvenTree/InvenTree/tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 40e63108e9..6dc8b7d297 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -210,25 +210,25 @@ def check_for_updates(): response = requests.get('https://api.github.com/repos/inventree/inventree/releases/latest') 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) tag = data.get('tag_name', None) 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) - if len(match.groups()) != 3: + if len(match.groups()) != 3: # pragma: no cover logger.warning(f"Version '{tag}' did not match expected pattern") return latest_version = [int(x) for x in match.groups()] 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}'") From 2ec59a6ad22200906ab2b520f042f7e0fe64fd1b Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 16 May 2022 19:45:00 +0200 Subject: [PATCH 20/33] extend tests for task_delete_succ --- InvenTree/InvenTree/test_tasks.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 0d043bd9f8..5a5e32d08d 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -2,6 +2,9 @@ Unit tests for task management """ +from datetime import timedelta + +from django.utils import timezone from django.test import TestCase from django_q.models import Schedule @@ -52,7 +55,19 @@ class InvenTreeTaskTests(TestCase): 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', + started=timezone.now() - timedelta(days=31) + ) InvenTree.tasks.offload_task(InvenTree.tasks.delete_successful_tasks) + threshold = timezone.now() - timedelta(days=30) + 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""" From f3c4720f5b316e4d4778bf2df5b8285bae6b0fb9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 May 2022 23:41:33 +0200 Subject: [PATCH 21/33] extend update check --- InvenTree/InvenTree/test_tasks.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 5a5e32d08d..74eee7c73d 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -9,6 +9,7 @@ from django.test import TestCase from django_q.models import Schedule import InvenTree.tasks +from common.models import InvenTreeSetting class ScheduledTaskTests(TestCase): @@ -75,4 +76,13 @@ class InvenTreeTaskTests(TestCase): 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)) From 09bda7e516b247cbc69541fa9e3339d7663488d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 May 2022 23:42:09 +0200 Subject: [PATCH 22/33] add checks for old_error_log --- InvenTree/InvenTree/test_tasks.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 74eee7c73d..9952eccff4 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -8,10 +8,16 @@ from django.utils import timezone from django.test import TestCase from django_q.models import Schedule +from error_report.models import Error + import InvenTree.tasks from common.models import InvenTreeSetting +threshold = timezone.now() - timedelta(days=30) +threshold_low = threshold - timedelta(days=1) + + class ScheduledTaskTests(TestCase): """ Unit tests for scheduled tasks @@ -72,8 +78,23 @@ class InvenTreeTaskTests(TestCase): 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 From 9f3fdcb59074d81235e5fb7f086743f7cc5d7f78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 May 2022 23:43:07 +0200 Subject: [PATCH 23/33] make test simpler --- InvenTree/InvenTree/test_tasks.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 9952eccff4..bbcf7a4799 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -64,16 +64,9 @@ class InvenTreeTaskTests(TestCase): """Test the task delete_successful_tasks""" from django_q.models import Success - Success.objects.create( - name='abc', - func='abc', - started=timezone.now() - timedelta(days=31) - ) + Success.objects.create(name='abc', func='abc', stopped=threshold, started=threshold_low) InvenTree.tasks.offload_task(InvenTree.tasks.delete_successful_tasks) - threshold = timezone.now() - timedelta(days=30) - results = Success.objects.filter( - started__lte=threshold - ) + results = Success.objects.filter(started__lte=threshold) self.assertEqual(len(results, 0)) def test_task_delete_old_error_logs(self): From 3e859f7abbcc0ba0b8bbc2815b087ad999706043 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 May 2022 23:51:17 +0200 Subject: [PATCH 24/33] fix assertation --- InvenTree/InvenTree/test_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index bbcf7a4799..0b6cd08ae1 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -67,7 +67,7 @@ class InvenTreeTaskTests(TestCase): 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)) + self.assertEqual(len(results), 0) def test_task_delete_old_error_logs(self): """Test the task delete_old_error_logs""" From b3e42f5fb04b992187cc1e01ea0b6b8d80953ced Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 May 2022 00:13:39 +0200 Subject: [PATCH 25/33] Add tests for offloading --- InvenTree/InvenTree/test_tasks.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 0b6cd08ae1..1502037dba 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -53,9 +53,31 @@ class ScheduledTaskTests(TestCase): 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 + InvenTree.tasks.offload_task('InvenTree') + # Non exsistent app + InvenTree.tasks.offload_task('InvenTreeABC.test_tasks.doesnotmatter') + # Non exsistent function + InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexsist') + def test_task_hearbeat(self): """Test the task heartbeat""" InvenTree.tasks.offload_task(InvenTree.tasks.heartbeat) From 09af7d964dec7f107a6924a2a542dfb91aae6a3d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 May 2022 00:24:02 +0200 Subject: [PATCH 26/33] raise a warning for assertations --- InvenTree/InvenTree/tasks.py | 21 ++++++++++++++++----- InvenTree/InvenTree/test_tasks.py | 11 ++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 6dc8b7d297..a750c393e6 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import re import json +import warnings import requests import logging @@ -12,6 +13,7 @@ from django.utils import timezone from django.core.exceptions import AppRegistryNotReady from django.db.utils import OperationalError, ProgrammingError from django.core import mail as django_mail +from django.conf import settings logger = logging.getLogger("inventree") @@ -53,6 +55,15 @@ def schedule_task(taskname, **kwargs): 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): """ Create an AsyncTask if workers are running. @@ -72,7 +83,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): 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") + raise_warning(f"Could not offload task '{taskname}' - database not ready") if is_worker_running() and not force_sync: # pragma: no cover # Running as asynchronous task @@ -80,7 +91,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): task = AsyncTask(taskname, *args, **kwargs) task.run() 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): @@ -92,14 +103,14 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): app, mod, func = taskname.split('.') app_mod = app + '.' + mod except ValueError: - logger.warning(f"WARNING: '{taskname}' not started - Malformed function path") + raise_warning(f"WARNING: '{taskname}' not started - Malformed function path") return # Import module from app try: _mod = importlib.import_module(app_mod) 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 # Retrieve function @@ -113,7 +124,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): if not _func: _func = eval(func) # pragma: no cover 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 # Workers are not running: run it as synchronous task diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 1502037dba..eb307f2292 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -72,11 +72,16 @@ class InvenTreeTaskTests(TestCase): # Error runs # Malformed taskname - InvenTree.tasks.offload_task('InvenTree') + with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTree' not started - Malformed function path"): + InvenTree.tasks.offload_task('InvenTree') + # Non exsistent app - InvenTree.tasks.offload_task('InvenTreeABC.test_tasks.doesnotmatter') + with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC'"): + InvenTree.tasks.offload_task('InvenTreeABC.test_tasks.doesnotmatter') + # Non exsistent function - InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexsist') + 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""" From 728cccc469db3c583a83ebbbb60d7f9594156f65 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 May 2022 00:53:51 +0200 Subject: [PATCH 27/33] fix assertation --- InvenTree/InvenTree/test_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index eb307f2292..c8497e2241 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -76,7 +76,7 @@ class InvenTreeTaskTests(TestCase): InvenTree.tasks.offload_task('InvenTree') # Non exsistent app - with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC'"): + 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 From fb55f5d2a2e0a243547a7a7d72002bd8fac24e90 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 May 2022 01:32:08 +0200 Subject: [PATCH 28/33] convert remaining function --- InvenTree/plugin/base/event/events.py | 4 ++-- InvenTree/plugin/base/locate/api.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/plugin/base/event/events.py b/InvenTree/plugin/base/event/events.py index 4b9c44521d..4dea7525b3 100644 --- a/InvenTree/plugin/base/event/events.py +++ b/InvenTree/plugin/base/event/events.py @@ -37,7 +37,7 @@ def trigger_event(event, *args, **kwargs): logger.debug(f"Event triggered: '{event}'") offload_task( - 'plugin.events.register_event', + register_event, event, *args, **kwargs @@ -72,7 +72,7 @@ def register_event(event, *args, **kwargs): # Offload a separate task for each plugin offload_task( - 'plugin.events.process_event', + process_event, slug, event, *args, diff --git a/InvenTree/plugin/base/locate/api.py b/InvenTree/plugin/base/locate/api.py index 30c6d749a9..020951b283 100644 --- a/InvenTree/plugin/base/locate/api.py +++ b/InvenTree/plugin/base/locate/api.py @@ -56,7 +56,7 @@ class LocatePluginView(APIView): try: 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 @@ -69,7 +69,7 @@ class LocatePluginView(APIView): try: 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 From 256451d82b590fce0daeba7c5c84f28216c8b76e Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 11:52:42 +1000 Subject: [PATCH 29/33] Fix context such that build output can be deleted --- InvenTree/build/api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index c8c54b3a43..c6c1c73128 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -282,6 +282,13 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView): 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() serializer_class = build.serializers.BuildOutputDeleteSerializer From 5a0acedce6994333a9cf98f307afa0ba64d244c3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 13:00:53 +1000 Subject: [PATCH 30/33] Add unit tests for BuildOutputCreate serializer --- InvenTree/build/serializers.py | 5 +- InvenTree/build/test_api.py | 96 ++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index ebd98fefc9..e4919f1433 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -199,7 +199,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): def validate_quantity(self, quantity): - if quantity < 0: + if quantity <= 0: raise ValidationError(_("Quantity must be greater than zero")) part = self.get_part() @@ -209,7 +209,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): if part.trackable: 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")) return quantity @@ -232,7 +232,6 @@ class BuildOutputCreateSerializer(serializers.Serializer): serial_numbers = serial_numbers.strip() - # TODO: Field level validation necessary here? return serial_numbers auto_allocate = serializers.BooleanField( diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index a54a92dda8..2c2afa1842 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -305,6 +305,102 @@ class BuildTest(BuildAPITest): 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) + class BuildAllocationTest(BuildAPITest): """ From 01a30935f08199d96fe875ab349401ff400e4a36 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 14:20:41 +1000 Subject: [PATCH 31/33] Add unit tests for BuildOutputDelete serializer --- InvenTree/build/test_api.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index 2c2afa1842..d66313c197 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -401,6 +401,54 @@ class BuildTest(BuildAPITest): # 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}) + + # 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) + class BuildAllocationTest(BuildAPITest): """ From e7b458978c197b780f75358f443d6e6a72070a86 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 15:10:48 +1000 Subject: [PATCH 32/33] More unit tests --- InvenTree/build/test_api.py | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index d66313c197..be64aa28c7 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -406,6 +406,16 @@ class BuildTest(BuildAPITest): 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) @@ -449,6 +459,61 @@ class BuildTest(BuildAPITest): 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): """ From 9bcbaaa5f540a0791d7d16ad1562e7c697f6a35d Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 16:57:31 +1000 Subject: [PATCH 33/33] Remove more python2 stuff --- InvenTree/InvenTree/filters.py | 3 --- InvenTree/InvenTree/metadata.py | 4 ---- InvenTree/InvenTree/models.py | 2 -- InvenTree/InvenTree/permissions.py | 3 --- InvenTree/InvenTree/tasks.py | 3 --- InvenTree/build/admin.py | 3 --- InvenTree/build/apps.py | 3 --- InvenTree/build/forms.py | 7 ------- InvenTree/build/tasks.py | 3 --- InvenTree/build/test_api.py | 3 --- InvenTree/build/tests.py | 3 --- InvenTree/common/admin.py | 3 --- InvenTree/common/tasks.py | 3 --- InvenTree/common/test_notifications.py | 3 --- InvenTree/common/tests.py | 3 +-- InvenTree/company/admin.py | 3 --- InvenTree/company/apps.py | 2 -- InvenTree/company/tests.py | 3 --- InvenTree/label/admin.py | 3 --- InvenTree/label/api.py | 3 --- InvenTree/label/serializers.py | 3 --- InvenTree/order/admin.py | 3 --- InvenTree/part/admin.py | 3 --- InvenTree/part/apps.py | 2 -- InvenTree/part/tasks.py | 3 --- InvenTree/part/test_api.py | 3 --- InvenTree/part/test_bom_item.py | 2 -- InvenTree/part/test_part.py | 3 --- InvenTree/plugin/admin.py | 2 -- InvenTree/plugin/apps.py | 2 -- InvenTree/plugin/base/locate/api.py | 3 --- .../plugin/builtin/integration/test_core_notifications.py | 3 --- InvenTree/plugin/test_api.py | 2 -- InvenTree/plugin/views.py | 2 -- InvenTree/report/admin.py | 3 --- InvenTree/report/api.py | 2 -- InvenTree/report/serializers.py | 2 -- InvenTree/report/tests.py | 2 -- InvenTree/stock/admin.py | 3 --- InvenTree/stock/apps.py | 1 - InvenTree/users/admin.py | 2 -- InvenTree/users/api.py | 3 --- InvenTree/users/apps.py | 3 --- InvenTree/users/tests.py | 3 --- 44 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 InvenTree/build/forms.py diff --git a/InvenTree/InvenTree/filters.py b/InvenTree/InvenTree/filters.py index 8272673fc3..f0058e399a 100644 --- a/InvenTree/InvenTree/filters.py +++ b/InvenTree/InvenTree/filters.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from rest_framework.filters import OrderingFilter diff --git a/InvenTree/InvenTree/metadata.py b/InvenTree/InvenTree/metadata.py index ae520e9dcb..85c5a4c8cc 100644 --- a/InvenTree/InvenTree/metadata.py +++ b/InvenTree/InvenTree/metadata.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - import logging from rest_framework import serializers diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 92db2012f8..73b2bf4572 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -2,8 +2,6 @@ Generic models which provide extra functionality over base Django model types. """ -from __future__ import unicode_literals - import re import os import logging diff --git a/InvenTree/InvenTree/permissions.py b/InvenTree/InvenTree/permissions.py index defb370435..920e111ce2 100644 --- a/InvenTree/InvenTree/permissions.py +++ b/InvenTree/InvenTree/permissions.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from rest_framework import permissions import users.models diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index a750c393e6..77f55df271 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import re import json import warnings diff --git a/InvenTree/build/admin.py b/InvenTree/build/admin.py index 43909d2197..32d843d057 100644 --- a/InvenTree/build/admin.py +++ b/InvenTree/build/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from import_export.admin import ImportExportModelAdmin diff --git a/InvenTree/build/apps.py b/InvenTree/build/apps.py index a5b69d365a..9025e77e0e 100644 --- a/InvenTree/build/apps.py +++ b/InvenTree/build/apps.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py deleted file mode 100644 index 77a42571d8..0000000000 --- a/InvenTree/build/forms.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Django Forms for interacting with Build objects -""" - -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals diff --git a/InvenTree/build/tasks.py b/InvenTree/build/tasks.py index b69057d444..daf231d951 100644 --- a/InvenTree/build/tasks.py +++ b/InvenTree/build/tasks.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from decimal import Decimal import logging diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index be64aa28c7..068493ad5e 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from datetime import datetime, timedelta from django.urls import reverse diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index e8ec8b67ca..d3c0c41112 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase from django.urls import reverse diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index b49bef1c07..f9ae7b557f 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from import_export.admin import ImportExportModelAdmin diff --git a/InvenTree/common/tasks.py b/InvenTree/common/tasks.py index c83f0e6bad..db63dc9c17 100644 --- a/InvenTree/common/tasks.py +++ b/InvenTree/common/tasks.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import logging from datetime import timedelta, datetime diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index 719d9291de..e16e28f339 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from common.notifications import NotificationMethod, SingleNotificationMethod, BulkNotificationMethod, storage from plugin.models import NotificationUserSetting from part.test_part import BaseNotificationIntegrationTest diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 6cf67c6583..8e3f69c21e 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals + from http import HTTPStatus import json from datetime import timedelta diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index 97327a559a..cc672f9ee5 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from import_export.admin import ImportExportModelAdmin diff --git a/InvenTree/company/apps.py b/InvenTree/company/apps.py index 41371dd739..a0cd4919cf 100644 --- a/InvenTree/company/apps.py +++ b/InvenTree/company/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index 79ffc34af5..edc34e7f2d 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase from django.core.exceptions import ValidationError diff --git a/InvenTree/label/admin.py b/InvenTree/label/admin.py index 8fee2b1f8f..96e90363e0 100644 --- a/InvenTree/label/admin.py +++ b/InvenTree/label/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from .models import StockItemLabel, StockLocationLabel, PartLabel diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index 34ced6a3cd..17aef828c0 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from io import BytesIO from PIL import Image diff --git a/InvenTree/label/serializers.py b/InvenTree/label/serializers.py index 47ccd51ba1..af6ae49e28 100644 --- a/InvenTree/label/serializers.py +++ b/InvenTree/label/serializers.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeAttachmentSerializerField diff --git a/InvenTree/order/admin.py b/InvenTree/order/admin.py index a1e1b74256..eaeebff04d 100644 --- a/InvenTree/order/admin.py +++ b/InvenTree/order/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from import_export.admin import ImportExportModelAdmin diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 30dad8e995..8021bd10fa 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from import_export.admin import ImportExportModelAdmin diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 93885995ac..b1dfcbe8bc 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import logging from django.db.utils import OperationalError, ProgrammingError diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index d0c91ac79d..7ab15a7200 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import logging from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index dc79a3a123..28df4503c9 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import PIL from django.urls import reverse diff --git a/InvenTree/part/test_bom_item.py b/InvenTree/part/test_bom_item.py index 0789ed08c3..1ee57e8d07 100644 --- a/InvenTree/part/test_bom_item.py +++ b/InvenTree/part/test_bom_item.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import transaction from django.test import TestCase diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index f1bfcab40a..09e8f97c64 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -1,8 +1,5 @@ # Tests for the Part model -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals from allauth.account.models import EmailAddress from django.contrib.auth import get_user_model diff --git a/InvenTree/plugin/admin.py b/InvenTree/plugin/admin.py index 256b5ce0da..dfc8e861f3 100644 --- a/InvenTree/plugin/admin.py +++ b/InvenTree/plugin/admin.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.contrib import admin diff --git a/InvenTree/plugin/apps.py b/InvenTree/plugin/apps.py index 521d42b743..233f037ec7 100644 --- a/InvenTree/plugin/apps.py +++ b/InvenTree/plugin/apps.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import logging diff --git a/InvenTree/plugin/base/locate/api.py b/InvenTree/plugin/base/locate/api.py index 020951b283..a6776f2d40 100644 --- a/InvenTree/plugin/base/locate/api.py +++ b/InvenTree/plugin/base/locate/api.py @@ -1,8 +1,5 @@ """API for location plugins""" -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from rest_framework import permissions from rest_framework.exceptions import ParseError, NotFound from rest_framework.response import Response diff --git a/InvenTree/plugin/builtin/integration/test_core_notifications.py b/InvenTree/plugin/builtin/integration/test_core_notifications.py index 02c91784e5..e93d2a84cd 100644 --- a/InvenTree/plugin/builtin/integration/test_core_notifications.py +++ b/InvenTree/plugin/builtin/integration/test_core_notifications.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from plugin.models import NotificationUserSetting from part.test_part import BaseNotificationIntegrationTest from plugin.builtin.integration.core_notifications import CoreNotificationsPlugin diff --git a/InvenTree/plugin/test_api.py b/InvenTree/plugin/test_api.py index 58911a9bd2..e3685969af 100644 --- a/InvenTree/plugin/test_api.py +++ b/InvenTree/plugin/test_api.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.urls import reverse diff --git a/InvenTree/plugin/views.py b/InvenTree/plugin/views.py index 1b45fefbb1..ee855094cc 100644 --- a/InvenTree/plugin/views.py +++ b/InvenTree/plugin/views.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.conf import settings diff --git a/InvenTree/report/admin.py b/InvenTree/report/admin.py index b76b8fab1b..6456bf72c5 100644 --- a/InvenTree/report/admin.py +++ b/InvenTree/report/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from .models import ReportSnippet, ReportAsset diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index 8a063001a6..f8b31fef99 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.utils.translation import gettext_lazy as _ from django.urls import include, path, re_path diff --git a/InvenTree/report/serializers.py b/InvenTree/report/serializers.py index 6e3e36df18..1db0cca1b9 100644 --- a/InvenTree/report/serializers.py +++ b/InvenTree/report/serializers.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeAttachmentSerializerField diff --git a/InvenTree/report/tests.py b/InvenTree/report/tests.py index a40990b527..d30a5814d9 100644 --- a/InvenTree/report/tests.py +++ b/InvenTree/report/tests.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import os import shutil diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 83de1d2484..3d931e2d7c 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from import_export.admin import ImportExportModelAdmin diff --git a/InvenTree/stock/apps.py b/InvenTree/stock/apps.py index de8e50cc26..ca24ee7312 100644 --- a/InvenTree/stock/apps.py +++ b/InvenTree/stock/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 9763644479..2784f28efe 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.utils.translation import gettext_lazy as _ diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index 47e073fb52..2a78abcbbf 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist diff --git a/InvenTree/users/apps.py b/InvenTree/users/apps.py index a4668c68e8..b610e23488 100644 --- a/InvenTree/users/apps.py +++ b/InvenTree/users/apps.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db.utils import OperationalError, ProgrammingError from django.apps import AppConfig diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py index e6a4019481..9dc90f5d2c 100644 --- a/InvenTree/users/tests.py +++ b/InvenTree/users/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase from django.apps import apps from django.urls import reverse