Skip to content

Commit

Permalink
Use protocols for mixins (instead of with_type_hint workaround)
Browse files Browse the repository at this point in the history
  • Loading branch information
medihack committed Feb 11, 2024
1 parent 025cb35 commit c5d55e9
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 49 deletions.
4 changes: 0 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@

## Maybe

- Replace with_type_hint by Protocols
-- RADIS currently uses Protocols for type hints but ADIT with_type_hint for mixins
-- Protocols would work instead for the with_type_hint implementation in mypy
-- Protocols are also the official way to type hint mixins
- New batch transfer
-- Create new batch transfer job and allow to add tasks
-- Add tasks manually or using a Excel file
Expand Down
6 changes: 3 additions & 3 deletions adit/batch_query/filters.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import django_filters
from django.views import View
from django.http import HttpRequest

from adit.core.filters import DicomJobFilter, DicomTaskFilter
from adit.core.forms import FilterSetFormHelper
from adit.core.utils.type_utils import with_type_hint

from .models import BatchQueryJob, BatchQueryResult, BatchQueryTask

Expand All @@ -18,8 +17,9 @@ class Meta(DicomTaskFilter.Meta):
model = BatchQueryTask


class BatchQueryResultFilter(django_filters.FilterSet, with_type_hint(View)):
class BatchQueryResultFilter(django_filters.FilterSet):
task_id = django_filters.NumberFilter(field_name="query_id", label="Task ID")
request: HttpRequest

class Meta:
model = BatchQueryResult
Expand Down
11 changes: 7 additions & 4 deletions adit/core/filters.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import django_filters
from django.views import View
from django.http import HttpRequest

from adit.core.forms import FilterSetFormHelper
from adit.core.utils.type_utils import with_type_hint

from .models import DicomJob, DicomTask


class DicomJobFilter(django_filters.FilterSet, with_type_hint(View)):
class DicomJobFilter(django_filters.FilterSet):
request: HttpRequest

class Meta:
model: type[DicomJob]
fields = ("status",)
Expand All @@ -21,7 +22,9 @@ def __init__(self, *args, **kwargs):
self.form.helper = form_helper


class DicomTaskFilter(django_filters.FilterSet, with_type_hint(View)):
class DicomTaskFilter(django_filters.FilterSet):
request: HttpRequest

class Meta:
model: type[DicomTask]
fields = ("status",)
Expand Down
69 changes: 44 additions & 25 deletions adit/core/mixins.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
from functools import reduce
from typing import Any, Protocol

from django.core.exceptions import SuspiciousOperation
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse
from django.views import View

# from django.forms.formsets import ORDERING_FIELD_NAME
from django.views.generic import ListView, TemplateView
from django.views.generic import TemplateView
from django_filters.filterset import FilterSet
from django_filters.views import FilterMixin

from .forms import PageSizeSelectForm
from .models import AppSettings
from .types import HtmxHttpRequest
from .utils.auth_utils import is_logged_in_user
from .utils.type_utils import with_type_hint


def deepgetattr(obj: object, attr: str):
"""Recurses through an attribute chain to get the ultimate value."""
return reduce(getattr, attr.split("."), obj)


class ViewProtocol(Protocol):
Expand All @@ -34,11 +26,15 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
...


class LockedMixin(with_type_hint(View)):
class LockedMixinProtocol(ViewProtocol, Protocol):
settings_model: type[AppSettings]
section_name = "Section"
section_name: str

def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:

class LockedMixin:
def dispatch(
self: LockedMixinProtocol, request: HttpRequest, *args: Any, **kwargs: Any
) -> HttpResponse:
settings = self.settings_model.get()
assert settings

Expand All @@ -63,7 +59,21 @@ def dispatch(
return super().dispatch(request, *args, **kwargs)


class RelatedFilterMixin(FilterMixin, with_type_hint(ListView)):
class RelatedFilterMixinProtocol(ViewProtocol, Protocol):
filterset: FilterSet
object_list: QuerySet

def get_strict(self) -> bool:
...

def get_filterset_class(self) -> type[FilterSet]:
...

def get_filterset(self, filterset_class: type[FilterSet]) -> FilterSet:
...


class RelatedFilterMixin(FilterMixin):
"""A mixin that provides a way to show and handle a FilterSet in a request.
The advantage is provided over FilterMixin is that it does use a special
Expand All @@ -74,6 +84,8 @@ class RelatedFilterMixin(FilterMixin, with_type_hint(ListView)):
It must be placed behind SingleTableMixin.
"""

request: HttpRequest

def get_filter_queryset(self):
raise NotImplementedError("Must be implemented by the derived view.")

Expand All @@ -85,7 +97,7 @@ def get_filterset_kwargs(self, _):
}
return kwargs

def get_context_data(self, **kwargs):
def get_context_data(self: RelatedFilterMixinProtocol, **kwargs):
context = super().get_context_data(**kwargs)

filterset_class = self.get_filterset_class()
Expand All @@ -102,14 +114,17 @@ def get_context_data(self, **kwargs):
return context


class PageSizeSelectMixin(with_type_hint(ListView)):
"""A mixin to show a page size selector.
class PageSizeSelectMixinProtocol(ViewProtocol, Protocol):
paginate_by: int
page_sizes: list[int]

Must be placed after the SingleTableMixin as it sets self.paginated_by
which is used by this mixin for the page size.
"""

def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
class PageSizeSelectMixin:
"""A mixin to show a page size selector."""

def get(
self: PageSizeSelectMixinProtocol, request: HttpRequest, *args: Any, **kwargs: Any
) -> HttpResponse:
# Make the initial paginate_by attribute the default page size if set
if not hasattr(self, "paginate_by") or self.paginate_by is None:
self.paginate_by = 50
Expand All @@ -120,11 +135,15 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
per_page = self.paginate_by

per_page = min(per_page, 100)
self.paginate_by = per_page # used by MultipleObjectMixin (and also django_tables2)
self.paginate_by = per_page # used by MultipleObjectMixin and django_tables2

return super().get(request, *args, **kwargs)

def get_context_data(self, **kwargs):
def get_context_data(self: PageSizeSelectMixinProtocol, **kwargs):
context = super().get_context_data(**kwargs)
context["page_size_select"] = PageSizeSelectForm(self.request.GET, [50, 100, 250, 500])

if not hasattr(self, "page_sizes") or self.page_sizes is None:
self.page_sizes = [50, 100, 250, 500]
context["page_size_select"] = PageSizeSelectForm(self.request.GET, self.page_sizes)

return context
13 changes: 0 additions & 13 deletions adit/core/utils/type_utils.py

This file was deleted.

0 comments on commit c5d55e9

Please sign in to comment.