mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +00:00 
			
		
		
		
	Add regex IPN filter for Part API
This commit is contained in:
		| @@ -5,9 +5,10 @@ Provides a JSON API for the Part app | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from django.conf.urls import url, include | ||||
| from django.urls import reverse | ||||
| from django.http import JsonResponse | ||||
| from django.db.models import Q, F, Count, Min, Max, Avg | ||||
| from django.db.models import Q, F, Count, Min, Max, Avg, query | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
|  | ||||
| from rest_framework import status | ||||
| @@ -15,12 +16,13 @@ from rest_framework.response import Response | ||||
| from rest_framework import filters, serializers | ||||
| from rest_framework import generics | ||||
|  | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from django_filters import rest_framework as rest_filters | ||||
|  | ||||
| from djmoney.money import Money | ||||
| from djmoney.contrib.exchange.models import convert_money | ||||
| from djmoney.contrib.exchange.exceptions import MissingRate | ||||
|  | ||||
| from django.conf.urls import url, include | ||||
| from django.urls import reverse | ||||
|  | ||||
| from .models import Part, PartCategory, BomItem | ||||
| from .models import PartParameter, PartParameterTemplate | ||||
| @@ -405,6 +407,74 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): | ||||
|         return response | ||||
|  | ||||
|  | ||||
| class PartFilter(rest_filters.FilterSet): | ||||
|     """ | ||||
|     Custom filters for the PartList endpoint. | ||||
|     Uses the django_filters extension framework | ||||
|     """ | ||||
|  | ||||
|     # Exact match for IPN | ||||
|     ipn = rest_filters.CharFilter( | ||||
|         label='Filter by exact IPN (internal part number)', | ||||
|         field_name='IPN', | ||||
|         lookup_expr="iexact" | ||||
|     ) | ||||
|  | ||||
|     # Regex match for IPN | ||||
|     ipn_regex = rest_filters.CharFilter( | ||||
|         field_name='IPN', lookup_expr='iregex' | ||||
|     ) | ||||
|  | ||||
|     # low_stock filter | ||||
|     low_stock = rest_filters.BooleanFilter(method='filter_low_stock') | ||||
|  | ||||
|     def filter_low_stock(self, queryset, name, value): | ||||
|         """ | ||||
|         Filter by "low stock" status | ||||
|         """ | ||||
|  | ||||
|         value = str2bool(value) | ||||
|  | ||||
|         if value: | ||||
|             # Ignore any parts which do not have a specified 'minimum_stock' level | ||||
|             queryset = queryset.exclude(minimum_stock=0) | ||||
|             # Filter items which have an 'in_stock' level lower than 'minimum_stock' | ||||
|             queryset = queryset.filter(Q(in_stock__lt=F('minimum_stock'))) | ||||
|         else: | ||||
|             # Filter items which have an 'in_stock' level higher than 'minimum_stock' | ||||
|             queryset = queryset.filter(Q(in_stock__gte=F('minimum_stock'))) | ||||
|  | ||||
|         return queryset | ||||
|  | ||||
|     # has_stock filter | ||||
|     has_stock = rest_filters.BooleanFilter(method='filter_has_stock') | ||||
|  | ||||
|     def filter_has_stock(self, queryset, name, value): | ||||
|  | ||||
|         value = str2bool(value) | ||||
|  | ||||
|         if value: | ||||
|             queryset = queryset.filter(Q(in_stock__gt=0)) | ||||
|         else: | ||||
|             queryset = queryset.filter(Q(in_stock__lte=0)) | ||||
|  | ||||
|         return queryset | ||||
|  | ||||
|     is_template = rest_filters.CharFilter() | ||||
|  | ||||
|     assembly = rest_filters.BooleanFilter() | ||||
|  | ||||
|     component = rest_filters.BooleanFilter() | ||||
|  | ||||
|     trackable = rest_filters.BooleanFilter() | ||||
|  | ||||
|     purchaseable = rest_filters.BooleanFilter() | ||||
|  | ||||
|     salable = rest_filters.BooleanFilter() | ||||
|  | ||||
|     active = rest_filters.BooleanFilter() | ||||
|  | ||||
|  | ||||
| class PartList(generics.ListCreateAPIView): | ||||
|     """ API endpoint for accessing a list of Part objects | ||||
|  | ||||
| @@ -427,8 +497,8 @@ class PartList(generics.ListCreateAPIView): | ||||
|     """ | ||||
|  | ||||
|     serializer_class = part_serializers.PartSerializer | ||||
|  | ||||
|     queryset = Part.objects.all() | ||||
|     filterset_class = PartFilter | ||||
|  | ||||
|     starred_parts = None | ||||
|  | ||||
| @@ -541,6 +611,10 @@ class PartList(generics.ListCreateAPIView): | ||||
|  | ||||
|         params = self.request.query_params | ||||
|  | ||||
|         # Annotate calculated data to the queryset | ||||
|         # (This will be used for further filtering) | ||||
|         queryset = part_serializers.PartSerializer.annotate_queryset(queryset) | ||||
|  | ||||
|         queryset = super().filter_queryset(queryset) | ||||
|  | ||||
|         # Filter by "uses" query - Limit to parts which use the provided part | ||||
| @@ -578,6 +652,17 @@ class PartList(generics.ListCreateAPIView): | ||||
|             else: | ||||
|                 queryset = queryset.filter(IPN='') | ||||
|  | ||||
|         # Filter by IPN | ||||
|         """ | ||||
|         ipn = params.get('ipn', None) | ||||
|  | ||||
|         if ipn is not None: | ||||
|  | ||||
|             queryset = queryset.filter(IPN=ipn) | ||||
|  | ||||
|         """ | ||||
|         # Filter by IPN (regex support) | ||||
|  | ||||
|         # Filter by whether the BOM has been validated (or not) | ||||
|         bom_valid = params.get('bom_valid', None) | ||||
|  | ||||
| @@ -643,36 +728,6 @@ class PartList(generics.ListCreateAPIView): | ||||
|                 except (ValueError, PartCategory.DoesNotExist): | ||||
|                     pass | ||||
|  | ||||
|         # Annotate calculated data to the queryset | ||||
|         # (This will be used for further filtering) | ||||
|         queryset = part_serializers.PartSerializer.annotate_queryset(queryset) | ||||
|  | ||||
|         # Filter by whether the part has stock | ||||
|         has_stock = params.get("has_stock", None) | ||||
|  | ||||
|         if has_stock is not None: | ||||
|             has_stock = str2bool(has_stock) | ||||
|  | ||||
|             if has_stock: | ||||
|                 queryset = queryset.filter(Q(in_stock__gt=0)) | ||||
|             else: | ||||
|                 queryset = queryset.filter(Q(in_stock__lte=0)) | ||||
|  | ||||
|         # If we are filtering by 'low_stock' status | ||||
|         low_stock = params.get('low_stock', None) | ||||
|  | ||||
|         if low_stock is not None: | ||||
|             low_stock = str2bool(low_stock) | ||||
|  | ||||
|             if low_stock: | ||||
|                 # Ignore any parts which do not have a specified 'minimum_stock' level | ||||
|                 queryset = queryset.exclude(minimum_stock=0) | ||||
|                 # Filter items which have an 'in_stock' level lower than 'minimum_stock' | ||||
|                 queryset = queryset.filter(Q(in_stock__lt=F('minimum_stock'))) | ||||
|             else: | ||||
|                 # Filter items which have an 'in_stock' level higher than 'minimum_stock' | ||||
|                 queryset = queryset.filter(Q(in_stock__gte=F('minimum_stock'))) | ||||
|  | ||||
|         # Filer by 'depleted_stock' status -> has no stock and stock items | ||||
|         depleted_stock = params.get('depleted_stock', None) | ||||
|  | ||||
| @@ -722,14 +777,7 @@ class PartList(generics.ListCreateAPIView): | ||||
|     ] | ||||
|  | ||||
|     filter_fields = [ | ||||
|         'is_template', | ||||
|         'variant_of', | ||||
|         'assembly', | ||||
|         'component', | ||||
|         'trackable', | ||||
|         'purchaseable', | ||||
|         'salable', | ||||
|         'active', | ||||
|     ] | ||||
|  | ||||
|     ordering_fields = [ | ||||
|   | ||||
							
								
								
									
										19
									
								
								InvenTree/part/migrations/0070_alter_part_variant_of.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								InvenTree/part/migrations/0070_alter_part_variant_of.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| # Generated by Django 3.2.4 on 2021-07-08 07:02 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('part', '0069_auto_20210701_0509'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='part', | ||||
|             name='variant_of', | ||||
|             field=models.ForeignKey(blank=True, help_text='Is this part a variant of another part?', limit_choices_to={'is_template': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='variants', to='part.part', verbose_name='Variant Of'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -692,7 +692,6 @@ class Part(MPTTModel): | ||||
|         null=True, blank=True, | ||||
|         limit_choices_to={ | ||||
|             'is_template': True, | ||||
|             'active': True, | ||||
|         }, | ||||
|         on_delete=models.SET_NULL, | ||||
|         help_text=_('Is this part a variant of another part?'), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user