diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py
index a736bfe6a1..7b8546b1c2 100644
--- a/InvenTree/InvenTree/version.py
+++ b/InvenTree/InvenTree/version.py
@@ -16,9 +16,13 @@ Increment thi API version number whenever there is a significant change to the A
 v3 -> 2021-05-22:
     - The updated StockItem "history tracking" now uses a different interface
 
+v4 -> 2021-06-01
+    - BOM items can now accept "variant stock" to be assigned against them
+    - Many slight API tweaks were needed to get this to work properly!
+
 """
 
-INVENTREE_API_VERSION = 3
+INVENTREE_API_VERSION = 4
 
 
 def inventreeInstanceName():
diff --git a/InvenTree/build/migrations/0028_builditem_bom_item.py b/InvenTree/build/migrations/0028_builditem_bom_item.py
new file mode 100644
index 0000000000..f93c63dc4c
--- /dev/null
+++ b/InvenTree/build/migrations/0028_builditem_bom_item.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2 on 2021-06-01 05:23
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('part', '0066_bomitem_allow_variants'),
+        ('build', '0027_auto_20210404_2016'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='builditem',
+            name='bom_item',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocate_build_items', to='part.bomitem'),
+        ),
+    ]
diff --git a/InvenTree/build/migrations/0029_auto_20210601_1525.py b/InvenTree/build/migrations/0029_auto_20210601_1525.py
new file mode 100644
index 0000000000..c5ea04b5c9
--- /dev/null
+++ b/InvenTree/build/migrations/0029_auto_20210601_1525.py
@@ -0,0 +1,62 @@
+# Generated by Django 3.2 on 2021-06-01 05:25
+
+from django.db import migrations
+
+
+def assign_bom_items(apps, schema_editor):
+    """
+    Run through existing BuildItem objects,
+    and assign a matching BomItem
+    """
+
+    BuildItem = apps.get_model('build', 'builditem')
+    BomItem = apps.get_model('part', 'bomitem')
+    Part = apps.get_model('part', 'part')
+    
+    print("Assigning BomItems to existing BuildItem objects")
+
+    count_valid = 0
+    count_total = 0
+
+    for build_item in BuildItem.objects.all():
+
+        # Try to find a BomItem which matches the BuildItem
+        # Note: Before this migration, variant stock assignment was not allowed,
+        #       so BomItem lookup should be pretty easy
+
+        count_total += 1
+
+        try:
+            bom_item = BomItem.objects.get(
+                part__id=build_item.build.part.pk,
+                sub_part__id=build_item.stock_item.part.pk,
+            )
+
+            build_item.bom_item = bom_item
+            build_item.save()
+
+            count_valid += 1
+
+        except BomItem.DoesNotExist:
+            pass
+
+    print(f"Assigned BomItem for {count_valid}/{count_total} entries")
+
+
+def unassign_bom_items(apps, schema_editor):
+    """
+    Reverse migration does not do anything.
+    Function here to preserve ability to reverse migration
+    """
+    pass
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('build', '0028_builditem_bom_item'),
+    ]
+
+    operations = [
+        migrations.RunPython(assign_bom_items, reverse_code=unassign_bom_items),
+    ]
diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index c80c0e8523..03a49b627f 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -30,6 +30,7 @@ from InvenTree.models import InvenTreeAttachment
 import common.models
 
 import InvenTree.fields
+import InvenTree.helpers
 
 from stock import models as StockModels
 from part import models as PartModels
@@ -880,9 +881,12 @@ class Build(MPTTModel):
             output - Build output (StockItem).
         """
 
+        # Remember, if 'variant' stock is allowed to be allocated, it becomes more complicated!
+        variants = part.get_descendants(include_self=True)
+
         allocations = BuildItem.objects.filter(
             build=self,
-            stock_item__part=part,
+            stock_item__part__pk__in=[p.pk for p in variants],
             install_into=output,
         )
 
@@ -1036,7 +1040,19 @@ class Build(MPTTModel):
             StockModels.StockItem.IN_STOCK_FILTER
         )
 
-        items = items.filter(part=part)
+        # Check if variants are allowed for this part
+        try:
+            bom_item = PartModels.BomItem.objects.get(part=self.part, sub_part=part)
+            allow_part_variants = bom_item.allow_variants
+        except PartModels.BomItem.DoesNotExist:
+            allow_part_variants = False
+
+        if allow_part_variants:
+            parts = part.get_descendants(include_self=True)
+            items = items.filter(part__pk__in=[p.pk for p in parts])
+
+        else:
+            items = items.filter(part=part)
 
         # Exclude any items which have already been allocated
         allocated = BuildItem.objects.filter(
@@ -1160,10 +1176,6 @@ class BuildItem(models.Model):
             if self.stock_item.part and self.stock_item.part.trackable and not self.install_into:
                 raise ValidationError(_('Build item must specify a build output, as master part is marked as trackable'))
 
-            # Allocated part must be in the BOM for the master part
-            if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False):
-                errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)]
-
             # Allocated quantity cannot exceed available stock quantity
             if self.quantity > self.stock_item.quantity:
                 errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})").format(
@@ -1189,6 +1201,61 @@ class BuildItem(models.Model):
         if len(errors) > 0:
             raise ValidationError(errors)
 
+        """
+        Attempt to find the "BomItem" which links this BuildItem to the build.
+
+        - If a BomItem is already set, and it is valid, then we are ok!
+        """
+
+        bom_item_valid = False
+
+        if self.bom_item:
+            """
+            A BomItem object has already been assigned. This is valid if:
+
+            a) It points to the same "part" as the referened build
+            b) Either:
+                i) The sub_part points to the same part as the referenced StockItem
+                ii) The BomItem allows variants and the part referenced by the StockItem
+                    is a variant of the sub_part referenced by the BomItem
+            """
+
+            if self.build and self.build.part == self.bom_item.part:
+
+                # Check that the sub_part points to the stock_item (either directly or via a variant)
+                if self.bom_item.sub_part == self.stock_item.part:
+                    bom_item_valid = True
+
+                elif self.bom_item.allow_variants and self.stock_item.part in self.bom_item.sub_part.get_descendants(include_self=False):
+                    bom_item_valid = True
+
+        # If the existing BomItem is *not* valid, try to find a match
+        if not bom_item_valid:
+
+            if self.build and self.stock_item:
+                ancestors = self.stock_item.part.get_ancestors(include_self=True, ascending=True)
+
+                for idx, ancestor in enumerate(ancestors):
+
+                    try:
+                        bom_item = PartModels.BomItem.objects.get(part=self.build.part, sub_part=ancestor)
+                    except PartModels.BomItem.DoesNotExist:
+                        continue
+                    
+                    # A matching BOM item has been found!
+                    if idx == 0 or bom_item.allow_variants:
+                        bom_item_valid = True
+                        self.bom_item = bom_item
+                        break
+
+        # BomItem did not exist or could not be validated.
+        # Search for a new one
+        if not bom_item_valid:
+
+            raise ValidationError({
+                'stock_item': _("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)
+            })
+
     @transaction.atomic
     def complete_allocation(self, user):
         """
@@ -1217,6 +1284,18 @@ class BuildItem(models.Model):
             # Simply remove the items from stock
             item.take_stock(self.quantity, user)
 
+    def getStockItemThumbnail(self):
+        """
+        Return qualified URL for part thumbnail image
+        """
+
+        if self.stock_item and self.stock_item.part:
+            return InvenTree.helpers.getMediaUrl(self.stock_item.part.image.thumbnail.url)
+        elif self.bom_item and self.stock_item.sub_part:
+            return InvenTree.helpers.getMediaUrl(self.bom_item.sub_part.image.thumbnail.url)
+        else:
+            return InvenTree.helpers.getBlankThumbnail()
+
     build = models.ForeignKey(
         Build,
         on_delete=models.CASCADE,
@@ -1225,6 +1304,15 @@ class BuildItem(models.Model):
         help_text=_('Build to allocate parts')
     )
 
+    # Internal model which links part <-> sub_part
+    # We need to track this separately, to allow for "variant' stock
+    bom_item = models.ForeignKey(
+        PartModels.BomItem,
+        on_delete=models.CASCADE,
+        related_name='allocate_build_items',
+        blank=True, null=True,
+    )
+
     stock_item = models.ForeignKey(
         'stock.StockItem',
         on_delete=models.CASCADE,
diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py
index 550a3c3a85..629422f6e5 100644
--- a/InvenTree/build/serializers.py
+++ b/InvenTree/build/serializers.py
@@ -97,9 +97,10 @@ class BuildSerializer(InvenTreeModelSerializer):
 class BuildItemSerializer(InvenTreeModelSerializer):
     """ Serializes a BuildItem object """
 
+    bom_part = serializers.IntegerField(source='bom_item.sub_part.pk', read_only=True)
     part = serializers.IntegerField(source='stock_item.part.pk', read_only=True)
     part_name = serializers.CharField(source='stock_item.part.full_name', read_only=True)
-    part_image = serializers.CharField(source='stock_item.part.image', read_only=True)
+    part_thumb = serializers.CharField(source='getStockItemThumbnail', read_only=True)
     stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
 
     quantity = serializers.FloatField()
@@ -108,11 +109,12 @@ class BuildItemSerializer(InvenTreeModelSerializer):
         model = BuildItem
         fields = [
             'pk',
+            'bom_part',
             'build',
             'install_into',
             'part',
             'part_name',
-            'part_image',
+            'part_thumb',
             'stock_item',
             'stock_item_detail',
             'quantity'
diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py
index 5bdd572145..537b0f9e40 100644
--- a/InvenTree/part/api.py
+++ b/InvenTree/part/api.py
@@ -821,6 +821,14 @@ class BomList(generics.ListCreateAPIView):
 
             queryset = queryset.filter(inherited=inherited)
 
+        # Filter by "allow_variants"
+        variants = params.get("allow_variants", None)
+
+        if variants is not None:
+            variants = str2bool(variants)
+
+            queryset = queryset.filter(allow_variants=variants)
+
         # Filter by part?
         part = params.get('part', None)
 
diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py
index 8f6e3d8898..95de4961f9 100644
--- a/InvenTree/part/forms.py
+++ b/InvenTree/part/forms.py
@@ -352,6 +352,7 @@ class EditBomItemForm(HelperForm):
             'reference',
             'overage',
             'note',
+            'allow_variants',
             'inherited',
             'optional',
         ]
diff --git a/InvenTree/part/migrations/0066_bomitem_allow_variants.py b/InvenTree/part/migrations/0066_bomitem_allow_variants.py
new file mode 100644
index 0000000000..e545c8e3cb
--- /dev/null
+++ b/InvenTree/part/migrations/0066_bomitem_allow_variants.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2 on 2021-06-01 03:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('part', '0065_auto_20210505_2144'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='bomitem',
+            name='allow_variants',
+            field=models.BooleanField(default=False, help_text='Stock items for variant parts can be used for this BOM item', verbose_name='Allow Variants'),
+        ),
+    ]
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index 7db998ab3d..7b9038fecb 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -2240,6 +2240,7 @@ class BomItem(models.Model):
         note: Note field for this BOM item
         checksum: Validation checksum for the particular BOM line item
         inherited: This BomItem can be inherited by the BOMs of variant parts
+        allow_variants: Stock for part variants can be substituted for this BomItem
     """
 
     def save(self, *args, **kwargs):
@@ -2288,6 +2289,12 @@ class BomItem(models.Model):
         help_text=_('This BOM item is inherited by BOMs for variant parts'),
     )
 
+    allow_variants = models.BooleanField(
+        default=False,
+        verbose_name=_('Allow Variants'),
+        help_text=_('Stock items for variant parts can be used for this BOM item')
+    )
+
     def get_item_hash(self):
         """ Calculate the checksum hash of this BOM line item:
 
diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py
index 04e0b7a119..d03a37c6dc 100644
--- a/InvenTree/part/serializers.py
+++ b/InvenTree/part/serializers.py
@@ -453,6 +453,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
     class Meta:
         model = BomItem
         fields = [
+            'allow_variants',
             'inherited',
             'note',
             'optional',
@@ -460,16 +461,16 @@ class BomItemSerializer(InvenTreeModelSerializer):
             'pk',
             'part',
             'part_detail',
+            'purchase_price_avg',
+            'purchase_price_max',
+            'purchase_price_min',
+            'purchase_price_range',
             'quantity',
             'reference',
             'sub_part',
             'sub_part_detail',
             # 'price_range',
             'validated',
-            'purchase_price_min',
-            'purchase_price_max',
-            'purchase_price_avg',
-            'purchase_price_range',
         ]
 
 
diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/bom.js
index e35a51d8bd..7328bcb331 100644
--- a/InvenTree/templates/js/bom.js
+++ b/InvenTree/templates/js/bom.js
@@ -285,11 +285,18 @@ function loadBomTable(table, options) {
         title: '{% trans "Optional" %}',
         searchable: false,
         formatter: function(value) {
-            if (value == '1') return '{% trans "true" %}';
-            if (value == '0') return '{% trans "false" %}';
+            return yesNoLabel(value);
         }
     });
 
+    cols.push({
+        field: 'allow_variants',
+        title: '{% trans "Allow Variants" %}',
+        formatter: function(value) {
+            return yesNoLabel(value);
+        }
+    })
+
     cols.push({
         field: 'inherited',
         title: '{% trans "Inherited" %}',
@@ -297,7 +304,7 @@ function loadBomTable(table, options) {
         formatter: function(value, row, index, field) {
             // This BOM item *is* inheritable, but is defined for this BOM
             if (!row.inherited) {
-                return "-"; 
+                return yesNoLabel(false);
             } else if (row.part == options.parent_id) {
                 return '{% trans "Inherited" %}';
             } else {
diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js
index 0233974741..9523d24d39 100644
--- a/InvenTree/templates/js/build.js
+++ b/InvenTree/templates/js/build.js
@@ -372,7 +372,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
                         data.forEach(function(item) {
 
                             // Group BuildItem objects by part
-                            var part = item.part;
+                            var part = item.bom_part || item.part;
                             var key = parseInt(part);
 
                             if (!(key in allocations)) {
@@ -461,6 +461,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
                 data: row.allocations,
                 showHeader: true,
                 columns: [
+                    {
+                        field: 'part',
+                        title: '{% trans "Part" %}',
+                        formatter: function(value, row) {
+
+                            var html = imageHoverIcon(row.part_thumb);
+                            html += renderLink(row.part_name, `/part/${value}/`);
+                            return html;
+                        }
+                    },
                     {
                         width: '50%',
                         field: 'quantity',
diff --git a/InvenTree/templates/js/part.js b/InvenTree/templates/js/part.js
index 81cc4e0630..7331ec25e0 100644
--- a/InvenTree/templates/js/part.js
+++ b/InvenTree/templates/js/part.js
@@ -5,6 +5,14 @@
  * Requires api.js to be loaded first
  */
 
+function yesNoLabel(value) {
+    if (value) {
+        return `<span class='label label-green'>{% trans "YES" %}</span>`;
+    } else {
+        return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
+    }
+}
+
 function toggleStar(options) {
     /* Toggle the 'starred' status of a part.
      * Performs AJAX queries and updates the display on the button.
@@ -662,16 +670,6 @@ function loadPartCategoryTable(table, options) {
     });
 }
 
-
-function yesNoLabel(value) {
-    if (value) {
-        return `<span class='label label-green'>{% trans "YES" %}</span>`;
-    } else {
-        return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
-    }
-}
-
-
 function loadPartTestTemplateTable(table, options) {
     /*
      * Load PartTestTemplate table.
diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js
index 1a052b5fa1..151265a3ae 100644
--- a/InvenTree/templates/js/stock.js
+++ b/InvenTree/templates/js/stock.js
@@ -1304,33 +1304,6 @@ function createNewStockItem(options) {
 function loadInstalledInTable(table, options) {
     /*
     * Display a table showing the stock items which are installed in this stock item.
-    * This is a multi-level tree table, where the "top level" items are Part objects,
-    * and the children of each top-level item are the associated installed stock items.
-    * 
-    * The process for retrieving data and displaying the table is as follows:
-    *
-    * A) Get BOM data for the stock item
-    *    - It is assumed that the stock item will be for an assembly
-    *      (otherwise why are we installing stuff anyway?)
-    *    - Request BOM items for stock_item.part (and only for trackable sub items)
-    * 
-    * B) Add parts to table
-    *    - Create rows for each trackable sub-part in the table
-    *
-    * C) Gather installed stock item data
-    *    - Get the list of installed stock items via the API
-    *    - If the Part reference is already in the table, add the sub-item as a child
-    *    - If this is a stock item for a *new* part, request that part from the API,
-    *      and add that part as a new row, then add the stock item as a child of that part
-    *
-    * D) Enjoy!
-    *
-    *
-    * And the options object contains the following things:
-    *
-    * - stock_item: The PK of the master stock_item object
-    * - part: The PK of the Part reference of the stock_item object
-    * - quantity: The quantity of the stock item
     */
 
     function updateCallbacks() {
@@ -1353,246 +1326,88 @@ function loadInstalledInTable(table, options) {
         });
     }
 
-    table.inventreeTable(
-        {
-            url: "{% url 'api-bom-list' %}",
-            queryParams: {
-                part: options.part,
-                sub_part_trackable: true,
-                sub_part_detail: true,
-            },
-            showColumns: false,
-            name: 'installed-in',
-            detailView: true,
-            detailViewByClick: true,
-            detailFilter: function(index, row) {
-                return row.installed_count && row.installed_count > 0;
-            },
-            detailFormatter: function(index, row, element) {
-                var subTableId = `installed-table-${row.sub_part}`;
+    table.inventreeTable({
+        url: "{% url 'api-stock-list' %}",
+        queryParams: {
+            installed_in: options.stock_item,
+            part_detail: true,
+        },
+        formatNoMatches: function() {
+            return '{% trans "No installed items" %}';
+        },
+        columns: [
+            {
+                field: 'part',
+                title: '{% trans "Part" %}',
+                formatter: function(value, row) {
+                    var html = '';
 
-                var html = `<div class='sub-table'><table class='table table-condensed table-striped' id='${subTableId}'></table></div>`;
+                    html += imageHoverIcon(row.part_detail.thumbnail);
+                    html += renderLink(row.part_detail.full_name, `/stock/item/${row.pk}/`);
 
-                element.html(html);
-
-                var subTable = $(`#${subTableId}`);
-
-                // Display a "sub table" showing all the linked stock items
-                subTable.bootstrapTable({
-                    data: row.installed_items,
-                    showHeader: true,
-                    columns: [
-                        {
-                            field: 'item',
-                            title: '{% trans "Stock Item" %}', 
-                            formatter: function(value, subrow, index, field) {
-
-                                var pk = subrow.pk;
-                                var html = '';
-
-                                if (subrow.serial && subrow.quantity == 1) {
-                                    html += `{% trans "Serial" %}: ${subrow.serial}`;
-                                } else {
-                                    html += `{% trans "Quantity" %}: ${subrow.quantity}`; 
-                                }
-
-                                return renderLink(html, `/stock/item/${subrow.pk}/`);
-                            },
-                        },
-                        {
-                            field: 'status',
-                            title: '{% trans "Status" %}',
-                            formatter: function(value, subrow, index, field) {
-                                return stockStatusDisplay(value);
-                            }
-                        },
-                        {
-                            field: 'batch',
-                            title: '{% trans "Batch" %}',
-                        },
-                        {
-                            field: 'actions',
-                            title: '',
-                            formatter: function(value, subrow, index) {
-
-                                var pk = subrow.pk;
-                                var html = '';
-
-                                // Add some buttons yo!
-                                html += `<div class='btn-group float-right' role='group'>`;
-
-                                html += makeIconButton('fa-unlink', 'button-uninstall', pk, "{% trans 'Uninstall stock item' %}");
-
-                                html += `</div>`;
-
-                                return html;
-                            }
-                        }
-                    ],
-                    onPostBody: function() {
-                        // Setup button callbacks
-                        subTable.find('.button-uninstall').click(function() {
-                            var pk = $(this).attr('pk');
-
-                            launchModalForm(
-                                "{% url 'stock-item-uninstall' %}",
-                                {
-                                    data: {
-                                        'items[]': [pk],
-                                    },
-                                    success: function() {
-                                        // Refresh entire table!
-                                        table.bootstrapTable('refresh');
-                                    }
-                                }
-                            );
-                        });
-                    }
-                });
-            },
-            columns: [
-                {
-                    checkbox: true,
-                    title: '{% trans "Select" %}',
-                    searchable: false,
-                    switchable: false,
-                },
-                {
-                    field: 'pk',
-                    title: 'ID',
-                    visible: false,
-                    switchable: false,
-                },
-                {
-                    field: 'part',
-                    title: '{% trans "Part" %}',
-                    sortable: true,
-                    formatter: function(value, row, index, field) {
-
-                        var url = `/part/${row.sub_part}/`;
-                        var thumb = row.sub_part_detail.thumbnail;
-                        var name = row.sub_part_detail.full_name;
-
-                        html = imageHoverIcon(thumb) + renderLink(name, url);
-
-                        if (row.not_in_bom) {
-                            html = `<i>${html}</i>`
-                        }
-
-                        return html;
-                    }
-                },
-                {
-                    field: 'installed',
-                    title: '{% trans "Installed" %}',
-                    sortable: false,
-                    formatter: function(value, row, index, field) {
-                        // Construct a progress showing how many items have been installed
-
-                        var installed = row.installed_count || 0;
-                        var required = row.quantity || 0;
-
-                        required *= options.quantity;
-
-                        var progress = makeProgressBar(installed, required, {
-                            id: row.sub_part.pk,
-                        });
-
-                        return progress;
-                    }
-                },
-                {
-                    field: 'actions',
-                    switchable: false,
-                    formatter: function(value, row) {
-                        var pk = row.sub_part;
-
-                        var html = `<div class='btn-group float-right' role='group'>`;
-
-                        html += makeIconButton('fa-link', 'button-install', pk, '{% trans "Install item" %}');
-
-                        html += `</div>`;
-
-                        return html;
-                    }
+                    return html;
                 }
-            ],
-            onLoadSuccess: function() {
-                // Grab a list of parts which are actually installed in this stock item
+            },
+            {
+                field: 'quantity',
+                title: '{% trans "Quantity" %}',
+                formatter: function(value, row) {
 
-                inventreeGet(
-                    "{% url 'api-stock-list' %}",
+                    var html = '';
+
+                    if (row.serial && row.quantity == 1) {
+                        html += `{% trans "Serial" %}: ${row.serial}`;
+                    } else {
+                        html += `${row.quantity}`;
+                    }
+
+                    return renderLink(html, `/stock/item/${row.pk}/`);
+                }
+            },
+            {
+                field: 'status',
+                title: '{% trans "Status" %}',
+                formatter: function(value, row) {
+                    return stockStatusDisplay(value);
+                }
+            },
+            {
+                field: 'batch',
+                title: '{% trans "Batch" %}',
+            },
+            {
+                field: 'buttons',
+                title: '',
+                switchable: false,
+                formatter: function(value, row) {
+                    var pk = row.pk;
+                    var html = '';
+
+                    html += `<div class='btn-group float-right' role='group'>`;
+                    html += makeIconButton('fa-unlink', 'button-uninstall', pk, '{% trans "Uninstall Stock Item" %}');
+                    html += `</div>`;
+
+                    return html;
+                }
+            }
+        ],
+        onPostBody: function() {
+            // Assign callbacks to the buttons
+            table.find('.button-uninstall').click(function() {
+                var pk = $(this).attr('pk');
+
+                launchModalForm(
+                    '{% url "stock-item-uninstall" %}',
                     {
-                        installed_in: options.stock_item,
-                        part_detail: true,
-                    },
-                    {
-                        success: function(stock_items) {
-
-                            var table_data = table.bootstrapTable('getData');
-
-                            stock_items.forEach(function(item) {
-
-                                var match = false;
-
-                                for (var idx = 0; idx < table_data.length; idx++) {
-
-                                    var row = table_data[idx];
-
-                                    // Check each row in the table to see if this stock item matches
-                                    table_data.forEach(function(row) {
-
-                                        // Match on "sub_part"
-                                        if (row.sub_part == item.part) {
-
-                                            // First time?
-                                            if (row.installed_count == null) {
-                                                row.installed_count = 0;
-                                                row.installed_items = [];
-                                            }
-
-                                            row.installed_count += item.quantity;
-                                            row.installed_items.push(item);
-
-                                            // Push the row back into the table
-                                            table.bootstrapTable('updateRow', idx, row, true);
-
-                                            match = true;
-                                        }
-
-                                    });
-
-                                    if (match) {
-                                        break;
-                                    }
-                                }
-
-                                if (!match) {
-                                    // The stock item did *not* match any items in the BOM!
-                                    // Add a new row to the table...
-
-                                    // Contruct a new "row" to add to the table
-                                    var new_row = {
-                                        sub_part: item.part,
-                                        sub_part_detail: item.part_detail,
-                                        not_in_bom: true,
-                                        installed_count: item.quantity,
-                                        installed_items: [item],
-                                    };
-
-                                    table.bootstrapTable('append', [new_row]);
-
-                                }
-                            });
-
-                            // Update button callback links
-                            updateCallbacks();
+                        data: {
+                            'items[]': pk,
+                        },
+                        success: function() {
+                            table.bootstrapTable('refresh');
                         }
                     }
-                );
-
-                updateCallbacks();
-            },
+                )
+            });
         }
-    );
+    });
 }
\ No newline at end of file
diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/table_filters.js
index 5f516e9419..d02fa50d80 100644
--- a/InvenTree/templates/js/table_filters.js
+++ b/InvenTree/templates/js/table_filters.js
@@ -49,6 +49,10 @@ function getAvailableTableFilters(tableKey) {
             inherited: {
                 type: 'bool',
                 title: '{% trans "Inherited" %}',
+            },
+            allow_variants: {
+                type: 'bool',
+                title: '{% trans "Allow Variant Stock" %}',
             }
         };
     }