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