diff --git a/InvenTree/build/migrations/0003_auto_20190525_2355.py b/InvenTree/build/migrations/0003_auto_20190525_2355.py new file mode 100644 index 0000000000..fe127061bc --- /dev/null +++ b/InvenTree/build/migrations/0003_auto_20190525_2355.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0002_auto_20190520_2204'), + ] + + operations = [ + migrations.AlterField( + model_name='build', + name='part', + field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'buildable': True, 'has_variants': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part'), + ), + ] diff --git a/InvenTree/build/migrations/0004_auto_20190525_2356.py b/InvenTree/build/migrations/0004_auto_20190525_2356.py new file mode 100644 index 0000000000..1a43e5bfc8 --- /dev/null +++ b/InvenTree/build/migrations/0004_auto_20190525_2356.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0003_auto_20190525_2355'), + ] + + operations = [ + migrations.AlterField( + model_name='build', + name='part', + field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'buildable': True, 'is_template': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part'), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index b5f5aa10eb..ad482b8dc6 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -50,6 +50,7 @@ class Build(models.Model): part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='builds', limit_choices_to={ + 'is_template': False, 'buildable': True, 'active': True }, diff --git a/InvenTree/company/migrations/0004_auto_20190525_2354.py b/InvenTree/company/migrations/0004_auto_20190525_2354.py new file mode 100644 index 0000000000..d402cdeed5 --- /dev/null +++ b/InvenTree/company/migrations/0004_auto_20190525_2354.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0003_remove_supplierpart_minimum'), + ] + + operations = [ + migrations.AlterField( + model_name='supplierpart', + name='part', + field=models.ForeignKey(help_text='Select part', limit_choices_to={'has_variants': False, 'purchaseable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_parts', to='part.Part'), + ), + ] diff --git a/InvenTree/company/migrations/0005_auto_20190525_2356.py b/InvenTree/company/migrations/0005_auto_20190525_2356.py new file mode 100644 index 0000000000..fe02be0d84 --- /dev/null +++ b/InvenTree/company/migrations/0005_auto_20190525_2356.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0004_auto_20190525_2354'), + ] + + operations = [ + migrations.AlterField( + model_name='supplierpart', + name='part', + field=models.ForeignKey(help_text='Select part', limit_choices_to={'is_template': False, 'purchaseable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_parts', to='part.Part'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index be59c2b66f..245264900d 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -188,7 +188,10 @@ class SupplierPart(models.Model): part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='supplier_parts', - limit_choices_to={'purchaseable': True}, + limit_choices_to={ + 'purchaseable': True, + 'is_template': False, + }, help_text='Select part', ) diff --git a/InvenTree/company/templates/company/partdetail.html b/InvenTree/company/templates/company/partdetail.html index 812ceacf04..ab4a369469 100644 --- a/InvenTree/company/templates/company/partdetail.html +++ b/InvenTree/company/templates/company/partdetail.html @@ -84,7 +84,7 @@ InvenTree | {{ company.name }} - Parts {{ pb.quantity }} {{ pb.cost }}
- +
diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index e069df6ed6..cbc1cdaf8c 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -93,6 +93,8 @@ class EditPartForm(HelperForm): 'name', 'IPN', 'variant', + 'is_template', + 'variant_of', 'description', 'keywords', 'URL', diff --git a/InvenTree/part/migrations/0003_auto_20190525_2226.py b/InvenTree/part/migrations/0003_auto_20190525_2226.py new file mode 100644 index 0000000000..3e805d460d --- /dev/null +++ b/InvenTree/part/migrations/0003_auto_20190525_2226.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2 on 2019-05-25 12:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0002_auto_20190520_2204'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='has_variants', + field=models.BooleanField(default=False, help_text='Is this part a template part?'), + ), + migrations.AddField( + 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, 'has_variants': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='variants', to='part.Part'), + ), + ] diff --git a/InvenTree/part/migrations/0004_auto_20190525_2356.py b/InvenTree/part/migrations/0004_auto_20190525_2356.py new file mode 100644 index 0000000000..7b9a5fe6ac --- /dev/null +++ b/InvenTree/part/migrations/0004_auto_20190525_2356.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2 on 2019-05-25 13:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0003_auto_20190525_2226'), + ] + + operations = [ + migrations.RenameField( + model_name='part', + old_name='has_variants', + new_name='is_template', + ), + 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'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 385bae7bf4..3b9a7aea60 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -192,6 +192,7 @@ class Part(models.Model): description: Longer form description of the part keywords: Optional keywords for improving part search results IPN: Internal part number (optional) + is_template: If True, this part is a 'template' part and cannot be instantiated as a StockItem URL: Link to an external page with more information about this part (e.g. internal Wiki) image: Image of this part default_location: Where the item is normally stored (may be null) @@ -252,12 +253,32 @@ class Part(models.Model): else: return static('/img/blank_image.png') + def clean(self): + """ Perform cleaning operations for the Part model """ + + if self.is_template and self.variant_of is not None: + raise ValidationError({ + 'is_template': _("Part cannot be a template part if it is a variant of another part"), + 'variant_of': _("Part cannot be a variant of another part if it is already a template"), + }) + name = models.CharField(max_length=100, blank=False, help_text='Part name', validators=[validators.validate_part_name] ) variant = models.CharField(max_length=32, blank=True, help_text='Part variant or revision code') + is_template = models.BooleanField(default=False, 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?') + description = models.CharField(max_length=250, blank=False, help_text='Part description') keywords = models.CharField(max_length=250, blank=True, help_text='Part keywords to improve visibility in search results') @@ -501,7 +522,10 @@ class Part(models.Model): Part may be stored in multiple locations """ - total = self.stock_entries.aggregate(total=Sum('quantity'))['total'] + if self.is_template: + total = sum([variant.total_stock for variant in self.variants.all()]) + else: + total = self.stock_entries.aggregate(total=Sum('quantity'))['total'] if total: return total @@ -747,6 +771,21 @@ class Part(models.Model): return data.export(file_format) + @property + def attachment_count(self): + """ Count the number of attachments for this part. + If the part is a variant of a template part, + include the number of attachments for the template part. + + """ + + n = self.attachments.count() + + if self.variant_of: + n += self.variant_of.attachments.count() + + return n + def attach_file(instance, filename): """ Function for storing a file for a PartAttachment diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 445a10601a..540f754765 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -85,6 +85,8 @@ class PartSerializer(serializers.ModelSerializer): 'full_name', 'name', 'IPN', + 'is_template', + 'variant_of', 'variant', 'description', 'keywords', diff --git a/InvenTree/part/templates/part/attachments.html b/InvenTree/part/templates/part/attachments.html index d0ccaf122d..493002d2f0 100644 --- a/InvenTree/part/templates/part/attachments.html +++ b/InvenTree/part/templates/part/attachments.html @@ -30,12 +30,26 @@ {{ attachment.comment }}
- +
{% endfor %} +{% if part.variant_of and part.variant_of.attachments.count > 0 %} + + + Attachments for template part {{ part.variant_of.full_name }} + + +{% for attachment in part.variant_of.attachments.all %} + + {{ attachment.basename }} + {{ attachment.comment }} + + +{% endfor %} +{% endif %} {% endblock %} diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index b561d08ae5..068731da64 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -52,6 +52,12 @@ Description {{ part.description }} + {% if part.variant_of %} + + Variant Of + {{ part.variant_of.full_name }} + + {% endif %} {% if part.keywords %} Keywords diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 8470677487..f91c5ac1e2 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -4,12 +4,24 @@ {% block content %} +{% if part.active == False %} +
+ This part is not active: +
+{% endif %} +{% if part.is_template %} +
+ This part is a template part.
+ It is not a real part, but real parts can be based on this template. +
+{% endif %} +{% if part.variant_of %} +
+ This part is a variant of {{ part.variant_of.full_name }} +
+{% endif %} +
- {% if part.active == False %} -
- This part ({{ part.full_name }}) is not active: -
- {% endif %}
@@ -35,10 +47,12 @@ + {% if part.is_template == False %} {% include "qr_button.html" %} + {% endif %}

diff --git a/InvenTree/part/templates/part/stock.html b/InvenTree/part/templates/part/stock.html index 3b2bdec166..e9c22bf1d9 100644 --- a/InvenTree/part/templates/part/stock.html +++ b/InvenTree/part/templates/part/stock.html @@ -13,6 +13,12 @@
+{% if part.is_template %} +
+ Showing stock for all variants of {{ part.full_name }} +
+{% endif %} + {% include "stock_table.html" %} {% endblock %} diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index ff2cbbf4a0..53a551f36c 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -2,6 +2,11 @@ Details + {% if part.is_template %} + + Variants {{ part.variants.count }} + + {% endif %} Stock {{ part.total_stock }} @@ -20,7 +25,7 @@ Used In{% if part.used_in_count > 0 %}{{ part.used_in_count }}{% endif %} {% endif %} - {% if part.purchaseable %} + {% if part.purchaseable and part.is_template == False %} Suppliers {{ part.supplier_count }} @@ -35,7 +40,7 @@ {% endif %} - Attachments {% if part.attachments.all|length > 0 %}{{ part.attachments.all|length }}{% endif %} + Attachments {% if part.attachment_count > 0 %}{{ part.attachment_count }}{% endif %} diff --git a/InvenTree/part/templates/part/variants.html b/InvenTree/part/templates/part/variants.html new file mode 100644 index 0000000000..0cf9735364 --- /dev/null +++ b/InvenTree/part/templates/part/variants.html @@ -0,0 +1,63 @@ +{% extends "part/part_base.html" %} +{% load static %} + +{% block details %} +{% include "part/tabs.html" with tab='variants' %} + +
+
+

Part Variants

+
+
+
+
+
+ +
+
+ {% if part.is_template and part.active %} + + {% endif %} +
+
+ +
+ + + + + + + + + {% for variant in part.variants.all %} + + + + + + {% endfor %} + +
VariantDescriptionStock
+
+ + {% if variant.image %} + + {% endif %} +
+ {{ variant.full_name }} +
{{ variant.description }}{{ variant.total_stock }}
+ +{% endblock %} + + +{% block js_ready %} +{{ block.super }} + + + $('#variant-table').bootstrapTable({ + search: true, + sortable: true, + }); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 900ebe8127..c8582160bb 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -26,14 +26,15 @@ part_detail_urls = [ url(r'^duplicate/', views.PartDuplicate.as_view(), name='part-duplicate'), url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'), - url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'), - url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'), + url(r'^variants/?', views.PartDetail.as_view(template_name='part/variants.html'), name='part-variants'), + url(r'^stock/?', views.PartDetail.as_view(template_name='part/stock.html'), name='part-stock'), + url(r'^allocation/?', views.PartDetail.as_view(template_name='part/allocation.html'), name='part-allocation'), url(r'^bom/?', views.PartDetail.as_view(template_name='part/bom.html'), name='part-bom'), url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'), - url(r'^stock/?', views.PartDetail.as_view(template_name='part/stock.html'), name='part-stock'), url(r'^used/?', views.PartDetail.as_view(template_name='part/used_in.html'), name='part-used-in'), - url(r'^allocation/?', views.PartDetail.as_view(template_name='part/allocation.html'), name='part-allocation'), url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'), + url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'), + url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'), url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'), diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index b6e0c63a70..77dfbe740e 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -189,7 +189,7 @@ function loadBomTable(table, options) { if (options.editable) { cols.push({ formatter: function(value, row, index, field) { - var bEdit = ""; + var bEdit = ""; var bDelt = ""; return "
" + bEdit + bDelt + "
"; diff --git a/InvenTree/static/script/inventree/build.js b/InvenTree/static/script/inventree/build.js index e3d1238e5e..0a9250ebb2 100644 --- a/InvenTree/static/script/inventree/build.js +++ b/InvenTree/static/script/inventree/build.js @@ -40,7 +40,7 @@ function loadAllocationTable(table, part_id, part, url, required, button) { formatter: function(value, row, index, field) { var html = value; - var bEdit = ""; + var bEdit = ""; var bDel = ""; html += "
" + bEdit + bDel + "
"; diff --git a/InvenTree/static/script/inventree/part.js b/InvenTree/static/script/inventree/part.js index 51d86f1c7a..1001ef11a1 100644 --- a/InvenTree/static/script/inventree/part.js +++ b/InvenTree/static/script/inventree/part.js @@ -124,7 +124,12 @@ function loadPartTable(table, url, options={}) { sortable: true, formatter: function(value, row, index, field) { + if (row.is_template) { + value = '' + value + ''; + } + var display = imageHoverIcon(row.image_url) + renderLink(value, row.url); + if (!row.active) { display = display + "INACTIVE"; } @@ -135,6 +140,14 @@ function loadPartTable(table, url, options={}) { sortable: true, field: 'description', title: 'Description', + formatter: function(value, row, index, field) { + + if (row.is_template) { + value = '' + value + ''; + } + + return value; + } }, { sortable: true, diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 0bff4a3873..ec998dde41 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -11,7 +11,7 @@ from django.urls import reverse from .models import StockLocation, StockItem from .models import StockItemTracking -from part.models import PartCategory +from part.models import Part, PartCategory from .serializers import StockItemSerializer, StockQuantitySerializer from .serializers import LocationSerializer @@ -263,12 +263,28 @@ class StockList(generics.ListCreateAPIView): we may wish to also request stock items from all child locations. """ - # Does the client wish to filter by stock location? - loc_id = self.request.query_params.get('location', None) - # Start with all objects stock_list = StockItem.objects.all() + # Does the client wish to filter by the Part ID? + part_id = self.request.query_params.get('part', None) + + if part_id: + try: + part = Part.objects.get(pk=part_id) + + # If the part is a Template part, select stock items for any "variant" parts under that template + if part.is_template: + stock_list = stock_list.filter(part__in=[part.id for part in Part.objects.filter(variant_of=part_id)]) + else: + stock_list = stock_list.filter(part=part_id) + + except Part.DoesNotExist: + pass + + # Does the client wish to filter by stock location? + loc_id = self.request.query_params.get('location', None) + if loc_id: try: location = StockLocation.objects.get(pk=loc_id) @@ -312,7 +328,6 @@ class StockList(generics.ListCreateAPIView): ] filter_fields = [ - 'part', 'supplier_part', 'customer', 'belongs_to', diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index ee8592bbb9..a05c8caef0 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -34,6 +34,7 @@ class CreateStockItemForm(HelperForm): 'location', 'quantity', 'batch', + 'serial', 'delete_on_deplete', 'status', 'notes', diff --git a/InvenTree/stock/migrations/0002_auto_20190525_2226.py b/InvenTree/stock/migrations/0002_auto_20190525_2226.py new file mode 100644 index 0000000000..b8607c3ec7 --- /dev/null +++ b/InvenTree/stock/migrations/0002_auto_20190525_2226.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 12:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'has_variants': True}, on_delete=django.db.models.deletion.CASCADE, related_name='stock_items', to='part.Part'), + ), + ] diff --git a/InvenTree/stock/migrations/0003_auto_20190525_2303.py b/InvenTree/stock/migrations/0003_auto_20190525_2303.py new file mode 100644 index 0000000000..e8f7708cfe --- /dev/null +++ b/InvenTree/stock/migrations/0003_auto_20190525_2303.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0002_auto_20190525_2226'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'has_variants': False}, on_delete=django.db.models.deletion.CASCADE, related_name='stock_items', to='part.Part'), + ), + ] diff --git a/InvenTree/stock/migrations/0004_auto_20190525_2356.py b/InvenTree/stock/migrations/0004_auto_20190525_2356.py new file mode 100644 index 0000000000..4b2f939eb2 --- /dev/null +++ b/InvenTree/stock/migrations/0004_auto_20190525_2356.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0003_auto_20190525_2303'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'is_template': False}, on_delete=django.db.models.deletion.CASCADE, related_name='stock_items', to='part.Part'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 3680079b30..f6651f6dd4 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -115,6 +115,22 @@ class StockItem(models.Model): system=True ) + def validate_unique(self, exclude=None): + super(StockItem, self).validate_unique(exclude) + + # If the Part object is a variant (of a template part), + # ensure that the serial number is unique + # across all variants of the same template part + + try: + if self.serial is not None and self.part.variant_of is not None: + if StockItem.objects.filter(part__variant_of=self.part.variant_of, serial=self.serial).exclude(id=self.id).exists(): + raise ValidationError({ + 'serial': _('A part with this serial number already exists for template part {part}'.format(part=self.part.variant_of)) + }) + except Part.DoesNotExist: + pass + def clean(self): """ Validate the StockItem object (separate to field validation) @@ -135,11 +151,18 @@ class StockItem(models.Model): }) if self.part is not None: + # A trackable part must have a serial number if self.part.trackable and not self.serial: raise ValidationError({ 'serial': _('Serial number must be set for trackable items') }) + # A template part cannot be instantiated as a StockItem + if self.part.is_template: + raise ValidationError({ + 'part': _('Stock item cannot be created for a template Part') + }) + except Part.DoesNotExist: # This gets thrown if self.supplier_part is null # TODO - Find a test than can be perfomed... @@ -186,7 +209,12 @@ class StockItem(models.Model): } ) - part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='stock_items', help_text='Base part') + part = models.ForeignKey('part.Part', on_delete=models.CASCADE, + related_name='stock_items', help_text='Base part', + limit_choices_to={ + 'is_template': False, + 'active': True, + }) supplier_part = models.ForeignKey('company.SupplierPart', blank=True, null=True, on_delete=models.SET_NULL, help_text='Select a matching supplier part for this stock item') diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index cd9fb615af..42ca16d3ba 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -208,28 +208,31 @@ class StockItemCreate(AjaxCreateView): try: part = Part.objects.get(id=part_id) - parts = form.fields['supplier_part'].queryset - parts = parts.filter(part=part.id) + + # Hide the 'part' field (as a valid part is selected) + form.fields['part'].widget = HiddenInput() # If the part is NOT purchaseable, hide the supplier_part field if not part.purchaseable: form.fields['supplier_part'].widget = HiddenInput() - form.fields['supplier_part'].queryset = parts + else: + # Pre-select the allowable SupplierPart options + parts = form.fields['supplier_part'].queryset + parts = parts.filter(part=part.id) - # If there is one (and only one) supplier part available, pre-select it - all_parts = parts.all() - if len(all_parts) == 1: + form.fields['supplier_part'].queryset = parts - # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate - form.fields['supplier_part'].initial = all_parts[0].id + # If there is one (and only one) supplier part available, pre-select it + all_parts = parts.all() + if len(all_parts) == 1: + + # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate + form.fields['supplier_part'].initial = all_parts[0].id except Part.DoesNotExist: pass - # Hide the 'part' field - form.fields['part'].widget = HiddenInput() - # Otherwise if the user has selected a SupplierPart, we know what Part they meant! elif form['supplier_part'].value() is not None: pass diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index f5411fabb8..fae7b04214 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -1,6 +1,8 @@
+ {% if part.is_template == False %} + {% endif %}