Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New orderby filters #1021

Merged
merged 4 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 29 additions & 16 deletions api/graphql/extensions/order_filter.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
from typing import Any
from typing import Protocol

import django_filters
from django.db.models import QuerySet
from django_filters.constants import EMPTY_VALUES


class CustomOrderingFilter(django_filters.OrderingFilter):
"""Ordering filter for handling custom 'order_by' filters."""
class OrderingFunc(Protocol):
def __call__(self, qs: QuerySet, *, desc: bool) -> QuerySet:
"""Custom ordering function."""


def __init__(self, *args: Any, **kwargs: Any) -> None:
self.custom_fields: dict[str, str] = self.normalize_fields(kwargs.pop("custom_fields", {}))
super().__init__(*args, **kwargs)
self.extra["choices"] += self.build_choices(self.custom_fields, {})
class CustomOrderingFilter(django_filters.OrderingFilter):
"""
Ordering filter for handling custom orderings by defining `order_by_{name}`
functions on its subclasses or filtersets that include the filter.
"""

def filter(self, qs: QuerySet, value: list[str]) -> QuerySet:
if value in EMPTY_VALUES:
return qs

for item in value.copy():
desc: bool = False
if item.startswith("-"):
item = item.removeprefix("-")
desc = True
ordering: list[str] = []
for param in value:
if param in EMPTY_VALUES:
continue

func_name = f"order_by_{param.removeprefix('-')}"

# Try to find an `ordering_func` on the `OrderingFilter` class or its `FilterSet` class.
ordering_func: OrderingFunc | None = getattr(self, func_name, None)
if ordering_func is None and hasattr(self, "parent"):
ordering_func = getattr(self.parent, func_name, None)

if item not in self.custom_fields:
# If no `ordering_func` was found, just order by the given field name.
if ordering_func is None or not callable(ordering_func):
ordering.append(self.get_ordering_value(param))
continue

value.remove(f"-{item}" if desc else item)
qs = getattr(self, f"order_by_{item}")(qs, desc=desc)
qs = ordering_func(qs, desc=param.startswith("-"))
# Save the order_by value wince the last `qs.order_by(*ordering)`
# will clear all ordering when set called.
ordering.extend(qs.query.order_by)

return super().filter(qs, value)
return qs.order_by(*ordering)
38 changes: 35 additions & 3 deletions api/graphql/types/application/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from django.contrib.auth import get_user_model
from django.contrib.postgres.search import SearchVector
from django.db import models
from django.db.models import QuerySet

from api.graphql.extensions.order_filter import CustomOrderingFilter
from applications.choices import ApplicantTypeChoice, ApplicationStatusChoice
from applications.models import Application
from applications.querysets.application import ApplicationQuerySet
Expand All @@ -22,13 +24,23 @@ class ApplicationFilterSet(BaseModelFilterSet):

text_search = django_filters.CharFilter(method="filter_by_text_search")

order_by = django_filters.OrderingFilter(fields=["pk", "applicant"])
order_by = CustomOrderingFilter(
fields=[
"pk",
"applicant",
"applicant_type",
"preferred_unit_name_fi",
"preferred_unit_name_en",
"preferred_unit_name_sv",
"application_status",
]
)

class Meta:
model = Application
fields = []

def filter_queryset(self, queryset: ApplicationQuerySet) -> ApplicationQuerySet:
def filter_queryset(self, queryset: ApplicationQuerySet) -> QuerySet:
return super().filter_queryset(queryset.with_applicant_alias())

@staticmethod
Expand All @@ -40,5 +52,25 @@ def filter_by_text_search(qs: ApplicationQuerySet, name: str, value: str) -> mod
return qs.annotate(search=vector).filter(search=query)

@staticmethod
def filter_by_status(qs: ApplicationQuerySet, name: str, value: list[str]) -> ApplicationQuerySet:
def filter_by_status(qs: ApplicationQuerySet, name: str, value: list[str]) -> QuerySet:
return qs.has_status_in(value)

@staticmethod
def order_by_applicant_type(qs: ApplicationQuerySet, desc: bool) -> QuerySet:
return qs.order_by_applicant_type(desc)

@staticmethod
def order_by_preferred_unit_name_fi(qs: ApplicationQuerySet, desc: bool) -> QuerySet:
return qs.order_by_preferred_unit_name(lang="fi", desc=desc)

@staticmethod
def order_by_preferred_unit_name_en(qs: ApplicationQuerySet, desc: bool) -> QuerySet:
return qs.order_by_preferred_unit_name(lang="en", desc=desc)

@staticmethod
def order_by_preferred_unit_name_sv(qs: ApplicationQuerySet, desc: bool) -> QuerySet:
return qs.order_by_preferred_unit_name(lang="sv", desc=desc)

@staticmethod
def order_by_application_status(qs: ApplicationQuerySet, desc: bool) -> QuerySet:
return qs.order_by_application_status(desc=desc)
37 changes: 36 additions & 1 deletion api/graphql/types/application_event/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.db import models
from django.db.models import QuerySet

from api.graphql.extensions.order_filter import CustomOrderingFilter
from applications.choices import ApplicantTypeChoice, ApplicationEventStatusChoice, ApplicationStatusChoice
from applications.models import ApplicationEvent
from applications.querysets.application_event import ApplicationEventQuerySet
Expand Down Expand Up @@ -43,7 +44,21 @@ class ApplicationEventFilterSet(BaseModelFilterSet):

text_search = django_filters.CharFilter(method="filter_text_search")

order_by = django_filters.OrderingFilter(fields=["pk", "applicant", "name_fi", "name_en", "name_sv"])
order_by = CustomOrderingFilter(
fields=[
"pk",
("application__id", "application_id"),
"applicant",
"name_fi",
"name_en",
"name_sv",
"status",
"application_status",
"preferred_unit_name_fi",
"preferred_unit_name_en",
"preferred_unit_name_sv",
],
)

class Meta:
model = ApplicationEvent
Expand Down Expand Up @@ -87,3 +102,23 @@ def filter_text_search(qs: ApplicationEventQuerySet, name: str, value: str) -> Q
vector = SearchVector("application__id", "id", "name", "applicant")
query = raw_prefixed_query(value)
return qs.annotate(search=vector).filter(search=query)

@staticmethod
def order_by_status(qs: ApplicationEventQuerySet, desc: bool) -> QuerySet:
return qs.order_by_application_event_status(desc=desc)

@staticmethod
def order_by_application_status(qs: ApplicationEventQuerySet, desc: bool) -> QuerySet:
return qs.order_by_application_status(desc=desc)

@staticmethod
def order_by_preferred_unit_name_fi(qs: ApplicationEventQuerySet, desc: bool) -> QuerySet:
return qs.order_by_preferred_unit_name(lang="fi", desc=desc)

@staticmethod
def order_by_preferred_unit_name_en(qs: ApplicationEventQuerySet, desc: bool) -> QuerySet:
return qs.order_by_preferred_unit_name(lang="en", desc=desc)

@staticmethod
def order_by_preferred_unit_name_sv(qs: ApplicationEventQuerySet, desc: bool) -> QuerySet:
return qs.order_by_preferred_unit_name(lang="sv", desc=desc)
12 changes: 10 additions & 2 deletions api/graphql/types/application_event/types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

import graphene
from django.db import models
from graphene_permissions.permissions import AllowAuthenticated
Expand All @@ -9,7 +11,7 @@
from api.graphql.types.application_event_schedule.types import ApplicationEventScheduleNode
from applications.choices import ApplicationEventStatusChoice
from applications.models import ApplicationEvent, EventReservationUnit
from common.typing import AnyUser
from common.typing import AnyUser, GQLInfo
from permissions.helpers import get_service_sectors_where_can_view_applications, get_units_where_can_view_applications


Expand All @@ -31,7 +33,7 @@ class ApplicationEventNode(DjangoAuthNode):
max_duration = Duration()

application_event_schedules = ApplicationEventScheduleNode.ListField()
event_reservation_units = EventReservationUnitNode.ListField()
event_reservation_units = EventReservationUnitNode.ListField(preferred_order=graphene.Int())

class Meta:
model = ApplicationEvent
Expand Down Expand Up @@ -68,3 +70,9 @@ def filter_queryset(cls, queryset: models.QuerySet, user: AnyUser) -> models.Que
| models.Q(event_reservation_units__reservation_unit__unit__in=units)
| models.Q(application__user=user)
).distinct()

def resolve_event_reservation_units(root: ApplicationEvent, info: GQLInfo, **kwargs: Any) -> models.QuerySet:
preferred_order: int | None = kwargs.get("preferred_order")
if preferred_order is not None:
return root.event_reservation_units.filter(preferred_order=preferred_order)
return root.event_reservation_units.all()
28 changes: 27 additions & 1 deletion api/graphql/types/application_event_schedule/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.contrib.postgres.search import SearchVector
from django.db.models import QuerySet

from api.graphql.extensions.order_filter import CustomOrderingFilter
from applications.choices import ApplicantTypeChoice, ApplicationEventStatusChoice
from applications.models import ApplicationEventSchedule
from applications.querysets.application_event_schedule import ApplicationEventScheduleQuerySet
Expand Down Expand Up @@ -33,11 +34,24 @@ class ApplicationEventScheduleFilterSet(BaseModelFilterSet):

text_search = django_filters.CharFilter(method="filter_text_search")

order_by = django_filters.OrderingFilter(
order_by = CustomOrderingFilter(
fields=[
"pk",
("application_event__id", "application_event_id"),
("application_event__application__id", "application_id"),
"applicant",
("application_event__name_fi", "application_event_name_fi"),
("application_event__name_en", "application_event_name_en"),
("application_event__name_sv", "application_event_name_sv"),
("allocated_reservation_unit__unit__name_fi", "allocated_unit_name_fi"),
matti-lamppu marked this conversation as resolved.
Show resolved Hide resolved
("allocated_reservation_unit__unit__name_en", "allocated_unit_name_en"),
("allocated_reservation_unit__unit__name_sv", "allocated_unit_name_sv"),
("allocated_reservation_unit__name_fi", "allocated_reservation_unit_name_fi"),
("allocated_reservation_unit__name_en", "allocated_reservation_unit_name_en"),
("allocated_reservation_unit__name_sv", "allocated_reservation_unit_name_sv"),
"allocated_time_of_week",
"application_status",
"application_event_status",
]
)

Expand All @@ -64,3 +78,15 @@ def filter_text_search(qs: ApplicationEventScheduleQuerySet, name: str, value: s
)
query = raw_prefixed_query(value)
return qs.annotate(search=vector).filter(search=query)

@staticmethod
def order_by_allocated_time_of_week(qs: ApplicationEventScheduleQuerySet, desc: bool) -> QuerySet:
return qs.order_by_allocated_time_of_week(desc=desc)

@staticmethod
def order_by_application_status(qs: ApplicationEventScheduleQuerySet, desc: bool) -> QuerySet:
return qs.order_by_application_status(desc=desc)

@staticmethod
def order_by_application_event_status(qs: ApplicationEventScheduleQuerySet, desc: bool) -> QuerySet:
return qs.order_by_application_event_status(desc=desc)
35 changes: 16 additions & 19 deletions api/graphql/types/banner_notification/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,20 @@
from common.querysets.banner_notification import BannerNotificationQuerySet


class BannerNotificationOrderingFilter(CustomOrderingFilter):
@staticmethod
def order_by_level(qs: BannerNotificationQuerySet, desc: bool) -> BannerNotificationQuerySet:
return qs.order_by_level(desc)

@staticmethod
def order_by_target(qs: BannerNotificationQuerySet, desc: bool) -> BannerNotificationQuerySet:
return qs.order_by_target(desc)

@staticmethod
def order_by_state(qs: BannerNotificationQuerySet, desc: bool) -> BannerNotificationQuerySet:
return qs.order_by_state(desc)


class BannerNotificationFilterSet(django_filters.FilterSet):
is_active = django_filters.BooleanFilter(method="filter_active")
is_visible = django_filters.BooleanFilter(method="filter_visible")
order_by = BannerNotificationOrderingFilter(
fields=(

order_by = CustomOrderingFilter(
fields=[
"pk",
"name",
("active_from", "starts"),
("active_until", "ends"),
),
custom_fields=(
"level",
"state",
"target",
),
],
)

class Meta:
Expand All @@ -58,3 +43,15 @@ def filter_visible(
value: bool,
) -> BannerNotificationQuerySet:
return qs.visible(self.request.user) if value else qs.hidden(self.request.user)

@staticmethod
def order_by_level(qs: BannerNotificationQuerySet, desc: bool) -> BannerNotificationQuerySet:
return qs.order_by_level(desc)

@staticmethod
def order_by_target(qs: BannerNotificationQuerySet, desc: bool) -> BannerNotificationQuerySet:
return qs.order_by_target(desc)

@staticmethod
def order_by_state(qs: BannerNotificationQuerySet, desc: bool) -> BannerNotificationQuerySet:
return qs.order_by_state(desc)
Loading