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
ajax_form_title = _("Delete Part Attachment")
ajax_template_name = "part/attachment_delete.html"
ajax_template_name = "attachment_delete.html"
context_object_name = "attachment"
def get_data(self):

View File

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

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 -*-
from __future__ import unicode_literals
import os
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from django.urls import reverse
@ -27,7 +29,7 @@ from datetime import datetime
from InvenTree import helpers
from InvenTree.status_codes import StockStatus
from InvenTree.models import InvenTreeTree
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField
from part import models as PartModels
@ -935,6 +937,21 @@ def before_delete_stock_item(sender, instance, using, **kwargs):
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):
""" 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 %}
<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 %}>
<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{% 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 %}
<!-- These tabs are to be implemented in the future -->
<li{% ifequal tab 'builds' %} class='active'{% endifequal %}>
@ -14,6 +17,15 @@
</li>
{% endif %}
<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>
</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'^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('^.*$', 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/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'^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 part.models import Part
from .models import StockItem, StockLocation, StockItemTracking
from .models import StockItem, StockLocation, StockItemTracking, StockItemAttachment
from .admin import StockItemResource
@ -37,6 +37,7 @@ from .forms import AdjustStockForm
from .forms import TrackingEntryForm
from .forms import SerializeStockForm
from .forms import ExportOptionsForm
from .forms import EditStockItemAttachmentForm
class StockIndex(ListView):
@ -149,6 +150,78 @@ class StockLocationQRCode(QRCodeView):
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):
""" 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 %}