diff --git a/packagedb/api.py b/packagedb/api.py index 5cb3f30f..783f8dc0 100644 --- a/packagedb/api.py +++ b/packagedb/api.py @@ -300,6 +300,20 @@ def filter(self, qs, value): return qs.distinct() if self.distinct else qs +PACKAGE_FILTER_SORT_FIELDS = [ + 'type', + 'namespace', + 'name', + 'version', + 'qualifiers', + 'subpath', + 'download_url', + 'filename', + 'size', + 'release_date', +] + + class PackageFilterSet(FilterSet): type = django_filters.CharFilter( lookup_expr='iexact', @@ -331,18 +345,7 @@ class PackageFilterSet(FilterSet): lookup_expr='icontains', ) - sort = OrderingFilter(fields=[ - 'type', - 'namespace', - 'name', - 'version', - 'qualifiers', - 'subpath', - 'download_url', - 'filename', - 'size', - 'release_date' - ]) + sort = OrderingFilter(fields=PACKAGE_FILTER_SORT_FIELDS) class Meta: model = Package @@ -828,7 +831,6 @@ def list(self, request, format=None): if addon_pipelines := validated_data.get('addon_pipelines', []): kwargs["addon_pipelines"] = addon_pipelines - lookups = purl_to_lookups(purl) packages = Package.objects.filter(**lookups) if packages.count() == 0: try: @@ -840,7 +842,8 @@ def list(self, request, format=None): return Response(message, status=status.HTTP_400_BAD_REQUEST) lookups = purl_to_lookups(purl) - packages = Package.objects.filter(**lookups).order_by('-version') + sort = validated_data.get('sort', []) + packages = Package.objects.filter(**lookups).order_by(*sort) if packages.count() == 0: message = {} if errors: diff --git a/packagedb/serializers.py b/packagedb/serializers.py index 1d5cf6f3..5374055b 100644 --- a/packagedb/serializers.py +++ b/packagedb/serializers.py @@ -7,14 +7,8 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -from django.http import HttpRequest from django.urls import reverse_lazy -from packagedb.models import DependentPackage -from packagedb.models import Package -from packagedb.models import PackageSet -from packagedb.models import PackageWatch -from packagedb.models import Party -from packagedb.models import Resource + from packageurl import PackageURL from rest_framework.exceptions import ValidationError from rest_framework.serializers import BooleanField @@ -29,6 +23,13 @@ from rest_framework.serializers import Serializer from rest_framework.serializers import SerializerMethodField +from packagedb.models import DependentPackage +from packagedb.models import Package +from packagedb.models import PackageSet +from packagedb.models import PackageWatch +from packagedb.models import Party +from packagedb.models import Resource + class ResourceAPISerializer(HyperlinkedModelSerializer): package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) @@ -373,27 +374,42 @@ class Meta: fields = ['depth', 'watch_interval', 'is_active'] +class CommaListField(ListField): + """ListField that allows also a str of comma-separated values as value.""" + + def to_internal_value(self, data): + if isinstance(data, str): + split_data = [] + for datum in data: + split_data.extend(datum.split(',')) + data = split_data + return super().to_internal_value(data) + + class CollectPackageSerializer(Serializer): purl = CharField(help_text="PackageURL strings in canonical form.") source_purl = CharField( - required=False, + required=False, help_text="Source PackageURL.", - ) - + ) addon_pipelines = ListField( child = CharField(), required=False, allow_empty=True, help_text="Addon pipelines to run on the package.", - ) - + ) + sort = CommaListField( + required=False, + help_text="Fields to sort Package results by.", + ) + def validate_purl(self, value): try: PackageURL.from_string(value) except ValueError as e: raise ValidationError(f'purl validation error: {e}') return value - + def validate_source_purl(self, value): if value: try: @@ -409,6 +425,12 @@ def validate_addon_pipelines(self, value): return value + def validate_sort(self, value): + invalid_sort_fields = [field for field in value if not is_supported_sort_field(field)] + if invalid_sort_fields: + raise ValidationError(f'Error unsupported sort fields: {",".join(invalid_sort_fields)}') + + return value class PackageVersSerializer(Serializer): purl = CharField() @@ -502,3 +524,8 @@ class PurltoGitRepoResponseSerializer(Serializer): def is_supported_addon_pipeline(addon_pipeline): from minecode.model_utils import SUPPORTED_ADDON_PIPELINES return addon_pipeline in SUPPORTED_ADDON_PIPELINES + + +def is_supported_sort_field(field): + from packagedb.api import PACKAGE_FILTER_SORT_FIELDS + return field in PACKAGE_FILTER_SORT_FIELDS