mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	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 <oliver.henry.walters@gmail.com>
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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), | ||||
|     ] | ||||
|   | ||||
| @@ -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 = [] | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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. | ||||
|                 # | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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(), | ||||
|   | ||||
| @@ -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 = [] | ||||
|   | ||||
| @@ -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) | ||||
|     ] | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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)) | ||||
|   | ||||
| @@ -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, '') | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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(), | ||||
|   | ||||
| @@ -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 = [] | ||||
|   | ||||
| @@ -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 = [] | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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', '')) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user