mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
dcf00d816d
@ -8,6 +8,9 @@ from __future__ import unicode_literals
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.http import JsonResponse
|
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 import permissions
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@ -41,6 +44,28 @@ class InfoView(AjaxView):
|
|||||||
return JsonResponse(data)
|
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):
|
class ActionPluginView(APIView):
|
||||||
"""
|
"""
|
||||||
Endpoint for running custom action plugins.
|
Endpoint for running custom action plugins.
|
||||||
|
@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -41,6 +42,8 @@ class InvenTreeAttachment(models.Model):
|
|||||||
Attributes:
|
Attributes:
|
||||||
attachment: File
|
attachment: File
|
||||||
comment: String descriptor for the attachment
|
comment: String descriptor for the attachment
|
||||||
|
user: User associated with file upload
|
||||||
|
upload_date: Date the file was uploaded
|
||||||
"""
|
"""
|
||||||
def getSubdir(self):
|
def getSubdir(self):
|
||||||
"""
|
"""
|
||||||
@ -55,6 +58,15 @@ class InvenTreeAttachment(models.Model):
|
|||||||
|
|
||||||
comment = models.CharField(max_length=100, help_text=_('File comment'))
|
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
|
@property
|
||||||
def basename(self):
|
def basename(self):
|
||||||
return os.path.basename(self.attachment.name)
|
return os.path.basename(self.attachment.name)
|
||||||
|
@ -12,6 +12,7 @@ from rest_framework import filters
|
|||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
|
from InvenTree.api import AttachmentMixin
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from company.models import SupplierPart
|
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)
|
API endpoint for listing (and creating) a SalesOrderAttachment (file upload)
|
||||||
"""
|
"""
|
||||||
@ -208,12 +209,6 @@ class SOAttachmentList(generics.ListCreateAPIView):
|
|||||||
queryset = SalesOrderAttachment.objects.all()
|
queryset = SalesOrderAttachment.objects.all()
|
||||||
serializer_class = SOAttachmentSerializer
|
serializer_class = SOAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
|
||||||
DjangoFilterBackend,
|
|
||||||
filters.OrderingFilter,
|
|
||||||
filters.SearchFilter,
|
|
||||||
]
|
|
||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
'order',
|
'order',
|
||||||
]
|
]
|
||||||
@ -399,7 +394,7 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView):
|
|||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
class POAttachmentList(generics.ListCreateAPIView):
|
class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)
|
API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)
|
||||||
"""
|
"""
|
||||||
@ -407,12 +402,6 @@ class POAttachmentList(generics.ListCreateAPIView):
|
|||||||
queryset = PurchaseOrderAttachment.objects.all()
|
queryset = PurchaseOrderAttachment.objects.all()
|
||||||
serializer_class = POAttachmentSerializer
|
serializer_class = POAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
|
||||||
DjangoFilterBackend,
|
|
||||||
filters.OrderingFilter,
|
|
||||||
filters.SearchFilter,
|
|
||||||
]
|
|
||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
'order',
|
'order',
|
||||||
]
|
]
|
||||||
|
26
InvenTree/order/migrations/0033_auto_20200512_1033.py
Normal file
26
InvenTree/order/migrations/0033_auto_20200512_1033.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
23
InvenTree/order/migrations/0034_auto_20200512_1054.py
Normal file
23
InvenTree/order/migrations/0034_auto_20200512_1054.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -12,39 +12,8 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div id='attachment-buttons'>
|
{% include "attachment_table.html" with attachments=order.attachments.all %}
|
||||||
<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 '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 %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -61,8 +30,10 @@ $("#new-attachment").click(function() {
|
|||||||
|
|
||||||
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
|
var url = `/order/purchase-order/attachment/${button.attr('pk')}/edit/`;
|
||||||
|
|
||||||
launchModalForm(button.attr('url'), {
|
launchModalForm(url, {
|
||||||
reload: true,
|
reload: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -70,7 +41,11 @@ $("#attachment-table").on('click', '.attachment-edit-button', function() {
|
|||||||
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
||||||
var button = $(this);
|
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,
|
reload: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,43 +8,11 @@
|
|||||||
|
|
||||||
{% include 'order/so_tabs.html' with tab='attachments' %}
|
{% include 'order/so_tabs.html' with tab='attachments' %}
|
||||||
|
|
||||||
<h4>{% trans "Sales Order Attachments" %}
|
<h4>{% trans "Sales Order Attachments" %}</h4>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div id='attachment-buttons'>
|
{% include "attachment_table.html" with attachments=order.attachments.all %}
|
||||||
<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>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -62,7 +30,9 @@ $("#new-attachment").click(function() {
|
|||||||
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
launchModalForm(button.attr('url'), {
|
var url = `/order/sales-order/attachment/${button.attr('pk')}/edit/`;
|
||||||
|
|
||||||
|
launchModalForm(url, {
|
||||||
reload: true,
|
reload: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -70,7 +40,9 @@ $("#attachment-table").on('click', '.attachment-edit-button', function() {
|
|||||||
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
launchModalForm(button.attr('url'), {
|
var url = `/order/sales-order/attachment/${button.attr('pk')}/delete/`;
|
||||||
|
|
||||||
|
launchModalForm(url, {
|
||||||
reload: true,
|
reload: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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'^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+)/edit/', views.PurchaseOrderAttachmentEdit.as_view(), name='po-attachment-edit'),
|
||||||
url(r'^(?P<pk>\d+)/delete/', views.PurchaseOrderAttachmentDelete.as_view(), name='po-attachment-delete'),
|
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'^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+)/edit/', views.SalesOrderAttachmentEdit.as_view(), name='so-attachment-edit'),
|
||||||
url(r'^(?P<pk>\d+)/delete/', views.SalesOrderAttachmentDelete.as_view(), name='so-attachment-delete'),
|
url(r'^(?P<pk>\d+)/delete/', views.SalesOrderAttachmentDelete.as_view(), name='so-attachment-delete'),
|
||||||
|
@ -93,6 +93,10 @@ class PurchaseOrderAttachmentCreate(AjaxCreateView):
|
|||||||
ajax_form_title = _("Add Purchase Order Attachment")
|
ajax_form_title = _("Add Purchase Order Attachment")
|
||||||
ajax_template_name = "modal_form.html"
|
ajax_template_name = "modal_form.html"
|
||||||
|
|
||||||
|
def post_save(self, **kwargs):
|
||||||
|
self.object.user = self.request.user
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
"success": _("Added attachment")
|
"success": _("Added attachment")
|
||||||
@ -133,6 +137,10 @@ class SalesOrderAttachmentCreate(AjaxCreateView):
|
|||||||
form_class = order_forms.EditSalesOrderAttachmentForm
|
form_class = order_forms.EditSalesOrderAttachmentForm
|
||||||
ajax_form_title = _('Add Sales Order Attachment')
|
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):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
'success': _('Added attachment')
|
'success': _('Added attachment')
|
||||||
|
@ -25,6 +25,7 @@ from . import serializers as part_serializers
|
|||||||
|
|
||||||
from InvenTree.views import TreeSerializer
|
from InvenTree.views import TreeSerializer
|
||||||
from InvenTree.helpers import str2bool, isNull
|
from InvenTree.helpers import str2bool, isNull
|
||||||
|
from InvenTree.api import AttachmentMixin
|
||||||
|
|
||||||
|
|
||||||
class PartCategoryTree(TreeSerializer):
|
class PartCategoryTree(TreeSerializer):
|
||||||
@ -106,7 +107,7 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
queryset = PartCategory.objects.all()
|
queryset = PartCategory.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class PartAttachmentList(generics.ListCreateAPIView):
|
class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for listing (and creating) a PartAttachment (file upload).
|
API endpoint for listing (and creating) a PartAttachment (file upload).
|
||||||
"""
|
"""
|
||||||
@ -114,14 +115,6 @@ class PartAttachmentList(generics.ListCreateAPIView):
|
|||||||
queryset = PartAttachment.objects.all()
|
queryset = PartAttachment.objects.all()
|
||||||
serializer_class = part_serializers.PartAttachmentSerializer
|
serializer_class = part_serializers.PartAttachmentSerializer
|
||||||
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
|
||||||
|
|
||||||
filter_backends = [
|
|
||||||
DjangoFilterBackend,
|
|
||||||
filters.OrderingFilter,
|
|
||||||
filters.SearchFilter,
|
|
||||||
]
|
|
||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
'part',
|
'part',
|
||||||
]
|
]
|
||||||
@ -296,24 +289,17 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
else:
|
else:
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def perform_create(self, serializer):
|
||||||
""" Override the default 'create' behaviour:
|
"""
|
||||||
We wish to save the user who created this part!
|
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 = serializer.save()
|
||||||
part.creation_user = request.user
|
part.creation_user = self.request.user
|
||||||
part.save()
|
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):
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
|
||||||
queryset = super().get_queryset(*args, **kwargs)
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
21
InvenTree/part/migrations/0036_partattachment_user.py
Normal file
21
InvenTree/part/migrations/0036_partattachment_user.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
18
InvenTree/part/migrations/0037_partattachment_upload_date.py
Normal file
18
InvenTree/part/migrations/0037_partattachment_upload_date.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -9,54 +9,7 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div id='attachment-buttons'>
|
{% include "attachment_table.html" with attachments=part.attachments.all %}
|
||||||
<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>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -73,7 +26,9 @@
|
|||||||
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
launchModalForm(button.attr('url'),
|
var url = `/part/attachment/${button.attr('pk')}/edit/`;
|
||||||
|
|
||||||
|
launchModalForm(url,
|
||||||
{
|
{
|
||||||
reload: true,
|
reload: true,
|
||||||
});
|
});
|
||||||
@ -82,7 +37,9 @@
|
|||||||
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
launchModalForm(button.attr('url'), {
|
var url = `/part/attachment/${button.attr('pk')}/delete/`;
|
||||||
|
|
||||||
|
launchModalForm(url, {
|
||||||
success: function() {
|
success: function() {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,11 @@ class PartAttachmentCreate(AjaxCreateView):
|
|||||||
ajax_form_title = _("Add part attachment")
|
ajax_form_title = _("Add part attachment")
|
||||||
ajax_template_name = "modal_form.html"
|
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):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
'success': _('Added attachment')
|
'success': _('Added attachment')
|
||||||
|
@ -29,6 +29,7 @@ from .serializers import StockItemAttachmentSerializer
|
|||||||
|
|
||||||
from InvenTree.views import TreeSerializer
|
from InvenTree.views import TreeSerializer
|
||||||
from InvenTree.helpers import str2bool, isNull
|
from InvenTree.helpers import str2bool, isNull
|
||||||
|
from InvenTree.api import AttachmentMixin
|
||||||
|
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
|
|
||||||
@ -478,6 +479,23 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
if sales_order:
|
if sales_order:
|
||||||
queryset = queryset.filter(sales_order=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)
|
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)
|
API endpoint for listing (and creating) a StockItemAttachment (file upload)
|
||||||
"""
|
"""
|
||||||
@ -636,12 +654,6 @@ class StockAttachmentList(generics.ListCreateAPIView):
|
|||||||
queryset = StockItemAttachment.objects.all()
|
queryset = StockItemAttachment.objects.all()
|
||||||
serializer_class = StockItemAttachmentSerializer
|
serializer_class = StockItemAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
|
||||||
DjangoFilterBackend,
|
|
||||||
filters.OrderingFilter,
|
|
||||||
filters.SearchFilter,
|
|
||||||
]
|
|
||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
'stock_item',
|
'stock_item',
|
||||||
]
|
]
|
||||||
|
21
InvenTree/stock/migrations/0037_stockitemattachment_user.py
Normal file
21
InvenTree/stock/migrations/0037_stockitemattachment_user.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -193,6 +193,16 @@ class LocationSerializer(InvenTreeModelSerializer):
|
|||||||
class StockItemAttachmentSerializer(InvenTreeModelSerializer):
|
class StockItemAttachmentSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for StockItemAttachment model """
|
""" 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:
|
class Meta:
|
||||||
model = StockItemAttachment
|
model = StockItemAttachment
|
||||||
|
|
||||||
@ -200,7 +210,9 @@ class StockItemAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'stock_item',
|
'stock_item',
|
||||||
'attachment',
|
'attachment',
|
||||||
'comment'
|
'comment',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,40 +10,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<h4>{% trans "Stock Item Attachments" %}</h4>
|
<h4>{% trans "Stock Item Attachments" %}</h4>
|
||||||
|
|
||||||
|
{% include "attachment_table.html" with attachments=item.attachments.all %}
|
||||||
<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 %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -60,7 +27,9 @@ $("#new-attachment").click(function() {
|
|||||||
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
$("#attachment-table").on('click', '.attachment-edit-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
launchModalForm(button.attr('url'),
|
var url = `/stock/item/attachment/${button.attr('pk')}/edit/`;
|
||||||
|
|
||||||
|
launchModalForm(url,
|
||||||
{
|
{
|
||||||
reload: true,
|
reload: true,
|
||||||
});
|
});
|
||||||
@ -69,7 +38,9 @@ $("#attachment-table").on('click', '.attachment-edit-button', function() {
|
|||||||
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
$("#attachment-table").on('click', '.attachment-delete-button', function() {
|
||||||
var button = $(this);
|
var button = $(this);
|
||||||
|
|
||||||
launchModalForm(button.attr('url'), {
|
var url = `/stock/item/attachment/${button.attr('pk')}/delete/`;
|
||||||
|
|
||||||
|
launchModalForm(url, {
|
||||||
success: function() {
|
success: function() {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,12 @@ class StockItemAttachmentCreate(AjaxCreateView):
|
|||||||
ajax_form_title = _("Add Stock Item Attachment")
|
ajax_form_title = _("Add Stock Item Attachment")
|
||||||
ajax_template_name = "modal_form.html"
|
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):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
'success': _("Added attachment")
|
'success': _("Added attachment")
|
||||||
|
40
InvenTree/templates/attachment_table.html
Normal file
40
InvenTree/templates/attachment_table.html
Normal file
@ -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>
|
@ -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).
|
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
|
## Developer Documentation
|
||||||
|
|
||||||
For code documentation, refer to the [developer documentation](http://inventree.readthedocs.io/en/latest/).
|
For code documentation, refer to the [developer documentation](http://inventree.readthedocs.io/en/latest/).
|
||||||
|
Loading…
Reference in New Issue
Block a user