mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-28 22:04:25 +00:00
Merge pull request #626 from SchrodingersGat/markdown-notes
Markdown notes
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@ python:
|
||||
|
||||
addons:
|
||||
apt-packages:
|
||||
-sqlite3
|
||||
- sqlite3
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
|
||||
@@ -17,6 +17,8 @@ import logging
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@@ -101,6 +103,8 @@ INSTALLED_APPS = [
|
||||
'django_cleanup', # Automatically delete orphaned MEDIA files
|
||||
'qr_code', # Generate QR codes
|
||||
'mptt', # Modified Preorder Tree Traversal
|
||||
'markdownx', # Markdown editing
|
||||
'markdownify', # Markdown template rendering
|
||||
]
|
||||
|
||||
LOGGING = {
|
||||
@@ -161,6 +165,37 @@ REST_FRAMEWORK = {
|
||||
|
||||
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
||||
|
||||
# Markdownx configuration
|
||||
# Ref: https://neutronx.github.io/django-markdownx/customization/
|
||||
MARKDOWNX_MEDIA_PATH = datetime.now().strftime('markdownx/%Y/%m/%d')
|
||||
|
||||
# Markdownify configuration
|
||||
# Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html
|
||||
|
||||
MARKDOWNIFY_WHITELIST_TAGS = [
|
||||
'a',
|
||||
'abbr',
|
||||
'b',
|
||||
'blockquote',
|
||||
'em',
|
||||
'h1', 'h2', 'h3',
|
||||
'i',
|
||||
'img',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'strong',
|
||||
'ul'
|
||||
]
|
||||
|
||||
MARKDOWNIFY_WHITELIST_ATTRS = [
|
||||
'href',
|
||||
'src',
|
||||
'alt',
|
||||
]
|
||||
|
||||
MARKDOWNIFY_BLEACH = True
|
||||
|
||||
DATABASES = {}
|
||||
|
||||
"""
|
||||
|
||||
@@ -5,6 +5,29 @@
|
||||
--basic-color: #333;
|
||||
}
|
||||
|
||||
.markdownx .row {
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
border: 1px solid #cce;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdownx-editor {
|
||||
width: 100%;
|
||||
border: 1px solid #cce;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.markdownx-preview {
|
||||
border: 1px solid #cce;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
max-width: 400px;
|
||||
@@ -305,6 +328,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
color: #333;
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
.modal textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,8 @@ urlpatterns = [
|
||||
|
||||
url(r'^api/', include(apipatterns)),
|
||||
url(r'^api-doc/', include_docs_urls(title='InvenTree API')),
|
||||
|
||||
url(r'^markdownx/', include('markdownx.urls')),
|
||||
]
|
||||
|
||||
# Static file access
|
||||
|
||||
@@ -15,6 +15,12 @@ def inventreeVersion():
|
||||
def inventreeCommitHash():
|
||||
""" Returns the git commit hash for the running codebase """
|
||||
|
||||
commit = str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip()
|
||||
return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip()
|
||||
|
||||
return commit
|
||||
|
||||
def inventreeCommitDate():
|
||||
""" Returns the git commit date for the running codebase """
|
||||
|
||||
d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip()
|
||||
|
||||
return d.split(' ')[0]
|
||||
|
||||
@@ -26,7 +26,6 @@ class EditBuildForm(HelperForm):
|
||||
'take_from',
|
||||
'batch',
|
||||
'URL',
|
||||
'notes',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 12:47
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0007_auto_20191118_2321'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True, help_text='Extra build notes'),
|
||||
),
|
||||
]
|
||||
@@ -16,6 +16,8 @@ from django.db import models, transaction
|
||||
from django.db.models import Sum
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from InvenTree.status_codes import BuildStatus
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
|
||||
@@ -92,7 +94,7 @@ class Build(models.Model):
|
||||
|
||||
URL = InvenTreeURLField(blank=True, help_text=_('Link to external URL'))
|
||||
|
||||
notes = models.TextField(blank=True, help_text=_('Extra build notes'))
|
||||
notes = MarkdownxField(blank=True, help_text=_('Extra build notes'))
|
||||
|
||||
@transaction.atomic
|
||||
def cancelBuild(self, user):
|
||||
|
||||
@@ -1,74 +1,67 @@
|
||||
{% extends "build/build_base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block details %}
|
||||
|
||||
{% include "build/tabs.html" with tab='details' %}
|
||||
|
||||
<h4>Build Details</h4>
|
||||
<h4>{% trans "Build Details" %}</h4>
|
||||
|
||||
<hr>
|
||||
|
||||
<table class='table table-striped'>
|
||||
<tr>
|
||||
<td>Title</td><td>{{ build.title }}</td>
|
||||
<td>{% trans "Title" %}</td><td>{{ build.title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Part</td><td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||
<td>{% trans "Part" %}</td><td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Quantity</td><td>{{ build.quantity }}</td>
|
||||
<td>{% trans "Quantity" %}</td><td>{{ build.quantity }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stock Source</td>
|
||||
<td>{% trans "Stock Source" %}</td>
|
||||
<td>
|
||||
{% if build.take_from %}
|
||||
<a href="{% url 'stock-location-detail' build.take_from.id %}">{{ build.take_from }}</a>
|
||||
{% else %}
|
||||
Stock can be taken from any available location.
|
||||
{% trans "Stock can be taken from any available location." %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td><td>{% include "build_status.html" with build=build %}</td>
|
||||
<td>{% trans "Status" %}</td><td>{% include "build_status.html" with build=build %}</td>
|
||||
</tr>
|
||||
{% if build.batch %}
|
||||
<tr>
|
||||
<td>Batch</td><td>{{ build.batch }}</td>
|
||||
<td>{% trans "Batch" %}</td><td>{{ build.batch }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if build.URL %}
|
||||
<tr>
|
||||
<td>URL</td><td><a href="{{ build.URL }}">{{ build.URL }}</a></td>
|
||||
<td>{% trans "URL" %}</td><td><a href="{{ build.URL }}">{{ build.URL }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Created</td><td>{{ build.creation_date }}</td>
|
||||
<td>{% trans "Created" %}</td><td>{{ build.creation_date }}</td>
|
||||
</tr>
|
||||
{% if build.is_active %}
|
||||
<tr>
|
||||
<td>Enough Parts?</td>
|
||||
<td>{% trans "Enough Parts?" %}</td>
|
||||
<td>
|
||||
{% if build.can_build %}
|
||||
Yes
|
||||
{% trans "Yes" %}
|
||||
{% else %}
|
||||
No
|
||||
{% trans "No" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if build.completion_date %}
|
||||
<tr>
|
||||
<td>Completed</td><td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
|
||||
<td>{% trans "Completed" %}</td><td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
{% if build.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>Notes</b></div>
|
||||
<div class="panel-body">{{ build.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{% extends "build/build_base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "build/tabs.html" with tab='notes' %}
|
||||
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Build 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 "Build 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'>
|
||||
{{ build.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'build-notes' build.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,13 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% if tab == 'details' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-detail' build.id %}">Details</a>
|
||||
<a href="{% url 'build-detail' build.id %}">{% trans "Details" %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-notes' build.id %}">{% trans "Notes" %}{% if build.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'allocate' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-allocate' build.id %}">Assign Parts</a>
|
||||
<a href="{% url 'build-allocate' build.id %}">{% trans "Assign Parts" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -25,6 +25,7 @@ build_detail_urls = [
|
||||
url(r'^auto-allocate/?', views.BuildAutoAllocate.as_view(), name='build-auto-allocate'),
|
||||
url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'),
|
||||
|
||||
url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'),
|
||||
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
|
||||
]
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
from django.forms import HiddenInput
|
||||
from django.urls import reverse
|
||||
|
||||
from part.models import Part
|
||||
from .models import Build, BuildItem
|
||||
@@ -309,6 +310,28 @@ class BuildComplete(AjaxUpdateView):
|
||||
}
|
||||
|
||||
|
||||
class BuildNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a Build object.
|
||||
"""
|
||||
|
||||
context_object_name = 'build'
|
||||
template_name = 'build/notes.html'
|
||||
model = Build
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('build-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 BuildDetail(DetailView):
|
||||
""" Detail view of a single Build object. """
|
||||
model = Build
|
||||
|
||||
@@ -27,7 +27,6 @@ class EditCompanyForm(HelperForm):
|
||||
'contact',
|
||||
'is_customer',
|
||||
'is_supplier',
|
||||
'notes'
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 12:31
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('company', '0009_auto_20191118_2323'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='company',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True),
|
||||
),
|
||||
]
|
||||
+25
-22
@@ -10,6 +10,7 @@ import os
|
||||
import math
|
||||
from decimal import Decimal
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
@@ -18,6 +19,8 @@ from django.apps import apps
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.status_codes import OrderStatus
|
||||
from common.models import Currency
|
||||
@@ -68,32 +71,32 @@ class Company(models.Model):
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=100, blank=False, unique=True,
|
||||
help_text='Company name')
|
||||
help_text=_('Company name'))
|
||||
|
||||
description = models.CharField(max_length=500, help_text='Description of the company')
|
||||
description = models.CharField(max_length=500, help_text=_('Description of the company'))
|
||||
|
||||
website = models.URLField(blank=True, help_text='Company website URL')
|
||||
website = models.URLField(blank=True, help_text=_('Company website URL'))
|
||||
|
||||
address = models.CharField(max_length=200,
|
||||
blank=True, help_text='Company address')
|
||||
blank=True, help_text=_('Company address'))
|
||||
|
||||
phone = models.CharField(max_length=50,
|
||||
blank=True, help_text='Contact phone number')
|
||||
blank=True, help_text=_('Contact phone number'))
|
||||
|
||||
email = models.EmailField(blank=True, help_text='Contact email address')
|
||||
email = models.EmailField(blank=True, help_text=_('Contact email address'))
|
||||
|
||||
contact = models.CharField(max_length=100,
|
||||
blank=True, help_text='Point of contact')
|
||||
blank=True, help_text=_('Point of contact'))
|
||||
|
||||
URL = InvenTreeURLField(blank=True, help_text='Link to external company information')
|
||||
URL = InvenTreeURLField(blank=True, help_text=_('Link to external company information'))
|
||||
|
||||
image = models.ImageField(upload_to=rename_company_image, max_length=255, null=True, blank=True)
|
||||
|
||||
notes = models.TextField(blank=True)
|
||||
notes = MarkdownxField(blank=True)
|
||||
|
||||
is_customer = models.BooleanField(default=False, help_text='Do you sell items to this company?')
|
||||
is_customer = models.BooleanField(default=False, help_text=_('Do you sell items to this company?'))
|
||||
|
||||
is_supplier = models.BooleanField(default=True, help_text='Do you purchase items from this company?')
|
||||
is_supplier = models.BooleanField(default=True, help_text=_('Do you purchase items from this company?'))
|
||||
|
||||
def __str__(self):
|
||||
""" Get string representation of a Company """
|
||||
@@ -223,32 +226,32 @@ class SupplierPart(models.Model):
|
||||
'purchaseable': True,
|
||||
'is_template': False,
|
||||
},
|
||||
help_text='Select part',
|
||||
help_text=_('Select part'),
|
||||
)
|
||||
|
||||
supplier = models.ForeignKey(Company, on_delete=models.CASCADE,
|
||||
related_name='parts',
|
||||
limit_choices_to={'is_supplier': True},
|
||||
help_text='Select supplier',
|
||||
help_text=_('Select supplier'),
|
||||
)
|
||||
|
||||
SKU = models.CharField(max_length=100, help_text='Supplier stock keeping unit')
|
||||
SKU = models.CharField(max_length=100, help_text=_('Supplier stock keeping unit'))
|
||||
|
||||
manufacturer = models.CharField(max_length=100, blank=True, help_text='Manufacturer')
|
||||
manufacturer = models.CharField(max_length=100, blank=True, help_text=_('Manufacturer'))
|
||||
|
||||
MPN = models.CharField(max_length=100, blank=True, help_text='Manufacturer part number')
|
||||
MPN = models.CharField(max_length=100, blank=True, help_text=_('Manufacturer part number'))
|
||||
|
||||
URL = InvenTreeURLField(blank=True, help_text='URL for external supplier part link')
|
||||
URL = InvenTreeURLField(blank=True, help_text=_('URL for external supplier part link'))
|
||||
|
||||
description = models.CharField(max_length=250, blank=True, help_text='Supplier part description')
|
||||
description = models.CharField(max_length=250, blank=True, help_text=_('Supplier part description'))
|
||||
|
||||
note = models.CharField(max_length=100, blank=True, help_text='Notes')
|
||||
note = models.CharField(max_length=100, blank=True, help_text=_('Notes'))
|
||||
|
||||
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text='Minimum charge (e.g. stocking fee)')
|
||||
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text=_('Minimum charge (e.g. stocking fee)'))
|
||||
|
||||
packaging = models.CharField(max_length=50, blank=True, help_text='Part packaging')
|
||||
packaging = models.CharField(max_length=50, blank=True, help_text=_('Part packaging'))
|
||||
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text='Order multiple')
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text=('Order multiple'))
|
||||
|
||||
# TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
|
||||
# lead_time = models.DurationField(blank=True, null=True)
|
||||
|
||||
@@ -18,13 +18,6 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if company.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>Notes</b></div>
|
||||
<div class="panel-body">{{ company.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{% extends "company/company_base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block details %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% include 'company/tabs.html' with tab='notes' %}
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Company 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 "Company 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'>
|
||||
{{ company.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'company-notes' company.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,23 +1,28 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% if tab == 'details' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail' company.id %}">Details</a>
|
||||
<a href="{% url 'company-detail' company.id %}">{% trans "Details" %}</a>
|
||||
</li>
|
||||
{% if company.is_supplier %}
|
||||
<li{% if tab == 'parts' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail-parts' company.id %}">Supplier Parts <span class='badge'>{{ company.part_count }}</span></a>
|
||||
<a href="{% url 'company-detail-parts' company.id %}">{% trans "Supplier Parts" %} <span class='badge'>{{ company.part_count }}</span></a>
|
||||
</li>
|
||||
<li{% if tab == 'stock' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail-stock' company.id %}">Stock <span class='badge'>{{ company.stock_count }}</a>
|
||||
<a href="{% url 'company-detail-stock' company.id %}">{% trans "Stock" %} <span class='badge'>{{ company.stock_count }}</a>
|
||||
</li>
|
||||
<li{% if tab == 'po' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail-purchase-orders' company.id %}">Purchase Orders <span class='badge'>{{ company.purchase_orders.count }}</span></a>
|
||||
<a href="{% url 'company-detail-purchase-orders' company.id %}">{% trans "Purchase Orders" %} <span class='badge'>{{ company.purchase_orders.count }}</span></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if company.is_customer %}
|
||||
{% if 0 %}
|
||||
<li{% if tab == 'co' %} class='active'{% endif %}>
|
||||
<a href="#">Sales Orders</a>
|
||||
<a href="#">{% trans "Sales Orders" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-notes' company.id %}">{% trans "Notes" %}{% if company.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -18,6 +18,7 @@ company_detail_urls = [
|
||||
url(r'parts/?', views.CompanyDetail.as_view(template_name='company/detail_part.html'), name='company-detail-parts'),
|
||||
url(r'stock/?', views.CompanyDetail.as_view(template_name='company/detail_stock.html'), name='company-detail-stock'),
|
||||
url(r'purchase-orders/?', views.CompanyDetail.as_view(template_name='company/detail_purchase_orders.html'), name='company-detail-purchase-orders'),
|
||||
url(r'notes/?', views.CompanyNotes.as_view(), name='company-notes'),
|
||||
|
||||
url(r'thumbnail/?', views.CompanyImage.as_view(), name='company-image'),
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ Django views for interacting with Company app
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
|
||||
from django.urls import reverse
|
||||
from django.forms import HiddenInput
|
||||
|
||||
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||
@@ -52,6 +53,28 @@ class CompanyIndex(ListView):
|
||||
return queryset
|
||||
|
||||
|
||||
class CompanyNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a Company object.
|
||||
"""
|
||||
|
||||
context_object_name = 'company'
|
||||
template_name = 'company/notes.html'
|
||||
model = Company
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('company-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 CompanyDetail(DetailView):
|
||||
""" Detail view for Company object """
|
||||
context_obect_name = 'company'
|
||||
|
||||
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
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 23:46
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0014_auto_20191118_2328'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='purchaseorder',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True, help_text='Order notes'),
|
||||
),
|
||||
]
|
||||
@@ -12,6 +12,8 @@ from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from stock.models import StockItem
|
||||
@@ -81,7 +83,7 @@ class Order(models.Model):
|
||||
|
||||
complete_date = models.DateField(blank=True, null=True)
|
||||
|
||||
notes = models.TextField(blank=True, help_text=_('Order notes'))
|
||||
notes = MarkdownxField(blank=True, help_text=_('Order notes'))
|
||||
|
||||
def place_order(self):
|
||||
""" Marks the order as PLACED. Order must be currently PENDING. """
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block page_title %}
|
||||
InvenTree | {{ order }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<div class='media'>
|
||||
<div class='media-left'>
|
||||
<img class='part-thumb'
|
||||
{% if order.supplier.image %}
|
||||
src="{{ order.supplier.image.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}
|
||||
/>
|
||||
</div>
|
||||
<div class='media-body'>
|
||||
<h4>{{ order }}</h4>
|
||||
<p>{{ order.description }}</p>
|
||||
{% if order.URL %}
|
||||
<a href="{{ order.URL }}">{{ order.URL }}</a>
|
||||
{% endif %}
|
||||
<p>
|
||||
<div class='btn-row'>
|
||||
<div class='btn-group'>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='edit-order' title='Edit order information'>
|
||||
<span class='glyphicon glyphicon-edit'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='export-order' title='Export order to file'>
|
||||
<span class='glyphicon glyphicon-download-alt'></span>
|
||||
</button>
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='place-order' title='Place order'>
|
||||
<span class='glyphicon glyphicon-send'></span>
|
||||
</button>
|
||||
{% elif order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='receive-order' title='Receive items'>
|
||||
<span class='glyphicon glyphicon-check'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='complete-order' title='Mark order as complete'>
|
||||
<span class='glyphicon glyphicon-ok'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.PENDING or order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='cancel-order' title='Cancel order'>
|
||||
<span class='glyphicon glyphicon-remove'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Purchase Order Details" %}</h4>
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<td>{% trans "Supplier" %}</td>
|
||||
<td><a href="{% url 'company-detail' order.supplier.id %}">{{ order.supplier }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{% include "order/order_status.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Created" %}</td>
|
||||
<td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td>
|
||||
</tr>
|
||||
{% if order.issue_date %}
|
||||
<tr>
|
||||
<td>{% trans "Issued" %}</td>
|
||||
<td>{{ order.issue_date }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.COMPLETE %}
|
||||
<tr>
|
||||
<td>{% trans "Received" %}</td>
|
||||
<td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class='container-fluid'>
|
||||
{% block details %}
|
||||
|
||||
<!-- Specific order details to go here -->
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
$("#place-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-issue' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$("#edit-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-edit' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,57 @@
|
||||
{% extends "order/order_base.html" %}
|
||||
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include 'order/tabs.html' with tab='notes' %}
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Order 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 "Order 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'>
|
||||
{{ order.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'purchase-order-notes' order.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,104 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "order/order_base.html" %}
|
||||
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block page_title %}
|
||||
InvenTree | {{ order }}
|
||||
{% endblock %}
|
||||
{% block details %}
|
||||
|
||||
{% block content %}
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<div class='media'>
|
||||
<div class='media-left'>
|
||||
<img class='part-thumb'
|
||||
{% if order.supplier.image %}
|
||||
src="{{ order.supplier.image.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}
|
||||
/>
|
||||
</div>
|
||||
<div class='media-body'>
|
||||
<h4>{{ order }}</h4>
|
||||
<p>{{ order.description }}</p>
|
||||
{% if order.URL %}
|
||||
<a href="{{ order.URL }}">{{ order.URL }}</a>
|
||||
{% endif %}
|
||||
<p>
|
||||
<div class='btn-row'>
|
||||
<div class='btn-group'>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='edit-order' title='Edit order information'>
|
||||
<span class='glyphicon glyphicon-edit'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='export-order' title='Export order to file'>
|
||||
<span class='glyphicon glyphicon-download-alt'></span>
|
||||
</button>
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='place-order' title='Place order'>
|
||||
<span class='glyphicon glyphicon-send'></span>
|
||||
</button>
|
||||
{% elif order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='receive-order' title='Receive items'>
|
||||
<span class='glyphicon glyphicon-check'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='complete-order' title='Mark order as complete'>
|
||||
<span class='glyphicon glyphicon-ok'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.PENDING or order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='cancel-order' title='Cancel order'>
|
||||
<span class='glyphicon glyphicon-remove'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Purchase Order Details" %}</h4>
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<td>{% trans "Supplier" %}</td>
|
||||
<td><a href="{% url 'company-detail' order.supplier.id %}">{{ order.supplier }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{% include "order/order_status.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Created" %}</td>
|
||||
<td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td>
|
||||
</tr>
|
||||
{% if order.issue_date %}
|
||||
<tr>
|
||||
<td>{% trans "Issued" %}</td>
|
||||
<td>{{ order.issue_date }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.COMPLETE %}
|
||||
<tr>
|
||||
<td>{% trans "Received" %}</td>
|
||||
<td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'order/tabs.html' with tab='details' %}
|
||||
|
||||
<hr>
|
||||
|
||||
<div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
|
||||
{% if order.status == OrderStatus.PENDING %}
|
||||
<button type='button' class='btn btn-default' id='new-po-line'>Add Line Item</button>
|
||||
<button type='button' class='btn btn-default' id='new-po-line'>{% trans "Add Line Item" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h4>Order Items</h4>
|
||||
<h4>{% trans "Order Items" %}</h4>
|
||||
|
||||
<table class='table table-striped table-condensed' id='po-lines-table' data-toolbar='#order-toolbar-buttons'>
|
||||
<thead>
|
||||
@@ -162,40 +80,11 @@ InvenTree | {{ order }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if order.notes %}
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'><b>{% trans "Notes" %}</b></div>
|
||||
<div class='panel-body'>{{ order.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
$("#place-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-issue' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$("#edit-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-edit' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
{{ block.super }}
|
||||
|
||||
$("#po-lines-table").on('click', ".line-receive", function() {
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% ifequal tab 'details' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'purchase-order-detail' order.id %}">{% trans "Items" %}</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>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -19,6 +19,8 @@ purchase_order_detail_urls = [
|
||||
|
||||
url(r'^export/?', views.PurchaseOrderExport.as_view(), name='purchase-order-export'),
|
||||
|
||||
url(r'^notes/', views.PurchaseOrderNotes.as_view(), name='purchase-order-notes'),
|
||||
|
||||
url(r'^.*$', views.PurchaseOrderDetail.as_view(), name='purchase-order-detail'),
|
||||
]
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
from django.forms import HiddenInput
|
||||
|
||||
import logging
|
||||
@@ -69,6 +70,28 @@ class PurchaseOrderDetail(DetailView):
|
||||
return ctx
|
||||
|
||||
|
||||
class PurchaseOrderNotes(UpdateView):
|
||||
""" View for updating the 'notes' field of a PurchaseOrder """
|
||||
|
||||
context_object_name = 'order'
|
||||
template_name = 'order/order_notes.html'
|
||||
model = PurchaseOrder
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
|
||||
return reverse('purchase-order-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 PurchaseOrderCreate(AjaxCreateView):
|
||||
""" View for creating a new PurchaseOrder object using a modal form """
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class PartResource(ModelResource):
|
||||
|
||||
|
||||
class PartAdmin(ImportExportModelAdmin):
|
||||
|
||||
|
||||
resource_class = PartResource
|
||||
|
||||
list_display = ('full_name', 'description', 'total_stock', 'category')
|
||||
|
||||
@@ -104,7 +104,6 @@ class EditPartForm(HelperForm):
|
||||
'default_supplier',
|
||||
'units',
|
||||
'minimum_stock',
|
||||
'notes',
|
||||
'active',
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-01-31 10:22
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0025_auto_20191118_2316'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='part',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(help_text='Part notes - supports Markdown formatting'),
|
||||
),
|
||||
]
|
||||
@@ -22,6 +22,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 datetime import datetime
|
||||
@@ -422,7 +424,7 @@ class Part(models.Model):
|
||||
|
||||
virtual = models.BooleanField(default=False, help_text=_('Is this a virtual part, such as a software product or license?'))
|
||||
|
||||
notes = models.TextField(blank=True)
|
||||
notes = MarkdownxField(help_text=_('Part notes - supports Markdown formatting'))
|
||||
|
||||
bom_checksum = models.CharField(max_length=128, blank=True, help_text=_('Stored BOM checksum'))
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="markdownx row">
|
||||
<div class="markdown col-md-6">
|
||||
{% include 'django/forms/widgets/textarea.html' %}
|
||||
</div>
|
||||
<div class="markdown col-md-6">
|
||||
<div class="markdownx-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
{% include "part/tabs.html" with tab="allocation" %}
|
||||
|
||||
<h3>Part Allocation</h3>
|
||||
<h4>Part Allocation</h4>
|
||||
|
||||
{% if part.allocated_build_count > 0 %}
|
||||
<h4>Allocated to Part Builds</h4>
|
||||
<table class='table table-striped table-condensed' id='build-table'>
|
||||
<tr>
|
||||
<th>Build</th>
|
||||
@@ -23,7 +21,6 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{% extends "part/part_base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block details %}
|
||||
|
||||
{% include 'part/tabs.html' with tab='attachments' %}
|
||||
|
||||
<h4>Part Attachments</h4>
|
||||
<h4>{% trans "Part Attachments" %}</h4>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id='attachment-buttons'>
|
||||
<div class="btn-group">
|
||||
<button type='button' class='btn btn-success' id='new-attachment'>Add Attachment</button>
|
||||
<button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field='file' data-searchable='true'>File</th>
|
||||
<th data-field='comment' data-searchable='true'>Comment</th>
|
||||
<th data-field='file' data-searchable='true'>{% trans "File" %}</th>
|
||||
<th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -31,10 +31,10 @@
|
||||
<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='Edit attachment ({{ attachment.basename }})'>
|
||||
<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='glyphicon glyphicon-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='Delete attachment ({{ attachment.basename }})'>
|
||||
<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='glyphicon glyphicon-trash'/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -147,13 +147,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if part.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>{% trans "Notes" %}</b></div>
|
||||
<div class="panel-body">{{ part.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_load %}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{% extends "part/part_base.html" %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include 'part/tabs.html' with tab='notes' %}
|
||||
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Part 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 "Part 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'>
|
||||
{{ part.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'part-notes' part.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -54,4 +54,7 @@
|
||||
<li{% ifequal tab 'attachments' %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'part-attachments' part.id %}">{% trans "Attachments" %} {% if part.attachment_count > 0 %}<span class="badge">{{ part.attachment_count }}</span>{% endif %}</a>
|
||||
</li>
|
||||
<li{% ifequal tab 'notes' %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'part-notes' part.id %}">{% trans "Notes" %}{% if part.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -48,11 +48,17 @@ def inventree_version(*args, **kwargs):
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_commit(*args, **kwargs):
|
||||
def inventree_commit_hash(*args, **kwargs):
|
||||
""" Return InvenTree git commit hash string """
|
||||
return version.inventreeCommitHash()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_commit_date(*args, **kwargs):
|
||||
""" Return InvenTree git commit date string """
|
||||
return version.inventreeCommitDate()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_github_url(*args, **kwargs):
|
||||
""" Return URL for InvenTree github site """
|
||||
|
||||
@@ -22,9 +22,13 @@ class TemplateTagTest(TestCase):
|
||||
self.assertEqual(type(inventree_extras.inventree_version()), str)
|
||||
|
||||
def test_hash(self):
|
||||
hash = inventree_extras.inventree_commit()
|
||||
hash = inventree_extras.inventree_commit_hash()
|
||||
self.assertEqual(len(hash), 7)
|
||||
|
||||
def test_date(self):
|
||||
d = inventree_extras.inventree_commit_date()
|
||||
self.assertEqual(len(d.split('-')), 3)
|
||||
|
||||
def test_github(self):
|
||||
self.assertIn('github.com', inventree_extras.inventree_github_url())
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ part_detail_urls = [
|
||||
url(r'^orders/?', views.PartDetail.as_view(template_name='part/orders.html'), name='part-orders'),
|
||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
||||
url(r'^notes/?', views.PartNotes.as_view(), name='part-notes'),
|
||||
|
||||
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
|
||||
|
||||
|
||||
+34
-1
@@ -11,7 +11,7 @@ from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import HttpResponseRedirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.views.generic import DetailView, ListView, FormView
|
||||
from django.views.generic import DetailView, ListView, FormView, UpdateView
|
||||
from django.forms.models import model_to_dict
|
||||
from django.forms import HiddenInput, CheckboxInput
|
||||
|
||||
@@ -519,6 +519,39 @@ class PartCreate(AjaxCreateView):
|
||||
return initials
|
||||
|
||||
|
||||
class PartNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a Part object.
|
||||
Presents a live markdown editor.
|
||||
"""
|
||||
|
||||
context_object_name = 'part'
|
||||
# form_class = part_forms.EditNotesForm
|
||||
template_name = 'part/notes.html'
|
||||
model = Part
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
""" Return the success URL for this form """
|
||||
|
||||
return reverse('part-notes', kwargs={'pk': self.get_object().id})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
part = self.get_object()
|
||||
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['editing'] = str2bool(self.request.GET.get('edit', ''))
|
||||
|
||||
ctx['starred'] = part.isStarredBy(self.request.user)
|
||||
ctx['disabled'] = not part.active
|
||||
|
||||
ctx['OrderStatus'] = OrderStatus
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class PartDetail(DetailView):
|
||||
""" Detail view for Part object
|
||||
"""
|
||||
|
||||
@@ -162,7 +162,6 @@ class EditStockItemForm(HelperForm):
|
||||
'serial',
|
||||
'batch',
|
||||
'status',
|
||||
'notes',
|
||||
'URL',
|
||||
'delete_on_deplete',
|
||||
]
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }} × {{ 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) {
|
||||
|
||||
@@ -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 }} × {{ 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 %}
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% 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>
|
||||
{% if 0 %}
|
||||
<!-- These tabs are to be implemented in the future -->
|
||||
<li{% ifequal tab 'builds' %} class='active'{% endifequal %}>
|
||||
<a href='#'>{% trans "Builds" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<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>
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-about'>
|
||||
<div class='modal-dialog'>
|
||||
@@ -14,23 +15,26 @@
|
||||
<div>
|
||||
<!--
|
||||
-->
|
||||
<h4>InvenTree Version Information</h4>
|
||||
<h4>{% trans "InvenTree Version Information" %}</h4>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tr>
|
||||
<td>Version</td><td><a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a></td>
|
||||
<td>{% trans "Version" %}</td><td><a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Commit Hash</td><td><a href="https://github.com/inventree/InvenTree/commit/{% inventree_commit %}">{% inventree_commit %}</a></td>
|
||||
<td>{% trans "Commit Hash" %}</td><td><a href="https://github.com/inventree/InvenTree/commit/{% inventree_commit_hash %}">{% inventree_commit_hash %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Commit Date" %}</td><td>{% inventree_commit_date %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>InvenTree Documenation</td>
|
||||
<td>{% trans "InvenTree Documentation" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}">{% inventree_docs_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>View Code on GitHub</td><td><a href="{% inventree_github_url %}">{% inventree_github_url %}</a></td>
|
||||
<td>{% trans "View Code on GitHub" %}</td><td><a href="{% inventree_github_url %}">{% inventree_github_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
|
||||
@@ -5,6 +5,8 @@ django-cors-headers==3.2.0 # CORS headers extension for DRF
|
||||
django_filter==2.2.0 # Extended filtering options
|
||||
django-mptt==0.10.0 # Modified Preorder Tree Traversal
|
||||
django-dbbackup==3.2.0 # Database backup / restore functionality
|
||||
django-markdownx==3.0.1 # Markdown form fields
|
||||
django-markdownify==0.8.0 # Markdown rendering
|
||||
coreapi==2.3.0 # API documentation
|
||||
pygments==2.2.0 # Syntax highlighting
|
||||
tablib==0.13.0 # Import / export data files
|
||||
|
||||
Reference in New Issue
Block a user