From 6c7b648133352e5c03856a82d6ab772b17eb66e9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 3 Jan 2021 23:06:51 +1100 Subject: [PATCH] Implement global settings for assembly and template values of Part model --- InvenTree/part/forms.py | 3 + .../migrations/0061_auto_20210103_2306.py | 74 +++++++++++++++ InvenTree/part/models.py | 90 +++++++++++++------ InvenTree/part/settings.py | 16 ++++ InvenTree/part/test_part.py | 4 + 5 files changed, 160 insertions(+), 27 deletions(-) create mode 100644 InvenTree/part/migrations/0061_auto_20210103_2306.py diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 1cbbec0b42..458667f3fe 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -230,6 +230,9 @@ class EditPartForm(HelperForm): 'default_supplier', 'units', 'minimum_stock', + 'component', + 'assembly', + 'is_template', 'trackable', 'purchaseable', 'salable', diff --git a/InvenTree/part/migrations/0061_auto_20210103_2306.py b/InvenTree/part/migrations/0061_auto_20210103_2306.py new file mode 100644 index 0000000000..fa1f8c04c6 --- /dev/null +++ b/InvenTree/part/migrations/0061_auto_20210103_2306.py @@ -0,0 +1,74 @@ +# Generated by Django 3.0.7 on 2021-01-03 12:06 + +import InvenTree.fields +import InvenTree.validators +from django.db import migrations, models +import django.db.models.deletion +import mptt.fields +import part.settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0055_auto_20201117_1453'), + ('part', '0060_merge_20201112_1722'), + ] + + operations = [ + migrations.AlterField( + model_name='part', + name='IPN', + field=models.CharField(blank=True, help_text='Internal Part Number', max_length=100, null=True, validators=[InvenTree.validators.validate_part_ipn], verbose_name='IPN'), + ), + migrations.AlterField( + model_name='part', + name='assembly', + field=models.BooleanField(default=part.settings.part_assembly_default, help_text='Can this part be built from other parts?', verbose_name='Assembly'), + ), + migrations.AlterField( + model_name='part', + name='category', + field=mptt.fields.TreeForeignKey(blank=True, help_text='Part category', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='parts', to='part.PartCategory', verbose_name='Category'), + ), + migrations.AlterField( + model_name='part', + name='default_location', + field=mptt.fields.TreeForeignKey(blank=True, help_text='Where is this item normally stored?', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_parts', to='stock.StockLocation', verbose_name='Default Location'), + ), + migrations.AlterField( + model_name='part', + name='description', + field=models.CharField(help_text='Part description', max_length=250, verbose_name='Description'), + ), + migrations.AlterField( + model_name='part', + name='is_template', + field=models.BooleanField(default=part.settings.part_template_default, help_text='Is this part a template part?', verbose_name='Is Template'), + ), + migrations.AlterField( + model_name='part', + name='keywords', + field=models.CharField(blank=True, help_text='Part keywords to improve visibility in search results', max_length=250, null=True, verbose_name='Keywords'), + ), + migrations.AlterField( + model_name='part', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'), + ), + migrations.AlterField( + model_name='part', + name='name', + field=models.CharField(help_text='Part name', max_length=100, validators=[InvenTree.validators.validate_part_name], verbose_name='Name'), + ), + migrations.AlterField( + model_name='part', + name='revision', + field=models.CharField(blank=True, help_text='Part revision or version number', max_length=100, null=True, verbose_name='Revision'), + ), + migrations.AlterField( + model_name='part', + name='variant_of', + field=models.ForeignKey(blank=True, help_text='Is this part a variant of another part?', limit_choices_to={'active': True, 'is_template': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='variants', to='part.Part', verbose_name='Variant Of'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index b9d63979e7..bcb9c99749 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -640,36 +640,69 @@ class Part(MPTTModel): parent_part.clean() parent_part.save() - name = models.CharField(max_length=100, blank=False, - help_text=_('Part name'), - validators=[validators.validate_part_name] - ) + name = models.CharField( + max_length=100, blank=False, + help_text=_('Part name'), + verbose_name=_('Name'), + validators=[validators.validate_part_name] + ) - is_template = models.BooleanField(default=False, help_text=_('Is this part a template part?')) + is_template = models.BooleanField( + default=part_settings.part_template_default, + verbose_name=_('Is Template'), + help_text=_('Is this part a template part?') + ) - variant_of = models.ForeignKey('part.Part', related_name='variants', - null=True, blank=True, - limit_choices_to={ - 'is_template': True, - 'active': True, - }, - on_delete=models.SET_NULL, - help_text=_('Is this part a variant of another part?')) + variant_of = models.ForeignKey( + 'part.Part', related_name='variants', + null=True, blank=True, + limit_choices_to={ + 'is_template': True, + 'active': True, + }, + on_delete=models.SET_NULL, + help_text=_('Is this part a variant of another part?'), + verbose_name=_('Variant Of'), + ) - description = models.CharField(max_length=250, blank=False, help_text=_('Part description')) + description = models.CharField( + max_length=250, blank=False, + verbose_name=_('Description'), + help_text=_('Part description') + ) - keywords = models.CharField(max_length=250, blank=True, null=True, help_text=_('Part keywords to improve visibility in search results')) + keywords = models.CharField( + max_length=250, blank=True, null=True, + verbose_name=_('Keywords'), + help_text=_('Part keywords to improve visibility in search results') + ) - category = TreeForeignKey(PartCategory, related_name='parts', - null=True, blank=True, - on_delete=models.DO_NOTHING, - help_text=_('Part category')) + category = TreeForeignKey( + PartCategory, related_name='parts', + null=True, blank=True, + on_delete=models.DO_NOTHING, + verbose_name=_('Category'), + help_text=_('Part category') + ) - IPN = models.CharField(max_length=100, blank=True, null=True, help_text=_('Internal Part Number'), validators=[validators.validate_part_ipn]) + IPN = models.CharField( + max_length=100, blank=True, null=True, + verbose_name=_('IPN'), + help_text=_('Internal Part Number'), + validators=[validators.validate_part_ipn] + ) - revision = models.CharField(max_length=100, blank=True, null=True, help_text=_('Part revision or version number')) + revision = models.CharField( + max_length=100, blank=True, null=True, + help_text=_('Part revision or version number'), + verbose_name=_('Revision'), + ) - link = InvenTreeURLField(blank=True, null=True, help_text=_('Link to external URL')) + link = InvenTreeURLField( + blank=True, null=True, + verbose_name=_('Link'), + help_text=_('Link to external URL') + ) image = StdImageField( upload_to=rename_part_image, @@ -679,10 +712,13 @@ class Part(MPTTModel): delete_orphans=True, ) - default_location = TreeForeignKey('stock.StockLocation', on_delete=models.SET_NULL, - blank=True, null=True, - help_text=_('Where is this item normally stored?'), - related_name='default_parts') + default_location = TreeForeignKey('stock.StockLocation', + on_delete=models.SET_NULL, + blank=True, null=True, + help_text=_('Where is this item normally stored?'), + related_name='default_parts', + verbose_name=_('Default Location'), + ) def get_default_location(self): """ Get the default location for a Part (may be None). @@ -733,7 +769,7 @@ class Part(MPTTModel): units = models.CharField(max_length=20, default="", blank=True, null=True, help_text=_('Stock keeping units for this part')) assembly = models.BooleanField( - default=False, + default=part_settings.part_assembly_default, verbose_name=_('Assembly'), help_text=_('Can this part be built from other parts?') ) diff --git a/InvenTree/part/settings.py b/InvenTree/part/settings.py index 8d87cdffe3..1705f96a80 100644 --- a/InvenTree/part/settings.py +++ b/InvenTree/part/settings.py @@ -8,6 +8,22 @@ from __future__ import unicode_literals from common.models import InvenTreeSetting +def part_assembly_default(): + """ + Returns the default value for the 'assembly' field of a Part object + """ + + return InvenTreeSetting.get_setting('PART_ASSEMBLY') + + +def part_template_default(): + """ + Returns the default value for the 'is_template' field of a Part object + """ + + return InvenTreeSetting.get_setting('PART_TEMPLATE') + + def part_component_default(): """ Returns the default value for the 'component' field of a Part object diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index c02be211b5..4c08911122 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -235,6 +235,8 @@ class PartSettingsTest(TestCase): InvenTreeSetting.set_setting('PART_PURCHASEABLE', val, self.user) InvenTreeSetting.set_setting('PART_SALABLE', val, self.user) InvenTreeSetting.set_setting('PART_TRACKABLE', val, self.user) + InvenTreeSetting.set_setting('PART_ASSEMBLY', val, self.user) + InvenTreeSetting.set_setting('PART_TEMPLATE', val, self.user) self.assertEqual(val, InvenTreeSetting.get_setting('PART_COMPONENT')) self.assertEqual(val, InvenTreeSetting.get_setting('PART_PURCHASEABLE')) @@ -247,6 +249,8 @@ class PartSettingsTest(TestCase): self.assertEqual(part.purchaseable, val) self.assertEqual(part.salable, val) self.assertEqual(part.trackable, val) + self.assertEqual(part.assembly, val) + self.assertEqual(part.is_template, val) Part.objects.filter(pk=part.pk).delete()