Merge remote-tracking branch 'inventree/master' into stock-expiry

# Conflicts:
#	InvenTree/common/models.py
This commit is contained in:
Oliver Walters 2021-01-05 00:58:32 +11:00
commit e715ea5d79
15 changed files with 246 additions and 46 deletions

View File

@ -111,6 +111,20 @@ class InvenTreeSetting(models.Model):
'validator': bool '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': { 'PART_COMPONENT': {
'name': _('Component'), 'name': _('Component'),
'description': _('Parts can be used as sub-components by default'), 'description': _('Parts can be used as sub-components by default'),
@ -139,6 +153,9 @@ class InvenTreeSetting(models.Model):
'validator': bool, 'validator': bool,
}, },
'PART_VIRTUAL': {
'name': _('Virtual'),
'description': _('Parts are virtual by default'),
'STOCK_ALLOW_EXPIRED_SALE': { 'STOCK_ALLOW_EXPIRED_SALE': {
'name': _('Sell Expired Stock'), 'name': _('Sell Expired Stock'),
'description': _('Allow sale of expired stock'), 'description': _('Allow sale of expired stock'),

View File

@ -31,7 +31,7 @@ class SettingsTest(TestCase):
# There should be two settings objects in the database # There should be two settings objects in the database
settings = InvenTreeSetting.objects.all() settings = InvenTreeSetting.objects.all()
self.assertEqual(settings.count(), 2) self.assertTrue(settings.count() >= 2)
instance_name = InvenTreeSetting.objects.get(pk=1) instance_name = InvenTreeSetting.objects.get(pk=1)
self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE') self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE')

View File

@ -232,9 +232,13 @@ class EditPartForm(HelperForm):
'default_expiry', 'default_expiry',
'units', 'units',
'minimum_stock', 'minimum_stock',
'component',
'assembly',
'is_template',
'trackable', 'trackable',
'purchaseable', 'purchaseable',
'salable', 'salable',
'virtual',
] ]

View File

@ -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'),
),
]

View File

@ -641,36 +641,69 @@ class Part(MPTTModel):
parent_part.clean() parent_part.clean()
parent_part.save() parent_part.save()
name = models.CharField(max_length=100, blank=False, name = models.CharField(
help_text=_('Part name'), max_length=100, blank=False,
validators=[validators.validate_part_name] 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', variant_of = models.ForeignKey(
null=True, blank=True, 'part.Part', related_name='variants',
limit_choices_to={ null=True, blank=True,
'is_template': True, limit_choices_to={
'active': True, 'is_template': True,
}, 'active': True,
on_delete=models.SET_NULL, },
help_text=_('Is this part a variant of another part?')) 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', category = TreeForeignKey(
null=True, blank=True, PartCategory, related_name='parts',
on_delete=models.DO_NOTHING, null=True, blank=True,
help_text=_('Part category')) 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( image = StdImageField(
upload_to=rename_part_image, upload_to=rename_part_image,
@ -680,10 +713,14 @@ class Part(MPTTModel):
delete_orphans=True, delete_orphans=True,
) )
default_location = TreeForeignKey('stock.StockLocation', on_delete=models.SET_NULL, default_location = TreeForeignKey(
blank=True, null=True, 'stock.StockLocation',
help_text=_('Where is this item normally stored?'), on_delete=models.SET_NULL,
related_name='default_parts') 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): def get_default_location(self):
""" Get the default location for a Part (may be None). """ Get the default location for a Part (may be None).
@ -753,7 +790,7 @@ class Part(MPTTModel):
) )
assembly = models.BooleanField( assembly = models.BooleanField(
default=False, default=part_settings.part_assembly_default,
verbose_name=_('Assembly'), verbose_name=_('Assembly'),
help_text=_('Can this part be built from other parts?') help_text=_('Can this part be built from other parts?')
) )
@ -785,11 +822,15 @@ class Part(MPTTModel):
help_text=_('Is this part active?')) help_text=_('Is this part active?'))
virtual = models.BooleanField( virtual = models.BooleanField(
default=False, default=part_settings.part_virtual_default,
verbose_name=_('Virtual'), verbose_name=_('Virtual'),
help_text=_('Is this a virtual part, such as a software product or license?')) 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')) bom_checksum = models.CharField(max_length=128, blank=True, help_text=_('Stored BOM checksum'))

View File

@ -8,6 +8,30 @@ from __future__ import unicode_literals
from common.models import InvenTreeSetting 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(): def part_component_default():
""" """
Returns the default value for the 'component' field of a Part object Returns the default value for the 'component' field of a Part object

View File

@ -235,6 +235,8 @@ class PartSettingsTest(TestCase):
InvenTreeSetting.set_setting('PART_PURCHASEABLE', val, self.user) InvenTreeSetting.set_setting('PART_PURCHASEABLE', val, self.user)
InvenTreeSetting.set_setting('PART_SALABLE', val, self.user) InvenTreeSetting.set_setting('PART_SALABLE', val, self.user)
InvenTreeSetting.set_setting('PART_TRACKABLE', 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_COMPONENT'))
self.assertEqual(val, InvenTreeSetting.get_setting('PART_PURCHASEABLE')) self.assertEqual(val, InvenTreeSetting.get_setting('PART_PURCHASEABLE'))
@ -247,6 +249,8 @@ class PartSettingsTest(TestCase):
self.assertEqual(part.purchaseable, val) self.assertEqual(part.purchaseable, val)
self.assertEqual(part.salable, val) self.assertEqual(part.salable, val)
self.assertEqual(part.trackable, val) self.assertEqual(part.trackable, val)
self.assertEqual(part.assembly, val)
self.assertEqual(part.is_template, val)
Part.objects.filter(pk=part.pk).delete() Part.objects.filter(pk=part.pk).delete()

View File

@ -13,7 +13,7 @@
{% block settings %} {% block settings %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<thead></thead> {% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PREFIX" %} {% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PREFIX" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_REGEX" %} {% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_REGEX" %}

View File

@ -13,11 +13,11 @@
{% block settings %} {% block settings %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<thead></thead> {% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE" icon="fa-info-circle" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" icon="fa-building" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %}
</tbody> </tbody>
</table> </table>

View File

@ -0,0 +1,12 @@
{% load i18n %}
<col width='25'>
<thead>
<tr>
<th></th>
<th>{% trans "Setting" %}</th>
<th>{% trans "Value" %}</th>
<th>{% trans "Description" %}</th>
<th></th>
</tr>
</thead>

View File

@ -14,16 +14,19 @@
<h4>{% trans "Part Options" %}</h4> <h4>{% trans "Part Options" %}</h4>
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<thead></thead> {% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %} {% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %} {% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %}
<tr><td colspan='4'></td></tr> <tr><td colspan='5 '></td></tr>
{% include "InvenTree/settings/setting.html" with key="PART_COMPONENT" %} {% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
{% include "InvenTree/settings/setting.html" with key="PART_PURCHASEABLE" %} {% include "InvenTree/settings/setting.html" with key="PART_ASSEMBLY" icon="fa-tools" %}
{% include "InvenTree/settings/setting.html" with key="PART_SALABLE" %} {% include "InvenTree/settings/setting.html" with key="PART_COMPONENT" icon="fa-th"%}
{% include "InvenTree/settings/setting.html" with key="PART_TRACKABLE" %} {% include "InvenTree/settings/setting.html" with key="PART_TRACKABLE" icon="fa-directions" %}
<tr><td colspan='4'></td></tr> {% include "InvenTree/settings/setting.html" with key="PART_PURCHASEABLE" icon="fa-shopping-cart" %}
{% include "InvenTree/settings/setting.html" with key="PART_SALABLE" icon="fa-dollar-sign" %}
{% include "InvenTree/settings/setting.html" with key="PART_VIRTUAL" icon="fa-ghost" %}
<tr><td colspan='5'></td></tr>
{% include "InvenTree/settings/setting.html" with key="PART_COPY_BOM" %} {% include "InvenTree/settings/setting.html" with key="PART_COPY_BOM" %}
{% include "InvenTree/settings/setting.html" with key="PART_COPY_PARAMETERS" %} {% include "InvenTree/settings/setting.html" with key="PART_COPY_PARAMETERS" %}
{% include "InvenTree/settings/setting.html" with key="PART_COPY_TESTS" %} {% include "InvenTree/settings/setting.html" with key="PART_COPY_TESTS" %}

View File

@ -11,7 +11,7 @@
{% block settings %} {% block settings %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<thead></thead> {% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PREFIX" %} {% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PREFIX" %}
</tbody> </tbody>

View File

@ -3,6 +3,11 @@
{% setting_object key as setting %} {% setting_object key as setting %}
<tr> <tr>
<td>
{% if icon %}
<span class='fas {{ icon }}'></span>
{% endif %}
</td>
<td><b>{{ setting.name }}</b></td> <td><b>{{ setting.name }}</b></td>
<td> <td>
{% if setting.is_bool %} {% if setting.is_bool %}
@ -11,7 +16,9 @@
</div> </div>
{% else %} {% else %}
{% if setting.value %} {% if setting.value %}
<b>{{ setting.value }}</b>{{ setting.units }}</td> <i><b>
{{ setting.value }}</b>{{ setting.units }}
</i>
{% else %} {% else %}
<i>{% trans "No value set" %}</i> <i>{% trans "No value set" %}</i>
{% endif %} {% endif %}

View File

@ -12,7 +12,7 @@
{% block settings %} {% block settings %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<thead></thead> {% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PREFIX" %} {% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PREFIX" %}
</tbody> </tbody>

View File

@ -10,4 +10,7 @@
{% endblock %} {% endblock %}
{% block settings %} {% block settings %}
<div class='alert alert-block alert-info'>
<i>No Stock settings available</i>
</div>
{% endblock %} {% endblock %}