From d36cf358f8edf29eb693ef4b6cd588c3ef347b40 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 6 Feb 2024 02:30:50 +0000 Subject: [PATCH] Bump to Dj 4.x (#6173) * bump to dj >4.2 * switch to experimental git release * bump django-import_export * bump mptt * replace is_ajax, which was removed https://docs.djangoproject.com/en/3.1/releases/3.1/#id2 * Save before accessing values in m2m/fk fields * move plugin init * use dev version of django for fix * update deps * fix deps * use django smaller 4.2 * fix reqs * fix merge * remove moved code * another merge fix * fix ajax call * fix refs * change python min v * fix deps * bump deps * fix deps * pin pillow * dj 4.1 upgrades * make diff smaller * bump all deps * drop down to py3.9 * bump versions * merge fix * fix diff * more bumping * diff cleanup * bump deps * fix reqs * use accurate state for model migrations using apps the historically correct state is used * try import * added more logs * add try here too * clean up rebuilds * Dj 4.2 (#161) * autochanges * bump * fix diff * fix diff * bump deps * fix req * remove select_related to test error influence * switch to mptt fork * fix reqs for upstream * move tracking ensureance into save * optimize check frequency * use psycopg instead of psycopg2 * fix header * just use the values * switch to dj < 4.2 * fix req * another req fix * switch to 4.2 again * fix merge error * Check for null pk in calculate_total_price Cannot access self.lines if pk is Null * use patched mptt * try psycopg2 again * Remove tree rebuild from migrations * Prevent notify_users if importing or migrating * Add order_by() to subquery annotations - Ref: https://stackoverflow.com/a/629691 * Update stock filters - Append order_by() * fix error if running without timezones in testing * add logging to figure this out * remove tz from self.creation if TZ is off * add tz? * move around? * only run the test i am trying to figure out not reproducible on my machine * only run the test i am trying to figure out not reproducible on my machine * run all tests again --------- Co-authored-by: Oliver --- InvenTree/InvenTree/settings.py | 12 +--- InvenTree/InvenTree/test_api.py | 8 +-- InvenTree/InvenTree/test_middleware.py | 4 +- InvenTree/build/admin.py | 8 +-- .../migrations/0013_auto_20200425_0507.py | 7 -- .../migrations/0029_auto_20210601_1525.py | 4 +- InvenTree/common/models.py | 9 ++- InvenTree/company/admin.py | 20 +++--- InvenTree/order/api.py | 4 +- InvenTree/order/models.py | 13 +++- InvenTree/order/test_api.py | 2 +- InvenTree/order/test_views.py | 3 +- InvenTree/part/filters.py | 8 ++- .../migrations/0020_auto_20190908_0404.py | 11 +-- .../migrations/0039_auto_20200515_1127.py | 9 --- InvenTree/part/models.py | 13 ++++ InvenTree/part/test_views.py | 2 +- InvenTree/plugin/test_plugin.py | 3 +- InvenTree/report/admin.py | 23 +++--- InvenTree/stock/admin.py | 14 ++-- InvenTree/stock/filters.py | 8 ++- .../migrations/0012_auto_20190908_0405.py | 11 +-- .../migrations/0022_auto_20200217_1109.py | 11 +-- InvenTree/stock/models.py | 3 +- InvenTree/users/admin.py | 6 +- InvenTree/users/api.py | 2 +- requirements-dev.txt | 32 ++++----- requirements.in | 8 ++- requirements.txt | 71 ++++++++++--------- 29 files changed, 151 insertions(+), 178 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 73aefd2a75..f04edf35e7 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -591,11 +591,6 @@ db_options = db_config.get('OPTIONS', db_config.get('options', {})) # Specific options for postgres backend if 'postgres' in db_engine: # pragma: no cover - from psycopg2.extensions import ( - ISOLATION_LEVEL_READ_COMMITTED, - ISOLATION_LEVEL_SERIALIZABLE, - ) - # Connection timeout if 'connect_timeout' not in db_options: # The DB server is in the same data center, it should not take very @@ -659,11 +654,7 @@ if 'postgres' in db_engine: # pragma: no cover serializable = get_boolean_setting( 'INVENTREE_DB_ISOLATION_SERIALIZABLE', 'database.serializable', False ) - db_options['isolation_level'] = ( - ISOLATION_LEVEL_SERIALIZABLE - if serializable - else ISOLATION_LEVEL_READ_COMMITTED - ) + db_options['isolation_level'] = 4 if serializable else 2 # Specific options for MySql / MariaDB backend elif 'mysql' in db_engine: # pragma: no cover @@ -963,7 +954,6 @@ TIME_ZONE = get_setting('INVENTREE_TIMEZONE', 'timezone', 'UTC') USE_I18N = True -USE_L10N = True # Do not use native timezone support in "test" mode # It generates a *lot* of cruft in the logs diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index aefe27cfec..58e18df2e5 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -25,7 +25,7 @@ class HTMLAPITests(InvenTreeTestCase): url = reverse('api-part-list') # Check JSON response - response = self.client.get(url, HTTP_ACCEPT='application/json') + response = self.client.get(url, headers={'accept': 'application/json'}) self.assertEqual(response.status_code, 200) def test_build_api(self): @@ -33,7 +33,7 @@ class HTMLAPITests(InvenTreeTestCase): url = reverse('api-build-list') # Check JSON response - response = self.client.get(url, HTTP_ACCEPT='application/json') + response = self.client.get(url, headers={'accept': 'application/json'}) self.assertEqual(response.status_code, 200) def test_stock_api(self): @@ -41,7 +41,7 @@ class HTMLAPITests(InvenTreeTestCase): url = reverse('api-stock-list') # Check JSON response - response = self.client.get(url, HTTP_ACCEPT='application/json') + response = self.client.get(url, headers={'accept': 'application/json'}) self.assertEqual(response.status_code, 200) def test_company_list(self): @@ -49,7 +49,7 @@ class HTMLAPITests(InvenTreeTestCase): url = reverse('api-company-list') # Check JSON response - response = self.client.get(url, HTTP_ACCEPT='application/json') + response = self.client.get(url, headers={'accept': 'application/json'}) self.assertEqual(response.status_code, 200) def test_not_found(self): diff --git a/InvenTree/InvenTree/test_middleware.py b/InvenTree/InvenTree/test_middleware.py index f750a339b2..8f091f34da 100644 --- a/InvenTree/InvenTree/test_middleware.py +++ b/InvenTree/InvenTree/test_middleware.py @@ -15,7 +15,9 @@ class MiddlewareTests(InvenTreeTestCase): def check_path(self, url, code=200, **kwargs): """Helper function to run a request.""" - response = self.client.get(url, HTTP_ACCEPT='application/json', **kwargs) + response = self.client.get( + url, headers={'accept': 'application/json'}, **kwargs + ) self.assertEqual(response.status_code, code) return response diff --git a/InvenTree/build/admin.py b/InvenTree/build/admin.py index fb99a898d9..b3d14c6ec6 100644 --- a/InvenTree/build/admin.py +++ b/InvenTree/build/admin.py @@ -51,6 +51,7 @@ class BuildResource(InvenTreeResource): notes = Field(attribute='notes') +@admin.register(Build) class BuildAdmin(ImportExportModelAdmin): """Class for managing the Build model via the admin interface""" @@ -83,6 +84,7 @@ class BuildAdmin(ImportExportModelAdmin): ] +@admin.register(BuildItem) class BuildItemAdmin(admin.ModelAdmin): """Class for managing the BuildItem model via the admin interface.""" @@ -98,6 +100,7 @@ class BuildItemAdmin(admin.ModelAdmin): ] +@admin.register(BuildLine) class BuildLineAdmin(admin.ModelAdmin): """Class for managing the BuildLine model via the admin interface""" @@ -112,8 +115,3 @@ class BuildLineAdmin(admin.ModelAdmin): 'build__reference', 'bom_item__sub_part__name', ] - - -admin.site.register(Build, BuildAdmin) -admin.site.register(BuildItem, BuildItemAdmin) -admin.site.register(BuildLine, BuildLineAdmin) diff --git a/InvenTree/build/migrations/0013_auto_20200425_0507.py b/InvenTree/build/migrations/0013_auto_20200425_0507.py index 4468b9aca5..15be35eea6 100644 --- a/InvenTree/build/migrations/0013_auto_20200425_0507.py +++ b/InvenTree/build/migrations/0013_auto_20200425_0507.py @@ -3,12 +3,6 @@ from django.db import migrations, models import django.db.models.deletion import mptt.fields -from build.models import Build - - -def update_tree(apps, schema_editor): - # Update the Build MPTT model - Build.objects.rebuild() class Migration(migrations.Migration): @@ -49,5 +43,4 @@ class Migration(migrations.Migration): field=models.PositiveIntegerField(db_index=True, default=0, editable=False), preserve_default=False, ), - migrations.RunPython(update_tree, reverse_code=migrations.RunPython.noop), ] diff --git a/InvenTree/build/migrations/0029_auto_20210601_1525.py b/InvenTree/build/migrations/0029_auto_20210601_1525.py index 68b1f098e1..3c98b504df 100644 --- a/InvenTree/build/migrations/0029_auto_20210601_1525.py +++ b/InvenTree/build/migrations/0029_auto_20210601_1525.py @@ -57,6 +57,4 @@ class Migration(migrations.Migration): ('build', '0028_builditem_bom_item'), ] - operations = [ - migrations.RunPython(assign_bom_items, reverse_code=migrations.RunPython.noop), - ] + operations = [] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 84d02cba26..888b36abe8 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -13,7 +13,7 @@ import math import os import re import uuid -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from enum import Enum from secrets import compare_digest from typing import Any, Callable, Dict, List, Tuple, TypedDict, Union @@ -2850,7 +2850,12 @@ class NotificationMessage(models.Model): def age(self): """Age of the message in seconds.""" - delta = now() - self.creation + # Add timezone information if TZ is enabled (in production mode mostly) + delta = now() - ( + self.creation.replace(tzinfo=timezone.utc) + if settings.USE_TZ + else self.creation + ) return delta.seconds def age_human(self): diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index 7f5a1ed54a..69136ad80c 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -33,6 +33,7 @@ class CompanyResource(InvenTreeResource): clean_model_instances = True +@admin.register(Company) class CompanyAdmin(ImportExportModelAdmin): """Admin class for the Company model.""" @@ -69,6 +70,7 @@ class SupplierPriceBreakInline(admin.TabularInline): model = SupplierPriceBreak +@admin.register(SupplierPart) class SupplierPartAdmin(ImportExportModelAdmin): """Admin class for the SupplierPart model.""" @@ -105,6 +107,7 @@ class ManufacturerPartResource(InvenTreeResource): manufacturer_name = Field(attribute='manufacturer__name', readonly=True) +@admin.register(ManufacturerPart) class ManufacturerPartAdmin(ImportExportModelAdmin): """Admin class for ManufacturerPart model.""" @@ -117,6 +120,7 @@ class ManufacturerPartAdmin(ImportExportModelAdmin): autocomplete_fields = ('part', 'manufacturer') +@admin.register(ManufacturerPartAttachment) class ManufacturerPartAttachmentAdmin(ImportExportModelAdmin): """Admin class for ManufacturerPartAttachment model.""" @@ -137,6 +141,7 @@ class ManufacturerPartParameterResource(InvenTreeResource): clean_model_instance = True +@admin.register(ManufacturerPartParameter) class ManufacturerPartParameterAdmin(ImportExportModelAdmin): """Admin class for ManufacturerPartParameter model.""" @@ -173,6 +178,7 @@ class SupplierPriceBreakResource(InvenTreeResource): MPN = Field(attribute='part__MPN', readonly=True) +@admin.register(SupplierPriceBreak) class SupplierPriceBreakAdmin(ImportExportModelAdmin): """Admin class for the SupplierPriceBreak model.""" @@ -197,6 +203,7 @@ class AddressResource(InvenTreeResource): company = Field(attribute='company', widget=widgets.ForeignKeyWidget(Company)) +@admin.register(Address) class AddressAdmin(ImportExportModelAdmin): """Admin class for the Address model.""" @@ -221,6 +228,7 @@ class ContactResource(InvenTreeResource): company = Field(attribute='company', widget=widgets.ForeignKeyWidget(Company)) +@admin.register(Contact) class ContactAdmin(ImportExportModelAdmin): """Admin class for the Contact model.""" @@ -229,15 +237,3 @@ class ContactAdmin(ImportExportModelAdmin): list_display = ('company', 'name', 'role', 'email', 'phone') search_fields = ['company', 'name', 'email'] - - -admin.site.register(Company, CompanyAdmin) -admin.site.register(SupplierPart, SupplierPartAdmin) -admin.site.register(SupplierPriceBreak, SupplierPriceBreakAdmin) - -admin.site.register(ManufacturerPart, ManufacturerPartAdmin) -admin.site.register(ManufacturerPartAttachment, ManufacturerPartAttachmentAdmin) -admin.site.register(ManufacturerPartParameter, ManufacturerPartParameterAdmin) - -admin.site.register(Address, AddressAdmin) -admin.site.register(Contact, ContactAdmin) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index f6779d31ca..db62dfb6ea 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -1362,8 +1362,8 @@ class OrderCalendarExport(ICalFeed): return super().__call__(request, *args, **kwargs) # No login yet - check in headers - if 'HTTP_AUTHORIZATION' in request.META: - auth = request.META['HTTP_AUTHORIZATION'].split() + if 'authorization' in request.headers: + auth = request.headers['authorization'].split() if len(auth) == 2: # NOTE: We are only support basic authentication for now. # diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 2eb3aecf0f..b9c076e1b8 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -78,7 +78,14 @@ class TotalPriceMixin(models.Model): """Update the total_price field when saved.""" # Recalculate total_price for this order self.update_total_price(commit=False) - super().save(*args, **kwargs) + + if hasattr(self, '_SAVING_TOTAL_PRICE') and self._SAVING_TOTAL_PRICE: + # Avoid recursion on save + return super().save(*args, **kwargs) + self._SAVING_TOTAL_PRICE = True + + # Save the object as we can not access foreign/m2m fields before saving + self.update_total_price(commit=True) total_price = InvenTreeModelMoneyField( null=True, @@ -136,6 +143,10 @@ class TotalPriceMixin(models.Model): total = Money(0, target_currency) + # Check if the order has been saved (otherwise we can't calculate the total price) + if self.pk is None: + return total + # order items for line in self.lines.all(): if not line.price: diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index a15bebce48..c971c3d6a5 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -634,7 +634,7 @@ class PurchaseOrderTest(OrderTest): response = self.client.get( reverse('api-po-so-calendar', kwargs={'ordertype': 'purchase-order'}), format='json', - HTTP_AUTHORIZATION=f'basic {base64_token}', + headers={'authorization': f'basic {base64_token}'}, ) self.assertEqual(response.status_code, 200) diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index 72afe05743..825495daa4 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -57,7 +57,8 @@ class PurchaseOrderTests(OrderViewTestCase): def test_po_export(self): """Export PurchaseOrder.""" response = self.client.get( - reverse('po-export', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest' + reverse('po-export', args=(1,)), + headers={'x-requested-with': 'XMLHttpRequest'}, ) # Response should be streaming-content (file download) diff --git a/InvenTree/part/filters.py b/InvenTree/part/filters.py index 15fb2afc8a..e16280dbe3 100644 --- a/InvenTree/part/filters.py +++ b/InvenTree/part/filters.py @@ -245,7 +245,9 @@ def annotate_variant_quantity(subquery: Q, reference: str = 'quantity'): Subquery( subquery.annotate( total=Func(F(reference), function='SUM', output_field=FloatField()) - ).values('total') + ) + .values('total') + .order_by() ), 0, output_field=FloatField(), @@ -270,7 +272,9 @@ def annotate_category_parts(): Subquery( subquery.annotate( total=Func(F('pk'), function='COUNT', output_field=IntegerField()) - ).values('total') + ) + .values('total') + .order_by() ), 0, output_field=IntegerField(), diff --git a/InvenTree/part/migrations/0020_auto_20190908_0404.py b/InvenTree/part/migrations/0020_auto_20190908_0404.py index 7766ba38f9..725ce67305 100644 --- a/InvenTree/part/migrations/0020_auto_20190908_0404.py +++ b/InvenTree/part/migrations/0020_auto_20190908_0404.py @@ -1,13 +1,6 @@ # Generated by Django 2.2.5 on 2019-09-08 04:04 from django.db import migrations -from part import models - - -def update_tree(apps, schema_editor): - # Update the PartCategory MPTT model - - models.PartCategory.objects.rebuild() class Migration(migrations.Migration): @@ -18,6 +11,4 @@ class Migration(migrations.Migration): ('part', '0019_auto_20190908_0404'), ] - operations = [ - migrations.RunPython(update_tree) - ] + operations = [] diff --git a/InvenTree/part/migrations/0039_auto_20200515_1127.py b/InvenTree/part/migrations/0039_auto_20200515_1127.py index ec97498148..a2a83d093c 100644 --- a/InvenTree/part/migrations/0039_auto_20200515_1127.py +++ b/InvenTree/part/migrations/0039_auto_20200515_1127.py @@ -2,13 +2,6 @@ from django.db import migrations, models -from part.models import Part - - -def update_tree(apps, schema_editor): - # Update the MPTT for Part model - Part.objects.rebuild() - class Migration(migrations.Migration): @@ -43,6 +36,4 @@ class Migration(migrations.Migration): field=models.PositiveIntegerField(db_index=True, default=0, editable=False), preserve_default=False, ), - - migrations.RunPython(update_tree, reverse_code=migrations.RunPython.noop) ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 31d4f68232..1dd0a0cbfd 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -452,6 +452,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) If the part image has been updated, then check if the "old" (previous) image is still used by another part. If not, it is considered "orphaned" and will be deleted. """ + _new = False if self.pk: try: previous = Part.objects.get(pk=self.pk) @@ -470,6 +471,8 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) previous.image.delete(save=False) except Part.DoesNotExist: pass + else: + _new = True self.full_clean() @@ -478,6 +481,10 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) except InvalidMove: raise ValidationError({'variant_of': _('Invalid choice for parent part')}) + if _new: + # Only run if the check was not run previously (due to not existing in the database) + self.ensure_trackable() + def __str__(self): """Return a string representation of the Part (for use in the admin interface).""" return f'{self.full_name} - {self.description}' @@ -827,6 +834,12 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) # Run custom validation for the name field self.validate_name() + if self.pk: + # Only run if the part already exists in the database + self.ensure_trackable() + + def ensure_trackable(self): + """Ensure that trackable is set correctly downline.""" if self.trackable: for part in self.get_used_in(): if not part.trackable: diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index ee8e10d978..c96e675405 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -108,7 +108,7 @@ class PartDetailTest(PartViewTestCase): """Test downloading a BOM for a valid part.""" response = self.client.get( reverse('api-bom-download', args=(1,)), - HTTP_X_REQUESTED_WITH='XMLHttpRequest', + headers={'x-requested-with': 'XMLHttpRequest'}, ) self.assertEqual(response.status_code, 200) self.assertIn('streaming_content', dir(response)) diff --git a/InvenTree/plugin/test_plugin.py b/InvenTree/plugin/test_plugin.py index cd76ce0704..557a86b75a 100644 --- a/InvenTree/plugin/test_plugin.py +++ b/InvenTree/plugin/test_plugin.py @@ -106,7 +106,6 @@ class InvenTreePluginTests(TestCase): LICENSE = 'MIT' cls.plugin_name = NameInvenTreePlugin() - cls.plugin_sample = SampleIntegrationPlugin() class VersionInvenTreePlugin(InvenTreePlugin): NAME = 'Version' @@ -140,7 +139,7 @@ class InvenTreePluginTests(TestCase): # is_sample self.assertEqual(self.plugin.is_sample, False) - self.assertEqual(self.plugin_sample.is_sample, True) + self.assertEqual(SampleIntegrationPlugin().is_sample, True) # slug self.assertEqual(self.plugin.slug, '') diff --git a/InvenTree/report/admin.py b/InvenTree/report/admin.py index 38edbb29c5..6a715f9134 100644 --- a/InvenTree/report/admin.py +++ b/InvenTree/report/admin.py @@ -15,31 +15,30 @@ from .models import ( ) +@admin.register( + BillOfMaterialsReport, + BuildReport, + PurchaseOrderReport, + ReturnOrderReport, + SalesOrderReport, + StockLocationReport, + TestReport, +) class ReportTemplateAdmin(admin.ModelAdmin): """Admin class for the various reporting models.""" list_display = ('name', 'description', 'template', 'filters', 'enabled', 'revision') +@admin.register(ReportSnippet) class ReportSnippetAdmin(admin.ModelAdmin): """Admin class for the ReportSnippet model.""" list_display = ('id', 'snippet', 'description') +@admin.register(ReportAsset) class ReportAssetAdmin(admin.ModelAdmin): """Admin class for the ReportAsset model.""" list_display = ('id', 'asset', 'description') - - -admin.site.register(ReportSnippet, ReportSnippetAdmin) -admin.site.register(ReportAsset, ReportAssetAdmin) - -admin.site.register(StockLocationReport, ReportTemplateAdmin) -admin.site.register(TestReport, ReportTemplateAdmin) -admin.site.register(BuildReport, ReportTemplateAdmin) -admin.site.register(BillOfMaterialsReport, ReportTemplateAdmin) -admin.site.register(PurchaseOrderReport, ReportTemplateAdmin) -admin.site.register(ReturnOrderReport, ReportTemplateAdmin) -admin.site.register(SalesOrderReport, ReportTemplateAdmin) diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index e1214ffebf..32d62d33e3 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -85,6 +85,7 @@ class LocationInline(admin.TabularInline): model = StockLocation +@admin.register(StockLocation) class LocationAdmin(ImportExportModelAdmin): """Admin class for Location.""" @@ -99,6 +100,7 @@ class LocationAdmin(ImportExportModelAdmin): autocomplete_fields = ['parent'] +@admin.register(StockLocationType) class LocationTypeAdmin(admin.ModelAdmin): """Admin class for StockLocationType.""" @@ -268,6 +270,7 @@ class StockItemResource(InvenTreeResource): StockItem.objects.rebuild() +@admin.register(StockItem) class StockItemAdmin(ImportExportModelAdmin): """Admin class for StockItem.""" @@ -292,6 +295,7 @@ class StockItemAdmin(ImportExportModelAdmin): ] +@admin.register(StockItemAttachment) class StockAttachmentAdmin(admin.ModelAdmin): """Admin class for StockAttachment.""" @@ -300,6 +304,7 @@ class StockAttachmentAdmin(admin.ModelAdmin): autocomplete_fields = ['stock_item'] +@admin.register(StockItemTracking) class StockTrackingAdmin(ImportExportModelAdmin): """Admin class for StockTracking.""" @@ -308,17 +313,10 @@ class StockTrackingAdmin(ImportExportModelAdmin): autocomplete_fields = ['item'] +@admin.register(StockItemTestResult) class StockItemTestResultAdmin(admin.ModelAdmin): """Admin class for StockItemTestResult.""" list_display = ('stock_item', 'test', 'result', 'value') autocomplete_fields = ['stock_item'] - - -admin.site.register(StockLocation, LocationAdmin) -admin.site.register(StockLocationType, LocationTypeAdmin) -admin.site.register(StockItem, StockItemAdmin) -admin.site.register(StockItemTracking, StockTrackingAdmin) -admin.site.register(StockItemAttachment, StockAttachmentAdmin) -admin.site.register(StockItemTestResult, StockItemTestResultAdmin) diff --git a/InvenTree/stock/filters.py b/InvenTree/stock/filters.py index c992fc05a0..8a214c0622 100644 --- a/InvenTree/stock/filters.py +++ b/InvenTree/stock/filters.py @@ -30,7 +30,9 @@ def annotate_location_items(filter: Q = None): Subquery( subquery.annotate( total=Func(F('pk'), function='COUNT', output_field=IntegerField()) - ).values('total') + ) + .values('total') + .order_by() ), 0, output_field=IntegerField(), @@ -50,7 +52,9 @@ def annotate_child_items(): Subquery( child_stock_query.annotate( count=Func(F('pk'), function='COUNT', output_field=IntegerField()) - ).values('count') + ) + .values('count') + .order_by() ), 0, output_field=IntegerField(), diff --git a/InvenTree/stock/migrations/0012_auto_20190908_0405.py b/InvenTree/stock/migrations/0012_auto_20190908_0405.py index 5f52e75093..699d3d4f90 100644 --- a/InvenTree/stock/migrations/0012_auto_20190908_0405.py +++ b/InvenTree/stock/migrations/0012_auto_20190908_0405.py @@ -2,13 +2,6 @@ from django.db import migrations -from stock import models - - -def update_tree(apps, schema_editor): - # Update the StockLocation MPTT model - - models.StockLocation.objects.rebuild() class Migration(migrations.Migration): @@ -19,6 +12,4 @@ class Migration(migrations.Migration): ('stock', '0011_auto_20190908_0404'), ] - operations = [ - migrations.RunPython(update_tree) - ] + operations = [] diff --git a/InvenTree/stock/migrations/0022_auto_20200217_1109.py b/InvenTree/stock/migrations/0022_auto_20200217_1109.py index f86fd51691..e42c1bcdd2 100644 --- a/InvenTree/stock/migrations/0022_auto_20200217_1109.py +++ b/InvenTree/stock/migrations/0022_auto_20200217_1109.py @@ -1,13 +1,6 @@ # Generated by Django 2.2.9 on 2020-02-17 11:09 from django.db import migrations -from stock import models - - -def update_stock_item_tree(apps, schema_editor): - # Update the StockItem MPTT model - - models.StockItem.objects.rebuild() class Migration(migrations.Migration): @@ -18,6 +11,4 @@ class Migration(migrations.Migration): ('stock', '0021_auto_20200215_2232'), ] - operations = [ - migrations.RunPython(update_stock_item_tree) - ] + operations = [] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index ec6251cb79..72d976ed50 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -107,7 +107,8 @@ class StockLocationManager(TreeManager): - Joins the StockLocationType by default for speedier icon access """ - return super().get_queryset().select_related('location_type') + # return super().get_queryset().select_related("location_type") + return super().get_queryset() class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree): diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 6d60460829..3916cfb382 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -13,6 +13,7 @@ from users.models import ApiToken, Owner, RuleSet User = get_user_model() +@admin.register(ApiToken) class ApiTokenAdmin(admin.ModelAdmin): """Admin class for the ApiToken model.""" @@ -288,6 +289,7 @@ class InvenTreeUserAdmin(UserAdmin): ) +@admin.register(Owner) class OwnerAdmin(admin.ModelAdmin): """Custom admin interface for the Owner model.""" @@ -299,7 +301,3 @@ admin.site.register(Group, RoleGroupAdmin) admin.site.unregister(User) admin.site.register(User, InvenTreeUserAdmin) - -admin.site.register(Owner, OwnerAdmin) - -admin.site.register(ApiToken, ApiTokenAdmin) diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index b5a8728f53..6d97ee3ee8 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -262,7 +262,7 @@ class GetAuthToken(APIView): ) # Add some metadata about the request - token.set_metadata('user_agent', request.META.get('HTTP_USER_AGENT', '')) + token.set_metadata('user_agent', request.headers.get('user-agent', '')) token.set_metadata('remote_addr', request.META.get('REMOTE_ADDR', '')) token.set_metadata('remote_host', request.META.get('REMOTE_HOST', '')) token.set_metadata('remote_user', request.META.get('REMOTE_USER', '')) diff --git a/requirements-dev.txt b/requirements-dev.txt index 6d34271c89..b1591cbea8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,7 +10,7 @@ asgiref==3.7.2 # django build==1.0.3 # via pip-tools -certifi==2023.7.22 +certifi==2023.11.17 # via # -c requirements.txt # requests @@ -33,13 +33,13 @@ coverage[toml]==5.5 # coveralls coveralls==2.1.2 # via -r requirements-dev.in -cryptography==41.0.6 +cryptography==41.0.7 # via # -c requirements.txt # pdfminer-six -distlib==0.3.7 +distlib==0.3.8 # via virtualenv -django==3.2.23 +django==4.2.9 # via # -c requirements.txt # django-slowtests @@ -53,7 +53,7 @@ filelock==3.13.1 # via virtualenv identify==2.5.31 # via pre-commit -idna==3.4 +idna==3.6 # via # -c requirements.txt # requests @@ -61,7 +61,7 @@ importlib-metadata==6.8.0 # via # -c requirements.txt # build -isort==5.12.0 +isort==5.13.2 # via -r requirements-dev.in nodeenv==1.8.0 # via pre-commit @@ -69,13 +69,13 @@ packaging==23.2 # via # -c requirements.txt # build -pdfminer-six==20221105 +pdfminer-six==20231228 # via -r requirements-dev.in pip-tools==7.3.0 # via -r requirements-dev.in -platformdirs==3.11.0 +platformdirs==4.1.0 # via virtualenv -pre-commit==3.5.0 +pre-commit==3.6.0 # via -r requirements-dev.in pycparser==2.21 # via @@ -83,10 +83,6 @@ pycparser==2.21 # cffi pyproject-hooks==1.0.0 # via build -pytz==2023.3.post1 - # via - # -c requirements.txt - # django pyyaml==6.0.1 # via # -c requirements.txt @@ -106,20 +102,20 @@ tomli==2.0.1 # build # pip-tools # pyproject-hooks -typing-extensions==4.8.0 +typing-extensions==4.9.0 # via # -c requirements.txt # asgiref # django-test-migrations -urllib3==2.0.7 +urllib3==2.1.0 # via # -c requirements.txt # requests -virtualenv==20.24.6 +virtualenv==20.25.0 # via pre-commit -wheel==0.41.3 +wheel==0.42.0 # via pip-tools -zipp==3.16.0 +zipp==3.16.2 # via # -c requirements.txt # importlib-metadata diff --git a/requirements.in b/requirements.in index ce2a2a36bb..db429e2fe7 100644 --- a/requirements.in +++ b/requirements.in @@ -1,5 +1,5 @@ # Please keep this list sorted - if you pin a version provide a reason -Django>=3.2.14,<4 # Django package +Django<5.0 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2 # Core cryptographic functionality django-allauth # SSO for external providers via OpenID @@ -13,11 +13,13 @@ django-filter # Extended filtering options django-flags # Feature flags django-formtools # Form wizard tools django-ical # iCal export for calendar views -django-import-export>=3.3.1 # Data import / export for admin interface +django-import-export # Data import / export for admin interface django-maintenance-mode # Shut down application while reloading etc. django-markdownify # Markdown rendering +django-mptt # Modified Preorder Tree Traversal +django-markdownify # Markdown rendering django-money>=3.0.0,<3.3.0 # Django app for currency management # FIXED 2023-10-31 3.3.0 breaks due to https://github.com/django-money/django-money/issues/731 -django-mptt==0.11.0 # Modified Preorder Tree Traversal +django-mptt # Modified Preorder Tree Traversal django-redis>=5.0.0 # Redis integration django-q2 # Background task scheduling django-q-sentry # sentry.io integration for django-q diff --git a/requirements.txt b/requirements.txt index d2048a13f1..2788ecd3fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,14 +5,16 @@ # pip-compile --output-file=requirements.txt requirements.in # asgiref==3.7.2 - # via django + # via + # django + # django-cors-headers async-timeout==4.0.3 # via redis -attrs==23.1.0 +attrs==23.2.0 # via # jsonschema # referencing -babel==2.13.1 +babel==2.14.0 # via py-moneyed backoff==2.2.1 # via @@ -25,7 +27,7 @@ bleach[css]==6.1.0 # django-markdownify brotli==1.1.0 # via fonttools -certifi==2023.7.22 +certifi==2023.11.17 # via # requests # sentry-sdk @@ -39,7 +41,7 @@ coreapi==2.3.3 # via -r requirements.in coreschema==0.0.4 # via coreapi -cryptography==41.0.6 +cryptography==41.0.7 # via # -r requirements.in # djangorestframework-simplejwt @@ -59,7 +61,7 @@ diff-match-patch==20230430 # via django-import-export dj-rest-auth==5.0.2 # via -r requirements.in -django==3.2.23 +django==4.2.9 # via # -r requirements.in # dj-rest-auth @@ -76,7 +78,6 @@ django==3.2.23 # django-js-asset # django-markdownify # django-money - # django-mptt # django-otp # django-picklefield # django-q2 @@ -101,7 +102,7 @@ django-allauth-2fa==0.11.1 # via -r requirements.in django-cleanup==8.0.0 # via -r requirements.in -django-cors-headers==4.3.0 +django-cors-headers==4.3.1 # via -r requirements.in django-crispy-forms==1.14.0 # via -r requirements.in @@ -109,17 +110,17 @@ django-dbbackup==4.0.2 # via -r requirements.in django-error-report-2==0.4.2 # via -r requirements.in -django-filter==23.3 +django-filter==23.5 # via -r requirements.in django-flags==5.0.13 # via -r requirements.in -django-formtools==2.4.1 +django-formtools==2.5.1 # via -r requirements.in django-ical==1.9.2 # via -r requirements.in -django-import-export==3.3.1 +django-import-export==3.3.5 # via -r requirements.in -django-js-asset==2.1.0 +django-js-asset==2.2.0 # via django-mptt django-maintenance-mode==0.21.0 # via -r requirements.in @@ -127,9 +128,9 @@ django-markdownify==0.9.3 # via -r requirements.in django-money==3.2.0 # via -r requirements.in -django-mptt==0.11.0 +django-mptt==0.16.0 # via -r requirements.in -django-otp==1.2.4 +django-otp==1.3.0 # via django-allauth-2fa django-picklefield==3.1 # via django-q2 @@ -141,7 +142,7 @@ django-recurrence==1.11.1 # via django-ical django-redis==5.4.0 # via -r requirements.in -django-sesame==3.2.1 +django-sesame==3.2.2 # via -r requirements.in django-sql-utils==0.7.0 # via -r requirements.in @@ -149,11 +150,11 @@ django-sslserver==0.22 # via -r requirements.in django-stdimage==6.0.2 # via -r requirements.in -django-taggit==4.0.0 +django-taggit==5.0.1 # via -r requirements.in django-user-sessions==2.0.0 # via -r requirements.in -django-weasyprint==2.2.1 +django-weasyprint==2.2.2 # via -r requirements.in django-xforwardedfor-middleware==2.0 # via -r requirements.in @@ -163,15 +164,15 @@ djangorestframework==3.14.0 # dj-rest-auth # djangorestframework-simplejwt # drf-spectacular -djangorestframework-simplejwt[crypto]==5.3.0 +djangorestframework-simplejwt[crypto]==5.3.1 # via -r requirements.in -drf-spectacular==0.26.5 +drf-spectacular==0.27.0 # via -r requirements.in -dulwich==0.21.6 +dulwich==0.21.7 # via -r requirements.in et-xmlfile==1.1.0 # via openpyxl -feedparser==6.0.10 +feedparser==6.0.11 # via -r requirements.in fonttools[woff]==4.44.0 # via @@ -189,7 +190,7 @@ html5lib==1.1 # via weasyprint icalendar==5.0.11 # via django-ical -idna==3.4 +idna==3.6 # via requests importlib-metadata==6.8.0 # via @@ -202,9 +203,9 @@ itypes==1.2.0 # via coreapi jinja2==3.1.3 # via coreschema -jsonschema==4.19.2 +jsonschema==4.20.0 # via drf-spectacular -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.12.1 # via jsonschema markdown==3.5.1 # via django-markdownify @@ -277,7 +278,7 @@ opentelemetry-util-http==0.43b0 # opentelemetry-instrumentation-wsgi packaging==23.2 # via gunicorn -pdf2image==1.16.3 +pdf2image==1.17.0 # via -r requirements.in pillow==10.2.0 # via @@ -316,13 +317,12 @@ python-dateutil==2.8.2 # icalendar python-dotenv==1.0.0 # via -r requirements.in -python-fsutil==0.12.0 +python-fsutil==0.13.0 # via django-maintenance-mode python3-openid==3.2.0 # via django-allauth pytz==2023.3.post1 # via - # django # django-dbbackup # djangorestframework # icalendar @@ -339,11 +339,11 @@ rapidfuzz==0.7.6 # via -r requirements.in redis==5.0.1 # via django-redis -referencing==0.30.2 +referencing==0.32.1 # via # jsonschema # jsonschema-specifications -regex==2023.10.3 +regex==2023.12.25 # via -r requirements.in requests==2.31.0 # via @@ -353,11 +353,11 @@ requests==2.31.0 # requests-oauthlib requests-oauthlib==1.3.1 # via django-allauth -rpds-py==0.12.0 +rpds-py==0.16.2 # via # jsonschema # referencing -sentry-sdk==1.34.0 +sentry-sdk==1.39.2 # via # -r requirements.in # django-q-sentry @@ -381,9 +381,10 @@ tinycss2==1.2.1 # bleach # cssselect2 # weasyprint -typing-extensions==4.8.0 +typing-extensions==4.9.0 # via # asgiref + # drf-spectacular # opentelemetry-sdk # py-moneyed # qrcode @@ -391,12 +392,12 @@ uritemplate==4.1.1 # via # coreapi # drf-spectacular -urllib3==2.0.7 +urllib3==2.1.0 # via # dulwich # requests # sentry-sdk -weasyprint==60.1 +weasyprint==60.2 # via # -r requirements.in # django-weasyprint @@ -415,7 +416,7 @@ xlrd==2.0.1 # via tablib xlwt==1.3.0 # via tablib -zipp==3.16.0 +zipp==3.16.2 # via importlib-metadata zopfli==0.2.3 # via fonttools