More typing fixes

This commit is contained in:
László Károlyi 2024-11-24 22:12:39 +01:00
parent c9487abab8
commit fabbfeb737
Signed by: karolyi
GPG key ID: 2DCAF25E55735BFE
12 changed files with 521 additions and 56 deletions

View file

@ -1,5 +1,399 @@
{
"files": {
"./backend/forum/account/forms.py": [
{
"code": "reportMissingTypeStubs",
"range": {
"startColumn": 5,
"endColumn": 36,
"lineCount": 1
}
},
{
"code": "reportUnsafeMultipleInheritance",
"range": {
"startColumn": 6,
"endColumn": 19,
"lineCount": 1
}
},
{
"code": "reportIncompatibleVariableOverride",
"range": {
"startColumn": 6,
"endColumn": 19,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 46,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 46,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 54,
"endColumn": 60,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 54,
"endColumn": 60,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 24,
"endColumn": 28,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 24,
"endColumn": 28,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 44,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 44,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 13,
"endColumn": 17,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 41,
"endColumn": 49,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 42,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportUnknownVariableType",
"range": {
"startColumn": 12,
"endColumn": 32,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 35,
"endColumn": 52,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 45,
"endColumn": 52,
"lineCount": 1
}
},
{
"code": "reportUnknownVariableType",
"range": {
"startColumn": 12,
"endColumn": 33,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 36,
"endColumn": 53,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 46,
"endColumn": 53,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 41,
"endColumn": 49,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 42,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 37,
"endColumn": 45,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 38,
"endColumn": 46,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 24,
"endColumn": 28,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 24,
"endColumn": 28,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 32,
"endColumn": 38,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 32,
"endColumn": 38,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 59,
"endColumn": 63,
"lineCount": 1
}
}
],
"./backend/forum/base/models/user.py": [
{
"code": "reportMissingTypeStubs",
@ -138,25 +532,43 @@
"endColumn": 36,
"lineCount": 1
}
}
],
"./backend/forum/base/views/frontend.py": [
{
"code": "reportAny",
"range": {
"startColumn": 29,
"endColumn": 41,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"code": "reportMissingTypeStubs",
"range": {
"startColumn": 47,
"endColumn": 53,
"startColumn": 5,
"endColumn": 36,
"lineCount": 1
}
},
{
"code": "reportAny",
"range": {
"startColumn": 33,
"endColumn": 39,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 47,
"endColumn": 53,
"startColumn": 33,
"endColumn": 39,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"code": "reportIncompatibleMethodOverride",
"range": {
"startColumn": 8,
"endColumn": 24,
@ -164,18 +576,34 @@
}
},
{
"code": "reportUnknownArgumentType",
"code": "reportImplicitOverride",
"range": {
"startColumn": 44,
"endColumn": 50,
"startColumn": 8,
"endColumn": 24,
"lineCount": 1
}
},
{
"code": "reportAny",
"code": "reportIncompatibleMethodOverride",
"range": {
"startColumn": 27,
"endColumn": 56,
"startColumn": 8,
"endColumn": 24,
"lineCount": 1
}
},
{
"code": "reportImplicitOverride",
"range": {
"startColumn": 8,
"endColumn": 24,
"lineCount": 1
}
},
{
"code": "reportUnusedParameter",
"range": {
"startColumn": 18,
"endColumn": 25,
"lineCount": 1
}
}

View file

@ -4,6 +4,7 @@ from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError
from django.db.models.query import QuerySet
from django.forms import ModelForm
from django.http.request import HttpRequest
from django.utils.functional import cached_property
from ktools.django.utils.translation import gettext_safelazy as _
@ -23,6 +24,11 @@ class ForumAuthForm(ChallengeFormMixin, AuthenticationForm):
_CACHE_KEY = 'invalid-login-attempts-for-{ip}'
_SKIP_FIRST_ATTEMPT = True
def __init__(self, request: HttpRequest, *args, **kwargs):
self._before_init(request=request)
AuthenticationForm.__init__(self, request=request, **kwargs)
self._after_init()
@final
class IntroductionModificationForm(ModelForm[IntroductionModification]):
@ -68,9 +74,7 @@ class IntroductionModificationForm(ModelForm[IntroductionModification]):
return None
def __init__(self, *args, user: User, **kwargs):
"""
Override the initial data here.
"""
'Override the initial data here.'
self._user = user
kwargs['initial'] = self._get_initial_data()
kwargs['instance'] = self.last_intro_mod
@ -78,12 +82,14 @@ class IntroductionModificationForm(ModelForm[IntroductionModification]):
*args, **kwargs)
@final
class SettingsForm(ModelForm[User]):
"""
A settings form in which the user can modify every setting EXCEPT
the introductions, because thosa are handled by the
`IntroductionModificationForm`.
"""
@final
class Meta:
model = User
fields = [
@ -93,15 +99,15 @@ class SettingsForm(ModelForm[User]):
'separate_bookmarked_topics', 'has_chat_enabled',
'expand_archived']
def clean_ignored_users(self) -> QuerySet:
ignored_users = self.cleaned_data['ignored_users'] # type: QuerySet
def clean_ignored_users(self) -> QuerySet[User]:
ignored_users: QuerySet[User] = self.cleaned_data['ignored_users']
if ignored_users.filter(slug=self.instance.slug).exists():
raise ValidationError(
message=_('Don\'t ignore yourself.'), code='invalid')
return ignored_users
def clean_friended_users(self):
friended = self.cleaned_data['friended_users'] # type: QuerySet
friended: QuerySet[User] = self.cleaned_data['friended_users']
if friended.filter(slug=self.instance.slug).exists():
raise ValidationError(
message=_('No need to friend yourself.'), code='invalid')

View file

@ -1,5 +1,4 @@
{% extends 'base-minimal.html' %}
{% from 'ktools/macros/render-form-simple.html' import render_form_simple with context %}
{% from 'macros/submit-progressbutton.html' import submit_progressbutton with context %}
{% block extra_scripts_head %}
@ -15,7 +14,13 @@
<p>{{ _('If you feel this is an error, please fill this form out and you\'ll be contacted later on.') }}</p>
<form method="post" id="form-do-not-scrape">
{% csrf_token %}
{{ render_form_simple(form=form, wrapper_class='do-not-scrape-form', namespace='forum') }}
{% if form.is_bound and '__all__' in form.errors %}
{{ ktools_render_alert(message=form.errors.__all__, request=request, variant='bootstrap5-v1', severity='danger')
}}
{% endif %}
{% for boundfield in form %}
{{- ktools_render_widget(boundfield=boundfield) }}
{% endfor %}
{{ submit_progressbutton(extra_classes='mt-3') }}
</form>
</center>

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from datetime import datetime
from operator import attrgetter
from typing import TYPE_CHECKING, final
@ -87,7 +88,7 @@ class Comment(Model):
moved_from = ForeignKey(
to=Topic, on_delete=SET_DEFAULT, null=True, default=None,
related_name='moved_from', verbose_name=_('Moved from'))
time = DateTimeField(verbose_name=_('Commented at'))
time = DateTimeField[datetime, datetime](verbose_name=_('Commented at'))
number = PositiveIntegerField(verbose_name=_('Comment number in topic'))
voting_value = SmallIntegerField(verbose_name=_('Value of up/downvotes'))
prev_comment = ForeignKey(

View file

@ -168,6 +168,7 @@ class User(AbstractUser):
if TYPE_CHECKING:
pk: int
slug: str
introductionmodification: ClassVar[
ReverseOneToOneDescriptor[User, IntroductionModification]]
is_staff: ClassVar[BooleanField[bool, bool]]

View file

@ -2,7 +2,6 @@ from typing import final
from django.forms.fields import CharField, EmailField
from django.forms.widgets import Textarea
from django.http.request import HttpRequest
from ktools.django.utils.translation import gettext_safelazy as _
from forum.utils.forms import ChallengeFormMixin
@ -20,10 +19,7 @@ class DoNotScrapeForm(ChallengeFormMixin):
label=_(message='Your name'))
email = EmailField(required=True, label=_('Your email address'))
message = CharField(
widget=Textarea(attrs=dict(rows=5)),
widget=Textarea(attrs=dict(style='height: 15em')),
label=_(message='State your business on the website.'))
def __init__(self, request: HttpRequest, **kwargs):
super().__init__(request=request, **kwargs)
widget: Textarea = self.fields['message'].widget
widget.attrs['style'] = 'height: 15em'
cleaned_data: dict[str, str]

View file

@ -1,5 +1,5 @@
from functools import cached_property
from typing import Any, Literal
from typing import Any, Literal, final, override
from django.conf import settings
from django.contrib.messages import info
@ -38,11 +38,13 @@ Message:
{message}""")
@final
class TopicListView(TemplateView):
'Main entry page, with the topic listings.'
template_name = 'base/topic-listing.html'
@override
def get_context_data(self, **kwargs) -> dict[str, Any]:
'Fill the context fit topic data.'
context = super().get_context_data(**kwargs)
@ -61,6 +63,7 @@ class TopicListView(TemplateView):
return context
@final
class TopicCommentListingView(CommentListViewBase):
'List comments in a certain topic.'
@ -75,20 +78,22 @@ class TopicCommentListingView(CommentListViewBase):
Return the keyword arguments for `Comment` filtering.
"""
try:
return Topic.objects.exclude(pk__in=excluded_pks).values_list(
pk: int = Topic.objects.exclude(pk__in=excluded_pks).values_list(
'pk', flat=True).get(slug=slug)
return pk
except Topic.DoesNotExist:
raise Http404
@cached_property
def comments_per_page(self) -> int:
'Return the shown topics per page for a user.'
comments_per_page = self.request.session.get('comments-per-page')
comments_per_page: int | None = \
self.request.session.get('comments-per-page')
if comments_per_page is not None and comments_per_page > 0:
return comments_per_page
self.request.session['comments-per-page'] = \
settings.PAGINATOR_MAX_COMMENTS_PER_PAGE
return settings.PAGINATOR_MAX_COMMENTS_PER_PAGE
PMCPP: int = getattr(settings, 'PAGINATOR_MAX_COMMENTS_PER_PAGE')
self.request.session['comments-per-page'] = PMCPP
return PMCPP
def _get_pageid(
self, qs_comments: QuerySet[Comment], comment: Comment | None
@ -164,6 +169,7 @@ ThreadVariantType = Literal[
'expand-comments-up-recursive', 'expand-comments-entire-thread']
@final
class TopicCommentThreadVariantView(CommentListViewBase):
'Displaying a `Comment` thread in one variant.'
template_name = 'base/comments-expansion.html'
@ -209,7 +215,7 @@ class TopicCommentThreadVariantView(CommentListViewBase):
def get_context_data(
self, topic_slug: str, comment_pk: int, scroll_to_pk: int,
variant: ThreadVariantType) -> dict:
variant: ThreadVariantType) -> dict[str, Any]:
'Add data for the template.'
context = super().get_context_data(
topic_slug=topic_slug, comment_pk=comment_pk,
@ -225,32 +231,36 @@ class TopicCommentThreadVariantView(CommentListViewBase):
return context
class DoNotScrapeView(FormView):
@final
class DoNotScrapeView(FormView[DoNotScrapeForm]):
'Template view for banned bots.'
template_name = 'do-not-scrape.html'
form_class = DoNotScrapeForm
success_url = reverse_lazy(viewname='forum:base:topic-listing')
@override
def get_form_kwargs(self):
'`DoNotScrapeForm` need the request passed.'
kwargs = super().get_form_kwargs()
kwargs.update(request=self.request)
return kwargs
@override
def form_valid(self, form: DoNotScrapeForm) -> HttpResponse:
'Send an email to the admins, notify the user.'
ip: str = self.request.META['REMOTE_ADDR']
mail_admins(
subject=_('DoNotScrape unban request'),
message=_UNBAN_MESSAGE.format(
name=form.cleaned_data['name'],
email=form.cleaned_data['email'],
ip=self.request.META['REMOTE_ADDR'],
message=form.cleaned_data['message']))
ip=ip, message=form.cleaned_data['message']))
info(request=self.request, message=_(
'Thanks. You\'ll be contacted soon.'))
return super().form_valid(form=form)
@final
class ReloadAccessDataView(OnlyAllowManagerIpMixin):
'Reload/redownload all the data for the module.'

View file

@ -100,7 +100,8 @@ TEMPLATES = _Lget( # pyright: ignore[reportUnknownVariableType]
'enabled': not DEBUG,
},
'autoescape': select_autoescape(
disabled_extensions=('.html.jinja', '.txt.jinja'), default=True),
disabled_extensions=('.html.jinja', '.txt.jinja'),
default=True),
'auto_reload': DEBUG,
'translation_engine': 'django.utils.translation',
# Set trimming/lstripping, see

View file

@ -1,6 +1,7 @@
from functools import cached_property
from ipaddress import IPv4Address, IPv6Address, ip_address
from logging import getLogger
from typing import override
from django.conf import settings
from django.http.request import HttpRequest
@ -23,8 +24,10 @@ class AccessEvaluatorBase(object):
'Base for evaluation classes.'
status: AccessType = AccessType.UNLISTED
_asn_description = '-'
result_description = '-'
_asn_description: str = '-'
result_description: str = '-'
_request: HttpRequest
_versions: AccessEvaluatorCacheVersionClass
def __init__(self, request: HttpRequest):
self._request = request
@ -75,10 +78,12 @@ class AccessEvaluatorBase(object):
class AccessEvaluator(AccessEvaluatorBase):
'Evaluating requests.'
_log_requests: bool = getattr(settings, 'ACCESS_GOVERNOR_LOG_REQUESTS')
@cached_property
def _referer(self) -> str:
return self._request.META.get('HTTP_REFERER', '')
ref: str = self._request.META.get('HTTP_REFERER', '')
return ref
def _match_wl(self) -> bool:
'Return `True` when whitelisted, `False` otherwise.'
@ -146,6 +151,7 @@ class AccessEvaluator(AccessEvaluatorBase):
return True
return False
@override
def __str__(self) -> str:
referer = f' <{self._referer!r}>' if self._referer else ''
return (
@ -156,10 +162,10 @@ class AccessEvaluator(AccessEvaluatorBase):
def process(self):
'Start processing.'
if not self._address and settings.ACCESS_GOVERNOR_LOG_REQUESTS:
if not self._address and self._log_requests:
LOGGER.debug(msg=self)
return
if not self._match_wl():
self._match_bl()
if settings.ACCESS_GOVERNOR_LOG_REQUESTS:
_ = self._match_bl()
if self._log_requests:
LOGGER.debug(msg=self)

View file

@ -1,5 +1,5 @@
from functools import cached_property
from typing import ClassVar, override
from typing import Any, ClassVar, override
from django.conf import settings
from django.core.cache import cache
@ -34,19 +34,26 @@ class ChallengeFormMixin(Form):
_ITERATIONS_INCREASE: ClassVar[int] = 2
_SKIP_FIRST_ATTEMPT: ClassVar[bool] = False
_request: HttpRequest
_cache_key: str
_request: HttpRequest = HttpRequest()
_cache_key: str = ''
_errors: None | dict[str | None, ValidationError] = None
h_challenge: CharField = _CHALLENGE_FIELD
h_response: CharField = _RESPONSE_FIELD
cleaned_data: dict[str, Any]
def __init__(self, request: HttpRequest, *args, **kwargs):
self._before_init(request=request)
super().__init__(*args, **kwargs)
self._after_init()
def _before_init(self, request: HttpRequest) -> None:
self._request = request
ip: str = request.META.get('REMOTE_ADDR') or ''
self._cache_key = self._CACHE_KEY.format(ip=ip)
kwargs['request'] = request
super().__init__(*args, **kwargs)
def _after_init(self) -> None:
self._setup_challenge_field()
def clean_h_challenge(self) -> str:

View file

@ -1,5 +1,7 @@
from typing import Callable, final
from django.http.request import HttpRequest
from django.http.response import HttpResponsePermanentRedirect
from django.http.response import HttpResponse, HttpResponsePermanentRedirect
from django.urls.base import reverse
from django.utils.cache import add_never_cache_headers
@ -7,11 +9,12 @@ from forum.utils.access_governor.checker import AccessEvaluator
from forum.utils.access_governor.ip_collector.reader import AccessType
@final
class DisableClientSideCachingMiddleware(object):
'https://stackoverflow.com/a/5882033/1067833'
no_access_path = reverse(viewname='forum:base:do-not-scrape')
def __init__(self, get_response):
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
'One-time configuration and initialization.'
self.get_response = get_response
@ -28,11 +31,12 @@ class DisableClientSideCachingMiddleware(object):
return response
@final
class AccessCheckCachingMiddleware(object):
'Check access.'
no_access_path = reverse(viewname='forum:base:do-not-scrape')
def __init__(self, get_response):
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
'One-time configuration and initialization.'
self.get_response = get_response

View file

@ -19,7 +19,7 @@ markdown2==2.5.1
maxminddb==2.6.2
mistune==3.0.2
mysqlclient==2.2.6
py-ktools[django] @ git+https://git.ksol.io/karolyi/py-ktools.git@e9dc5a07ec5f35ee10b1321456c3649059822b70
py-ktools[django] @ git+https://git.ksol.io/karolyi/py-ktools.git@d231505686e59376a9462a2841b35ea9723faf1b
redis==5.2.0
rjsmin==1.2.3
sqlparse==0.5.2