Skip to content

Commit

Permalink
add generic types to DatePaginator (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuadavidthomas authored Jul 31, 2024
1 parent 5605dc8 commit f06c6db
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
### Added

- Added a `{% startswith %}` utility templatetag filter.
- Added a generic type argument to `DatePaginator`.

### Changed

- `date_page_range` is now explicitly a required argument to `DatePaginator`. Previously, it was allowed to be `None`, but would still fail if `date_range` was not passed in instead. `date_range` has been removed as an argument, see below.
- Types within `DatePaginator` have been adjusted.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion src/django_twc_toolbox/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from django.conf import settings

from django_twc_toolbox._types import override
from ._types import override

DJANGO_TWC_TOOLBOX_SETTINGS_NAME = "DJANGO_TWC_TOOLBOX"

Expand Down
57 changes: 34 additions & 23 deletions src/django_twc_toolbox/paginator.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
# pyright: reportAny=false,reportPrivateUsage=false
from __future__ import annotations

import datetime
import warnings
from typing import TYPE_CHECKING
from typing import Generic
from typing import TypeVar
from typing import cast

from django.core.paginator import Page
from django.core.paginator import Paginator
from django.db.models.query import QuerySet
from django.utils.functional import cached_property

from ._types import override

if TYPE_CHECKING:
from typing import Any

from django.core.paginator import _SupportsPagination


class DatePaginator(Paginator):
_T = TypeVar("_T")


class DatePaginator(Generic[_T], Paginator[_T]):
def __init__(
self,
object_list: _SupportsPagination,
object_list: _SupportsPagination[_T],
date_field: str,
page_date_range: datetime.timedelta,
**kwargs: Any,
Expand All @@ -28,8 +37,7 @@ def __init__(

if kwargs.get("orphans", None):
warnings.warn(
"The `orphans` parameter is not applicable for DatePaginator and "
"will be ignored.",
"The `orphans` parameter is not applicable for DatePaginator and will be ignored.",
UserWarning,
stacklevel=2,
)
Expand All @@ -41,27 +49,29 @@ def __init__(
)

@cached_property
def date_segments(self) -> list[tuple[datetime.datetime, datetime.datetime]]:
def date_segments(self) -> list[tuple[datetime.date, datetime.date]]:
# Check if the object_list is empty.
# Check for `exists()` first for performance, falling back in case it's
# not a QuerySet.
if isinstance(self.object_list, QuerySet): # pyright: ignore[reportUnknownMemberType]
if not self.object_list.exists():
return []
exists = True
# Check for `exists()` first for performance, falling back in case it's not a QuerySet.
if hasattr(self.object_list, "exists"):
exists = self.object_list.exists()
elif not self.object_list:
exists = False

if not exists:
return []

if isinstance(self.object_list, QuerySet): # pyright: ignore[reportUnknownMemberType]
if isinstance(self.object_list, QuerySet):
first_obj = self.object_list.first()
last_obj = self.object_list.last()
else:
first_obj = self.object_list[0]
last_obj = self.object_list[-1]

first_date = getattr(first_obj, self.date_field)
last_date = getattr(last_obj, self.date_field)
first_date = cast(datetime.date, getattr(first_obj, self.date_field))
last_date = cast(datetime.date, getattr(last_obj, self.date_field))

segments = []
segments: list[tuple[datetime.date, datetime.date]] = []
current_start_date = first_date
if self.chronological:
# if chronological, we are moving forward in time through the `object_list`
Expand Down Expand Up @@ -110,7 +120,8 @@ def date_segments(self) -> list[tuple[datetime.datetime, datetime.datetime]]:

return segments

def page(self, number: int | str) -> DatePage:
@override
def page(self, number: int | str) -> DatePage[_T]:
number = self.validate_number(number)
start_date, end_date = self.date_segments[number - 1]

Expand All @@ -119,12 +130,12 @@ def page(self, number: int | str) -> DatePage:
return self._get_page(object_list, number, self, start_date, end_date)

def _get_page_object_list_for_range(
self, start_date: datetime.datetime, end_date: datetime.datetime
self, start_date: datetime.date, end_date: datetime.date
) -> QuerySet[Any] | list[Any]:
# to make mypy happy
object_list: QuerySet[Any] | list[Any]
object_list: QuerySet[Any] | list[_T]

if isinstance(self.object_list, QuerySet): # pyright: ignore[reportUnknownMemberType]
if isinstance(self.object_list, QuerySet):
if self.chronological:
filter_kwargs = {
f"{self.date_field}__gte": start_date,
Expand Down Expand Up @@ -174,7 +185,7 @@ def chronological(self) -> bool:
if self.count == 1:
return True

if isinstance(self.object_list, QuerySet): # pyright: ignore[reportUnknownMemberType]
if isinstance(self.object_list, QuerySet):
first_obj = self.object_list.first()
last_obj = self.object_list.last()
else:
Expand All @@ -186,7 +197,7 @@ def chronological(self) -> bool:

return first_date < last_date

def _get_page(self, *args, **kwargs) -> DatePage:
def _get_page(self, *args: Any, **kwargs: Any) -> DatePage[_T]:
return DatePage(*args, **kwargs)

@cached_property
Expand Down Expand Up @@ -225,12 +236,12 @@ def _check_object_list_is_ordered(self):
)


class DatePage(Page):
class DatePage(Page[_T]):
def __init__(
self,
object_list: _SupportsPagination,
object_list: _SupportsPagination[_T],
number: int,
paginator: DatePaginator,
paginator: DatePaginator[_T],
start_date: datetime.datetime,
end_date: datetime.datetime,
) -> None:
Expand Down

0 comments on commit f06c6db

Please sign in to comment.