Fix type errors (#81)
This brings down the number of type errors from 118 down to 92. I think most of the remaining errors are due to the */models.py files where I'm not sure what to do about it. I run the type checker in docker via `just typecheck`.
The change in 0af3fbcac4
requires this change upstream: https://github.com/valberg/django-registries/pull/28 we can omit that commit for now.
Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/81
Reviewed-by: benjaoming <benjaoming@data.coop>
Co-authored-by: Reynir Björnsson <reynir@reynir.dk>
Co-committed-by: Reynir Björnsson <reynir@reynir.dk>
This commit is contained in:
parent
408970f16e
commit
c9b0c19fec
10 changed files with 34 additions and 31 deletions
|
@ -33,12 +33,11 @@ class OrderAdminForm(forms.ModelForm):
|
||||||
def clean(self) -> None:
|
def clean(self) -> None:
|
||||||
"""Clean the order."""
|
"""Clean the order."""
|
||||||
cd = super().clean()
|
cd = super().clean()
|
||||||
if not cd["account"] and cd["member"]:
|
if cd and not cd["account"] and cd["member"]:
|
||||||
try:
|
try:
|
||||||
cd["account"] = models.Account.objects.get_or_create(owner=cd["member"])[0]
|
cd["account"] = models.Account.objects.get_or_create(owner=cd["member"])[0]
|
||||||
except models.Account.MultipleObjectsReturned:
|
except models.Account.MultipleObjectsReturned:
|
||||||
cd["account"] = models.Account.objects.filter(owner=cd["member"]).first()
|
cd["account"] = models.Account.objects.filter(owner=cd["member"]).first()
|
||||||
return cd
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Order)
|
@admin.register(models.Order)
|
||||||
|
|
|
@ -85,6 +85,7 @@ def order_pay(request: HttpRequest, order_id: int) -> HttpResponse:
|
||||||
|
|
||||||
# TODO(benjaoming): Redirect with status=303
|
# TODO(benjaoming): Redirect with status=303
|
||||||
# https://git.data.coop/data.coop/membersystem/issues/63
|
# https://git.data.coop/data.coop/membersystem/issues/63
|
||||||
|
# TODO(reynir): url is None if session is not active
|
||||||
return redirect(checkout_session.url)
|
return redirect(checkout_session.url)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from accounting.models import Order
|
from accounting.models import Order
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
from django_stubs_ext import StrOrPromise
|
||||||
|
|
||||||
from .models import Membership
|
from .models import Membership
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ class BaseEmail(EmailMessage):
|
||||||
template = "membership/email/base.txt"
|
template = "membership/email/base.txt"
|
||||||
# Optional: Set to a template path for subject
|
# Optional: Set to a template path for subject
|
||||||
template_subject = None
|
template_subject = None
|
||||||
default_subject = "SET SUBJECT HERE"
|
default_subject : StrOrPromise = "SET SUBJECT HERE"
|
||||||
|
|
||||||
def __init__(self, request: HttpRequest, *args, **kwargs) -> None: # noqa: ANN002, ANN003
|
def __init__(self, request: HttpRequest, *args, **kwargs) -> None: # noqa: ANN002, ANN003
|
||||||
self.context = kwargs.pop("context", {})
|
self.context = kwargs.pop("context", {})
|
||||||
|
@ -90,7 +91,7 @@ class BaseEmail(EmailMessage):
|
||||||
|
|
||||||
def send_with_feedback(self, *, success_msg: str | None = None, no_message: bool = False) -> None:
|
def send_with_feedback(self, *, success_msg: str | None = None, no_message: bool = False) -> None:
|
||||||
"""Send email, possibly adding feedback via django.contrib.messages."""
|
"""Send email, possibly adding feedback via django.contrib.messages."""
|
||||||
if not success_msg:
|
if success_msg is None:
|
||||||
success_msg = _("Email successfully sent to {}").format(", ".join(self.to))
|
success_msg = _("Email successfully sent to {}").format(", ".join(self.to))
|
||||||
try:
|
try:
|
||||||
self.send(fail_silently=False)
|
self.send(fail_silently=False)
|
||||||
|
|
|
@ -2,13 +2,17 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission as DjangoPermission
|
from django.contrib.auth.models import Permission as DjangoPermission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
PERMISSIONS = []
|
if TYPE_CHECKING:
|
||||||
|
from django_stubs_ext import StrOrPromise
|
||||||
|
|
||||||
|
PERMISSIONS : list[Permission] = []
|
||||||
|
|
||||||
|
|
||||||
def persist_permissions(*args, **kwargs) -> None: # type: ignore[no-untyped-def] # noqa: ANN002, ANN003
|
def persist_permissions(*args, **kwargs) -> None: # type: ignore[no-untyped-def] # noqa: ANN002, ANN003
|
||||||
|
@ -21,7 +25,7 @@ def persist_permissions(*args, **kwargs) -> None: # type: ignore[no-untyped-def
|
||||||
class Permission:
|
class Permission:
|
||||||
"""Dataclass to define a permission."""
|
"""Dataclass to define a permission."""
|
||||||
|
|
||||||
name: str
|
name: StrOrPromise
|
||||||
codename: str
|
codename: str
|
||||||
app_label: str
|
app_label: str
|
||||||
model: str
|
model: str
|
||||||
|
|
|
@ -25,6 +25,7 @@ from .selectors import get_memberships
|
||||||
from .selectors import get_subscription_periods
|
from .selectors import get_subscription_periods
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from utils.types import AuthenticatedHttpRequest
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ member_view = namespaced_decorator_factory(namespace="member", base_path="member
|
||||||
name="membership-overview",
|
name="membership-overview",
|
||||||
login_required=True,
|
login_required=True,
|
||||||
)
|
)
|
||||||
def membership_overview(request: HttpRequest) -> HttpResponse:
|
def membership_overview(request: AuthenticatedHttpRequest) -> HttpResponse:
|
||||||
"""View to show the membership overview."""
|
"""View to show the membership overview."""
|
||||||
memberships = get_memberships(member=request.user)
|
memberships = get_memberships(member=request.user)
|
||||||
current_membership = memberships.current()
|
current_membership = memberships.current()
|
||||||
|
@ -69,7 +70,7 @@ admin_members_view = namespaced_decorator_factory(
|
||||||
login_required=True,
|
login_required=True,
|
||||||
permissions=[ADMINISTRATE_MEMBERS.path],
|
permissions=[ADMINISTRATE_MEMBERS.path],
|
||||||
)
|
)
|
||||||
def members_admin(request: HttpRequest) -> HttpResponse:
|
def members_admin(request: AuthenticatedHttpRequest) -> HttpResponse:
|
||||||
"""View to list all members."""
|
"""View to list all members."""
|
||||||
users = get_members()
|
users = get_members()
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ def members_admin(request: HttpRequest) -> HttpResponse:
|
||||||
login_required=True,
|
login_required=True,
|
||||||
permissions=[ADMINISTRATE_MEMBERS.path],
|
permissions=[ADMINISTRATE_MEMBERS.path],
|
||||||
)
|
)
|
||||||
def members_admin_detail(request: HttpRequest, member_id: int) -> HttpResponse:
|
def members_admin_detail(request: AuthenticatedHttpRequest, member_id: int) -> HttpResponse:
|
||||||
"""View to show the details of a member."""
|
"""View to show the details of a member."""
|
||||||
member = get_member(member_id=member_id)
|
member = get_member(member_id=member_id)
|
||||||
subscription_periods = get_subscription_periods(member=member)
|
subscription_periods = get_subscription_periods(member=member)
|
||||||
|
@ -123,12 +124,6 @@ def members_admin_detail(request: HttpRequest, member_id: int) -> HttpResponse:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidTokenError(HttpResponseForbidden):
|
|
||||||
"""Exception raised when an invalid token is encountered."""
|
|
||||||
|
|
||||||
content = "Token not valid - maybe it expired?"
|
|
||||||
|
|
||||||
|
|
||||||
@ratelimit(group="membership", key="ip", rate="10/d", method="ALL", block=True)
|
@ratelimit(group="membership", key="ip", rate="10/d", method="ALL", block=True)
|
||||||
@member_view(
|
@member_view(
|
||||||
paths="invite/<str:referral_code>/<str:token>/",
|
paths="invite/<str:referral_code>/<str:token>/",
|
||||||
|
@ -152,7 +147,7 @@ def invite(request: HttpRequest, referral_code: str, token: str) -> HttpResponse
|
||||||
token_valid = default_token_generator.check_token(membership.user, token)
|
token_valid = default_token_generator.check_token(membership.user, token)
|
||||||
|
|
||||||
if not token_valid:
|
if not token_valid:
|
||||||
raise InvalidTokenError
|
return HttpResponseForbidden("Token not valid - maybe it expired?")
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = InviteForm(membership=membership, data=request.POST)
|
form = InviteForm(membership=membership, data=request.POST)
|
||||||
|
|
|
@ -108,7 +108,7 @@ AUTHENTICATION_BACKENDS = (
|
||||||
WSGI_APPLICATION = "project.wsgi.application"
|
WSGI_APPLICATION = "project.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = []
|
AUTH_PASSWORD_VALIDATORS : list[dict[str, str]] = []
|
||||||
|
|
||||||
LANGUAGE_CODE = "da-dk"
|
LANGUAGE_CODE = "da-dk"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django_registries.registry import Interface
|
||||||
from django_registries.registry import Registry
|
from django_registries.registry import Registry
|
||||||
|
|
||||||
|
|
||||||
class ServiceRegistry(Registry):
|
class ServiceRegistry(Registry["ServiceInterface"]):
|
||||||
"""Registry for services."""
|
"""Registry for services."""
|
||||||
|
|
||||||
implementations_module = "services"
|
implementations_module = "services"
|
||||||
|
@ -39,9 +39,9 @@ class ServiceInterface(Interface):
|
||||||
# This is a way of saying that the service is "mandatory" to have.
|
# This is a way of saying that the service is "mandatory" to have.
|
||||||
auto_create: bool = False
|
auto_create: bool = False
|
||||||
|
|
||||||
request_types: list[str, str] = DEFAULT_SERVICE_REQUEST_TYPES
|
request_types: list[ServiceRequests] = DEFAULT_SERVICE_REQUEST_TYPES
|
||||||
|
|
||||||
subscribe_fields: tuple[tuple[str, forms.Field]] = []
|
subscribe_fields: tuple[tuple[str, forms.Field],...] = ()
|
||||||
|
|
||||||
def get_form_class(self) -> type:
|
def get_form_class(self) -> type:
|
||||||
"""Get the form class for the service."""
|
"""Get the form class for the service."""
|
||||||
|
|
|
@ -14,7 +14,7 @@ from services.registry import ServiceRegistry
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpRequest
|
from utils.types import AuthenticatedHttpRequest
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
services_view = namespaced_decorator_factory(
|
services_view = namespaced_decorator_factory(
|
||||||
|
@ -28,16 +28,16 @@ services_view = namespaced_decorator_factory(
|
||||||
name="list",
|
name="list",
|
||||||
login_required=True,
|
login_required=True,
|
||||||
)
|
)
|
||||||
def services_overview(request: HttpRequest) -> HttpResponse:
|
def services_overview(request: AuthenticatedHttpRequest) -> HttpResponse:
|
||||||
"""View all services."""
|
"""View all services."""
|
||||||
active_services = get_services(user=request.user)
|
active_services = get_services(user=request.user)
|
||||||
|
|
||||||
active_service_classes = [service.__class__ for service in active_services]
|
active_service_classes = [service.__class__ for service in active_services]
|
||||||
|
|
||||||
services = [service for _, service in ServiceRegistry.get_items() if service not in active_service_classes]
|
non_active_services = [service for _, service in ServiceRegistry.get_items() if service not in active_service_classes]
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"non_active_services": services,
|
"non_active_services": non_active_services,
|
||||||
"active_services": active_services,
|
"active_services": active_services,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ def services_overview(request: HttpRequest) -> HttpResponse:
|
||||||
name="detail",
|
name="detail",
|
||||||
login_required=True,
|
login_required=True,
|
||||||
)
|
)
|
||||||
def service_detail(request: HttpRequest, service_slug: str) -> HttpResponse:
|
def service_detail(request: AuthenticatedHttpRequest, service_slug: str) -> HttpResponse:
|
||||||
"""View a service."""
|
"""View a service."""
|
||||||
service = ServiceRegistry.get(slug=service_slug)
|
service = ServiceRegistry.get(slug=service_slug)
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ def service_detail(request: HttpRequest, service_slug: str) -> HttpResponse:
|
||||||
name="subscribe",
|
name="subscribe",
|
||||||
login_required=True,
|
login_required=True,
|
||||||
)
|
)
|
||||||
def service_subscribe(request: HttpRequest, service_slug: str) -> HttpResponse:
|
def service_subscribe(request: AuthenticatedHttpRequest, service_slug: str) -> HttpResponse:
|
||||||
"""Subscribe to a service."""
|
"""Subscribe to a service."""
|
||||||
service = ServiceRegistry.get(slug=service_slug)
|
service = ServiceRegistry.get(slug=service_slug)
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,5 @@ from django.http import HttpRequest
|
||||||
class AuthenticatedHttpRequest(HttpRequest):
|
class AuthenticatedHttpRequest(HttpRequest):
|
||||||
"""HttpRequest with an authenticated user."""
|
"""HttpRequest with an authenticated user."""
|
||||||
|
|
||||||
|
# XXX(reynir): Should this be Member instead?!
|
||||||
user: User
|
user: User
|
||||||
|
|
|
@ -19,6 +19,7 @@ if TYPE_CHECKING:
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django_stubs_ext import StrOrPromise
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -26,18 +27,18 @@ class Row:
|
||||||
"""A row in a table."""
|
"""A row in a table."""
|
||||||
|
|
||||||
data: dict[str, str]
|
data: dict[str, str]
|
||||||
actions: list[dict[str, str]]
|
actions: list[dict[str, StrOrPromise]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RowAction:
|
class RowAction:
|
||||||
"""An action that can be performed on a row in a table."""
|
"""An action that can be performed on a row in a table."""
|
||||||
|
|
||||||
label: str
|
label: StrOrPromise
|
||||||
url_name: str
|
url_name: str
|
||||||
url_kwargs: dict[str, str]
|
url_kwargs: dict[str, str]
|
||||||
|
|
||||||
def render(self, obj: Model) -> dict[str, str]:
|
def render(self, obj: Model) -> dict[str, StrOrPromise]:
|
||||||
"""Render the action as a dictionary for the given object."""
|
"""Render the action as a dictionary for the given object."""
|
||||||
url = reverse(
|
url = reverse(
|
||||||
self.url_name,
|
self.url_name,
|
||||||
|
@ -53,7 +54,7 @@ class RenderConfig:
|
||||||
entity_name: str
|
entity_name: str
|
||||||
entity_name_plural: str
|
entity_name_plural: str
|
||||||
objects: QuerySet
|
objects: QuerySet
|
||||||
columns: list[tuple[str, str]]
|
columns: list[tuple[str, StrOrPromise]]
|
||||||
row_actions: list[RowAction] | None = None
|
row_actions: list[RowAction] | None = None
|
||||||
list_actions: list[tuple[str, str]] | None = None
|
list_actions: list[tuple[str, str]] | None = None
|
||||||
paginate_by: int | None = None
|
paginate_by: int | None = None
|
||||||
|
@ -88,7 +89,8 @@ class RenderConfig:
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
with queries_disabled():
|
with queries_disabled():
|
||||||
row = Row(
|
row = Row(
|
||||||
data={column: getattr(obj, column[0]) for column in columns},
|
# XXX(reynir): we never use the key
|
||||||
|
data={column[0]: getattr(obj, column[0]) for column in columns},
|
||||||
actions=[action.render(obj) for action in row_actions],
|
actions=[action.render(obj) for action in row_actions],
|
||||||
)
|
)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
|
Loading…
Add table
Reference in a new issue