Skip to content

Commit

Permalink
Add date range query to cyberstorm API
Browse files Browse the repository at this point in the history
/api/cyberstorm/listing/{community-id}/ now supports the query parameters updated_before, updated_after, created_before, and created_after.
  • Loading branch information
x753 authored and Oksamies committed Jun 19, 2024
1 parent 4c9d27c commit e5ae167
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 2 deletions.
123 changes: 123 additions & 0 deletions django/thunderstore/api/cyberstorm/tests/test_package_listing_list.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -609,6 +610,128 @@ def test_listing_by_community_view__when_package_listed_in_multiple_communities_
)


@pytest.mark.django_db
def test_listing_by_community_view__returns_created_within_specified_date_range(
api_client: APIClient,
community: Community,
) -> None:
PackageListingFactory(
community_=community,
package_kwargs={"date_created": "2024-01-01 01:23:45Z"},
)
day7 = PackageListingFactory(
community_=community,
package_kwargs={"date_created": "2024-01-07 00:00:01Z"},
)
day14 = PackageListingFactory(
community_=community,
package_kwargs={"date_created": "2024-01-14 23:59:59Z"},
)

response = api_client.get(
f"/api/cyberstorm/listing/{community.identifier}/?created_before=2024-01-14&created_after=2024-01-07",
)
result1 = response.json()

assert result1["count"] == 2
assert any(item["name"] == day7.package.name for item in result1["results"])
assert any(item["name"] == day14.package.name for item in result1["results"])

response = api_client.get(
f"/api/cyberstorm/listing/{community.identifier}/?created_after=2024-01-07",
)
result2 = response.json()

assert result2["count"] == 2
assert any(item["name"] == day7.package.name for item in result2["results"])
assert any(item["name"] == day14.package.name for item in result2["results"])

response = api_client.get(
f"/api/cyberstorm/listing/{community.identifier}/?created_before=2024-01-06",
)
result3 = response.json()

assert result3["count"] == 1


@pytest.mark.django_db
def test_listing_by_community_view__returns_updated_within_specified_date_range(
api_client: APIClient,
community: Community,
) -> None:

p1v1 = PackageVersionFactory(
version_number="1.0.0",
)
p1v1.date_created = datetime(2024, 1, 1)
p1v1.save()
p1v2 = PackageVersionFactory(
version_number="2.0.0",
package=p1v1.package,
)
p1v2.date_created = datetime(2024, 1, 7)
p1v2.save()
p1v3 = PackageVersionFactory(
version_number="3.0.0",
package=p1v1.package,
)
p1v3.date_created = datetime(2024, 1, 14)
p1v3.save()

p2v1 = PackageVersionFactory(
version_number="1.0.0",
)
p2v1.date_created = datetime(2024, 1, 3)
p2v1.save()
p2v2 = PackageVersionFactory(
version_number="2.0.0",
package=p2v1.package,
)
p2v2.date_created = datetime(2024, 1, 14)
p2v2.save()

p3v1 = PackageVersionFactory(
version_number="1.0.0",
)
p3v1.date_created = datetime(2024, 2, 1)
p3v1.save()

PackageListingFactory(
community_=community,
package=p1v1.package,
)
PackageListingFactory(
community_=community,
package=p2v1.package,
)
PackageListingFactory(
community_=community,
package=p3v1.package,
)

response = api_client.get(
f"/api/cyberstorm/listing/{community.identifier}/?updated_before=2024-01-10&updated_after=2024-01-05",
)
result = response.json()

assert result["count"] == 1
assert p1v2.package.name == result["results"][0]["name"]

response = api_client.get(
f"/api/cyberstorm/listing/{community.identifier}/?updated_before=2024-01-02",
)
result = response.json()

assert result["count"] == 1

response = api_client.get(
f"/api/cyberstorm/listing/{community.identifier}/?updated_after=2024-01-05",
)
result = response.json()

assert result["count"] == 3


@pytest.mark.django_db
def test_listing_by_community_view__does_not_return_rejected_packages(
api_client: APIClient,
Expand Down
97 changes: 95 additions & 2 deletions django/thunderstore/api/cyberstorm/views/package_listing_list.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from copy import deepcopy
from datetime import datetime, time
from typing import List, Optional, OrderedDict, Tuple
from urllib.parse import urlencode

from django.conf import settings
from django.core.paginator import Page
from django.db.models import Count, OuterRef, Q, QuerySet, Subquery, Sum
from django.db.models import Count, Exists, OuterRef, Q, QuerySet, Subquery, Sum
from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from rest_framework import serializers
from rest_framework.generics import ListAPIView, get_object_or_404
Expand All @@ -19,7 +21,12 @@
PackageListing,
PackageListingSection,
)
from thunderstore.repository.models import Namespace, Package, get_package_dependants
from thunderstore.repository.models import (
Namespace,
Package,
PackageVersion,
get_package_dependants,
)

# Keys are values expected in requests, values are args for .order_by().
ORDER_ARGS = {
Expand Down Expand Up @@ -52,6 +59,10 @@ class PackageListRequestSerializer(serializers.Serializer):
page = serializers.IntegerField(default=1, min_value=1)
q = serializers.CharField(required=False, help_text="Free text search")
section = serializers.UUIDField(required=False)
created_after = serializers.DateField(required=False)
created_before = serializers.DateField(required=False)
updated_after = serializers.DateField(required=False)
updated_before = serializers.DateField(required=False)


class PackageListResponseSerializer(serializers.Serializer):
Expand Down Expand Up @@ -151,6 +162,20 @@ def filter_queryset(self, queryset: QuerySet[Package]) -> QuerySet[Package]:
qs = filter_not_in_categories(params["excluded_categories"], qs)
qs = filter_by_section(params.get("section"), qs)
qs = filter_by_query(params.get("q"), qs)
if any(
[
(created_after := params.get("created_after")),
(created_before := params.get("created_before")),
],
):
qs = filter_by_creation_date(created_after, created_before, qs)
if any(
[
(updated_after := params.get("updated_after")),
(updated_before := params.get("updated_before")),
],
):
qs = filter_by_update_date(updated_after, updated_before, qs)

return qs.order_by(
"-is_pinned",
Expand Down Expand Up @@ -463,6 +488,74 @@ def filter_by_query(
return queryset.exclude(icontains_query).distinct()


def filter_by_creation_date(
after: Optional[datetime],
before: Optional[datetime],
queryset: QuerySet[Package],
) -> QuerySet[Package]:
"""
Filter out packages that have not been created in the given date constraints
"""

q = Q()

# From 2024-01-01 to 2024-01-02 should include all packages uploaded on 2024-01-02
if after:
q.add(
Q(date_created__gte=datetime.combine(after, time.min, tzinfo=timezone.utc)),
Q.AND,
)
if before:
q.add(
Q(
date_created__lte=datetime.combine(
before,
time.max,
tzinfo=timezone.utc,
),
),
Q.AND,
)
return queryset.filter(q).distinct()


def filter_by_update_date(
after: Optional[datetime],
before: Optional[datetime],
queryset: QuerySet[Package],
) -> QuerySet[Package]:
"""
Filter out packages that have not been updated in the given date constraints
"""

q = Q(package=OuterRef("pk"))

# From 2024-01-01 to 2024-01-02 should include all packages uploaded on 2024-01-02
if after:
q.add(
Q(date_created__gte=datetime.combine(after, time.min, tzinfo=timezone.utc)),
Q.AND,
)
if before:
q.add(
Q(
date_created__lte=datetime.combine(
before,
time.max,
tzinfo=timezone.utc,
),
),
Q.AND,
)

versions = PackageVersion.objects.filter(q)
return (
queryset.annotate(has_matching_versions=Exists(versions))
.filter(has_matching_versions=True)
.distinct()
)


def filter_by_review_status(
require_approval: bool,
queryset: QuerySet[Package],
Expand Down

0 comments on commit e5ae167

Please sign in to comment.