Merge pull request #134 from SchrodingersGat/tweaks

Tweaks
This commit is contained in:
Oliver 2019-04-18 22:09:15 +10:00 committed by GitHub
commit ba72ced00c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 154 additions and 109 deletions

View File

@ -23,7 +23,10 @@ def DownloadFile(data, filename, content_type='application/text'):
filename = WrapWithQuotes(filename)
wrapper = FileWrapper(io.StringIO(data))
if type(data) == str:
wrapper = FileWrapper(io.StringIO(data))
else:
wrapper = FileWrapper(io.BytesIO(data))
response = StreamingHttpResponse(wrapper, content_type=content_type)
response['Content-Length'] = len(data)

View File

@ -57,7 +57,6 @@ INSTALLED_APPS = [
# Third part add-ons
'django_filters',
'rest_framework',
'simple_history',
'crispy_forms',
'import_export',
'django_cleanup',
@ -72,7 +71,6 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
'InvenTree.middleware.AuthRequiredMiddleware'
]

View File

@ -12,7 +12,7 @@ class PartAdmin(ImportExportModelAdmin):
list_display = ('name', 'IPN', 'description', 'total_stock', 'category')
class PartCategoryAdmin(admin.ModelAdmin):
class PartCategoryAdmin(ImportExportModelAdmin):
list_display = ('name', 'pathstring', 'description')

View File

@ -87,13 +87,12 @@ class PartList(generics.ListCreateAPIView):
childs = category.getUniqueChildren()
for child in childs:
# Ignore the top-level category (already filtered)
if child == cat_id:
if str(child) == str(cat_id):
continue
flt |= Q(category=child)
parts_list = parts_list.filter(flt)
# Default - return all parts
return parts_list
permission_classes = [

View File

@ -25,74 +25,82 @@
<hr>
<table class='table table-striped'>
<tr>
<td>Part name</td>
<td>{{ part.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ part.description }}</td>
</tr>
{% if part.IPN %}
<tr>
<td>IPN</td>
<td>{{ part.IPN }}</td>
</tr>
{% endif %}
<tr>
<td>Category</td>
<td>
{% if part.category %}
<a href="{% url 'category-detail' part.category.id %}">{{ part.category.pathstring }}</a>
{% endif %}
</td>
</tr>
{% if part.default_location %}
<tr>
<td>Default Location</td>
<td><a href="{% url 'stock-location-detail' part.default_location.id %}">{{ part.default_location.pathstring }}</a></td>
</tr>
{% endif %}
{% if part.default_supplier %}
<tr>
<td>Default Supplier</td>
<td><a href="{% url 'supplier-part-detail' part.default_supplier.id %}">
{{ part.default_supplier.supplier.name }} | {{ part.default_supplier.SKU }}
</a></td>
</tr>
{% endif %}
<tr>
<td>Units</td>
<td>{{ part.units }}</td>
</tr>
<tr>
<td>Buildable</td>
<td>{% include "yesnolabel.html" with value=part.buildable %}</td>
</tr>
<tr>
<td>Consumable</td>
<td>{% include "yesnolabel.html" with value=part.consumable %}</td>
</tr>
<tr>
<td>Trackable</td>
<td>{% include "yesnolabel.html" with value=part.trackable %}</td>
</tr>
<tr>
<td>Purchaseable</td>
<td>{% include "yesnolabel.html" with value=part.purchaseable %}</td>
</tr>
<tr>
<td>Salable</td>
<td>{% include "yesnolabel.html" with value=part.salable %}</td>
</tr>
{% if part.minimum_stock > 0 %}
<tr>
<td>Minimum Stock</td>
<td>{{ part.minimum_stock }}</td>
</tr>
{% endif %}
</table>
<div class='row'>
<div class='col-sm-6'>
<table class='table table-striped'>
<tr>
<td>Part name</td>
<td>{{ part.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ part.description }}</td>
</tr>
{% if part.IPN %}
<tr>
<td>IPN</td>
<td>{{ part.IPN }}</td>
</tr>
{% endif %}
<tr>
<td>Category</td>
<td>
{% if part.category %}
<a href="{% url 'category-detail' part.category.id %}">{{ part.category.pathstring }}</a>
{% endif %}
</td>
</tr>
{% if part.default_location %}
<tr>
<td>Default Location</td>
<td><a href="{% url 'stock-location-detail' part.default_location.id %}">{{ part.default_location.pathstring }}</a></td>
</tr>
{% endif %}
{% if part.default_supplier %}
<tr>
<td>Default Supplier</td>
<td><a href="{% url 'supplier-part-detail' part.default_supplier.id %}">
{{ part.default_supplier.supplier.name }} | {{ part.default_supplier.SKU }}
</a></td>
</tr>
{% endif %}
<tr>
<td>Units</td>
<td>{{ part.units }}</td>
</tr>
</table>
</div>
<div class='col-sm-6'>
<table class='table table-striped'>
<tr>
<td>Buildable</td>
<td>{% include "yesnolabel.html" with value=part.buildable %}</td>
</tr>
<tr>
<td>Consumable</td>
<td>{% include "yesnolabel.html" with value=part.consumable %}</td>
</tr>
<tr>
<td>Trackable</td>
<td>{% include "yesnolabel.html" with value=part.trackable %}</td>
</tr>
<tr>
<td>Purchaseable</td>
<td>{% include "yesnolabel.html" with value=part.purchaseable %}</td>
</tr>
<tr>
<td>Salable</td>
<td>{% include "yesnolabel.html" with value=part.salable %}</td>
</tr>
{% if part.minimum_stock > 0 %}
<tr>
<td>Minimum Stock</td>
<td>{{ part.minimum_stock }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
{% if part.notes %}
<div class="panel panel-default">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -21,7 +21,10 @@ function downloadBom(options = {}) {
<div class='controls'>
<select id='bom-format' class='select'>
<option value='csv'>CSV</option>
<option value='xls'>XLSX</option>
<option value='tsv'>TSV</option>
<option value='xls'>XLS</option>
<option value='xlsx'>XLSX</option>
<option value='ods'>ODS</option>
<option value='yaml'>YAML</option>
<option value='json'>JSON</option>
<option value='xml'>XML</option>
@ -72,20 +75,15 @@ function loadBomTable(table, options) {
field: 'pk',
title: 'ID',
visible: false,
}
},
{
checkbox: true,
title: 'Select',
searchable: false,
sortable: false,
},
];
if (options.editable) {
cols.push({
formatter: function(value, row, index, field) {
var bEdit = "<button class='btn btn-success bom-edit-button btn-sm' type='button' url='" + row.url + "edit'>Edit</button>";
var bDelt = "<button class='btn btn-danger bom-delete-button btn-sm' type='button' url='" + row.url + "delete'>Delete</button>";
return "<div class='btn-group'>" + bEdit + bDelt + "</div>";
}
});
}
// Part column
cols.push(
{
@ -126,8 +124,17 @@ function loadBomTable(table, options) {
}
);
// If we are NOT editing, display the available stock
if (!options.editable) {
if (options.editable) {
cols.push({
formatter: function(value, row, index, field) {
var bEdit = "<button class='btn btn-success bom-edit-button btn-sm' type='button' url='" + row.url + "edit'>Edit</button>";
var bDelt = "<button class='btn btn-danger bom-delete-button btn-sm' type='button' url='" + row.url + "delete'>Delete</button>";
return "<div class='btn-group'>" + bEdit + bDelt + "</div>";
}
});
}
else {
cols.push(
{
field: 'sub_part.available_stock',

View File

@ -1,19 +1,19 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from import_export.admin import ImportExportModelAdmin
from .models import StockLocation, StockItem
from .models import StockItemTracking
class LocationAdmin(admin.ModelAdmin):
class LocationAdmin(ImportExportModelAdmin):
list_display = ('name', 'pathstring', 'description')
class StockItemAdmin(SimpleHistoryAdmin):
class StockItemAdmin(ImportExportModelAdmin):
list_display = ('part', 'quantity', 'location', 'status', 'updated')
class StockTrackingAdmin(admin.ModelAdmin):
class StockTrackingAdmin(ImportExportModelAdmin):
list_display = ('item', 'date', 'title')

View File

@ -2,6 +2,8 @@ from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter
from django.conf.urls import url, include
from django.db.models import Q
from django.shortcuts import get_object_or_404
from .models import StockLocation, StockItem
from .models import StockItemTracking
@ -202,7 +204,36 @@ class StockList(generics.ListCreateAPIView):
Create a new StockItem
"""
queryset = StockItem.objects.all()
def get_queryset(self):
"""
If the query includes a particular location,
we may wish to also request stock items from all child locations.
This is set by the optional param 'include_child_categories'
"""
# Does the client wish to filter by category?
loc_id = self.request.query_params.get('location', None)
# Start with all objects
stock_list = StockItem.objects.all()
if loc_id:
location = get_object_or_404(StockLocation, pk=loc_id)
# Filter by the supplied category
flt = Q(location=loc_id)
if self.request.query_params.get('include_child_locations', None):
childs = location.getUniqueChildren()
for child in childs:
# Ignore the top-level category (already filtered!)
if str(child) == str(loc_id):
continue
flt |= Q(location=child)
stock_list = stock_list.filter(flt)
return stock_list
serializer_class = StockItemSerializer
@ -219,7 +250,6 @@ class StockList(generics.ListCreateAPIView):
filter_fields = [
'part',
'uuid',
'location',
'supplier_part',
'customer',
'belongs_to',

View File

@ -160,7 +160,8 @@
loadStockTable($("#stock-table"), {
params: {
{% if location %}
location: {{ location.id }}
location: {{ location.id }},
include_child_locations: true,
{% endif %}
},
url: "{% url 'api-stock-list' %}",

View File

@ -3,7 +3,7 @@
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header clearfix content-heading">
<a class="navbar-brand" id='logo' href="{% url 'index' %}"><img src="{% static 'img/inventree.png' %}" width="40" height="40"/></a>
<a class="navbar-brand" id='logo' href="{% url 'index' %}" style="padding-top: 7px; padding-bottom: 5px;"><img src="{% static 'img/inventree.png' %}" width="40" height="40" style="display:block; margin: auto;"/></a>
</div>
<ul class="nav navbar-nav">
<li><a href="{% url 'part-index' %}">Parts</a></li>

View File

@ -10,7 +10,7 @@
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="inventree.svg"
inkscape:version="0.91 r13725"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
version="1.1"
id="svg2"
viewBox="0 0 400 400.00001"
@ -21,16 +21,16 @@
inkscape:export-ydpi="115.2">
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="24"
inkscape:window-x="65"
inkscape:window-height="1056"
inkscape:window-width="1855"
inkscape:window-y="0"
inkscape:window-x="-8"
inkscape:window-height="1137"
inkscape:window-width="1920"
showgrid="false"
inkscape:current-layer="layer1"
inkscape:document-units="px"
inkscape:cy="237.03857"
inkscape:cx="195.55918"
inkscape:zoom="1.4"
inkscape:cy="521.06718"
inkscape:cx="-180.35636"
inkscape:zoom="0.35"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
@ -90,7 +90,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@ -158,14 +158,14 @@
inkscape:export-ydpi="299.91324" />
<g
id="text4273"
style="font-style:normal;font-weight:normal;font-size:217.54554749px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;opacity:0.76999996;fill:#ffeeaa;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:217.54554749px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;opacity:0.76999996;fill:#335d88;fill-opacity:1;stroke:#ede3d8;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(1.1055685,0.12077953,0.13042915,-1.1045144,260.24897,1649.2316)"
inkscape:export-xdpi="299.91324"
inkscape:export-ydpi="299.91324">
<path
inkscape:connector-curvature="0"
id="path4577"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:217.55203247px;line-height:125%;font-family:Brussels;-inkscape-font-specification:'Brussels, Light';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffeeaa;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:217.55203247px;line-height:125%;font-family:Brussels;-inkscape-font-specification:'Brussels, Light';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#335d88;fill-opacity:1;stroke:#ede3d8;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -117.40075,599.71964 c 5.27286,1.59179 8.93096,3.72644 11.01731,6.40882 2.16744,2.68089 3.19894,6.45951 3.10336,11.34732 0,0 -1.08272,55.36631 -1.08272,55.36631 -0.21906,11.20195 -7.38435,16.55674 -21.8761,16.0485 0,0 -2.92196,-0.10247 -2.92196,-0.10247 0,0 -0.15971,8.03102 -0.15971,8.03102 0,0 39.16281,5.47451 39.16281,5.47451 0,0 12.568242,0.24647 12.568242,0.24647 0,0 1.566329,-81.64215 1.566329,-81.64215 0.09195,-4.79265 1.142855,-8.19902 3.144966,-10.21177 1.990467,-1.93135 5.341631,-2.94441 10.01779,-3.04567 0,0 8.490412,-0.0219 8.490412,-0.0219 0,0 0.13793,-7.29971 0.13793,-7.29971 0,0 -72.942109,-11.42831 -72.942109,-11.42831 0,0 -0.15928,8.00932 -0.15928,8.00932 0,0 9.93273,2.8197 9.93273,2.8197 0,0 0,0 0,0 m 26.057646,150.04813 c 4.860402,-0.19831 8.905931,-1.73322 12.161092,-4.58865 3.313681,-2.83452 4.997535,-6.2059 5.072638,-10.13186 0.07509,-3.92554 -1.480873,-7.22278 -4.687942,-9.90881 -3.147235,-2.70919 -7.135665,-4.06169 -11.990765,-4.04062 -5.005803,0.0217 -9.283919,1.44206 -12.809479,4.27943 -3.55676,2.86247 -5.38718,6.35506 -5.46763,10.45908 -0.0805,4.10447 1.61494,7.51856 5.06366,10.22424 3.41899,2.68235 7.647131,3.91166 12.658426,3.70719 0,0 0,0 0,0" />
</g>
</g>

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -3,7 +3,6 @@ psycopg2>=2.8.1
pillow>=5.0.0
djangorestframework>=3.6.2
django_filter>=1.0.2
django-simple-history>=1.8.2
coreapi>=2.3.0
pygments>=2.2.0
tablib>=0.13.0