diff --git a/.github/workflows/docker_build.yaml b/.github/workflows/docker_build.yaml index 89d52664e4..26fc69a0f5 100644 --- a/.github/workflows/docker_build.yaml +++ b/.github/workflows/docker_build.yaml @@ -8,7 +8,7 @@ on: - 'master' jobs: - + docker: runs-on: ubuntu-latest diff --git a/.github/workflows/mysql.yaml b/.github/workflows/mysql.yaml index 087a866fbd..5bafe56253 100644 --- a/.github/workflows/mysql.yaml +++ b/.github/workflows/mysql.yaml @@ -33,7 +33,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 ports: - 3306:3306 - + steps: - name: Checkout Code uses: actions/checkout@v2 diff --git a/.github/workflows/postgresql.yaml b/.github/workflows/postgresql.yaml index ae8f52b962..3481895d85 100644 --- a/.github/workflows/postgresql.yaml +++ b/.github/workflows/postgresql.yaml @@ -5,7 +5,7 @@ name: PostgreSQL on: ["push", "pull_request"] jobs: - + test: runs-on: ubuntu-latest diff --git a/InvenTree/InvenTree/api_tester.py b/InvenTree/InvenTree/api_tester.py index 2e69e40969..eb92bd80c1 100644 --- a/InvenTree/InvenTree/api_tester.py +++ b/InvenTree/InvenTree/api_tester.py @@ -83,7 +83,7 @@ class InvenTreeAPITestCase(APITestCase): self.assertEqual(response.status_code, code) return response - + def post(self, url, data): """ Issue a POST request diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index e072f5a5ea..669b55b0c0 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -71,7 +71,7 @@ def status_codes(request): def user_roles(request): """ Return a map of the current roles assigned to the user. - + Roles are denoted by their simple names, and then the permission type. Permissions can be access as follows: diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index 04ceabccd8..06de4861ec 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -17,5 +17,5 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): """ Do not get any rates... """ - + return {} diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 155f77c639..c496c1bb22 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -102,5 +102,5 @@ class RoundingDecimalField(models.DecimalField): } defaults.update(kwargs) - + return super().formfield(**kwargs) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index d5508b7db2..1097c5663b 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -35,7 +35,7 @@ def generateTestKey(test_name): """ Generate a test 'key' for a given test name. This must not have illegal chars as it will be used for dict lookup in a template. - + Tests must be named such that they will have unique keys. """ @@ -102,7 +102,7 @@ def TestIfImageURL(url): '.tif', '.tiff', '.webp', '.gif', ] - + def str2bool(text, test=True): """ Test if a string 'looks' like a boolean value. @@ -137,10 +137,10 @@ def isNull(text): """ Test if a string 'looks' like a null value. This is useful for querying the API against a null key. - + Args: text: Input text - + Returns: True if the text looks like a null value """ @@ -157,7 +157,7 @@ def normalize(d): d = Decimal(d) d = d.normalize() - + # Ref: https://docs.python.org/3/library/decimal.html return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize() @@ -165,14 +165,14 @@ def normalize(d): def increment(n): """ Attempt to increment an integer (or a string that looks like an integer!) - + e.g. 001 -> 002 2 -> 3 AB01 -> AB02 QQQ -> QQQ - + """ value = str(n).strip() @@ -314,7 +314,7 @@ def MakeBarcode(object_name, object_pk, object_data={}, **kwargs): def GetExportFormats(): """ Return a list of allowable file formats for exporting data """ - + return [ 'csv', 'tsv', @@ -327,7 +327,7 @@ def GetExportFormats(): def DownloadFile(data, filename, content_type='application/text'): """ Create a dynamic file for the user to download. - + Args: data: Raw file data (string or bytes) filename: Filename for the file download @@ -525,7 +525,7 @@ def addUserPermission(user, permission): """ Shortcut function for adding a certain permission to a user. """ - + perm = Permission.objects.get(codename=permission) user.user_permissions.add(perm) @@ -576,7 +576,7 @@ def getOldestMigrationFile(app, exclude_extension=True, ignore_initial=True): continue num = int(f.split('_')[0]) - + if oldest_file is None or num < oldest_num: oldest_num = num oldest_file = f @@ -585,7 +585,7 @@ def getOldestMigrationFile(app, exclude_extension=True, ignore_initial=True): oldest_file = oldest_file.replace('.py', '') return oldest_file - + def getNewestMigrationFile(app, exclude_extension=True): """ diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 8494b52a10..5822f8a19f 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -129,7 +129,7 @@ class InvenTreeTree(MPTTModel): Here an 'item' is considered to be the 'leaf' at the end of each branch, and the exact nature here will depend on the class implementation. - + The default implementation returns zero """ return 0 diff --git a/InvenTree/InvenTree/permissions.py b/InvenTree/InvenTree/permissions.py index 973395a2a0..defb370435 100644 --- a/InvenTree/InvenTree/permissions.py +++ b/InvenTree/InvenTree/permissions.py @@ -17,7 +17,7 @@ class RolePermission(permissions.BasePermission): - PUT - PATCH - DELETE - + Specify the required "role" using the role_required attribute. e.g. diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 6b40b8eb31..fa7674723c 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -44,7 +44,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): In addition to running validators on the serializer fields, this class ensures that the underlying model is also validated. """ - + # Run any native validation checks first (may throw an ValidationError) data = super(serializers.ModelSerializer, self).validate(data) diff --git a/InvenTree/InvenTree/status.py b/InvenTree/InvenTree/status.py index 5531d4c270..970e88831d 100644 --- a/InvenTree/InvenTree/status.py +++ b/InvenTree/InvenTree/status.py @@ -64,7 +64,7 @@ def is_email_configured(): if not settings.EMAIL_HOST_USER: configured = False - + # Display warning unless in test mode if not settings.TESTING: logger.warning("EMAIL_HOST_USER is not configured") diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index 6294eba06e..c73ef10018 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -16,7 +16,7 @@ class StatusCode: # If the key cannot be found, pass it back if key not in cls.options.keys(): return key - + value = cls.options.get(key, key) color = cls.colors.get(key, 'grey') diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index 52765db2a7..8435d756fb 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -119,7 +119,7 @@ class APITests(InvenTreeAPITestCase): self.assertNotIn('add', roles[rule]) self.assertNotIn('change', roles[rule]) self.assertNotIn('delete', roles[rule]) - + def test_with_superuser(self): """ Superuser should have *all* roles assigned diff --git a/InvenTree/InvenTree/test_tasks.py b/InvenTree/InvenTree/test_tasks.py index 02e8d14e5e..e9c9d9f01c 100644 --- a/InvenTree/InvenTree/test_tasks.py +++ b/InvenTree/InvenTree/test_tasks.py @@ -37,7 +37,7 @@ class ScheduledTaskTests(TestCase): # Attempt to schedule the same task again InvenTree.tasks.schedule_task(task, schedule_type=Schedule.MINUTES, minutes=5) self.assertEqual(self.get_tasks(task).count(), 1) - + # But the 'minutes' should have been updated t = Schedule.objects.get(func=task) self.assertEqual(t.minutes, 5) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index af812fe8a3..d65829cf8e 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -97,7 +97,7 @@ class TestHelpers(TestCase): self.assertEqual(helpers.getMediaUrl('xx/yy.png'), '/media/xx/yy.png') def testDecimal2String(self): - + self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345') self.assertEqual(helpers.decimal2string('test'), 'test') @@ -205,7 +205,7 @@ class TestMPTT(TestCase): child = StockLocation.objects.get(pk=5) parent.parent = child - + with self.assertRaises(InvalidMove): parent.save() @@ -223,7 +223,7 @@ class TestMPTT(TestCase): drawer.save() self.assertNotEqual(tree, drawer.tree_id) - + class TestSerialNumberExtraction(TestCase): """ Tests for serial number extraction code """ diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index c57e82addc..ab2ced7d5e 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -81,7 +81,7 @@ settings_urls = [ url(r'^user/?', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings-user'), url(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'), url(r'^i18n/?', include('django.conf.urls.i18n')), - + url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'), url(r'^report/?', SettingsView.as_view(template_name='InvenTree/settings/report.html'), name='settings-report'), url(r'^category/?', SettingCategorySelectView.as_view(), name='settings-category'), @@ -137,7 +137,7 @@ urlpatterns = [ url(r'^login/?', auth_views.LoginView.as_view(), name='login'), url(r'^logout/', auth_views.LogoutView.as_view(template_name='registration/logged_out.html'), name='logout'), - + url(r'^settings/', include(settings_urls)), url(r'^edit-user/', EditUserView.as_view(), name='edit-user'), diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index d5fcb8822e..1b6a6b3f0b 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -130,7 +130,7 @@ def validate_overage(value): if i < 0: raise ValidationError(_("Overage value must not be negative")) - + # Looks like an integer! return True except ValueError: diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index def4b34781..d285efae36 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -176,7 +176,7 @@ class InvenTreeRoleMixin(PermissionRequiredMixin): if role not in RuleSet.RULESET_NAMES: raise ValueError(f"Role '{role}' is not a valid role") - + if permission not in RuleSet.RULESET_PERMISSIONS: raise ValueError(f"Permission '{permission}' is not a valid permission") @@ -223,7 +223,7 @@ class InvenTreeRoleMixin(PermissionRequiredMixin): Return the 'permission_class' required for the current View. Must be one of: - + - view - change - add @@ -389,7 +389,7 @@ class QRCodeView(AjaxView): """ ajax_template_name = "qr_code.html" - + def get(self, request, *args, **kwargs): self.request = request self.pk = self.kwargs['pk'] @@ -398,7 +398,7 @@ class QRCodeView(AjaxView): def get_qr_data(self): """ Returns the text object to render to a QR code. The actual rendering will be handled by the template """ - + return None def get_context_data(self): @@ -406,7 +406,7 @@ class QRCodeView(AjaxView): Explicity passes the parameter 'qr_data' """ - + context = {} qr = self.get_qr_data() @@ -415,7 +415,7 @@ class QRCodeView(AjaxView): context['qr_data'] = qr else: context['error_msg'] = 'Error generating QR code' - + return context @@ -507,7 +507,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView): """ super(UpdateView, self).get(request, *args, **kwargs) - + return self.renderJsonResponse(request, self.get_form(), context=self.get_context_data()) def save(self, object, form, **kwargs): @@ -673,7 +673,7 @@ class SetPasswordView(AjaxUpdateView): p1 = request.POST.get('enter_password', '') p2 = request.POST.get('confirm_password', '') - + if valid: # Passwords must match @@ -712,7 +712,7 @@ class IndexView(TemplateView): # Generate a list of orderable parts which have stock below their minimum values # TODO - Is there a less expensive way to get these from the database # context['to_order'] = [part for part in Part.objects.filter(purchaseable=True) if part.need_to_restock()] - + # Generate a list of assembly parts which have stock below their minimum values # TODO - Is there a less expensive way to get these from the database # context['to_build'] = [part for part in Part.objects.filter(assembly=True) if part.need_to_restock()] @@ -752,7 +752,7 @@ class DynamicJsView(TemplateView): template_name = "" content_type = 'text/javascript' - + class SettingsView(TemplateView): """ View for configuring User settings @@ -830,7 +830,7 @@ class AppearanceSelectView(FormView): if form.is_valid(): theme_selected = form.cleaned_data['name'] - + # Set color theme to form selection user_theme.name = theme_selected user_theme.save() @@ -893,7 +893,7 @@ class DatabaseStatsView(AjaxView): # Part stats ctx['part_count'] = Part.objects.count() ctx['part_cat_count'] = PartCategory.objects.count() - + # Stock stats ctx['stock_item_count'] = StockItem.objects.count() ctx['stock_loc_count'] = StockLocation.objects.count() diff --git a/InvenTree/barcodes/api.py b/InvenTree/barcodes/api.py index e6b3ea84e3..6ab848c3f6 100644 --- a/InvenTree/barcodes/api.py +++ b/InvenTree/barcodes/api.py @@ -73,7 +73,7 @@ class BarcodeScan(APIView): # A plugin has been found! if plugin is not None: - + # Try to associate with a stock item item = plugin.getStockItem() @@ -133,7 +133,7 @@ class BarcodeScan(APIView): class BarcodeAssign(APIView): """ Endpoint for assigning a barcode to a stock item. - + - This only works if the barcode is not already associated with an object in the database - If the barcode does not match an object, then the barcode hash is assigned to the StockItem """ @@ -178,7 +178,7 @@ class BarcodeAssign(APIView): # Matching plugin was found if plugin is not None: - + hash = plugin.hash() response['hash'] = hash response['plugin'] = plugin.name @@ -234,7 +234,7 @@ class BarcodeAssign(APIView): barcode_api_urls = [ url(r'^link/$', BarcodeAssign.as_view(), name='api-barcode-link'), - + # Catch-all performs barcode 'scan' url(r'^.*$', BarcodeScan.as_view(), name='api-barcode-scan'), ] diff --git a/InvenTree/barcodes/barcode.py b/InvenTree/barcodes/barcode.py index a00e91d7e4..7ab9f3716a 100644 --- a/InvenTree/barcodes/barcode.py +++ b/InvenTree/barcodes/barcode.py @@ -21,7 +21,7 @@ def hash_barcode(barcode_data): HACK: Remove any 'non printable' characters from the hash, as it seems browers will remove special control characters... - + TODO: Work out a way around this! """ diff --git a/InvenTree/barcodes/tests.py b/InvenTree/barcodes/tests.py index 5f178d923c..1d8f53ec4c 100644 --- a/InvenTree/barcodes/tests.py +++ b/InvenTree/barcodes/tests.py @@ -92,7 +92,7 @@ class BarcodeAPITest(APITestCase): data = response.data self.assertEqual(response.status_code, status.HTTP_200_OK) - + self.assertIn('stockitem', data) pk = data['stockitem']['pk'] @@ -121,7 +121,7 @@ class BarcodeAPITest(APITestCase): data = response.data self.assertEqual(response.status_code, status.HTTP_200_OK) - + self.assertIn('success', data) hash = data['hash'] diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 10cc7e2024..160642281a 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -20,7 +20,7 @@ from .serializers import BuildSerializer, BuildItemSerializer class BuildList(generics.ListCreateAPIView): """ API endpoint for accessing a list of Build objects. - + - GET: Return list of objects (with filters) - POST: Create a new Build object """ @@ -65,7 +65,7 @@ class BuildList(generics.ListCreateAPIView): queryset = BuildSerializer.annotate_queryset(queryset) return queryset - + def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index c5da505f43..a278b4e17c 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -118,7 +118,7 @@ class Build(MPTTModel): def get_absolute_url(self): return reverse('build-detail', kwargs={'pk': self.id}) - + reference = models.CharField( unique=True, max_length=64, @@ -168,7 +168,7 @@ class Build(MPTTModel): null=True, blank=True, help_text=_('SalesOrder to which this build is allocated') ) - + take_from = models.ForeignKey( 'stock.StockLocation', verbose_name=_('Source Location'), @@ -177,7 +177,7 @@ class Build(MPTTModel): null=True, blank=True, help_text=_('Select location to take stock from for this build (leave blank to take from any stock location)') ) - + destination = models.ForeignKey( 'stock.StockLocation', verbose_name=_('Destination Location'), @@ -207,7 +207,7 @@ class Build(MPTTModel): validators=[MinValueValidator(0)], help_text=_('Build status code') ) - + batch = models.CharField( verbose_name=_('Batch Code'), max_length=100, @@ -215,9 +215,9 @@ class Build(MPTTModel): null=True, help_text=_('Batch code for this build output') ) - + creation_date = models.DateField(auto_now_add=True, editable=False, verbose_name=_('Creation Date')) - + target_date = models.DateField( null=True, blank=True, verbose_name=_('Target completion date'), @@ -251,7 +251,7 @@ class Build(MPTTModel): help_text=_('User responsible for this build order'), related_name='builds_responsible', ) - + link = InvenTree.fields.InvenTreeURLField( verbose_name=_('External Link'), blank=True, help_text=_('Link to external URL') @@ -272,7 +272,7 @@ class Build(MPTTModel): else: descendants = self.get_descendants(include_self=True) Build.objects.filter(parent__pk__in=[d.pk for d in descendants]) - + def sub_build_count(self, cascade=True): """ Return the number of sub builds under this one. @@ -295,7 +295,7 @@ class Build(MPTTModel): query = query.filter(Build.OVERDUE_FILTER) return query.exists() - + @property def active(self): """ @@ -441,7 +441,7 @@ class Build(MPTTModel): # Extract the "most recent" build order reference builds = cls.objects.exclude(reference=None) - + if not builds.exists(): return None @@ -543,7 +543,7 @@ class Build(MPTTModel): - The sub_item in the BOM line must *not* be trackable - There is only a single stock item available (which has not already been allocated to this build) - The stock item has an availability greater than zero - + Returns: A list object containing the StockItem objects to be allocated (and the quantities). Each item in the list is a dict as follows: @@ -648,7 +648,7 @@ class Build(MPTTModel): """ Deletes all stock allocations for this build. """ - + allocations = BuildItem.objects.filter(build=self) allocations.delete() @@ -1145,7 +1145,7 @@ class BuildItem(models.Model): """ self.validate_unique() - + super().clean() errors = {} @@ -1159,7 +1159,7 @@ class BuildItem(models.Model): # Allocated part must be in the BOM for the master part if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False): errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)] - + # Allocated quantity cannot exceed available stock quantity if self.quantity > self.stock_item.quantity: errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})").format( diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html index dee90a26a0..de07614c8e 100644 --- a/InvenTree/build/templates/build/allocate.html +++ b/InvenTree/build/templates/build/allocate.html @@ -86,7 +86,7 @@ } ); }); - + $("#btn-order-parts").click(function() { launchModalForm("/order/purchase-order/order-parts/", { data: { @@ -94,7 +94,7 @@ }, }); }); - + {% endif %} {% endblock %} diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index c0f6e400b6..177fad8d6c 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -230,5 +230,5 @@ src="{% static 'img/blank_image.png' %}" } ); }); - + {% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/index.html b/InvenTree/build/templates/build/index.html index 0ff4b78ab8..d5e6484d56 100644 --- a/InvenTree/build/templates/build/index.html +++ b/InvenTree/build/templates/build/index.html @@ -17,9 +17,9 @@