Merge pull request #792 from SchrodingersGat/stock-item-attachment

Stock item attachment
This commit is contained in:
Oliver 2020-05-07 10:07:31 +10:00 committed by GitHub
commit 5747a07f7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 254 additions and 9 deletions

View File

@ -135,7 +135,7 @@ class PartAttachmentDelete(AjaxDeleteView):
model = PartAttachment model = PartAttachment
ajax_form_title = _("Delete Part Attachment") ajax_form_title = _("Delete Part Attachment")
ajax_template_name = "part/attachment_delete.html" ajax_template_name = "attachment_delete.html"
context_object_name = "attachment" context_object_name = "attachment"
def get_data(self): def get_data(self):

View File

@ -8,7 +8,7 @@ from import_export.resources import ModelResource
from import_export.fields import Field from import_export.fields import Field
import import_export.widgets as widgets import import_export.widgets as widgets
from .models import StockLocation, StockItem from .models import StockLocation, StockItem, StockItemAttachment
from .models import StockItemTracking from .models import StockItemTracking
from build.models import Build from build.models import Build
@ -108,6 +108,11 @@ class StockItemAdmin(ImportExportModelAdmin):
list_display = ('part', 'quantity', 'location', 'status', 'updated') list_display = ('part', 'quantity', 'location', 'status', 'updated')
class StockAttachmentAdmin(admin.ModelAdmin):
list_display = ('stock_item', 'attachment', 'comment')
class StockTrackingAdmin(ImportExportModelAdmin): class StockTrackingAdmin(ImportExportModelAdmin):
list_display = ('item', 'date', 'title') list_display = ('item', 'date', 'title')
@ -115,3 +120,4 @@ class StockTrackingAdmin(ImportExportModelAdmin):
admin.site.register(StockLocation, LocationAdmin) admin.site.register(StockLocation, LocationAdmin)
admin.site.register(StockItem, StockItemAdmin) admin.site.register(StockItem, StockItemAdmin)
admin.site.register(StockItemTracking, StockTrackingAdmin) admin.site.register(StockItemTracking, StockTrackingAdmin)
admin.site.register(StockItemAttachment, StockAttachmentAdmin)

View File

@ -13,7 +13,21 @@ from mptt.fields import TreeNodeChoiceField
from InvenTree.helpers import GetExportFormats from InvenTree.helpers import GetExportFormats
from InvenTree.forms import HelperForm from InvenTree.forms import HelperForm
from .models import StockLocation, StockItem, StockItemTracking from .models import StockLocation, StockItem, StockItemTracking, StockItemAttachment
class EditStockItemAttachmentForm(HelperForm):
"""
Form for creating / editing a StockItemAttachment object
"""
class Meta:
model = StockItemAttachment
fields = [
'stock_item',
'attachment',
'comment'
]
class EditStockLocationForm(HelperForm): class EditStockLocationForm(HelperForm):

View File

@ -0,0 +1,27 @@
# Generated by Django 3.0.5 on 2020-05-06 23:36
import InvenTree.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('stock', '0035_auto_20200502_2308'),
]
operations = [
migrations.CreateModel(
name='StockItemAttachment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('attachment', models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment)),
('comment', models.CharField(help_text='File comment', max_length=100)),
('stock_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='stock.StockItem')),
],
options={
'abstract': False,
},
),
]

View File

@ -6,6 +6,8 @@ Stock database model definitions
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import os
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.urls import reverse from django.urls import reverse
@ -27,7 +29,7 @@ from datetime import datetime
from InvenTree import helpers from InvenTree import helpers
from InvenTree.status_codes import StockStatus from InvenTree.status_codes import StockStatus
from InvenTree.models import InvenTreeTree from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField from InvenTree.fields import InvenTreeURLField
from part import models as PartModels from part import models as PartModels
@ -935,6 +937,21 @@ def before_delete_stock_item(sender, instance, using, **kwargs):
StockItem.objects.rebuild() StockItem.objects.rebuild()
class StockItemAttachment(InvenTreeAttachment):
"""
Model for storing file attachments against a StockItem object.
"""
def getSubdir(self):
return os.path.join("stock_files", str(self.stock_item.id))
stock_item = models.ForeignKey(
StockItem,
on_delete=models.CASCADE,
related_name='attachments'
)
class StockItemTracking(models.Model): class StockItemTracking(models.Model):
""" Stock tracking entry - breacrumb for keeping track of automated stock transactions """ Stock tracking entry - breacrumb for keeping track of automated stock transactions

View File

@ -0,0 +1,82 @@
{% extends "stock/item_base.html" %}
{% load static %}
{% load i18n %}
{% block details %}
{% include "stock/tabs.html" with tab='attachments' %}
<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>
{% endblock %}
{% block js_ready %}
{{ block.super }}
$("#new-attachment").click(function() {
launchModalForm("{% url 'stock-item-attachment-create' %}?item={{ item.id }}",
{
reload: true,
});
});
$("#attachment-table").on('click', '.attachment-edit-button', function() {
var button = $(this);
launchModalForm(button.attr('url'),
{
reload: true,
});
});
$("#attachment-table").on('click', '.attachment-delete-button', function() {
var button = $(this);
launchModalForm(button.attr('url'), {
success: function() {
location.reload();
}
});
});
$("#attachment-table").inventreeTable({
});
{% endblock %}

View File

@ -1,12 +1,15 @@
{% load i18n %} {% load i18n %}
<ul class='nav nav-tabs'> <ul class='nav nav-tabs'>
<li{% ifequal tab 'tracking' %} class='active'{% endifequal %}>
<a href="{% url 'stock-item-detail' item.id %}">{% trans "Tracking" %}</a>
</li>
<li{% ifequal tab 'children' %} class='active'{% endifequal %}> <li{% ifequal tab 'children' %} class='active'{% endifequal %}>
<a href="{% url 'stock-item-children' item.id %}">{% trans "Children" %}{% if item.child_count > 0 %}<span class='badge'>{{ item.child_count }}</span>{% endif %}</a> <a href="{% url 'stock-item-children' item.id %}">{% trans "Children" %}{% if item.child_count > 0 %}<span class='badge'>{{ item.child_count }}</span>{% endif %}</a>
</li> </li>
<li{% ifequal tab 'tracking' %} class='active'{% endifequal %}>
<a href="{% url 'stock-item-detail' item.id %}">
{% trans "Tracking" %}
{% if item.tracking_info.count > 0 %}<span class='badge'>{{ item.tracking_info.count }}</span>{% endif %}
</a>
</li>
{% if 0 %} {% if 0 %}
<!-- These tabs are to be implemented in the future --> <!-- These tabs are to be implemented in the future -->
<li{% ifequal tab 'builds' %} class='active'{% endifequal %}> <li{% ifequal tab 'builds' %} class='active'{% endifequal %}>
@ -14,6 +17,15 @@
</li> </li>
{% endif %} {% endif %}
<li{% ifequal tab 'notes' %} class='active'{% endifequal %}> <li{% ifequal tab 'notes' %} class='active'{% endifequal %}>
<a href="{% url 'stock-item-notes' item.id %}">{% trans "Notes" %}{% if item.notes %} <span class='fas fa-info-circle'></span>{% endif %}</a> <a href="{% url 'stock-item-notes' item.id %}">
{% trans "Notes" %}
{% if item.notes %} <span class='fas fa-info-circle'></span>{% endif %}
</a>
</li>
<li{% if tab == 'attachments' %} class='active'{% endif %}>
<a href="{% url 'stock-item-attachments' item.id %}">
{% trans "Attachments" %}
{% if item.attachments.count > 0 %}<span class='badge'>{{ item.attachments.count }}</span>{% endif %}
</a>
</li> </li>
</ul> </ul>

View File

@ -25,6 +25,7 @@ stock_item_detail_urls = [
url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'), url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'),
url(r'^children/', views.StockItemDetail.as_view(template_name='stock/item_childs.html'), name='stock-item-children'), url(r'^children/', views.StockItemDetail.as_view(template_name='stock/item_childs.html'), name='stock-item-children'),
url(r'^attachments/', views.StockItemDetail.as_view(template_name='stock/item_attachments.html'), name='stock-item-attachments'),
url(r'^notes/', views.StockItemNotes.as_view(), name='stock-item-notes'), url(r'^notes/', views.StockItemNotes.as_view(), name='stock-item-notes'),
url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'), url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
@ -50,6 +51,12 @@ stock_urls = [
url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'), url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'),
url(r'^item/attachment/', include([
url(r'^new/', views.StockItemAttachmentCreate.as_view(), name='stock-item-attachment-create'),
url(r'^(?P<pk>\d+)/edit/', views.StockItemAttachmentEdit.as_view(), name='stock-item-attachment-edit'),
url(r'^(?P<pk>\d+)/delete/', views.StockItemAttachmentDelete.as_view(), name='stock-item-attachment-delete'),
])),
url(r'^track/', include(stock_tracking_urls)), url(r'^track/', include(stock_tracking_urls)),
url(r'^adjust/?', views.StockAdjust.as_view(), name='stock-adjust'), url(r'^adjust/?', views.StockAdjust.as_view(), name='stock-adjust'),

View File

@ -26,7 +26,7 @@ from datetime import datetime
from company.models import Company, SupplierPart from company.models import Company, SupplierPart
from part.models import Part from part.models import Part
from .models import StockItem, StockLocation, StockItemTracking from .models import StockItem, StockLocation, StockItemTracking, StockItemAttachment
from .admin import StockItemResource from .admin import StockItemResource
@ -37,6 +37,7 @@ from .forms import AdjustStockForm
from .forms import TrackingEntryForm from .forms import TrackingEntryForm
from .forms import SerializeStockForm from .forms import SerializeStockForm
from .forms import ExportOptionsForm from .forms import ExportOptionsForm
from .forms import EditStockItemAttachmentForm
class StockIndex(ListView): class StockIndex(ListView):
@ -149,6 +150,78 @@ class StockLocationQRCode(QRCodeView):
return None return None
class StockItemAttachmentCreate(AjaxCreateView):
"""
View for adding a new attachment for a StockItem
"""
model = StockItemAttachment
form_class = EditStockItemAttachmentForm
ajax_form_title = _("Add Stock Item Attachment")
ajax_template_name = "modal_form.html"
def get_data(self):
return {
'success': _("Added attachment")
}
def get_initial(self):
"""
Get initial data for the new StockItem attachment object.
- Client must provide a valid StockItem ID
"""
initials = super().get_initial()
try:
initials['stock_item'] = StockItem.objects.get(id=self.request.GET.get('item', None))
except (ValueError, StockItem.DoesNotExist):
pass
return initials
def get_form(self):
form = super().get_form()
form.fields['stock_item'].widget = HiddenInput()
return form
class StockItemAttachmentEdit(AjaxUpdateView):
"""
View for editing a StockItemAttachment object.
"""
model = StockItemAttachment
form_class = EditStockItemAttachmentForm
ajax_form_title = _("Edit Stock Item Attachment")
def get_form(self):
form = super().get_form()
form.fields['stock_item'].widget = HiddenInput()
return form
class StockItemAttachmentDelete(AjaxDeleteView):
"""
View for deleting a StockItemAttachment object.
"""
model = StockItemAttachment
ajax_form_title = _("Delete Stock Item Attachment")
ajax_template_name = "attachment_delete.html"
context_object_name = "attachment"
def get_data(self):
return {
'danger': _("Deleted attachment"),
}
class StockExportOptions(AjaxView): class StockExportOptions(AjaxView):
""" Form for selecting StockExport options """ """ Form for selecting StockExport options """

View File

@ -0,0 +1,7 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% block pre_form_content %}
{% trans "Are you sure you want to delete this attachment?" %}
<br>
{% endblock %}