Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Commit

Permalink
chore(filters): added type annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
c0rydoras committed Apr 29, 2024
1 parent 85a9cf9 commit 962a674
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 30 deletions.
29 changes: 24 additions & 5 deletions timed/employment/filters.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from django.db.models import Q
from django_filters.constants import EMPTY_VALUES
from django_filters.rest_framework import DateFilter, Filter, FilterSet, NumberFilter

from timed.employment import models
from timed.employment.models import User

if TYPE_CHECKING:
from typing import TypeVar

from django.db.models import QuerySet

T = TypeVar("T", QuerySet)


class YearFilter(Filter):
"""Filter to filter a queryset by year."""

def filter(self, qs, value):
def filter(self, qs: T, value: int) -> T:
if value in EMPTY_VALUES:
return qs

Expand Down Expand Up @@ -54,15 +65,21 @@ class UserFilterSet(FilterSet):
is_accountant = NumberFilter(field_name="is_accountant")
is_external = NumberFilter(method="filter_is_external")

def filter_is_external(self, queryset, _name, value):
def filter_is_external(
self, queryset: QuerySet[models.User], _name: str, value: int
) -> QuerySet[models.User]:
return queryset.filter(employments__is_external=value)

def filter_is_reviewer(self, queryset, _name, value):
def filter_is_reviewer(
self, queryset: QuerySet[models.User], _name: str, value: int
) -> QuerySet[models.User]:
if value:
return queryset.filter(pk__in=User.objects.all_reviewers())
return queryset.exclude(pk__in=User.objects.all_reviewers())

def filter_is_supervisor(self, queryset, _name, value):
def filter_is_supervisor(
self, queryset: QuerySet[models.User], _name: str, value: int
) -> QuerySet[models.User]:
if value:
return queryset.filter(pk__in=User.objects.all_supervisors())
return queryset.exclude(pk__in=User.objects.all_supervisors())
Expand All @@ -81,7 +98,9 @@ class Meta:
class EmploymentFilterSet(FilterSet):
date = DateFilter(method="filter_date")

def filter_date(self, queryset, _name, value):
def filter_date(
self, queryset: QuerySet[models.Employment], _name: str, value: int
) -> QuerySet[models.Employment]:
return queryset.filter(
Q(start_date__lte=value)
& Q(Q(end_date__gte=value) | Q(end_date__isnull=True))
Expand Down
23 changes: 15 additions & 8 deletions timed/projects/filters.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""Filters for filtering the data of the projects app endpoints."""

from __future__ import annotations

from datetime import date, timedelta
from typing import TYPE_CHECKING

from django.db.models import Count, Q
from django_filters.constants import EMPTY_VALUES
from django_filters.rest_framework import BaseInFilter, Filter, FilterSet, NumberFilter

from timed.projects import models

if TYPE_CHECKING:
from django.db.models import QuerySet


class NumberInFilter(BaseInFilter, NumberFilter):
pass
Expand Down Expand Up @@ -36,7 +42,9 @@ class ProjectFilterSet(FilterSet):
has_reviewer = NumberFilter(method="filter_has_reviewer")
customer = NumberInFilter(field_name="customer")

def filter_has_manager(self, queryset, _name, value):
def filter_has_manager(
self, queryset: QuerySet[models.Project], _name: str, value: int
) -> QuerySet[models.Project]:
if not value: # pragma: no cover
return queryset
return queryset.filter(
Expand All @@ -52,7 +60,9 @@ def filter_has_manager(self, queryset, _name, value):
)
)

def filter_has_reviewer(self, queryset, _name, value):
def filter_has_reviewer(
self, queryset: QuerySet[models.Project], _name: str, value: int
) -> QuerySet[models.Project]:
if not value: # pragma: no cover
return queryset
return queryset.filter(
Expand Down Expand Up @@ -88,17 +98,14 @@ class MyMostFrequentTaskFilter(Filter):
# would be more desirable to assign an ordering field frecency and to
# limit by use paging. This is way harder to implement therefore on hold.

def filter(self, qs, value):
def filter(
self, qs: QuerySet[models.Task], value: int | str | tuple | list | None
) -> QuerySet[models.Task]:
"""Filter for given most frequently used tasks.
Most frequently used tasks are only counted within last
few months as older tasks are not relevant anymore
for today's usage.
:param QuerySet qs: The queryset to filter
:param int value: number of most frequent items
:return: The filtered queryset
:rtype: QuerySet
"""
if value in EMPTY_VALUES:
return qs
Expand Down
23 changes: 19 additions & 4 deletions timed/reports/filters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from django.db.models import DurationField, F, Q, Sum, Value
from django.db.models.functions import Coalesce
from django_filters.rest_framework import (
Expand All @@ -9,9 +13,16 @@

from timed.projects.models import CustomerAssignee, ProjectAssignee, TaskAssignee

if TYPE_CHECKING:
from django.db.models import QuerySet

from timed.tracking.models import Report


class StatisticFiltersetBase:
def filter_has_reviewer(self, queryset, _name, value):
def filter_has_reviewer(
self, queryset: QuerySet[Report], _name: str, value: int
) -> QuerySet[Report]:
if not value: # pragma: no cover
return queryset

Expand All @@ -37,7 +48,9 @@ def filter_has_reviewer(self, queryset, _name, value):
)
return queryset.filter_aggregate(the_filter).filter_base(the_filter)

def filter_cost_center(self, queryset, _name, value):
def filter_cost_center(
self, queryset: QuerySet[Report], _name: str, value: int
) -> QuerySet[Report]:
"""Filter report by cost center.
The filter behaves slightly different depending on what the
Expand Down Expand Up @@ -73,7 +86,7 @@ def filter_cost_center(self, queryset, _name, value):
# Project or task: Filter both to get the correct result
return queryset.filter_base(filter_q).filter_aggregate(filter_q)

def filter_queryset(self, queryset):
def filter_queryset(self, queryset: QuerySet[Report]) -> QuerySet[Report]:
qs = super().filter_queryset(queryset)

duration_ref = self._refs["reports_ref"] + "__duration"
Expand All @@ -88,7 +101,9 @@ def filter_queryset(self, queryset):
return full_qs.values()


def statistic_filterset_builder(name, reports_ref, project_ref, customer_ref, task_ref):
def statistic_filterset_builder(
name: str, reports_ref: str, project_ref: str, customer_ref: str, task_ref: str
) -> type[StatisticFiltersetBase, FilterSet]:
reports_prefix = f"{reports_ref}__" if reports_ref else ""
project_prefix = f"{project_ref}__" if project_ref else ""
customer_prefix = f"{customer_ref}__" if customer_ref else ""
Expand Down
11 changes: 10 additions & 1 deletion timed/subscription/filters.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from django_filters import FilterSet, NumberFilter

from timed.projects.models import Project

from . import models

if TYPE_CHECKING:
from django.db.models import QuerySet


class PackageFilter(FilterSet):
customer = NumberFilter(method="filter_customer")

def filter_customer(self, queryset, _name, value):
def filter_customer(
self, queryset: QuerySet[models.Package], _name: str, value: int
) -> QuerySet[models.Package]:
billing_types = Project.objects.filter(customer=value).values("billing_type")
return queryset.filter(billing_type__in=billing_types)

Expand Down
39 changes: 27 additions & 12 deletions timed/tracking/filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Filters for filtering the data of the tracking app endpoints."""

from __future__ import annotations

from functools import wraps
from typing import TYPE_CHECKING

from django.db.models import Q
from django_filters.constants import EMPTY_VALUES
Expand All @@ -16,8 +19,16 @@
from timed.projects.models import CustomerAssignee, ProjectAssignee, TaskAssignee
from timed.tracking import models

if TYPE_CHECKING:
from typing import Callable, TypeVar

from django.db.models import QuerySet

T = TypeVar("T") # used for self
G = TypeVar("G", QuerySet) # used for qs

def boolean_filter(func):

def boolean_filter(func: Callable[[T, G, str], G]) -> Callable[[T, G, bool], G]:
"""Cast the passed query parameter into a boolean.
:param function func: The function to decorate
Expand Down Expand Up @@ -45,14 +56,12 @@ class ActivityActiveFilter(Filter):
"""

@boolean_filter
def filter(self, qs, _value):
"""Filter the queryset.
:param QuerySet qs: The queryset to filter
:param bool value: Whether the activities should be active
:return: The filtered queryset
:rtype: QuerySet
"""
def filter(
self,
qs: QuerySet[models.Activity],
_value: bool, # noqa: FBT001
) -> QuerySet[models.Activity]:
"""Filter the queryset."""
return qs.filter(to_time__exact=None).distinct()


Expand Down Expand Up @@ -104,7 +113,9 @@ class ReportFilterSet(FilterSet):
cost_center = NumberFilter(method="filter_cost_center")
rejected = NumberFilter(field_name="rejected")

def filter_has_reviewer(self, queryset, _name, value):
def filter_has_reviewer(
self, queryset: QuerySet[models.Report], _name: str, value: int
) -> QuerySet[models.Report]:
if not value: # pragma: no cover
return queryset

Expand Down Expand Up @@ -158,7 +169,9 @@ def filter_has_reviewer(self, queryset, _name, value):
| reports_task_assignee_is_reviewer
)

def filter_editable(self, queryset, _name, value):
def filter_editable(
self, queryset: QuerySet[models.Report], _name: str, value: int
) -> QuerySet[models.Report]:
"""Filter reports whether they are editable by current user.
When set to `1` filter all results to what is editable by current
Expand Down Expand Up @@ -203,7 +216,9 @@ def filter_editable(self, queryset, _name, value):

return queryset.exclude(editable_filter)

def filter_cost_center(self, queryset, _name, value):
def filter_cost_center(
self, queryset: QuerySet[models.Report], _name: str, value: int
) -> QuerySet[models.Report]:
"""Filter report by cost center.
Cost center on task has higher priority over project cost
Expand Down

0 comments on commit 962a674

Please sign in to comment.