From 0b26d68d0feca4368aebd4699c367ac8e6947461 Mon Sep 17 00:00:00 2001 From: eeintech Date: Fri, 16 Oct 2020 15:29:58 -0500 Subject: [PATCH] Added admin view, improved validation of part related relationship --- InvenTree/part/admin.py | 8 ++++- InvenTree/part/models.py | 36 +++++++++++++++++----- InvenTree/part/templates/part/related.html | 2 ++ InvenTree/part/test_views.py | 23 ++++++++++++++ InvenTree/part/views.py | 6 ++-- InvenTree/users/models.py | 1 + 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index f0c9e3f233..7476197547 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -9,7 +9,7 @@ from import_export.fields import Field import import_export.widgets as widgets from .models import PartCategory, Part -from .models import PartAttachment, PartStar +from .models import PartAttachment, PartStar, PartRelated from .models import BomItem from .models import PartParameterTemplate, PartParameter from .models import PartTestTemplate @@ -121,6 +121,11 @@ class PartCategoryAdmin(ImportExportModelAdmin): search_fields = ('name', 'description') +class PartRelatedAdmin(admin.ModelAdmin): + ''' Class to manage PartRelated objects ''' + pass + + class PartAttachmentAdmin(admin.ModelAdmin): list_display = ('part', 'attachment', 'comment') @@ -279,6 +284,7 @@ class PartSellPriceBreakAdmin(admin.ModelAdmin): admin.site.register(Part, PartAdmin) admin.site.register(PartCategory, PartCategoryAdmin) +admin.site.register(PartRelated, PartRelatedAdmin) admin.site.register(PartAttachment, PartAttachmentAdmin) admin.site.register(PartStar, PartStarAdmin) admin.site.register(BomItem, BomItemAdmin) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index bf979bfbb5..db0be1952f 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1754,16 +1754,18 @@ class BomItem(models.Model): class PartRelated(models.Model): """ Store and handle related parts (eg. mating connector, crimps, etc.) """ - part_1 = models.ForeignKey(Part, related_name='related_parts_1', on_delete=models.DO_NOTHING) + part_1 = models.ForeignKey(Part, related_name='related_parts_1', + on_delete=models.DO_NOTHING) - part_2 = models.ForeignKey(Part, related_name='related_parts_2', on_delete=models.DO_NOTHING, - help_text=_('Choose Related Part')) + part_2 = models.ForeignKey(Part, related_name='related_parts_2', + on_delete=models.DO_NOTHING, + help_text=_('Select Related Part')) def __str__(self): - return f'{self.part_1} <-> {self.part_2}' + return f'{self.part_1} <--> {self.part_2}' - def create_relationship(self, part_1, part_2): - ''' Create relationship between two parts ''' + def validate(self, part_1, part_2): + ''' Validate that the two parts relationship is unique ''' validate = True @@ -1771,15 +1773,35 @@ class PartRelated(models.Model): related_parts = PartRelated.objects.all() # Check if part exist and there are not the same part - if (part_1 in parts and part_2 in parts) and (part_1 is not part_2): + if (part_1 in parts and part_2 in parts) and (part_1.pk != part_2.pk): # Check if relation exists already for relation in related_parts: if (part_1 == relation.part_1 and part_2 == relation.part_2) \ or (part_1 == relation.part_2 and part_2 == relation.part_1): validate = False + break else: validate = False + return validate + + def clean(self): + ''' Overwrite clean method to check that relation is unique ''' + + validate = self.validate(self.part_1, self.part_2) + + if not validate: + error_message = _('Error creating relationship: check that ' + 'the part is not related to itself ' + 'and that the relationship is unique') + + raise ValidationError(error_message) + + def create_relationship(self, part_1, part_2): + ''' Create relationship between two parts ''' + + validate = self.validate(part_1, part_2) + if validate: # Add relationship self.part_1 = part_1 diff --git a/InvenTree/part/templates/part/related.html b/InvenTree/part/templates/part/related.html index 8fd167b592..8c5cc074c8 100644 --- a/InvenTree/part/templates/part/related.html +++ b/InvenTree/part/templates/part/related.html @@ -11,10 +11,12 @@
+ {% if roles.part.change %} + {% endif %}
diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index d8c345d243..1ad5c33e45 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -201,6 +201,29 @@ class PartTests(PartViewTestCase): self.assertEqual(response.status_code, 200) +class PartRelatedTests(PartViewTestCase): + + def test_valid_create(self): + """ test creation of an attachment for a valid part """ + + response = self.client.get(reverse('part-related-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # TODO - Create a new attachment using this view + + def test_invalid_create(self): + """ test creation of an attachment for an invalid part """ + + # TODO + pass + + def test_edit(self): + """ test editing an attachment """ + + # TODO + pass + + class PartAttachmentTests(PartViewTestCase): def test_valid_create(self): diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 6ae782ab2c..b4d8839e13 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -113,10 +113,12 @@ class PartRelatedCreate(AjaxCreateView): # Get existing related parts related_parts = [related_part[1].pk for related_part in parent_part.get_related_parts()] - # Build updated choice list excluding parts already related to parent part + # Build updated choice list excluding + # - parts already related to parent part + # - the parent part itself updated_choices = [] for choice in form.fields["part_2"].choices: - if choice[0] not in related_parts: + if (choice[0] not in related_parts) and (choice[0] != parent_part.pk): updated_choices.append(choice) # Update choices for related part diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index d3c713d07d..d1a3a3b084 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -57,6 +57,7 @@ class RuleSet(models.Model): 'part_parttesttemplate', 'part_partparametertemplate', 'part_partparameter', + 'part_partrelated', ], 'stock': [ 'stock_stockitem',