mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 04:26:44 +00:00
Merge pull request #2020 from SchrodingersGat/mpn-sorting-fix
Mpn sorting fix
This commit is contained in:
commit
610c05384b
51
InvenTree/InvenTree/filters.py
Normal file
51
InvenTree/InvenTree/filters.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeOrderingFilter(OrderingFilter):
|
||||||
|
"""
|
||||||
|
Custom OrderingFilter class which allows aliased filtering of related fields.
|
||||||
|
|
||||||
|
To use, simply specify this filter in the "filter_backends" section.
|
||||||
|
|
||||||
|
filter_backends = [
|
||||||
|
InvenTreeOrderingFilter,
|
||||||
|
]
|
||||||
|
|
||||||
|
Then, specify a ordering_field_aliases attribute:
|
||||||
|
|
||||||
|
ordering_field_alises = {
|
||||||
|
'name': 'part__part__name',
|
||||||
|
'SKU': 'part__SKU',
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_ordering(self, request, queryset, view):
|
||||||
|
|
||||||
|
ordering = super().get_ordering(request, queryset, view)
|
||||||
|
|
||||||
|
aliases = getattr(view, 'ordering_field_aliases', None)
|
||||||
|
|
||||||
|
# Attempt to map ordering fields based on provided aliases
|
||||||
|
if ordering is not None and aliases is not None:
|
||||||
|
"""
|
||||||
|
Ordering fields should be mapped to separate fields
|
||||||
|
"""
|
||||||
|
|
||||||
|
for idx, field in enumerate(ordering):
|
||||||
|
|
||||||
|
reverse = False
|
||||||
|
|
||||||
|
if field.startswith('-'):
|
||||||
|
field = field[1:]
|
||||||
|
reverse = True
|
||||||
|
|
||||||
|
if field in aliases:
|
||||||
|
ordering[idx] = aliases[field]
|
||||||
|
|
||||||
|
if reverse:
|
||||||
|
ordering[idx] = '-' + ordering[idx]
|
||||||
|
|
||||||
|
return ordering
|
@ -7,11 +7,12 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters import rest_framework as rest_filters
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework import filters, status
|
from rest_framework import filters, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from InvenTree.filters import InvenTreeOrderingFilter
|
||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
from InvenTree.api import AttachmentMixin
|
from InvenTree.api import AttachmentMixin
|
||||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
||||||
@ -144,7 +145,7 @@ class POList(generics.ListCreateAPIView):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter,
|
filters.OrderingFilter,
|
||||||
]
|
]
|
||||||
@ -214,6 +215,14 @@ class POLineItemList(generics.ListCreateAPIView):
|
|||||||
queryset = PurchaseOrderLineItem.objects.all()
|
queryset = PurchaseOrderLineItem.objects.all()
|
||||||
serializer_class = POLineItemSerializer
|
serializer_class = POLineItemSerializer
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
|
||||||
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
|
queryset = POLineItemSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -226,18 +235,26 @@ class POLineItemList(generics.ListCreateAPIView):
|
|||||||
return self.serializer_class(*args, **kwargs)
|
return self.serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter
|
InvenTreeOrderingFilter
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ordering_field_aliases = {
|
||||||
|
'MPN': 'part__manufacturer_part__MPN',
|
||||||
|
'SKU': 'part__SKU',
|
||||||
|
'part_name': 'part__part__name',
|
||||||
|
}
|
||||||
|
|
||||||
ordering_fields = [
|
ordering_fields = [
|
||||||
'part__part__name',
|
'MPN',
|
||||||
'part__MPN',
|
'part_name',
|
||||||
'part__SKU',
|
'purchase_price',
|
||||||
'reference',
|
|
||||||
'quantity',
|
'quantity',
|
||||||
'received',
|
'received',
|
||||||
|
'reference',
|
||||||
|
'SKU',
|
||||||
|
'total_price',
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
@ -262,6 +279,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
queryset = PurchaseOrderLineItem.objects.all()
|
queryset = PurchaseOrderLineItem.objects.all()
|
||||||
serializer_class = POLineItemSerializer
|
serializer_class = POLineItemSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
|
||||||
|
queryset = POLineItemSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
@ -272,7 +297,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
serializer_class = SOAttachmentSerializer
|
serializer_class = SOAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
@ -396,7 +421,7 @@ class SOList(generics.ListCreateAPIView):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter,
|
filters.OrderingFilter,
|
||||||
]
|
]
|
||||||
@ -495,7 +520,7 @@ class SOLineItemList(generics.ListCreateAPIView):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter
|
filters.OrderingFilter
|
||||||
]
|
]
|
||||||
@ -580,7 +605,7 @@ class SOAllocationList(generics.ListCreateAPIView):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default filterable fields
|
# Default filterable fields
|
||||||
@ -598,7 +623,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
serializer_class = POAttachmentSerializer
|
serializer_class = POAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
|
@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
from django.db.models import Case, When, Value
|
from django.db.models import Case, When, Value
|
||||||
from django.db.models import BooleanField
|
from django.db.models import BooleanField, ExpressionWrapper, F
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from sql_util.utils import SubqueryCount
|
from sql_util.utils import SubqueryCount
|
||||||
@ -109,6 +110,23 @@ class POSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
class POLineItemSerializer(InvenTreeModelSerializer):
|
class POLineItemSerializer(InvenTreeModelSerializer):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def annotate_queryset(queryset):
|
||||||
|
"""
|
||||||
|
Add some extra annotations to this queryset:
|
||||||
|
|
||||||
|
- Total price = purchase_price * quantity
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
total_price=ExpressionWrapper(
|
||||||
|
F('purchase_price') * F('quantity'),
|
||||||
|
output_field=models.DecimalField()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
part_detail = kwargs.pop('part_detail', False)
|
part_detail = kwargs.pop('part_detail', False)
|
||||||
@ -123,6 +141,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
quantity = serializers.FloatField(default=1)
|
quantity = serializers.FloatField(default=1)
|
||||||
received = serializers.FloatField(default=0)
|
received = serializers.FloatField(default=0)
|
||||||
|
|
||||||
|
total_price = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
|
part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
|
||||||
supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
|
supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
|
||||||
|
|
||||||
@ -158,6 +178,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
'purchase_price_string',
|
'purchase_price_string',
|
||||||
'destination',
|
'destination',
|
||||||
'destination_detail',
|
'destination_detail',
|
||||||
|
'total_price',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@ $("#po-line-table").inventreeTable({
|
|||||||
{
|
{
|
||||||
field: 'part',
|
field: 'part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortName: 'part__part__name',
|
sortName: 'part_name',
|
||||||
title: '{% trans "Part" %}',
|
title: '{% trans "Part" %}',
|
||||||
switchable: false,
|
switchable: false,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
@ -314,7 +314,7 @@ $("#po-line-table").inventreeTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortName: 'part__SKU',
|
sortName: 'SKU',
|
||||||
field: 'supplier_part_detail.SKU',
|
field: 'supplier_part_detail.SKU',
|
||||||
title: '{% trans "SKU" %}',
|
title: '{% trans "SKU" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
@ -327,7 +327,7 @@ $("#po-line-table").inventreeTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortName: 'part__MPN',
|
sortName: 'MPN',
|
||||||
field: 'supplier_part_detail.manufacturer_part_detail.MPN',
|
field: 'supplier_part_detail.manufacturer_part_detail.MPN',
|
||||||
title: '{% trans "MPN" %}',
|
title: '{% trans "MPN" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
@ -364,6 +364,7 @@ $("#po-line-table").inventreeTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
field: 'total_price',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'total_price',
|
field: 'total_price',
|
||||||
title: '{% trans "Total price" %}',
|
title: '{% trans "Total price" %}',
|
||||||
@ -384,7 +385,7 @@ $("#po-line-table").inventreeTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortable: true,
|
sortable: false,
|
||||||
field: 'received',
|
field: 'received',
|
||||||
switchable: false,
|
switchable: false,
|
||||||
title: '{% trans "Received" %}',
|
title: '{% trans "Received" %}',
|
||||||
|
@ -43,6 +43,7 @@ from .serializers import StockItemTestResultSerializer
|
|||||||
from InvenTree.views import TreeSerializer
|
from InvenTree.views import TreeSerializer
|
||||||
from InvenTree.helpers import str2bool, isNull
|
from InvenTree.helpers import str2bool, isNull
|
||||||
from InvenTree.api import AttachmentMixin
|
from InvenTree.api import AttachmentMixin
|
||||||
|
from InvenTree.filters import InvenTreeOrderingFilter
|
||||||
|
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
|
|
||||||
@ -882,10 +883,16 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter,
|
InvenTreeOrderingFilter,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ordering_field_aliases = {
|
||||||
|
'SKU': 'supplier_part__SKU',
|
||||||
|
}
|
||||||
|
|
||||||
ordering_fields = [
|
ordering_fields = [
|
||||||
|
'batch',
|
||||||
|
'location',
|
||||||
'part__name',
|
'part__name',
|
||||||
'part__IPN',
|
'part__IPN',
|
||||||
'updated',
|
'updated',
|
||||||
@ -893,10 +900,13 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
'expiry_date',
|
'expiry_date',
|
||||||
'quantity',
|
'quantity',
|
||||||
'status',
|
'status',
|
||||||
|
'SKU',
|
||||||
]
|
]
|
||||||
|
|
||||||
ordering = [
|
ordering = [
|
||||||
'part__name'
|
'part__name',
|
||||||
|
'quantity',
|
||||||
|
'location',
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
|
@ -921,8 +921,10 @@ function loadStockTable(table, options) {
|
|||||||
|
|
||||||
return renderLink(text, link);
|
return renderLink(text, link);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
col = {
|
||||||
|
|
||||||
field: 'supplier_part',
|
field: 'supplier_part',
|
||||||
title: '{% trans "Supplier Part" %}',
|
title: '{% trans "Supplier Part" %}',
|
||||||
visible: params['supplier_part_detail'] || false,
|
visible: params['supplier_part_detail'] || false,
|
||||||
@ -944,15 +946,25 @@ function loadStockTable(table, options) {
|
|||||||
|
|
||||||
return renderLink(text, link);
|
return renderLink(text, link);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (!options.params.ordering) {
|
||||||
|
col.sortable = true;
|
||||||
|
col.sortName = 'SKU';
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.push(col);
|
||||||
|
|
||||||
col = {
|
col = {
|
||||||
field: 'purchase_price_string',
|
field: 'purchase_price_string',
|
||||||
title: '{% trans "Purchase Price" %}',
|
title: '{% trans "Purchase Price" %}',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!options.params.ordering) {
|
if (!options.params.ordering) {
|
||||||
col['sortable'] = true;
|
col.sortable = true;
|
||||||
|
col.sortName = 'purchase_price';
|
||||||
};
|
};
|
||||||
|
|
||||||
columns.push(col);
|
columns.push(col);
|
||||||
|
|
||||||
columns.push({
|
columns.push({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user