mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #792 from SchrodingersGat/stock-item-attachment
Stock item attachment
This commit is contained in:
commit
5747a07f7b
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
27
InvenTree/stock/migrations/0036_stockitemattachment.py
Normal file
27
InvenTree/stock/migrations/0036_stockitemattachment.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
@ -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
|
||||
|
||||
|
82
InvenTree/stock/templates/stock/item_attachments.html
Normal file
82
InvenTree/stock/templates/stock/item_attachments.html
Normal 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 %}
|
@ -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>
|
@ -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'),
|
||||
|
@ -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 """
|
||||
|
||||
|
7
InvenTree/templates/attachment_delete.html
Normal file
7
InvenTree/templates/attachment_delete.html
Normal 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 %}
|
Loading…
Reference in New Issue
Block a user