More typing fixes
This commit is contained in:
parent
c9487abab8
commit
fabbfeb737
12 changed files with 521 additions and 56 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,16 +14,22 @@
|
|||
<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>
|
||||
|
||||
{% jsmin %}
|
||||
<script type="text/javascript">
|
||||
Forum.onDomReady(document).then(() => {
|
||||
Forum.doNotScrape.init()
|
||||
})
|
||||
Forum.onDomReady(document).then(() => {
|
||||
Forum.doNotScrape.init()
|
||||
})
|
||||
</script>
|
||||
{% endjsmin %}
|
||||
{% endblock main_content %}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue