2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-19 21:45:39 +00:00

Merge remote-tracking branch 'inventree/master' into scheduling

# Conflicts:
#	InvenTree/InvenTree/version.py

(Update API version)
This commit is contained in:
Oliver
2022-03-08 12:25:36 +11:00
63 changed files with 20987 additions and 19004 deletions

View File

@ -28,6 +28,8 @@ from djmoney.contrib.exchange.exceptions import MissingRate
from decimal import Decimal, InvalidOperation
from part.admin import PartResource
from .models import Part, PartCategory, PartRelated
from .models import BomItem, BomItemSubstitute
from .models import PartParameter, PartParameterTemplate
@ -46,6 +48,7 @@ import order.models
from . import serializers as part_serializers
from InvenTree.helpers import str2bool, isNull, increment
from InvenTree.helpers import DownloadFile
from InvenTree.api import AttachmentMixin
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
@ -865,6 +868,22 @@ class PartList(generics.ListCreateAPIView):
queryset = self.filter_queryset(self.get_queryset())
# Check if we wish to export the queried data to a file.
# If so, skip pagination!
export_format = request.query_params.get('export', None)
if export_format:
export_format = str(export_format).strip().lower()
if export_format in ['csv', 'tsv', 'xls', 'xlsx']:
dataset = PartResource().export(queryset=queryset)
filedata = dataset.export(export_format)
filename = f"InvenTree_Parts.{export_format}"
return DownloadFile(filedata, filename)
page = self.paginate_queryset(queryset)
if page is not None:

View File

@ -2651,7 +2651,7 @@ class BomItem(models.Model, DataImportMixin):
def get_api_url():
return reverse('api-bom-list')
def get_valid_parts_for_allocation(self):
def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
"""
Return a list of valid parts which can be allocated against this BomItem:
@ -2666,13 +2666,14 @@ class BomItem(models.Model, DataImportMixin):
parts.add(self.sub_part)
# Variant parts (if allowed)
if self.allow_variants:
if allow_variants and self.allow_variants:
for variant in self.sub_part.get_descendants(include_self=False):
parts.add(variant)
# Substitute parts
for sub in self.substitutes.all():
parts.add(sub.part)
if allow_substitutes:
for sub in self.substitutes.all():
parts.add(sub.part)
return parts

View File

@ -153,9 +153,6 @@
<h4>{% trans "Parts" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
<button type='button' class='btn btn-outline-secondary' id='part-export' title='{% trans "Export Part Data" %}'>
<span class='fas fa-file-download'></span> {% trans "Export" %}
</button>
{% if roles.part.add %}
<button type='button' class='btn btn-success' id='part-create' title='{% trans "Create new part" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Part" %}
@ -290,13 +287,6 @@
});
});
$("#part-export").click(function() {
var url = "{% url 'part-export' %}?category={{ category.id }}";
location.href = url;
});
{% if roles.part.add %}
$("#part-create").click(function() {

View File

@ -28,11 +28,6 @@
</div>
</div>
<div class='panel-content'>
{% if part.is_template %}
<div class='alert alert-info alert-block'>
{% blocktrans with full_name=part.full_name%}Showing stock for all variants of <em>{{full_name}}</em>{% endblocktrans %}
</div>
{% endif %}
{% include "stock_table.html" %}
</div>
</div>
@ -296,9 +291,7 @@
</button>
<ul class='dropdown-menu' role='menu'>
<li><a class='dropdown-item' href='#' id='bom-upload'><span class='fas fa-file-upload'></span> {% trans "Upload BOM" %}</a></li>
{% if part.variant_of %}
<li><a class='dropdown-item' href='#' id='bom-duplicate'><span class='fas fa-clone'></span> {% trans "Copy BOM" %}</a></li>
{% endif %}
<li><a class='dropdown-item' href='#' id='validate-bom'><span class='fas fa-clipboard-check icon-green'></span> {% trans "Validate BOM" %}</a></li>
</ul>
</div>
@ -851,14 +844,7 @@
],
url: "{% url 'api-stock-list' %}",
});
$("#stock-export").click(function() {
exportStock({
part: {{ part.pk }}
});
});
$('#item-create').click(function () {
createNewStockItem({
data: {

View File

@ -5,7 +5,7 @@ This module provides template tags for extra functionality,
over and above the built-in Django tags.
"""
from datetime import date
from datetime import date, datetime
import os
import sys
@ -87,7 +87,9 @@ def render_date(context, date_object):
# Update the context cache
context['user_date_format'] = user_date_format
return date_object.strftime(user_date_format)
if isinstance(date_object, (datetime, date)):
return date_object.strftime(user_date_format)
return date_object
@register.simple_tag()

View File

@ -58,14 +58,6 @@ class PartListTest(PartViewTestCase):
self.assertIn('parts', keys)
self.assertIn('user', keys)
def test_export(self):
""" Export part data to CSV """
response = self.client.get(reverse('part-export'), {'parts': '1,2,3,4,5,6,7,8,9,10'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
self.assertIn('streaming_content', dir(response))
class PartDetailTest(PartViewTestCase):

View File

@ -80,9 +80,6 @@ part_urls = [
# Download a BOM upload template
url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'),
# Export data for multiple parts
url(r'^export/', views.PartExport.as_view(), name='part-export'),
# Individual part using pk
url(r'^(?P<pk>\d+)/', include(part_detail_urls)),

View File

@ -49,13 +49,11 @@ from . import settings as part_settings
from .bom import MakeBomTemplate, ExportBom, IsValidBOMFormat
from order.models import PurchaseOrderLineItem
from .admin import PartResource
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.views import QRCodeView
from InvenTree.views import InvenTreeRoleMixin
from InvenTree.helpers import DownloadFile, str2bool
from InvenTree.helpers import str2bool
class PartIndex(InvenTreeRoleMixin, ListView):
@ -709,69 +707,6 @@ class BomUpload(InvenTreeRoleMixin, DetailView):
template_name = 'part/upload_bom.html'
class PartExport(AjaxView):
""" Export a CSV file containing information on multiple parts """
role_required = 'part.view'
def get_parts(self, request):
""" Extract part list from the POST parameters.
Parts can be supplied as:
- Part category
- List of part PK values
"""
# Filter by part category
cat_id = request.GET.get('category', None)
part_list = None
if cat_id is not None:
try:
category = PartCategory.objects.get(pk=cat_id)
part_list = category.get_parts()
except (ValueError, PartCategory.DoesNotExist):
pass
# Backup - All parts
if part_list is None:
part_list = Part.objects.all()
# Also optionally filter by explicit list of part IDs
part_ids = request.GET.get('parts', '')
parts = []
for pk in part_ids.split(','):
try:
parts.append(int(pk))
except ValueError:
pass
if len(parts) > 0:
part_list = part_list.filter(pk__in=parts)
# Prefetch related fields to reduce DB hits
part_list = part_list.prefetch_related(
'category',
'used_in',
'builds',
'supplier_parts__purchase_order_line_items',
'stock_items__allocations',
)
return part_list
def get(self, request, *args, **kwargs):
parts = self.get_parts(request)
dataset = PartResource().export(queryset=parts)
csv = dataset.export('csv')
return DownloadFile(csv, 'InvenTree_Parts.csv')
class BomUploadTemplate(AjaxView):
"""
Provide a BOM upload template file for download.