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
ed6abcdf32
@ -4,8 +4,11 @@ Generic models which provide extra functionality over base Django model types.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
@ -15,6 +18,51 @@ from mptt.models import MPTTModel, TreeForeignKey
|
||||
from .validators import validate_tree_name
|
||||
|
||||
|
||||
def rename_attachment(instance, filename):
|
||||
"""
|
||||
Function for renaming an attachment file.
|
||||
The subdirectory for the uploaded file is determined by the implementing class.
|
||||
|
||||
Args:
|
||||
instance: Instance of a PartAttachment object
|
||||
filename: name of uploaded file
|
||||
|
||||
Returns:
|
||||
path to store file, format: '<subdir>/<id>/filename'
|
||||
"""
|
||||
|
||||
# Construct a path to store a file attachment for a given model type
|
||||
return os.path.join(instance.getSubdir(), filename)
|
||||
|
||||
|
||||
class InvenTreeAttachment(models.Model):
|
||||
""" Provides an abstracted class for managing file attachments.
|
||||
|
||||
Attributes:
|
||||
attachment: File
|
||||
comment: String descriptor for the attachment
|
||||
"""
|
||||
def getSubdir(self):
|
||||
"""
|
||||
Return the subdirectory under which attachments should be stored.
|
||||
Note: Re-implement this for each subclass of InvenTreeAttachment
|
||||
"""
|
||||
|
||||
return "attachments"
|
||||
|
||||
attachment = models.FileField(upload_to=rename_attachment,
|
||||
help_text=_('Select file to attach'))
|
||||
|
||||
comment = models.CharField(max_length=100, help_text=_('File comment'))
|
||||
|
||||
@property
|
||||
def basename(self):
|
||||
return os.path.basename(self.attachment.name)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class InvenTreeTree(MPTTModel):
|
||||
""" Provides an abstracted self-referencing tree model for data categories.
|
||||
|
||||
|
@ -4,7 +4,7 @@ Provides information on the current InvenTree version
|
||||
|
||||
import subprocess
|
||||
|
||||
INVENTREE_SW_VERSION = "0.0.8"
|
||||
INVENTREE_SW_VERSION = "0.0.9"
|
||||
|
||||
|
||||
def inventreeVersion():
|
||||
|
@ -100,7 +100,7 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
|
||||
});
|
||||
|
||||
$("#company-order-2").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-create' %}",
|
||||
launchModalForm("{% url 'po-create' %}",
|
||||
{
|
||||
data: {
|
||||
supplier: {{ company.id }},
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
|
||||
function newOrder() {
|
||||
launchModalForm("{% url 'purchase-order-create' %}",
|
||||
launchModalForm("{% url 'po-create' %}",
|
||||
{
|
||||
data: {
|
||||
supplier: {{ company.id }},
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ from mptt.fields import TreeNodeChoiceField
|
||||
from InvenTree.forms import HelperForm
|
||||
|
||||
from stock.models import StockLocation
|
||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||
from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment
|
||||
|
||||
|
||||
class IssuePurchaseOrderForm(HelperForm):
|
||||
@ -74,6 +74,18 @@ class EditPurchaseOrderForm(HelperForm):
|
||||
]
|
||||
|
||||
|
||||
class EditPurchaseOrderAttachmentForm(HelperForm):
|
||||
""" Form for editing a PurchaseOrderAttachment object """
|
||||
|
||||
class Meta:
|
||||
model = PurchaseOrderAttachment
|
||||
fields = [
|
||||
'order',
|
||||
'attachment',
|
||||
'comment'
|
||||
]
|
||||
|
||||
|
||||
class EditPurchaseOrderLineItemForm(HelperForm):
|
||||
""" Form for editing a PurchaseOrderLineItem object """
|
||||
|
||||
|
27
InvenTree/order/migrations/0016_purchaseorderattachment.py
Normal file
27
InvenTree/order/migrations/0016_purchaseorderattachment.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by Django 2.2.9 on 2020-03-22 07:01
|
||||
|
||||
import InvenTree.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0015_auto_20200201_2346'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PurchaseOrderAttachment',
|
||||
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)),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='order.PurchaseOrder')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -14,6 +14,7 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from stock.models import StockItem
|
||||
@ -21,6 +22,7 @@ from company.models import Company, SupplierPart
|
||||
|
||||
from InvenTree.helpers import decimal2string
|
||||
from InvenTree.status_codes import OrderStatus
|
||||
from InvenTree.models import InvenTreeAttachment
|
||||
|
||||
|
||||
class Order(models.Model):
|
||||
@ -136,7 +138,7 @@ class PurchaseOrder(Order):
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('purchase-order-detail', kwargs={'pk': self.id})
|
||||
return reverse('po-detail', kwargs={'pk': self.id})
|
||||
|
||||
@transaction.atomic
|
||||
def add_line_item(self, supplier_part, quantity, group=True, reference=''):
|
||||
@ -239,6 +241,17 @@ class PurchaseOrder(Order):
|
||||
self.complete_order() # This will save the model
|
||||
|
||||
|
||||
class PurchaseOrderAttachment(InvenTreeAttachment):
|
||||
"""
|
||||
Model for storing file attachments against a PurchaseOrder object
|
||||
"""
|
||||
|
||||
def getSubdir(self):
|
||||
return os.path.join("po_files", str(self.order.id))
|
||||
|
||||
order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, related_name="attachments")
|
||||
|
||||
|
||||
class OrderLineItem(models.Model):
|
||||
""" Abstract model for an order line item
|
||||
|
||||
|
@ -107,7 +107,7 @@ InvenTree | {{ order }}
|
||||
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
$("#place-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-issue' order.id %}",
|
||||
launchModalForm("{% url 'po-issue' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
});
|
||||
@ -115,7 +115,7 @@ $("#place-order").click(function() {
|
||||
{% endif %}
|
||||
|
||||
$("#edit-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-edit' order.id %}",
|
||||
launchModalForm("{% url 'po-edit' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
@ -123,7 +123,7 @@ $("#edit-order").click(function() {
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-cancel' order.id %}", {
|
||||
launchModalForm("{% url 'po-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
|
@ -50,7 +50,7 @@
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'purchase-order-notes' order.id %}?edit=1";
|
||||
location.href = "{% url 'po-notes' order.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
|
81
InvenTree/order/templates/order/po_attachments.html
Normal file
81
InvenTree/order/templates/order/po_attachments.html
Normal file
@ -0,0 +1,81 @@
|
||||
{% extends "order/order_base.html" %}
|
||||
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include 'order/tabs.html' with tab='attachments' %}
|
||||
|
||||
<h4>{% trans "Purchase Order Attachments" %}
|
||||
|
||||
<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 'po-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
|
||||
<span class='glyphicon glyphicon-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='glyphicon glyphicon-trash'/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
$("#new-attachment").click(function() {
|
||||
launchModalForm("{% url 'po-attachment-create' %}?order={{ order.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'), {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
|
||||
$("#attachment-table").inventreeTable({
|
||||
});
|
||||
|
||||
{% endblock %}
|
7
InvenTree/order/templates/order/po_delete.html
Normal file
7
InvenTree/order/templates/order/po_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 %}
|
@ -12,7 +12,7 @@
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td>{% include "hover_image.html" with image=order.supplier.image hover=True %}<a href="{{ order.supplier.get_absolute_url }}purchase-orders/">{{ order.supplier.name }}</a></td>
|
||||
<td><a href="{% url 'purchase-order-detail' order.id %}">{{ order }}</a></td>
|
||||
<td><a href="{% url 'po-detail' order.id %}">{{ order }}</a></td>
|
||||
<td>{{ order.description }}</td>
|
||||
<td>{% include "order/order_status.html" %}</td>
|
||||
<td>{{ order.lines.count }}</td>
|
||||
|
@ -92,7 +92,7 @@ $("#po-lines-table").on('click', ".line-receive", function() {
|
||||
|
||||
console.log('clicked! ' + button.attr('pk'));
|
||||
|
||||
launchModalForm("{% url 'purchase-order-receive' order.id %}", {
|
||||
launchModalForm("{% url 'po-receive' order.id %}", {
|
||||
reload: true,
|
||||
data: {
|
||||
line: button.attr('pk')
|
||||
@ -109,7 +109,7 @@ $("#po-lines-table").on('click', ".line-receive", function() {
|
||||
});
|
||||
|
||||
$("#receive-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-receive' order.id %}", {
|
||||
launchModalForm("{% url 'po-receive' order.id %}", {
|
||||
reload: true,
|
||||
secondary: [
|
||||
{
|
||||
@ -123,13 +123,13 @@ $("#receive-order").click(function() {
|
||||
});
|
||||
|
||||
$("#complete-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-complete' order.id %}", {
|
||||
launchModalForm("{% url 'po-complete' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
|
||||
$("#export-order").click(function() {
|
||||
location.href = "{% url 'purchase-order-export' order.id %}";
|
||||
location.href = "{% url 'po-export' order.id %}";
|
||||
});
|
||||
|
||||
{% if order.status == OrderStatus.PENDING %}
|
||||
|
@ -18,7 +18,7 @@ InvenTree | Purchase Orders
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed po-table' id='purchase-order-table'>
|
||||
<table class='table table-striped table-condensed po-table' data-toolbar='#table-buttons' id='purchase-order-table'>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@ -27,7 +27,7 @@ InvenTree | Purchase Orders
|
||||
{{ block.super }}
|
||||
|
||||
$("#po-create").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-create' %}",
|
||||
launchModalForm("{% url 'po-create' %}",
|
||||
{
|
||||
follow: true,
|
||||
}
|
||||
|
@ -2,9 +2,16 @@
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% ifequal tab 'details' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'purchase-order-detail' order.id %}">{% trans "Items" %}</a>
|
||||
<a href="{% url 'po-detail' order.id %}">{% trans "Items" %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'attachments' %} class='active'{% endif %}>
|
||||
<a href="{% url 'po-attachments' order.id %}">{% trans "Attachments" %}
|
||||
{% if order.attachments.count > 0 %}
|
||||
<span class='badge'>{{ order.attachments.count }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li{% ifequal tab 'notes' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'purchase-order-notes' order.id %}">{% trans "Notes" %}{% if order.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
<a href="{% url 'po-notes' order.id %}">{% trans "Notes" %}{% if order.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -40,7 +40,7 @@ class OrderViewTestCase(TestCase):
|
||||
class OrderListTest(OrderViewTestCase):
|
||||
|
||||
def test_order_list(self):
|
||||
response = self.client.get(reverse('purchase-order-index'))
|
||||
response = self.client.get(reverse('po-index'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -50,14 +50,14 @@ class POTests(OrderViewTestCase):
|
||||
|
||||
def test_detail_view(self):
|
||||
""" Retrieve PO detail view """
|
||||
response = self.client.get(reverse('purchase-order-detail', args=(1,)))
|
||||
response = self.client.get(reverse('po-detail', args=(1,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
keys = response.context.keys()
|
||||
self.assertIn('OrderStatus', keys)
|
||||
|
||||
def test_po_create(self):
|
||||
""" Launch forms to create new PurchaseOrder"""
|
||||
url = reverse('purchase-order-create')
|
||||
url = reverse('po-create')
|
||||
|
||||
# Without a supplier ID
|
||||
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
@ -74,13 +74,13 @@ class POTests(OrderViewTestCase):
|
||||
def test_po_edit(self):
|
||||
""" Launch form to edit a PurchaseOrder """
|
||||
|
||||
response = self.client.get(reverse('purchase-order-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
response = self.client.get(reverse('po-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_po_export(self):
|
||||
""" Export PurchaseOrder """
|
||||
|
||||
response = self.client.get(reverse('purchase-order-export', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
response = self.client.get(reverse('po-export', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
# Response should be streaming-content (file download)
|
||||
self.assertIn('streaming_content', dir(response))
|
||||
@ -88,7 +88,7 @@ class POTests(OrderViewTestCase):
|
||||
def test_po_issue(self):
|
||||
""" Test PurchaseOrderIssue view """
|
||||
|
||||
url = reverse('purchase-order-issue', args=(1,))
|
||||
url = reverse('po-issue', args=(1,))
|
||||
|
||||
order = PurchaseOrder.objects.get(pk=1)
|
||||
self.assertEqual(order.status, OrderStatus.PENDING)
|
||||
@ -183,7 +183,7 @@ class TestPOReceive(OrderViewTestCase):
|
||||
self.po = PurchaseOrder.objects.get(pk=1)
|
||||
self.po.status = OrderStatus.PLACED
|
||||
self.po.save()
|
||||
self.url = reverse('purchase-order-receive', args=(1,))
|
||||
self.url = reverse('po-receive', args=(1,))
|
||||
|
||||
def post(self, data, validate=None):
|
||||
|
||||
|
@ -9,19 +9,26 @@ from django.conf.urls import url, include
|
||||
|
||||
from . import views
|
||||
|
||||
purchase_order_attachment_urls = [
|
||||
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'),
|
||||
]
|
||||
|
||||
purchase_order_detail_urls = [
|
||||
|
||||
url(r'^cancel/?', views.PurchaseOrderCancel.as_view(), name='purchase-order-cancel'),
|
||||
url(r'^edit/?', views.PurchaseOrderEdit.as_view(), name='purchase-order-edit'),
|
||||
url(r'^issue/?', views.PurchaseOrderIssue.as_view(), name='purchase-order-issue'),
|
||||
url(r'^receive/?', views.PurchaseOrderReceive.as_view(), name='purchase-order-receive'),
|
||||
url(r'^complete/?', views.PurchaseOrderComplete.as_view(), name='purchase-order-complete'),
|
||||
url(r'^cancel/?', views.PurchaseOrderCancel.as_view(), name='po-cancel'),
|
||||
url(r'^edit/?', views.PurchaseOrderEdit.as_view(), name='po-edit'),
|
||||
url(r'^issue/?', views.PurchaseOrderIssue.as_view(), name='po-issue'),
|
||||
url(r'^receive/?', views.PurchaseOrderReceive.as_view(), name='po-receive'),
|
||||
url(r'^complete/?', views.PurchaseOrderComplete.as_view(), name='po-complete'),
|
||||
|
||||
url(r'^export/?', views.PurchaseOrderExport.as_view(), name='purchase-order-export'),
|
||||
url(r'^export/?', views.PurchaseOrderExport.as_view(), name='po-export'),
|
||||
|
||||
url(r'^notes/', views.PurchaseOrderNotes.as_view(), name='purchase-order-notes'),
|
||||
url(r'^notes/', views.PurchaseOrderNotes.as_view(), name='po-notes'),
|
||||
|
||||
url(r'^.*$', views.PurchaseOrderDetail.as_view(), name='purchase-order-detail'),
|
||||
url(r'^attachments/', views.PurchaseOrderDetail.as_view(template_name='order/po_attachments.html'), name='po-attachments'),
|
||||
url(r'^.*$', views.PurchaseOrderDetail.as_view(), name='po-detail'),
|
||||
]
|
||||
|
||||
po_line_item_detail_urls = [
|
||||
@ -39,7 +46,7 @@ po_line_urls = [
|
||||
|
||||
purchase_order_urls = [
|
||||
|
||||
url(r'^new/', views.PurchaseOrderCreate.as_view(), name='purchase-order-create'),
|
||||
url(r'^new/', views.PurchaseOrderCreate.as_view(), name='po-create'),
|
||||
|
||||
url(r'^order-parts/', views.OrderParts.as_view(), name='order-parts'),
|
||||
|
||||
@ -48,8 +55,10 @@ purchase_order_urls = [
|
||||
|
||||
url(r'^line/', include(po_line_urls)),
|
||||
|
||||
url(r'^attachments/', include(purchase_order_attachment_urls)),
|
||||
|
||||
# Display complete list of purchase orders
|
||||
url(r'^.*$', views.PurchaseOrderIndex.as_view(), name='purchase-order-index'),
|
||||
url(r'^.*$', views.PurchaseOrderIndex.as_view(), name='po-index'),
|
||||
]
|
||||
|
||||
order_urls = [
|
||||
|
@ -15,7 +15,7 @@ from django.forms import HiddenInput
|
||||
import logging
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||
from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment
|
||||
from .admin import POLineItemResource
|
||||
from build.models import Build
|
||||
from company.models import Company, SupplierPart
|
||||
@ -70,6 +70,84 @@ class PurchaseOrderDetail(DetailView):
|
||||
return ctx
|
||||
|
||||
|
||||
class PurchaseOrderAttachmentCreate(AjaxCreateView):
|
||||
"""
|
||||
View for creating a new PurchaseOrderAtt
|
||||
"""
|
||||
|
||||
model = PurchaseOrderAttachment
|
||||
form_class = order_forms.EditPurchaseOrderAttachmentForm
|
||||
ajax_form_title = _("Add Purchase Order Attachment")
|
||||
ajax_template_name = "modal_form.html"
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
"success": _("Added attachment")
|
||||
}
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Get initial data for creating a new PurchaseOrderAttachment object.
|
||||
|
||||
- Client must request this form with a parent PurchaseOrder in midn.
|
||||
- e.g. ?order=<pk>
|
||||
"""
|
||||
|
||||
initials = super(AjaxCreateView, self).get_initial()
|
||||
|
||||
initials["order"] = PurchaseOrder.objects.get(id=self.request.GET.get('order', -1))
|
||||
|
||||
return initials
|
||||
|
||||
def get_form(self):
|
||||
"""
|
||||
Create a form to upload a new PurchaseOrderAttachment
|
||||
|
||||
- Hide the 'order' field
|
||||
"""
|
||||
|
||||
form = super(AjaxCreateView, self).get_form()
|
||||
|
||||
form.fields['order'].widget = HiddenInput()
|
||||
|
||||
return form
|
||||
|
||||
|
||||
class PurchaseOrderAttachmentEdit(AjaxUpdateView):
|
||||
""" View for editing a PurchaseOrderAttachment object """
|
||||
|
||||
model = PurchaseOrderAttachment
|
||||
form_class = order_forms.EditPurchaseOrderAttachmentForm
|
||||
ajax_form_title = _("Edit Attachment")
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': _('Attachment updated')
|
||||
}
|
||||
|
||||
def get_form(self):
|
||||
form = super(AjaxUpdateView, self).get_form()
|
||||
|
||||
# Hide the 'order' field
|
||||
form.fields['order'].widget = HiddenInput()
|
||||
|
||||
return form
|
||||
|
||||
|
||||
class PurchaseOrderAttachmentDelete(AjaxDeleteView):
|
||||
""" View for deleting a PurchaseOrderAttachment """
|
||||
|
||||
model = PurchaseOrderAttachment
|
||||
ajax_form_title = _("Delete Attachment")
|
||||
ajax_template_name = "order/po_delete.html"
|
||||
context_object_name = "attachment"
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
"danger": _("Deleted attachment")
|
||||
}
|
||||
|
||||
|
||||
class PurchaseOrderNotes(UpdateView):
|
||||
""" View for updating the 'notes' field of a PurchaseOrder """
|
||||
|
||||
|
19
InvenTree/part/migrations/0032_auto_20200322_0453.py
Normal file
19
InvenTree/part/migrations/0032_auto_20200322_0453.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-03-22 04:53
|
||||
|
||||
import InvenTree.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0031_auto_20200318_1044'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='partattachment',
|
||||
name='attachment',
|
||||
field=models.FileField(help_text='Select file to attach', upload_to=InvenTree.models.rename_attachment),
|
||||
),
|
||||
]
|
@ -34,7 +34,7 @@ import hashlib
|
||||
|
||||
from InvenTree import helpers
|
||||
from InvenTree import validators
|
||||
from InvenTree.models import InvenTreeTree
|
||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.helpers import decimal2string
|
||||
|
||||
@ -941,28 +941,17 @@ def attach_file(instance, filename):
|
||||
return os.path.join('part_files', str(instance.part.id), filename)
|
||||
|
||||
|
||||
class PartAttachment(models.Model):
|
||||
""" A PartAttachment links a file to a part
|
||||
Parts can have multiple files such as datasheets, etc
|
||||
|
||||
Attributes:
|
||||
part: Link to a Part object
|
||||
attachment: File
|
||||
comment: String descriptor for the attachment
|
||||
class PartAttachment(InvenTreeAttachment):
|
||||
"""
|
||||
Model for storing file attachments against a Part object
|
||||
"""
|
||||
|
||||
def getSubdir(self):
|
||||
return os.path.join("part_files", str(self.part.id))
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE,
|
||||
related_name='attachments')
|
||||
|
||||
attachment = models.FileField(upload_to=attach_file,
|
||||
help_text=_('Select file to attach'))
|
||||
|
||||
comment = models.CharField(max_length=100, help_text=_('File comment'))
|
||||
|
||||
@property
|
||||
def basename(self):
|
||||
return os.path.basename(self.attachment.name)
|
||||
|
||||
|
||||
class PartStar(models.Model):
|
||||
""" A PartStar object creates a relationship between a User and a Part.
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "modal_delete_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
Are you sure you wish to delete this attachment?
|
||||
{% trans "Are you sure you want to delete this attachment?" %}
|
||||
<br>
|
||||
This will remove the file '{{ attachment.basename }}'.
|
||||
{% endblock %}
|
@ -13,20 +13,20 @@ from django.conf.urls import url, include
|
||||
from . import views
|
||||
|
||||
part_attachment_urls = [
|
||||
url('^new/?', views.PartAttachmentCreate.as_view(), name='part-attachment-create'),
|
||||
url(r'^new/?', views.PartAttachmentCreate.as_view(), name='part-attachment-create'),
|
||||
url(r'^(?P<pk>\d+)/edit/?', views.PartAttachmentEdit.as_view(), name='part-attachment-edit'),
|
||||
url(r'^(?P<pk>\d+)/delete/?', views.PartAttachmentDelete.as_view(), name='part-attachment-delete'),
|
||||
]
|
||||
|
||||
part_parameter_urls = [
|
||||
|
||||
url('^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
|
||||
url('^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
|
||||
url('^template/(?P<pk>\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'),
|
||||
url(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
|
||||
url(r'^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
|
||||
url(r'^template/(?P<pk>\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'),
|
||||
|
||||
url('^new/', views.PartParameterCreate.as_view(), name='part-param-create'),
|
||||
url('^(?P<pk>\d+)/edit/', views.PartParameterEdit.as_view(), name='part-param-edit'),
|
||||
url('^(?P<pk>\d+)/delete/', views.PartParameterDelete.as_view(), name='part-param-delete'),
|
||||
url(r'^new/', views.PartParameterCreate.as_view(), name='part-param-create'),
|
||||
url(r'^(?P<pk>\d+)/edit/', views.PartParameterEdit.as_view(), name='part-param-edit'),
|
||||
url(r'^(?P<pk>\d+)/delete/', views.PartParameterDelete.as_view(), name='part-param-delete'),
|
||||
|
||||
]
|
||||
|
||||
|
@ -112,6 +112,7 @@ class PartAttachmentCreate(AjaxCreateView):
|
||||
|
||||
class PartAttachmentEdit(AjaxUpdateView):
|
||||
""" View for editing a PartAttachment object """
|
||||
|
||||
model = PartAttachment
|
||||
form_class = part_forms.EditPartAttachmentForm
|
||||
ajax_template_name = 'modal_form.html'
|
||||
|
@ -117,7 +117,7 @@
|
||||
{% if item.purchase_order %}
|
||||
<tr>
|
||||
<td>{% trans "Purchase Order" %}</td>
|
||||
<td><a href="{% url 'purchase-order-detail' item.purchase_order.id %}">{{ item.purchase_order }}</a></td>
|
||||
<td><a href="{% url 'po-detail' item.purchase_order.id %}">{{ item.purchase_order }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.customer %}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<li><a href="{% url 'stock-index' %}">{% trans "Stock" %}</a></li>
|
||||
<li><a href="{% url 'build-index' %}">{% trans "Build" %}</a></li>
|
||||
<li><a href="{% url 'company-index' %}">{% trans "Suppliers" %}</a></li>
|
||||
<li><a href="{% url 'purchase-order-index' %}">{% trans "Orders" %}</a></li>
|
||||
<li><a href="{% url 'po-index' %}">{% trans "Orders" %}</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% include "search_form.html" %}
|
||||
|
@ -21,6 +21,6 @@ For site administrator and project code documentation, refer to the [developer d
|
||||
|
||||
Refer to the [getting started guide](https://inventree.readthedocs.io/en/latest/start.html) for installation and setup instructions.
|
||||
|
||||
### Third Party
|
||||
## Third Party Extensions
|
||||
|
||||
[InvenTree Docker](https://github.com/Zeigren/inventree-docker) - A docker build for InvenTree by [Zeigren](https://github.com/Zeigren)
|
||||
|
Loading…
Reference in New Issue
Block a user