diff --git a/InvenTree/InvenTree/mixins.py b/InvenTree/InvenTree/mixins.py index e77027734b..41eb8a5ec6 100644 --- a/InvenTree/InvenTree/mixins.py +++ b/InvenTree/InvenTree/mixins.py @@ -9,6 +9,59 @@ from InvenTree.fields import InvenTreeNotesField from InvenTree.helpers import remove_non_printable_characters, strip_html_tags +class DiffMixin: + """Mixin which can be used to determine which fields have changed, compared to the instance saved to the database.""" + + def get_db_instance(self): + """Return the instance of the object saved in the database. + + Returns: + object: Instance of the object saved in the database + """ + + if self.pk: + try: + return self.__class__.objects.get(pk=self.pk) + except self.__class__.DoesNotExist: + pass + + return None + + def get_field_deltas(self): + """Return a dict of field deltas. + + Compares the current instance with the instance saved in the database, + and returns a dict of fields which have changed. + + Returns: + dict: Dict of field deltas + """ + + db_instance = self.get_db_instance() + + if db_instance is None: + return {} + + deltas = {} + + for field in self._meta.fields: + if field.name == 'id': + continue + + if getattr(self, field.name) != getattr(db_instance, field.name): + deltas[field.name] = { + 'old': getattr(db_instance, field.name), + 'new': getattr(self, field.name), + } + + return deltas + + def has_field_changed(self, field_name): + """Determine if a particular field has changed.""" + + return field_name in self.get_field_deltas() + + class CleanMixin(): """Model mixin class which cleans inputs using the Mozilla bleach tools.""" diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index e051f23bc6..238583421b 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -28,6 +28,7 @@ from build.validators import generate_next_build_reference, validate_build_order import InvenTree.fields import InvenTree.helpers import InvenTree.helpers_model +import InvenTree.mixins import InvenTree.models import InvenTree.ready import InvenTree.tasks @@ -44,7 +45,7 @@ import users.models logger = logging.getLogger('inventree') -class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNotesMixin, InvenTree.models.MetadataMixin, InvenTree.models.ReferenceIndexingMixin): +class Build(MPTTModel, InvenTree.mixins.DiffMixin, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNotesMixin, InvenTree.models.MetadataMixin, InvenTree.models.ReferenceIndexingMixin): """A Build object organises the creation of new StockItem objects from other existing StockItem objects. Attributes: @@ -108,6 +109,12 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models. self.validate_reference_field(self.reference) self.reference_int = self.rebuild_reference_field(self.reference) + # Prevent changing target part after creation + if self.has_field_changed('part'): + raise ValidationError({ + 'part': _('Build order part cannot be changed') + }) + try: super().save(*args, **kwargs) except InvalidMove: diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index 9569aa0870..d8300d0958 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -471,6 +471,28 @@ class BuildTest(BuildTestBase): # Check that the "consumed_by" item count has increased self.assertEqual(StockItem.objects.filter(consumed_by=self.build).count(), n + 8) + def test_change_part(self): + """Try to change target part after creating a build""" + + bo = Build.objects.create( + reference='BO-9999', + title='Some new build', + part=self.assembly, + quantity=5, + issued_by=get_user_model().objects.get(pk=1), + ) + + assembly_2 = Part.objects.create( + name="Another assembly", + description="A different assembly", + assembly=True, + ) + + # Should not be able to change the part after the Build is saved + with self.assertRaises(ValidationError): + bo.part = assembly_2 + bo.save() + def test_cancel(self): """Test cancellation of the build""" # TODO diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index b37c2fcab9..6ef72a04f1 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -130,6 +130,9 @@ function editBuildOrder(pk) { var fields = buildFormFields(); + // Cannot edit "part" field after creation + delete fields['part']; + constructForm(`{% url "api-build-list" %}${pk}/`, { fields: fields, reload: true,