diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py
index abb6102a6f..eb87b8f77a 100644
--- a/InvenTree/InvenTree/api.py
+++ b/InvenTree/InvenTree/api.py
@@ -8,6 +8,9 @@ from __future__ import unicode_literals
 from django.utils.translation import ugettext as _
 from django.http import JsonResponse
 
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework import filters
+
 from rest_framework import permissions
 from rest_framework.response import Response
 from rest_framework.views import APIView
@@ -41,6 +44,28 @@ class InfoView(AjaxView):
         return JsonResponse(data)
 
 
+class AttachmentMixin:
+    """
+    Mixin for creating attachment objects,
+    and ensuring the user information is saved correctly.
+    """
+
+    permission_classes = [permissions.IsAuthenticated]
+
+    filter_backends = [
+        DjangoFilterBackend,
+        filters.OrderingFilter,
+        filters.SearchFilter,
+    ]
+
+    def perform_create(self, serializer):
+        """ Save the user information when a file is uploaded """
+
+        attachment = serializer.save()
+        attachment.user = self.request.user
+        attachment.save()
+
+
 class ActionPluginView(APIView):
     """
     Endpoint for running custom action plugins.
diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py
index d2a5c5daa6..e192e34a0f 100644
--- a/InvenTree/InvenTree/models.py
+++ b/InvenTree/InvenTree/models.py
@@ -7,6 +7,7 @@ from __future__ import unicode_literals
 import os
 
 from django.db import models
+from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.utils.translation import gettext_lazy as _
 
@@ -41,6 +42,8 @@ class InvenTreeAttachment(models.Model):
     Attributes:
         attachment: File
         comment: String descriptor for the attachment
+        user: User associated with file upload
+        upload_date: Date the file was uploaded
     """
     def getSubdir(self):
         """
@@ -55,6 +58,15 @@ class InvenTreeAttachment(models.Model):
 
     comment = models.CharField(max_length=100, help_text=_('File comment'))
 
+    user = models.ForeignKey(
+        User,
+        on_delete=models.SET_NULL,
+        blank=True, null=True,
+        help_text=_('User'),
+    )
+
+    upload_date = models.DateField(auto_now_add=True, null=True, blank=True)
+
     @property
     def basename(self):
         return os.path.basename(self.attachment.name)
diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py
index cb48b4c11d..dba493baab 100644
--- a/InvenTree/order/api.py
+++ b/InvenTree/order/api.py
@@ -12,6 +12,7 @@ from rest_framework import filters
 from django.conf.urls import url, include
 
 from InvenTree.helpers import str2bool
+from InvenTree.api import AttachmentMixin
 
 from part.models import Part
 from company.models import SupplierPart
@@ -200,7 +201,7 @@ class POLineItemDetail(generics.RetrieveUpdateAPIView):
     ]
 
 
-class SOAttachmentList(generics.ListCreateAPIView):
+class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
     """
     API endpoint for listing (and creating) a SalesOrderAttachment (file upload)
     """
@@ -208,12 +209,6 @@ class SOAttachmentList(generics.ListCreateAPIView):
     queryset = SalesOrderAttachment.objects.all()
     serializer_class = SOAttachmentSerializer
 
-    filter_backends = [
-        DjangoFilterBackend,
-        filters.OrderingFilter,
-        filters.SearchFilter,
-    ]
-
     filter_fields = [
         'order',
     ]
@@ -399,7 +394,7 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView):
     permission_classes = [permissions.IsAuthenticated]
 
 
-class POAttachmentList(generics.ListCreateAPIView):
+class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
     """
     API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)
     """
@@ -407,12 +402,6 @@ class POAttachmentList(generics.ListCreateAPIView):
     queryset = PurchaseOrderAttachment.objects.all()
     serializer_class = POAttachmentSerializer
 
-    filter_backends = [
-        DjangoFilterBackend,
-        filters.OrderingFilter,
-        filters.SearchFilter,
-    ]
-
     filter_fields = [
         'order',
     ]
diff --git a/InvenTree/order/migrations/0033_auto_20200512_1033.py b/InvenTree/order/migrations/0033_auto_20200512_1033.py
new file mode 100644
index 0000000000..2c3abbb0d0
--- /dev/null
+++ b/InvenTree/order/migrations/0033_auto_20200512_1033.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.0.5 on 2020-05-12 10:33
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('order', '0032_auto_20200427_0044'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='purchaseorderattachment',
+            name='user',
+            field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AddField(
+            model_name='salesorderattachment',
+            name='user',
+            field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/InvenTree/order/migrations/0034_auto_20200512_1054.py b/InvenTree/order/migrations/0034_auto_20200512_1054.py
new file mode 100644
index 0000000000..9124bb1cce
--- /dev/null
+++ b/InvenTree/order/migrations/0034_auto_20200512_1054.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.0.5 on 2020-05-12 10:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('order', '0033_auto_20200512_1033'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='purchaseorderattachment',
+            name='upload_date',
+            field=models.DateField(auto_now_add=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='salesorderattachment',
+            name='upload_date',
+            field=models.DateField(auto_now_add=True, null=True),
+        ),
+    ]
diff --git a/InvenTree/order/templates/order/po_attachments.html b/InvenTree/order/templates/order/po_attachments.html
index 388b7197b6..a08b618fca 100644
--- a/InvenTree/order/templates/order/po_attachments.html
+++ b/InvenTree/order/templates/order/po_attachments.html
@@ -12,39 +12,8 @@
 
 <hr>
 
-<div id='attachment-buttons'>
-    <div class='btn-group'>
-        <button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
-    </div>
-</div>
+{% include "attachment_table.html" with attachments=order.attachments.all %}
 
-<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
-    <thead>
-        <tr>
-            <th data-field='file' data-searchable='true'>{% trans "File" %}</th>
-            <th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for attachment in order.attachments.all %}
-        <tr>
-            <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
-            <td>{{ attachment.comment }}</td>
-            <td>
-                <div class='btn-group' style='float: right;'>    
-                    <button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'po-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
-                        <span class='fas fa-edit'/>
-                    </button>
-                    <button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'po-attachment-delete' attachment.id %}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
-                        <span class='fas fa-trash-alt icon-red'/>
-                    </button>
-                </div>
-            </td>
-        </tr>
-        {% endfor %}
-    </tbody>
-</table>
 
 {% endblock %}
 
@@ -61,8 +30,10 @@ $("#new-attachment").click(function() {
 
 $("#attachment-table").on('click', '.attachment-edit-button', function() {
     var button = $(this);
+    
+    var url = `/order/purchase-order/attachment/${button.attr('pk')}/edit/`;
 
-    launchModalForm(button.attr('url'), {
+    launchModalForm(url, {
         reload: true,
     });
 });
@@ -70,7 +41,11 @@ $("#attachment-table").on('click', '.attachment-edit-button', function() {
 $("#attachment-table").on('click', '.attachment-delete-button', function() {
     var button = $(this);
 
-    launchModalForm(button.attr('url'), {
+    var url = `/order/purchase-order/attachment/${button.attr('pk')}/delete/`;
+
+    console.log("url: " + url);
+
+    launchModalForm(url, {
         reload: true,
     });
 });
diff --git a/InvenTree/order/templates/order/so_attachments.html b/InvenTree/order/templates/order/so_attachments.html
index ca4170beff..aff62213e5 100644
--- a/InvenTree/order/templates/order/so_attachments.html
+++ b/InvenTree/order/templates/order/so_attachments.html
@@ -8,43 +8,11 @@
 
 {% include 'order/so_tabs.html' with tab='attachments' %}
 
-<h4>{% trans "Sales Order Attachments" %}
+<h4>{% trans "Sales Order Attachments" %}</h4>
 
 <hr>
 
-<div id='attachment-buttons'>
-    <div class='btn-group'>
-        <button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
-    </div>
-</div>
-
-<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
-    <thead>
-        <tr>
-            <th data-field='file' data-searchable='true'>{% trans "File" %}</th>
-            <th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for attachment in order.attachments.all %}
-        <tr>
-            <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
-            <td>{{ attachment.comment }}</td>
-            <td>
-                <div class='btn-group' style='float: right;'>    
-                    <button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'so-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
-                        <span class='fas fa-edit'/>
-                    </button>
-                    <button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'so-attachment-delete' attachment.id %}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
-                        <span class='fas fa-trash-alt icon-red'/>
-                    </button>
-                </div>
-            </td>
-        </tr>
-        {% endfor %}
-    </tbody>
-</table>
+{% include "attachment_table.html" with attachments=order.attachments.all %}
 
 {% endblock %}
 
@@ -62,7 +30,9 @@ $("#new-attachment").click(function() {
 $("#attachment-table").on('click', '.attachment-edit-button', function() {
     var button = $(this);
 
-    launchModalForm(button.attr('url'), {
+    var url = `/order/sales-order/attachment/${button.attr('pk')}/edit/`;
+
+    launchModalForm(url, {
         reload: true,
     });
 });
@@ -70,7 +40,9 @@ $("#attachment-table").on('click', '.attachment-edit-button', function() {
 $("#attachment-table").on('click', '.attachment-delete-button', function() {
     var button = $(this);
 
-    launchModalForm(button.attr('url'), {
+    var url = `/order/sales-order/attachment/${button.attr('pk')}/delete/`;
+
+    launchModalForm(url, {
         reload: true,
     });
 });
diff --git a/InvenTree/order/urls.py b/InvenTree/order/urls.py
index daa83eb5ab..22104df5c7 100644
--- a/InvenTree/order/urls.py
+++ b/InvenTree/order/urls.py
@@ -42,7 +42,7 @@ purchase_order_urls = [
         ])),
     ])),
 
-    url(r'^attachments/', include([
+    url(r'^attachment/', include([
         url(r'^new/', views.PurchaseOrderAttachmentCreate.as_view(), name='po-attachment-create'),
         url(r'^(?P<pk>\d+)/edit/', views.PurchaseOrderAttachmentEdit.as_view(), name='po-attachment-edit'),
         url(r'^(?P<pk>\d+)/delete/', views.PurchaseOrderAttachmentDelete.as_view(), name='po-attachment-delete'),
@@ -86,7 +86,7 @@ sales_order_urls = [
         ])),
     ])),
 
-    url(r'^attachments/', include([
+    url(r'^attachment/', include([
         url(r'^new/', views.SalesOrderAttachmentCreate.as_view(), name='so-attachment-create'),
         url(r'^(?P<pk>\d+)/edit/', views.SalesOrderAttachmentEdit.as_view(), name='so-attachment-edit'),
         url(r'^(?P<pk>\d+)/delete/', views.SalesOrderAttachmentDelete.as_view(), name='so-attachment-delete'),
diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py
index 02132d34cc..370c629cac 100644
--- a/InvenTree/order/views.py
+++ b/InvenTree/order/views.py
@@ -93,6 +93,10 @@ class PurchaseOrderAttachmentCreate(AjaxCreateView):
     ajax_form_title = _("Add Purchase Order Attachment")
     ajax_template_name = "modal_form.html"
 
+    def post_save(self, **kwargs):
+        self.object.user = self.request.user
+        self.object.save()
+
     def get_data(self):
         return {
             "success": _("Added attachment")
@@ -133,6 +137,10 @@ class SalesOrderAttachmentCreate(AjaxCreateView):
     form_class = order_forms.EditSalesOrderAttachmentForm
     ajax_form_title = _('Add Sales Order Attachment')
 
+    def post_save(self, **kwargs):
+        self.object.user = self.request.user
+        self.object.save()
+
     def get_data(self):
         return {
             'success': _('Added attachment')
diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py
index 8f9121885d..84ff83af0a 100644
--- a/InvenTree/part/api.py
+++ b/InvenTree/part/api.py
@@ -25,6 +25,7 @@ from . import serializers as part_serializers
 
 from InvenTree.views import TreeSerializer
 from InvenTree.helpers import str2bool, isNull
+from InvenTree.api import AttachmentMixin
 
 
 class PartCategoryTree(TreeSerializer):
@@ -106,7 +107,7 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
     queryset = PartCategory.objects.all()
 
 
-class PartAttachmentList(generics.ListCreateAPIView):
+class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
     """
     API endpoint for listing (and creating) a PartAttachment (file upload).
     """
@@ -114,14 +115,6 @@ class PartAttachmentList(generics.ListCreateAPIView):
     queryset = PartAttachment.objects.all()
     serializer_class = part_serializers.PartAttachmentSerializer
 
-    permission_classes = [permissions.IsAuthenticated]
-
-    filter_backends = [
-        DjangoFilterBackend,
-        filters.OrderingFilter,
-        filters.SearchFilter,
-    ]
-
     filter_fields = [
         'part',
     ]
@@ -296,24 +289,17 @@ class PartList(generics.ListCreateAPIView):
         else:
             return Response(data)
 
-    def create(self, request, *args, **kwargs):
-        """ Override the default 'create' behaviour:
+    def perform_create(self, serializer):
+        """
         We wish to save the user who created this part!
 
-        Note: Implementation coped from DRF class CreateModelMixin
+        Note: Implementation copied from DRF class CreateModelMixin
         """
 
-        serializer = self.get_serializer(data=request.data)
-        serializer.is_valid(raise_exception=True)
-        
-        # Record the user who created this Part object
         part = serializer.save()
-        part.creation_user = request.user
+        part.creation_user = self.request.user
         part.save()
 
-        headers = self.get_success_headers(serializer.data)
-        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
     def get_queryset(self, *args, **kwargs):
 
         queryset = super().get_queryset(*args, **kwargs)
diff --git a/InvenTree/part/migrations/0036_partattachment_user.py b/InvenTree/part/migrations/0036_partattachment_user.py
new file mode 100644
index 0000000000..d59bc7ffb2
--- /dev/null
+++ b/InvenTree/part/migrations/0036_partattachment_user.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.5 on 2020-05-12 10:33
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('part', '0035_auto_20200406_0045'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='partattachment',
+            name='user',
+            field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/InvenTree/part/migrations/0037_partattachment_upload_date.py b/InvenTree/part/migrations/0037_partattachment_upload_date.py
new file mode 100644
index 0000000000..4169870178
--- /dev/null
+++ b/InvenTree/part/migrations/0037_partattachment_upload_date.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.5 on 2020-05-12 10:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('part', '0036_partattachment_user'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='partattachment',
+            name='upload_date',
+            field=models.DateField(auto_now_add=True, null=True),
+        ),
+    ]
diff --git a/InvenTree/part/templates/part/attachments.html b/InvenTree/part/templates/part/attachments.html
index b89a40e654..965645b748 100644
--- a/InvenTree/part/templates/part/attachments.html
+++ b/InvenTree/part/templates/part/attachments.html
@@ -9,54 +9,7 @@
 
 <hr>
 
-<div id='attachment-buttons'>
-    <div class="btn-group">
-        <button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
-    </div>
-</div>
-
-
-<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
-    <thead>
-        <tr>
-            <th data-field='file' data-searchable='true'>{% trans "File" %}</th>
-            <th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for attachment in part.attachments.all %}
-        <tr>
-            <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
-            <td>{{ attachment.comment }}</td>
-            <td>
-                <div class='btn-group' style='float: right;'>    
-                    <button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'part-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
-                        <span class='fas fa-edit'/>
-                    </button>
-                    <button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'part-attachment-delete' attachment.id %}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
-                        <span class='fas fa-trash-alt icon-red'/>
-                    </button>
-                </div>
-            </td>
-        </tr>
-        {% endfor %}
-    </tbody>
-{% if part.variant_of and part.variant_of.attachments.count > 0 %}
-<tr>
-    <td colspan='3'>
-        Attachments for template part <b><i>{{ part.variant_of.full_name }}</i></b>
-    </td>
-</tr>
-{% for attachment in part.variant_of.attachments.all %}
-<tr>
-    <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
-    <td>{{ attachment.comment }}</td>
-    <td></td>
-</tr>
-{% endfor %}
-{% endif %}
-</table>
+{% include "attachment_table.html" with attachments=part.attachments.all %}
 
 {% endblock %}
 
@@ -73,7 +26,9 @@
     $("#attachment-table").on('click', '.attachment-edit-button', function() {
         var button = $(this);
 
-        launchModalForm(button.attr('url'), 
+        var url = `/part/attachment/${button.attr('pk')}/edit/`;
+
+        launchModalForm(url, 
             {
                 reload: true,
             });
@@ -82,7 +37,9 @@
     $("#attachment-table").on('click', '.attachment-delete-button', function() {
         var button = $(this);
 
-        launchModalForm(button.attr('url'), {
+        var url = `/part/attachment/${button.attr('pk')}/delete/`;
+
+        launchModalForm(url, {
             success: function() {
                 location.reload();
             }
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 89a285cf60..ce2fc415be 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -74,6 +74,11 @@ class PartAttachmentCreate(AjaxCreateView):
     ajax_form_title = _("Add part attachment")
     ajax_template_name = "modal_form.html"
 
+    def post_save(self):
+        """ Record the user that uploaded the attachment """
+        self.object.user = self.request.user
+        self.object.save()
+
     def get_data(self):
         return {
             'success': _('Added attachment')
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index f72f2590cc..1c654d8b3c 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -29,6 +29,7 @@ from .serializers import StockItemAttachmentSerializer
 
 from InvenTree.views import TreeSerializer
 from InvenTree.helpers import str2bool, isNull
+from InvenTree.api import AttachmentMixin
 
 from decimal import Decimal, InvalidOperation
 
@@ -478,6 +479,23 @@ class StockList(generics.ListCreateAPIView):
 
         if sales_order:
             queryset = queryset.filter(sales_order=sales_order)
+
+        # Filter by "serialized" status?
+        serialized = params.get('serialized', None)
+
+        if serialized is not None:
+            serialized = str2bool(serialized)
+
+            if serialized:
+                queryset = queryset.exclude(serial=None)
+            else:
+                queryset = queryset.filter(serial=None)
+
+        # Filter by serial number?
+        serial_number = params.get('serial', None)
+
+        if serial_number is not None:
+            queryset = queryset.filter(serial=serial_number)
         
         in_stock = self.request.query_params.get('in_stock', None)
 
@@ -628,7 +646,7 @@ class StockList(generics.ListCreateAPIView):
     ]
 
 
-class StockAttachmentList(generics.ListCreateAPIView):
+class StockAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
     """
     API endpoint for listing (and creating) a StockItemAttachment (file upload)
     """
@@ -636,12 +654,6 @@ class StockAttachmentList(generics.ListCreateAPIView):
     queryset = StockItemAttachment.objects.all()
     serializer_class = StockItemAttachmentSerializer
 
-    filter_backends = [
-        DjangoFilterBackend,
-        filters.OrderingFilter,
-        filters.SearchFilter,
-    ]
-
     filter_fields = [
         'stock_item',
     ]
diff --git a/InvenTree/stock/migrations/0037_stockitemattachment_user.py b/InvenTree/stock/migrations/0037_stockitemattachment_user.py
new file mode 100644
index 0000000000..b0f87df81a
--- /dev/null
+++ b/InvenTree/stock/migrations/0037_stockitemattachment_user.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.5 on 2020-05-12 10:33
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('stock', '0036_stockitemattachment'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='stockitemattachment',
+            name='user',
+            field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/InvenTree/stock/migrations/0038_stockitemattachment_upload_date.py b/InvenTree/stock/migrations/0038_stockitemattachment_upload_date.py
new file mode 100644
index 0000000000..0347b6f0ea
--- /dev/null
+++ b/InvenTree/stock/migrations/0038_stockitemattachment_upload_date.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.5 on 2020-05-12 10:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('stock', '0037_stockitemattachment_user'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='stockitemattachment',
+            name='upload_date',
+            field=models.DateField(auto_now_add=True, null=True),
+        ),
+    ]
diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py
index 69cb726f73..622f93e620 100644
--- a/InvenTree/stock/serializers.py
+++ b/InvenTree/stock/serializers.py
@@ -193,6 +193,16 @@ class LocationSerializer(InvenTreeModelSerializer):
 class StockItemAttachmentSerializer(InvenTreeModelSerializer):
     """ Serializer for StockItemAttachment model """
 
+    def __init_(self, *args, **kwargs):
+        user_detail = kwargs.pop('user_detail', False)
+
+        super().__init__(*args, **kwargs)
+
+        if user_detail is not True:
+            self.fields.pop('user_detail')
+
+    user_detail = UserSerializerBrief(source='user', read_only=True)
+
     class Meta:
         model = StockItemAttachment
 
@@ -200,7 +210,9 @@ class StockItemAttachmentSerializer(InvenTreeModelSerializer):
             'pk',
             'stock_item',
             'attachment',
-            'comment'
+            'comment',
+            'user',
+            'user_detail',
         ]
 
 
diff --git a/InvenTree/stock/templates/stock/item_attachments.html b/InvenTree/stock/templates/stock/item_attachments.html
index 6aeff554d0..2d056afbcf 100644
--- a/InvenTree/stock/templates/stock/item_attachments.html
+++ b/InvenTree/stock/templates/stock/item_attachments.html
@@ -10,40 +10,7 @@
 <hr>
 <h4>{% trans "Stock Item Attachments" %}</h4>
 
-
-<div id='attachment-buttons'>
-    <div class="btn-group">
-        <button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
-    </div>
-</div>
-
-<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
-    <thead>
-        <tr>
-            <th data-field='file' data-searchable='true'>{% trans "File" %}</th>
-            <th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for attachment in item.attachments.all %}
-        <tr>
-            <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
-            <td>{{ attachment.comment }}</td>
-            <td>
-                <div class='btn-group' style='float: right;'>    
-                    <button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'stock-item-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
-                        <span class='fas fa-edit'/>
-                    </button>
-                    <button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'stock-item-attachment-delete' attachment.id %}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
-                        <span class='fas fa-trash-alt icon-red'/>
-                    </button>
-                </div>
-            </td>
-        </tr>
-        {% endfor %}
-    </tbody>
-</table>
+{% include "attachment_table.html" with attachments=item.attachments.all %}
 
 {% endblock %}
 
@@ -60,7 +27,9 @@ $("#new-attachment").click(function() {
 $("#attachment-table").on('click', '.attachment-edit-button', function() {
     var button = $(this);
 
-    launchModalForm(button.attr('url'), 
+    var url = `/stock/item/attachment/${button.attr('pk')}/edit/`;
+
+    launchModalForm(url, 
         {
             reload: true,
         });
@@ -69,7 +38,9 @@ $("#attachment-table").on('click', '.attachment-edit-button', function() {
 $("#attachment-table").on('click', '.attachment-delete-button', function() {
     var button = $(this);
 
-    launchModalForm(button.attr('url'), {
+    var url = `/stock/item/attachment/${button.attr('pk')}/delete/`;
+
+    launchModalForm(url, {
         success: function() {
             location.reload();
         }
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index 19edf8666d..e616be1f35 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -160,6 +160,12 @@ class StockItemAttachmentCreate(AjaxCreateView):
     ajax_form_title = _("Add Stock Item Attachment")
     ajax_template_name = "modal_form.html"
 
+    def post_save(self, **kwargs):
+        """ Record the user that uploaded the attachment """
+        
+        self.object.user = self.request.user
+        self.object.save()
+
     def get_data(self):
         return {
             'success': _("Added attachment")
diff --git a/InvenTree/templates/attachment_table.html b/InvenTree/templates/attachment_table.html
new file mode 100644
index 0000000000..090ae566f6
--- /dev/null
+++ b/InvenTree/templates/attachment_table.html
@@ -0,0 +1,40 @@
+{% load i18n %}
+
+<div id='attachment-buttons'>
+    <div class='btn-group'>
+        <button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
+    </div>
+</div>
+
+<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
+    <thead>
+        <tr>
+            <th data-field='file' data-sortable='true' data-searchable='true'>{% trans "File" %}</th>
+            <th data-field='comment' data-sortable='true' data-searchable='true'>{% trans "Comment" %}</th>
+            <th data-field='user' data-sortable='true' data-searchable='true'>{% trans "Uploaded" %}</th>
+            <th></th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for attachment in attachments %}
+        <tr>
+            <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
+            <td>{{ attachment.comment }}</td>
+            <td>
+                {% if attachment.upload_date %}{{ attachment.upload_date }}{% endif %}
+                {% if attachment.user %}<span class='badge'>{{ attachment.user.username }}</div>{% endif %}
+            </td>
+            <td>
+                <div class='btn-group' style='float: right;'>    
+                    <button type='button' class='btn btn-default btn-glyph attachment-edit-button' pk="{{ attachment.id }}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
+                        <span class='fas fa-edit'/>
+                    </button>
+                    <button type='button' class='btn btn-default btn-glyph attachment-delete-button' pk="{{ attachment.id }}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
+                        <span class='fas fa-trash-alt icon-red'/>
+                    </button>
+                </div>
+            </td>
+        </tr>
+        {% endfor %}
+    </tbody>
+</table>
\ No newline at end of file
diff --git a/README.md b/README.md
index 7afe6269ae..d4a3dfc869 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,15 @@ Refer to the [getting started guide](https://inventree.github.io/docs/start/inst
 
 For InvenTree documentation, refer to the [InvenTre documentation website](https://inventree.github.io).
 
+## Integration
+
+InvenTree is designed to be extensible, and provides multiple options for integration with external applications or addition of custom plugins:
+
+* [InvenTree API](https://inventree.github.io/docs/extend/api)
+* [Python module](https://inventree.github.io/docs/extend/python)
+* [Plugin interface](https://inventree.github.io/docs/extend/plugins)
+* [Third party](https://inventree.github.io/docs/extend/integrate)
+
 ## Developer Documentation
 
 For code documentation, refer to the [developer documentation](http://inventree.readthedocs.io/en/latest/).