diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index ab1e7cf049..6183d6cc78 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -110,7 +110,21 @@ class InvenTreeSetting(models.Model): 'default': True, 'validator': bool }, - + + 'PART_TEMPLATE': { + 'name': _('Template'), + 'description': _('Parts are templates by default'), + 'default': False, + 'validator': bool, + }, + + 'PART_ASSEMBLY': { + 'name': _('Assembly'), + 'description': _('Parts can be assembled from other components by default'), + 'default': False, + 'validator': bool, + }, + 'PART_COMPONENT': { 'name': _('Component'), 'description': _('Parts can be used as sub-components by default'), @@ -139,6 +153,9 @@ class InvenTreeSetting(models.Model): 'validator': bool, }, + 'PART_VIRTUAL': { + 'name': _('Virtual'), + 'description': _('Parts are virtual by default'), 'STOCK_ALLOW_EXPIRED_SALE': { 'name': _('Sell Expired Stock'), 'description': _('Allow sale of expired stock'), diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 6d6e517ef5..d8777785f9 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -31,7 +31,7 @@ class SettingsTest(TestCase): # There should be two settings objects in the database settings = InvenTreeSetting.objects.all() - self.assertEqual(settings.count(), 2) + self.assertTrue(settings.count() >= 2) instance_name = InvenTreeSetting.objects.get(pk=1) self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE') diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 4f55ccb14d..1ac62161a5 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -232,9 +232,13 @@ class EditPartForm(HelperForm): 'default_expiry', 'units', 'minimum_stock', + 'component', + 'assembly', + 'is_template', 'trackable', 'purchaseable', 'salable', + 'virtual', ] diff --git a/InvenTree/part/migrations/0061_auto_20210103_2313.py b/InvenTree/part/migrations/0061_auto_20210103_2313.py new file mode 100644 index 0000000000..ca0c2a277f --- /dev/null +++ b/InvenTree/part/migrations/0061_auto_20210103_2313.py @@ -0,0 +1,85 @@ +# Generated by Django 3.0.7 on 2021-01-03 12:13 + +import InvenTree.fields +import InvenTree.validators +from django.db import migrations, models +import django.db.models.deletion +import markdownx.models +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='notes', + field=markdownx.models.MarkdownxField(blank=True, help_text='Part notes - supports Markdown formatting', null=True, verbose_name='Notes'), + ), + 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'), + ), + migrations.AlterField( + model_name='part', + name='virtual', + field=models.BooleanField(default=part.settings.part_virtual_default, help_text='Is this a virtual part, such as a software product or license?', verbose_name='Virtual'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 390583bd97..8c88adf747 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -641,36 +641,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, @@ -680,10 +713,14 @@ 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). @@ -753,7 +790,7 @@ class Part(MPTTModel): ) assembly = models.BooleanField( - default=False, + default=part_settings.part_assembly_default, verbose_name=_('Assembly'), help_text=_('Can this part be built from other parts?') ) @@ -785,11 +822,15 @@ class Part(MPTTModel): help_text=_('Is this part active?')) virtual = models.BooleanField( - default=False, + default=part_settings.part_virtual_default, verbose_name=_('Virtual'), help_text=_('Is this a virtual part, such as a software product or license?')) - notes = MarkdownxField(blank=True, null=True, help_text=_('Part notes - supports Markdown formatting')) + notes = MarkdownxField( + blank=True, null=True, + verbose_name=_('Notes'), + help_text=_('Part notes - supports Markdown formatting') + ) bom_checksum = models.CharField(max_length=128, blank=True, help_text=_('Stored BOM checksum')) diff --git a/InvenTree/part/settings.py b/InvenTree/part/settings.py index 8d87cdffe3..801b4dd2ec 100644 --- a/InvenTree/part/settings.py +++ b/InvenTree/part/settings.py @@ -8,6 +8,30 @@ 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_virtual_default(): + """ + Returns the default value for the 'is_virtual' field of Part object + """ + + return InvenTreeSetting.get_setting('PART_VIRTUAL') + + 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() diff --git a/InvenTree/templates/InvenTree/settings/build.html b/InvenTree/templates/InvenTree/settings/build.html index 781402795b..7d04a8f8b7 100644 --- a/InvenTree/templates/InvenTree/settings/build.html +++ b/InvenTree/templates/InvenTree/settings/build.html @@ -13,7 +13,7 @@ {% block settings %}
+ {% if icon %} + + {% endif %} + | {{ setting.name }} | {% if setting.is_bool %} @@ -11,7 +16,9 @@ {% else %} {% if setting.value %} - {{ setting.value }}{{ setting.units }} | + + {{ setting.value }}{{ setting.units }} + {% else %} {% trans "No value set" %} {% endif %} diff --git a/InvenTree/templates/InvenTree/settings/so.html b/InvenTree/templates/InvenTree/settings/so.html index 368374532f..4ef1709068 100644 --- a/InvenTree/templates/InvenTree/settings/so.html +++ b/InvenTree/templates/InvenTree/settings/so.html @@ -12,7 +12,7 @@ {% block settings %}