From 1bdcbd1974192f36613cc4b295ffee58b33abd64 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 2 Feb 2020 12:11:18 +1100 Subject: [PATCH] Markdownify the 'notes' field for StockItem - New tab interface for the StockItem page - Display / editing of notes field with markdown --- InvenTree/stock/forms.py | 1 - .../migrations/0018_auto_20200202_0103.py | 19 ++ InvenTree/stock/models.py | 4 +- InvenTree/stock/templates/stock/item.html | 235 +---------------- .../stock/templates/stock/item_base.html | 247 ++++++++++++++++++ .../stock/templates/stock/item_notes.html | 57 ++++ InvenTree/stock/templates/stock/tabs.html | 10 + InvenTree/stock/urls.py | 2 + InvenTree/stock/views.py | 23 +- 9 files changed, 365 insertions(+), 233 deletions(-) create mode 100644 InvenTree/stock/migrations/0018_auto_20200202_0103.py create mode 100644 InvenTree/stock/templates/stock/item_base.html create mode 100644 InvenTree/stock/templates/stock/item_notes.html create mode 100644 InvenTree/stock/templates/stock/tabs.html diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 8145a17f89..5c84f3b4df 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -162,7 +162,6 @@ class EditStockItemForm(HelperForm): 'serial', 'batch', 'status', - 'notes', 'URL', 'delete_on_deplete', ] diff --git a/InvenTree/stock/migrations/0018_auto_20200202_0103.py b/InvenTree/stock/migrations/0018_auto_20200202_0103.py new file mode 100644 index 0000000000..9cf90ceebe --- /dev/null +++ b/InvenTree/stock/migrations/0018_auto_20200202_0103.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.9 on 2020-02-02 01:03 + +from django.db import migrations +import markdownx.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0017_auto_20191118_2311'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='notes', + field=markdownx.models.MarkdownxField(blank=True, help_text='Stock Item Notes'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 1122d4326c..d0bc23f14d 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -16,6 +16,8 @@ from django.contrib.auth.models import User from django.db.models.signals import pre_delete from django.dispatch import receiver +from markdownx.models import MarkdownxField + from mptt.models import TreeForeignKey from decimal import Decimal, InvalidOperation @@ -358,7 +360,7 @@ class StockItem(models.Model): choices=StockStatus.items(), validators=[MinValueValidator(0)]) - notes = models.CharField(max_length=250, blank=True, help_text=_('Stock Item Notes')) + notes = MarkdownxField(blank=True, help_text=_('Stock Item Notes')) # If stock item is incoming, an (optional) ETA field # expected_arrival = models.DateField(null=True, blank=True) diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index f6494edd93..38c1d7dcac 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -1,160 +1,12 @@ -{% extends "stock/stock_app_base.html" %} +{% extends "stock/item_base.html" %} + {% load static %} {% load inventree_extras %} {% load i18n %} -{% block content %} -
-
-

{% trans "Stock Item Details" %}

- {% if item.serialized %} -

{{ item.part.full_name}} # {{ item.serial }}

- {% else %} -

{{ item.quantity }} × {{ item.part.full_name }}

- {% endif %} -

-

- {% include "qr_button.html" %} - {% if item.in_stock %} - {% if not item.serialized %} - - - - {% if item.part.trackable %} - - {% endif %} - {% endif %} - - - {% endif %} - - -
-

- {% if item.serialized %} -
- {% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %} -
- {% elif item.delete_on_deplete %} -
- {% trans "This stock item will be automatically deleted when all stock is depleted." %} -
- {% endif %} -
+{% block details %} -
-
- - - - - - {% if item.belongs_to %} - - - - - {% elif item.location %} - - - - - {% endif %} - {% if item.serialized %} - - - - - {% else %} - - - - - {% endif %} - {% if item.batch %} - - - - - {% endif %} - {% if item.build %} - - - - - {% endif %} - {% if item.purchase_order %} - - - - - {% endif %} - {% if item.customer %} - - - - - {% endif %} - {% if item.URL %} - - - - - {% endif %} - {% if item.supplier_part %} - - - - - - - - - {% endif %} - - - - - - - {% if item.stocktake_date %} - - {% else %} - - {% endif %} - - - - - - {% if item.notes %} - - - - - {% endif %} -
Part - {% include "hover_image.html" with image=item.part.image hover=True %} - {{ item.part.full_name }} -
{% trans "Belongs To" %}{{ item.belongs_to }}
{% trans "Location" %}{{ item.location.name }}
{% trans "Serial Number" %}{{ item.serial }}
{% trans "Quantity" %}{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}
{% trans "Batch" %}{{ item.batch }}
{% trans "Build" %}{{ item.build }}
{% trans "Purchase Order" %}{{ item.purchase_order }}
{% trans "Customer" %}{{ item.customer.name }}
{% trans "URL" %}{{ item.URL }}
{% trans "Supplier" %}{{ item.supplier_part.supplier.name }}
{% trans "Supplier Part" %}{{ item.supplier_part.SKU }}
{% trans "Last Updated" %}{{ item.updated }}
{% trans "Last Stocktake" %}{{ item.stocktake_date }} {{ item.stocktake_user }}{% trans "No stocktake performed" %}
{% trans "Status" %}{{ item.get_status_display }}
{% trans "Notes" %}{{ item.notes }}
-
-
+{% include "stock/tabs.html" with tab="tracking" %}

{% trans "Stock Tracking Information" %}

@@ -167,6 +19,7 @@ {% endblock %} + {% block js_ready %} {{ block.super }} @@ -179,84 +32,6 @@ ); }); - $("#stock-serialize").click(function() { - launchModalForm( - "{% url 'stock-item-serialize' item.id %}", - { - reload: true, - } - ); - }); - - $("#stock-duplicate").click(function() { - launchModalForm( - "{% url 'stock-item-create' %}", - { - follow: true, - data: { - copy: {{ item.id }}, - }, - } - ); - }); - - $("#stock-edit").click(function () { - launchModalForm( - "{% url 'stock-item-edit' item.id %}", - { - reload: true, - submit_text: "Save", - } - ); - }); - - $("#show-qr-code").click(function() { - launchModalForm("{% url 'stock-item-qr' item.id %}", - { - no_post: true, - }); - }); - - {% if item.in_stock %} - - function itemAdjust(action) { - launchModalForm("/stock/adjust/", - { - data: { - action: action, - item: {{ item.id }}, - }, - reload: true, - follow: true, - } - ); - } - - $("#stock-move").click(function() { - itemAdjust("move"); - }); - - $("#stock-count").click(function() { - itemAdjust('count'); - }); - - $('#stock-remove').click(function() { - itemAdjust('take'); - }); - - $('#stock-add').click(function() { - itemAdjust('add'); - }); - - {% endif %} - - $("#stock-delete").click(function () { - launchModalForm( - "{% url 'stock-item-delete' item.id %}", - { - redirect: "{% url 'part-stock' item.part.id %}" - }); - }); loadStockTrackingTable($("#track-table"), { params: function(p) { diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html new file mode 100644 index 0000000000..5caead54a7 --- /dev/null +++ b/InvenTree/stock/templates/stock/item_base.html @@ -0,0 +1,247 @@ +{% extends "stock/stock_app_base.html" %} +{% load static %} +{% load inventree_extras %} +{% load i18n %} +{% block content %} + + +
+
+

{% trans "Stock Item Details" %}

+ {% if item.serialized %} +

{{ item.part.full_name}} # {{ item.serial }}

+ {% else %} +

{{ item.quantity }} × {{ item.part.full_name }}

+ {% endif %} +

+

+ {% include "qr_button.html" %} + {% if item.in_stock %} + {% if not item.serialized %} + + + + {% if item.part.trackable %} + + {% endif %} + {% endif %} + + + {% endif %} + + +
+

+ {% if item.serialized %} +
+ {% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %} +
+ {% elif item.delete_on_deplete %} +
+ {% trans "This stock item will be automatically deleted when all stock is depleted." %} +
+ {% endif %} +
+ +
+
+ + + + + + {% if item.belongs_to %} + + + + + {% elif item.location %} + + + + + {% endif %} + {% if item.serialized %} + + + + + {% else %} + + + + + {% endif %} + {% if item.batch %} + + + + + {% endif %} + {% if item.build %} + + + + + {% endif %} + {% if item.purchase_order %} + + + + + {% endif %} + {% if item.customer %} + + + + + {% endif %} + {% if item.URL %} + + + + + {% endif %} + {% if item.supplier_part %} + + + + + + + + + {% endif %} + + + + + + + {% if item.stocktake_date %} + + {% else %} + + {% endif %} + + + + + +
Part + {% include "hover_image.html" with image=item.part.image hover=True %} + {{ item.part.full_name }} +
{% trans "Belongs To" %}{{ item.belongs_to }}
{% trans "Location" %}{{ item.location.name }}
{% trans "Serial Number" %}{{ item.serial }}
{% trans "Quantity" %}{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}
{% trans "Batch" %}{{ item.batch }}
{% trans "Build" %}{{ item.build }}
{% trans "Purchase Order" %}{{ item.purchase_order }}
{% trans "Customer" %}{{ item.customer.name }}
{% trans "URL" %}{{ item.URL }}
{% trans "Supplier" %}{{ item.supplier_part.supplier.name }}
{% trans "Supplier Part" %}{{ item.supplier_part.SKU }}
{% trans "Last Updated" %}{{ item.updated }}
{% trans "Last Stocktake" %}{{ item.stocktake_date }} {{ item.stocktake_user }}{% trans "No stocktake performed" %}
{% trans "Status" %}{{ item.get_status_display }}
+
+
+ + +
+
+{% block details %} + +{% endblock %} +
+ +{% endblock %} + +{% block js_ready %} + +{{ block.super }} + +$("#stock-serialize").click(function() { + launchModalForm( + "{% url 'stock-item-serialize' item.id %}", + { + reload: true, + } + ); +}); + +$("#stock-duplicate").click(function() { + launchModalForm( + "{% url 'stock-item-create' %}", + { + follow: true, + data: { + copy: {{ item.id }}, + }, + } + ); +}); + +$("#stock-edit").click(function () { + launchModalForm( + "{% url 'stock-item-edit' item.id %}", + { + reload: true, + submit_text: "Save", + } + ); +}); + +$("#show-qr-code").click(function() { + launchModalForm("{% url 'stock-item-qr' item.id %}", + { + no_post: true, + }); +}); + +{% if item.in_stock %} + +function itemAdjust(action) { + launchModalForm("/stock/adjust/", + { + data: { + action: action, + item: {{ item.id }}, + }, + reload: true, + follow: true, + } + ); +} + +$("#stock-move").click(function() { + itemAdjust("move"); +}); + +$("#stock-count").click(function() { + itemAdjust('count'); +}); + +$('#stock-remove').click(function() { + itemAdjust('take'); +}); + +$('#stock-add').click(function() { + itemAdjust('add'); +}); + +{% endif %} + +$("#stock-delete").click(function () { + launchModalForm( + "{% url 'stock-item-delete' item.id %}", + { + redirect: "{% url 'part-stock' item.part.id %}" + }); +}); + +{% endblock %} diff --git a/InvenTree/stock/templates/stock/item_notes.html b/InvenTree/stock/templates/stock/item_notes.html new file mode 100644 index 0000000000..a5abd13781 --- /dev/null +++ b/InvenTree/stock/templates/stock/item_notes.html @@ -0,0 +1,57 @@ +{% extends "stock/item_base.html" %} + +{% load static %} +{% load inventree_extras %} +{% load i18n %} +{% load markdownify %} + +{% block details %} + +{% include "stock/tabs.html" with tab="notes" %} + +{% if editing %} +

{% trans "Stock Item Notes" %}

+
+ +
+ {% csrf_token %} + + {{ form }} +
+ +
+ +{{ form.media }} + +{% else %} +
+
+

{% trans "Stock Item Notes" %}

+
+
+ +
+
+
+
+
+ {{ item.notes | markdownify }} +
+
+ +{% endif %} + +{% endblock %} + +{% block js_ready %} + +{{ block.super }} + +{% if editing %} +{% else %} +$("#edit-notes").click(function() { + location.href = "{% url 'stock-item-notes' item.id %}?edit=1"; +}); +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/tabs.html b/InvenTree/stock/templates/stock/tabs.html new file mode 100644 index 0000000000..89ffa49e86 --- /dev/null +++ b/InvenTree/stock/templates/stock/tabs.html @@ -0,0 +1,10 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index fa849211f3..50ac170ce3 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -24,6 +24,8 @@ stock_item_detail_urls = [ url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'), + url(r'^notes/', views.StockItemNotes.as_view(), name='stock-item-notes'), + url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'), ] diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index dc56aa87ff..cb478e42f3 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.views.generic.edit import FormMixin -from django.views.generic import DetailView, ListView +from django.views.generic import DetailView, ListView, UpdateView from django.forms.models import model_to_dict from django.forms import HiddenInput from django.urls import reverse @@ -83,6 +83,27 @@ class StockItemDetail(DetailView): model = StockItem +class StockItemNotes(UpdateView): + """ View for editing the 'notes' field of a StockItem object """ + + context_object_name = 'item' + template_name = 'stock/item_notes.html' + model = StockItem + + fields = ['notes'] + + def get_success_url(self): + return reverse('stock-item-notes', kwargs={'pk': self.get_object().id}) + + def get_context_data(self, **kwargs): + + ctx = super().get_context_data(**kwargs) + + ctx['editing'] = str2bool(self.request.GET.get('edit', '')) + + return ctx + + class StockLocationEdit(AjaxUpdateView): """ View for editing details of a StockLocation.