Markdownify the 'notes' field for StockItem

- New tab interface for the StockItem page
- Display / editing of notes field with markdown
This commit is contained in:
Oliver Walters 2020-02-02 12:11:18 +11:00
parent 908e2ef8bc
commit 1bdcbd1974
9 changed files with 365 additions and 233 deletions

View File

@ -162,7 +162,6 @@ class EditStockItemForm(HelperForm):
'serial',
'batch',
'status',
'notes',
'URL',
'delete_on_deplete',
]

View File

@ -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'),
),
]

View File

@ -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)

View File

@ -1,160 +1,12 @@
{% extends "stock/stock_app_base.html" %}
{% extends "stock/item_base.html" %}
{% load static %}
{% load inventree_extras %}
{% load i18n %}
{% block content %}
<div class='row'>
<div class='col-sm-6'>
<h3>{% trans "Stock Item Details" %}</h3>
{% if item.serialized %}
<p><i>{{ item.part.full_name}} # {{ item.serial }}</i></p>
{% else %}
<p><i>{{ item.quantity }} &times {{ item.part.full_name }}</i></p>
{% endif %}
<p>
<div class='btn-group'>
{% include "qr_button.html" %}
{% if item.in_stock %}
{% if not item.serialized %}
<button type='button' class='btn btn-default btn-glyph' id='stock-add' title='Add to stock'>
<span class='glyphicon glyphicon-plus-sign' style='color: #1a1;'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-remove' title='Take from stock'>
<span class='glyphicon glyphicon-minus-sign' style='color: #a11;'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-count' title='Count stock'>
<span class='glyphicon glyphicon-ok-circle'/>
</button>
{% if item.part.trackable %}
<button type='button' class='btn btn-default btn-glyph' id='stock-serialize' title='Serialize stock'>
<span class='glyphicon glyphicon-th-list'/>
</button>
{% endif %}
{% endif %}
<button type='button' class='btn btn-default btn-glyph' id='stock-move' title='Transfer stock'>
<span class='glyphicon glyphicon-transfer' style='color: #11a;'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-duplicate' title='Duplicate stock item'>
<span class='glyphicon glyphicon-duplicate'/>
</button>
{% endif %}
<button type='button' class='btn btn-default btn-glyph' id='stock-edit' title='Edit stock item'>
<span class='glyphicon glyphicon-edit'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-delete' title='Edit stock item'>
<span class='glyphicon glyphicon-trash'/>
</button>
</div>
</p>
{% if item.serialized %}
<div class='alert alert-block alert-info'>
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
</div>
{% elif item.delete_on_deplete %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
</div>
{% endif %}
</div>
{% block details %}
<div class='row'>
<div class='col-sm-6'>
<table class="table table-striped">
<tr>
<td>Part</td>
<td>
{% include "hover_image.html" with image=item.part.image hover=True %}
<a href="{% url 'part-stock' item.part.id %}">{{ item.part.full_name }}
</td>
</tr>
{% if item.belongs_to %}
<tr>
<td>{% trans "Belongs To" %}</td>
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
</tr>
{% elif item.location %}
<tr>
<td>{% trans "Location" %}</td>
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
</tr>
{% endif %}
{% if item.serialized %}
<tr>
<td>{% trans "Serial Number" %}</td>
<td>{{ item.serial }}</td>
</tr>
{% else %}
<tr>
<td>{% trans "Quantity" %}</td>
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
</tr>
{% endif %}
{% if item.batch %}
<tr>
<td>{% trans "Batch" %}</td>
<td>{{ item.batch }}</td>
</tr>
{% endif %}
{% if item.build %}
<tr>
<td>{% trans "Build" %}</td>
<td><a href="{% url 'build-detail' item.build.id %}">{{ item.build }}</a></td>
</tr>
{% endif %}
{% 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>
</tr>
{% endif %}
{% if item.customer %}
<tr>
<td>{% trans "Customer" %}</td>
<td>{{ item.customer.name }}</td>
</tr>
{% endif %}
{% if item.URL %}
<tr>
<td>{% trans "URL" %}</td>
<td><a href="{{ item.URL }}">{{ item.URL }}</a></td>
</tr>
{% endif %}
{% if item.supplier_part %}
<tr>
<td>{% trans "Supplier" %}</td>
<td><a href="{% url 'company-detail' item.supplier_part.supplier.id %}">{{ item.supplier_part.supplier.name }}</a></td>
</tr>
<tr>
<td>{% trans "Supplier Part" %}</td>
<td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td>
</tr>
{% endif %}
<tr>
<td>{% trans "Last Updated" %}</td>
<td>{{ item.updated }}</td>
</tr>
<tr>
<td>{% trans "Last Stocktake" %}</td>
{% if item.stocktake_date %}
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
{% else %}
<td>{% trans "No stocktake performed" %}</td>
{% endif %}
</tr>
<tr>
<td>{% trans "Status" %}</td>
<td>{{ item.get_status_display }}</td>
</tr>
{% if item.notes %}
<tr>
<td>{% trans "Notes" %}</td>
<td>{{ item.notes }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
{% include "stock/tabs.html" with tab="tracking" %}
<hr>
<h4>{% trans "Stock Tracking Information" %}</h4>
@ -167,6 +19,7 @@
</table>
{% 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) {

View File

@ -0,0 +1,247 @@
{% extends "stock/stock_app_base.html" %}
{% load static %}
{% load inventree_extras %}
{% load i18n %}
{% block content %}
<div class='row'>
<div class='col-sm-6'>
<h3>{% trans "Stock Item Details" %}</h3>
{% if item.serialized %}
<p><i>{{ item.part.full_name}} # {{ item.serial }}</i></p>
{% else %}
<p><i>{{ item.quantity }} &times {{ item.part.full_name }}</i></p>
{% endif %}
<p>
<div class='btn-group'>
{% include "qr_button.html" %}
{% if item.in_stock %}
{% if not item.serialized %}
<button type='button' class='btn btn-default btn-glyph' id='stock-add' title='Add to stock'>
<span class='glyphicon glyphicon-plus-sign' style='color: #1a1;'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-remove' title='Take from stock'>
<span class='glyphicon glyphicon-minus-sign' style='color: #a11;'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-count' title='Count stock'>
<span class='glyphicon glyphicon-ok-circle'/>
</button>
{% if item.part.trackable %}
<button type='button' class='btn btn-default btn-glyph' id='stock-serialize' title='Serialize stock'>
<span class='glyphicon glyphicon-th-list'/>
</button>
{% endif %}
{% endif %}
<button type='button' class='btn btn-default btn-glyph' id='stock-move' title='Transfer stock'>
<span class='glyphicon glyphicon-transfer' style='color: #11a;'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-duplicate' title='Duplicate stock item'>
<span class='glyphicon glyphicon-duplicate'/>
</button>
{% endif %}
<button type='button' class='btn btn-default btn-glyph' id='stock-edit' title='Edit stock item'>
<span class='glyphicon glyphicon-edit'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='stock-delete' title='Edit stock item'>
<span class='glyphicon glyphicon-trash'/>
</button>
</div>
</p>
{% if item.serialized %}
<div class='alert alert-block alert-info'>
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
</div>
{% elif item.delete_on_deplete %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
</div>
{% endif %}
</div>
<div class='row'>
<div class='col-sm-6'>
<table class="table table-striped">
<tr>
<td>Part</td>
<td>
{% include "hover_image.html" with image=item.part.image hover=True %}
<a href="{% url 'part-stock' item.part.id %}">{{ item.part.full_name }}
</td>
</tr>
{% if item.belongs_to %}
<tr>
<td>{% trans "Belongs To" %}</td>
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
</tr>
{% elif item.location %}
<tr>
<td>{% trans "Location" %}</td>
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
</tr>
{% endif %}
{% if item.serialized %}
<tr>
<td>{% trans "Serial Number" %}</td>
<td>{{ item.serial }}</td>
</tr>
{% else %}
<tr>
<td>{% trans "Quantity" %}</td>
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
</tr>
{% endif %}
{% if item.batch %}
<tr>
<td>{% trans "Batch" %}</td>
<td>{{ item.batch }}</td>
</tr>
{% endif %}
{% if item.build %}
<tr>
<td>{% trans "Build" %}</td>
<td><a href="{% url 'build-detail' item.build.id %}">{{ item.build }}</a></td>
</tr>
{% endif %}
{% 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>
</tr>
{% endif %}
{% if item.customer %}
<tr>
<td>{% trans "Customer" %}</td>
<td>{{ item.customer.name }}</td>
</tr>
{% endif %}
{% if item.URL %}
<tr>
<td>{% trans "URL" %}</td>
<td><a href="{{ item.URL }}">{{ item.URL }}</a></td>
</tr>
{% endif %}
{% if item.supplier_part %}
<tr>
<td>{% trans "Supplier" %}</td>
<td><a href="{% url 'company-detail' item.supplier_part.supplier.id %}">{{ item.supplier_part.supplier.name }}</a></td>
</tr>
<tr>
<td>{% trans "Supplier Part" %}</td>
<td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td>
</tr>
{% endif %}
<tr>
<td>{% trans "Last Updated" %}</td>
<td>{{ item.updated }}</td>
</tr>
<tr>
<td>{% trans "Last Stocktake" %}</td>
{% if item.stocktake_date %}
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
{% else %}
<td>{% trans "No stocktake performed" %}</td>
{% endif %}
</tr>
<tr>
<td>{% trans "Status" %}</td>
<td>{{ item.get_status_display }}</td>
</tr>
</table>
</div>
</div>
<hr>
<div class='container-fluid'>
{% block details %}
<!-- Stock item details go here -->
{% endblock %}
</div>
{% 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 %}

View File

@ -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 %}
<h4>{% trans "Stock Item Notes" %}</h4>
<hr>
<form method='POST'>
{% csrf_token %}
{{ form }}
<hr>
<input type='submit' value='{% trans "Save" %}'/>
</form>
{{ form.media }}
{% else %}
<div class='row'>
<div class='col-sm-6'>
<h4>{% trans "Stock Item Notes" %}</h4>
</div>
<div class='col-sm-6'>
<button title='{% trans "Edit notes" %}' class='btn btn-default btn-glyph float-right' id='edit-notes'><span class='glyphicon glyphicon-edit'></span></button>
</div>
</div>
<hr>
<div class='panel panel-default'>
<div class='panel-content'>
{{ item.notes | markdownify }}
</div>
</div>
{% 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 %}

View File

@ -0,0 +1,10 @@
{% 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 'notes' %} class='active'{% endifequal %}>
<a href="{% url 'stock-item-notes' item.id %}">{% trans "Notes" %}{% if item.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
</li>
</ul>

View File

@ -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'),
]

View File

@ -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.