mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Metadata fix (#4725)
* Add 'clean' method to MetadataMixin class - Ensure that the "metadata" is a valid dict object * Add "overwrite" option for set_metadata method * Update unit tests * full_clean -> clean * Cleanup * Fix for MetadataMixin * Updates for unit tests * Test
This commit is contained in:
		| @@ -60,6 +60,27 @@ class MetadataMixin(models.Model): | |||||||
|         """Meta for MetadataMixin.""" |         """Meta for MetadataMixin.""" | ||||||
|         abstract = True |         abstract = True | ||||||
|  |  | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """Save the model instance, and perform validation on the metadata field.""" | ||||||
|  |         self.validate_metadata() | ||||||
|  |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def clean(self, *args, **kwargs): | ||||||
|  |         """Perform model validation on the metadata field.""" | ||||||
|  |         super().clean() | ||||||
|  |  | ||||||
|  |         self.validate_metadata() | ||||||
|  |  | ||||||
|  |     def validate_metadata(self): | ||||||
|  |         """Validate the metadata field.""" | ||||||
|  |  | ||||||
|  |         # Ensure that the 'metadata' field is a valid dict object | ||||||
|  |         if self.metadata is None: | ||||||
|  |             self.metadata = {} | ||||||
|  |  | ||||||
|  |         if type(self.metadata) is not dict: | ||||||
|  |             raise ValidationError({'metadata': _('Metadata must be a python dict object')}) | ||||||
|  |  | ||||||
|     metadata = models.JSONField( |     metadata = models.JSONField( | ||||||
|         blank=True, null=True, |         blank=True, null=True, | ||||||
|         verbose_name=_('Plugin Metadata'), |         verbose_name=_('Plugin Metadata'), | ||||||
| @@ -80,16 +101,16 @@ class MetadataMixin(models.Model): | |||||||
|  |  | ||||||
|         return self.metadata.get(key, backup_value) |         return self.metadata.get(key, backup_value) | ||||||
|  |  | ||||||
|     def set_metadata(self, key: str, data, commit: bool = True): |     def set_metadata(self, key: str, data, commit: bool = True, overwrite: bool = False): | ||||||
|         """Save the provided metadata under the provided key. |         """Save the provided metadata under the provided key. | ||||||
|  |  | ||||||
|         Args: |         Args: | ||||||
|             key (str): Key for saving metadata |             key (str): Key for saving metadata | ||||||
|             data (Any): Data object to save - must be able to be rendered as a JSON string |             data (Any): Data object to save - must be able to be rendered as a JSON string | ||||||
|             commit (bool, optional): If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted. Defaults to True. |             commit (bool, optional): If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted. Defaults to True. | ||||||
|  |             overwrite (bool): If true, delete existing metadata before adding new value | ||||||
|         """ |         """ | ||||||
|         if self.metadata is None: |         if overwrite or self.metadata is None: | ||||||
|             # Handle a null field value |  | ||||||
|             self.metadata = {} |             self.metadata = {} | ||||||
|  |  | ||||||
|         self.metadata[key] = data |         self.metadata[key] = data | ||||||
|   | |||||||
| @@ -579,7 +579,7 @@ class BuildTest(BuildTestBase): | |||||||
|  |  | ||||||
|         for model in [Build, BuildItem]: |         for model in [Build, BuildItem]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |             self.assertEqual(len(p.metadata.keys()), 0) | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -135,7 +135,7 @@ class CompanySimpleTest(TestCase): | |||||||
|     def test_metadata(self): |     def test_metadata(self): | ||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         p = Company.objects.first() |         p = Company.objects.first() | ||||||
|         self.assertIsNone(p.metadata) |         self.assertIn(p.metadata, [None, {}]) | ||||||
|  |  | ||||||
|         self.assertIsNone(p.get_metadata('test')) |         self.assertIsNone(p.get_metadata('test')) | ||||||
|         self.assertEqual(p.get_metadata('test', backup_value=123), 123) |         self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
| @@ -227,7 +227,7 @@ class ManufacturerPartSimpleTest(TestCase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [ManufacturerPart, SupplierPart]: |         for model in [ManufacturerPart, SupplierPart]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |             self.assertIn(p.metadata, [None, {}]) | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -135,7 +135,6 @@ class LabelTest(InvenTreeAPITestCase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [StockItemLabel, StockLocationLabel, PartLabel]: |         for model in [StockItemLabel, StockLocationLabel, PartLabel]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -303,8 +303,6 @@ class SalesOrderTest(TestCase): | |||||||
|         for model in [SalesOrder, SalesOrderLineItem, SalesOrderExtraLine, SalesOrderShipment]: |         for model in [SalesOrder, SalesOrderLineItem, SalesOrderExtraLine, SalesOrderShipment]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|  |  | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -390,7 +390,14 @@ class OrderTest(TestCase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderExtraLine]: |         for model in [PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderExtraLine]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |             # Setting metadata to something *other* than a dict will fail | ||||||
|  |             with self.assertRaises(django_exceptions.ValidationError): | ||||||
|  |                 p.metadata = 'test' | ||||||
|  |                 p.save() | ||||||
|  |  | ||||||
|  |             # Reset metadata to known state | ||||||
|  |             p.metadata = {} | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -250,7 +250,6 @@ class BomItemTest(TestCase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [BomItem]: |         for model in [BomItem]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -47,7 +47,6 @@ class TestParams(TestCase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [PartParameterTemplate]: |         for model in [PartParameterTemplate]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -277,7 +277,6 @@ class PartTest(TestCase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [Part]: |         for model in [Part]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -299,7 +299,7 @@ class ReportTest(InvenTreeAPITestCase): | |||||||
|         if self.model is not None: |         if self.model is not None: | ||||||
|             p = self.model.objects.first() |             p = self.model.objects.first() | ||||||
|  |  | ||||||
|             self.assertIsNone(p.metadata) |             self.assertEqual(p.metadata, {}) | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
| @@ -921,7 +921,6 @@ class StockTest(StockTestBase): | |||||||
|         """Unit tests for the metadata field.""" |         """Unit tests for the metadata field.""" | ||||||
|         for model in [StockItem, StockLocation]: |         for model in [StockItem, StockLocation]: | ||||||
|             p = model.objects.first() |             p = model.objects.first() | ||||||
|             self.assertIsNone(p.metadata) |  | ||||||
|  |  | ||||||
|             self.assertIsNone(p.get_metadata('test')) |             self.assertIsNone(p.get_metadata('test')) | ||||||
|             self.assertEqual(p.get_metadata('test', backup_value=123), 123) |             self.assertEqual(p.get_metadata('test', backup_value=123), 123) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user