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": { "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": [ "./backend/forum/base/models/user.py": [
{ {
"code": "reportMissingTypeStubs", "code": "reportMissingTypeStubs",
@ -138,25 +532,43 @@
"endColumn": 36, "endColumn": 36,
"lineCount": 1 "lineCount": 1
} }
}
],
"./backend/forum/base/views/frontend.py": [
{
"code": "reportAny",
"range": {
"startColumn": 29,
"endColumn": 41,
"lineCount": 1
}
}, },
{ {
"code": "reportUnknownParameterType", "code": "reportMissingTypeStubs",
"range": { "range": {
"startColumn": 47, "startColumn": 5,
"endColumn": 53, "endColumn": 36,
"lineCount": 1
}
},
{
"code": "reportAny",
"range": {
"startColumn": 33,
"endColumn": 39,
"lineCount": 1 "lineCount": 1
} }
}, },
{ {
"code": "reportMissingParameterType", "code": "reportMissingParameterType",
"range": { "range": {
"startColumn": 47, "startColumn": 33,
"endColumn": 53, "endColumn": 39,
"lineCount": 1 "lineCount": 1
} }
}, },
{ {
"code": "reportUnknownMemberType", "code": "reportIncompatibleMethodOverride",
"range": { "range": {
"startColumn": 8, "startColumn": 8,
"endColumn": 24, "endColumn": 24,
@ -164,18 +576,34 @@
} }
}, },
{ {
"code": "reportUnknownArgumentType", "code": "reportImplicitOverride",
"range": { "range": {
"startColumn": 44, "startColumn": 8,
"endColumn": 50, "endColumn": 24,
"lineCount": 1 "lineCount": 1
} }
}, },
{ {
"code": "reportAny", "code": "reportIncompatibleMethodOverride",
"range": { "range": {
"startColumn": 27, "startColumn": 8,
"endColumn": 56, "endColumn": 24,
"lineCount": 1
}
},
{
"code": "reportImplicitOverride",
"range": {
"startColumn": 8,
"endColumn": 24,
"lineCount": 1
}
},
{
"code": "reportUnusedParameter",
"range": {
"startColumn": 18,
"endColumn": 25,
"lineCount": 1 "lineCount": 1
} }
} }

View file

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

View file

@ -1,5 +1,4 @@
{% extends 'base-minimal.html' %} {% 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 %} {% from 'macros/submit-progressbutton.html' import submit_progressbutton with context %}
{% block extra_scripts_head %} {% block extra_scripts_head %}
@ -15,16 +14,22 @@
<p>{{ _('If you feel this is an error, please fill this form out and you\'ll be contacted later on.') }}</p> <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"> <form method="post" id="form-do-not-scrape">
{% csrf_token %} {% 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') }} {{ submit_progressbutton(extra_classes='mt-3') }}
</form> </form>
</center> </center>
{% jsmin %} {% jsmin %}
<script type="text/javascript"> <script type="text/javascript">
Forum.onDomReady(document).then(() => { Forum.onDomReady(document).then(() => {
Forum.doNotScrape.init() Forum.doNotScrape.init()
}) })
</script> </script>
{% endjsmin %} {% endjsmin %}
{% endblock main_content %} {% endblock main_content %}

View file

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

View file

@ -168,6 +168,7 @@ class User(AbstractUser):
if TYPE_CHECKING: if TYPE_CHECKING:
pk: int pk: int
slug: str
introductionmodification: ClassVar[ introductionmodification: ClassVar[
ReverseOneToOneDescriptor[User, IntroductionModification]] ReverseOneToOneDescriptor[User, IntroductionModification]]
is_staff: ClassVar[BooleanField[bool, bool]] 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.fields import CharField, EmailField
from django.forms.widgets import Textarea from django.forms.widgets import Textarea
from django.http.request import HttpRequest
from ktools.django.utils.translation import gettext_safelazy as _ from ktools.django.utils.translation import gettext_safelazy as _
from forum.utils.forms import ChallengeFormMixin from forum.utils.forms import ChallengeFormMixin
@ -20,10 +19,7 @@ class DoNotScrapeForm(ChallengeFormMixin):
label=_(message='Your name')) label=_(message='Your name'))
email = EmailField(required=True, label=_('Your email address')) email = EmailField(required=True, label=_('Your email address'))
message = CharField( message = CharField(
widget=Textarea(attrs=dict(rows=5)), widget=Textarea(attrs=dict(style='height: 15em')),
label=_(message='State your business on the website.')) label=_(message='State your business on the website.'))
def __init__(self, request: HttpRequest, **kwargs): cleaned_data: dict[str, str]
super().__init__(request=request, **kwargs)
widget: Textarea = self.fields['message'].widget
widget.attrs['style'] = 'height: 15em'

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
from functools import cached_property from functools import cached_property
from typing import ClassVar, override from typing import Any, ClassVar, override
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
@ -34,19 +34,26 @@ class ChallengeFormMixin(Form):
_ITERATIONS_INCREASE: ClassVar[int] = 2 _ITERATIONS_INCREASE: ClassVar[int] = 2
_SKIP_FIRST_ATTEMPT: ClassVar[bool] = False _SKIP_FIRST_ATTEMPT: ClassVar[bool] = False
_request: HttpRequest _request: HttpRequest = HttpRequest()
_cache_key: str _cache_key: str = ''
_errors: None | dict[str | None, ValidationError] = None _errors: None | dict[str | None, ValidationError] = None
h_challenge: CharField = _CHALLENGE_FIELD h_challenge: CharField = _CHALLENGE_FIELD
h_response: CharField = _RESPONSE_FIELD h_response: CharField = _RESPONSE_FIELD
cleaned_data: dict[str, Any]
def __init__(self, request: HttpRequest, *args, **kwargs): 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 self._request = request
ip: str = request.META.get('REMOTE_ADDR') or '' ip: str = request.META.get('REMOTE_ADDR') or ''
self._cache_key = self._CACHE_KEY.format(ip=ip) 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() self._setup_challenge_field()
def clean_h_challenge(self) -> str: 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.request import HttpRequest
from django.http.response import HttpResponsePermanentRedirect from django.http.response import HttpResponse, HttpResponsePermanentRedirect
from django.urls.base import reverse from django.urls.base import reverse
from django.utils.cache import add_never_cache_headers 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 from forum.utils.access_governor.ip_collector.reader import AccessType
@final
class DisableClientSideCachingMiddleware(object): class DisableClientSideCachingMiddleware(object):
'https://stackoverflow.com/a/5882033/1067833' 'https://stackoverflow.com/a/5882033/1067833'
no_access_path = reverse(viewname='forum:base:do-not-scrape') 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.' 'One-time configuration and initialization.'
self.get_response = get_response self.get_response = get_response
@ -28,11 +31,12 @@ class DisableClientSideCachingMiddleware(object):
return response return response
@final
class AccessCheckCachingMiddleware(object): class AccessCheckCachingMiddleware(object):
'Check access.' 'Check access.'
no_access_path = reverse(viewname='forum:base:do-not-scrape') 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.' 'One-time configuration and initialization.'
self.get_response = get_response self.get_response = get_response

View file

@ -19,7 +19,7 @@ markdown2==2.5.1
maxminddb==2.6.2 maxminddb==2.6.2
mistune==3.0.2 mistune==3.0.2
mysqlclient==2.2.6 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 redis==5.2.0
rjsmin==1.2.3 rjsmin==1.2.3
sqlparse==0.5.2 sqlparse==0.5.2