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": {
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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'
|
|
||||||
|
|
|
@ -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.'
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue