First pass of Type Annotations

This commit is contained in:
Martin Mahner 2020-06-03 19:01:14 +02:00
parent 08f7d9c27f
commit cf3ad14795
12 changed files with 204 additions and 123 deletions

View file

@ -1,4 +1,7 @@
from typing import Dict, List, Optional, Tuple, Type, Union
from django.apps import AppConfig, apps from django.apps import AppConfig, apps
from django.core.handlers.wsgi import WSGIRequest
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -118,7 +121,7 @@ class dpasteAppConfig(AppConfig):
PLAIN_CODE_SYMBOL = "_code" PLAIN_CODE_SYMBOL = "_code"
@property @property
def TEXT_FORMATTER(self): def TEXT_FORMATTER(self,) -> List[Tuple[str, str, Type[object]]]:
""" """
Choices list with all "Text" lexer. Prepend keys with an underscore Choices list with all "Text" lexer. Prepend keys with an underscore
so they don't accidentally clash with a Pygments Lexer name. so they don't accidentally clash with a Pygments Lexer name.
@ -130,6 +133,8 @@ class dpasteAppConfig(AppConfig):
Lexer Highlight Class) Lexer Highlight Class)
If the Highlight Class is not given, PygmentsHighlighter is used. If the Highlight Class is not given, PygmentsHighlighter is used.
@FIXME: Make `Type[object]` use the Type[Highlighter] class.
""" """
from dpaste.highlight import ( from dpaste.highlight import (
PlainTextHighlighter, PlainTextHighlighter,
@ -144,7 +149,9 @@ class dpasteAppConfig(AppConfig):
] ]
@property @property
def CODE_FORMATTER(self): def CODE_FORMATTER(
self,
) -> List[Union[Tuple[str, str, Type[object]], Tuple[str, str]]]:
""" """
Choices list with all "Code" Lexer. Each list Choices list with all "Code" Lexer. Each list
contains a lexer tuple of: contains a lexer tuple of:
@ -621,7 +628,7 @@ class dpasteAppConfig(AppConfig):
CACHE_TIMEOUT = 60 * 10 CACHE_TIMEOUT = 60 * 10
@staticmethod @staticmethod
def get_base_url(request=None): def get_base_url(request: Optional[WSGIRequest] = None) -> str:
""" """
String. The full qualified hostname and path to the dpaste instance. String. The full qualified hostname and path to the dpaste instance.
This is used to generate a link in the API response. If the "Sites" This is used to generate a link in the API response. If the "Sites"
@ -637,7 +644,7 @@ class dpasteAppConfig(AppConfig):
return "https://dpaste-base-url.example.org" return "https://dpaste-base-url.example.org"
@property @property
def extra_template_context(self): def extra_template_context(self) -> Dict[str, str]:
""" """
Returns a dictionary with context variables which are passed to Returns a dictionary with context variables which are passed to
all Template Views. all Template Views.

View file

@ -1,7 +1,9 @@
import datetime from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple, Type, Union
from django import forms from django import forms
from django.apps import apps from django.apps import apps
from django.core.handlers.wsgi import WSGIRequest
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .highlight import LEXER_CHOICES, LEXER_DEFAULT, LEXER_KEYS from .highlight import LEXER_CHOICES, LEXER_DEFAULT, LEXER_KEYS
@ -10,7 +12,9 @@ from .models import Snippet
config = apps.get_app_config("dpaste") config = apps.get_app_config("dpaste")
def get_expire_values(expires): def get_expire_values(
expires: str,
) -> Union[Tuple[None, int], Tuple[datetime, int]]:
if expires == "never": if expires == "never":
expire_type = Snippet.EXPIRE_KEEP expire_type = Snippet.EXPIRE_KEEP
expires = None expires = None
@ -20,9 +24,7 @@ def get_expire_values(expires):
else: else:
expire_type = Snippet.EXPIRE_TIME expire_type = Snippet.EXPIRE_TIME
expires = expires and expires or config.EXPIRE_DEFAULT expires = expires and expires or config.EXPIRE_DEFAULT
expires = datetime.datetime.now() + datetime.timedelta( expires = datetime.now() + timedelta(seconds=int(expires))
seconds=int(expires)
)
return expires, expire_type return expires, expire_type
@ -59,7 +61,7 @@ class SnippetForm(forms.ModelForm):
model = Snippet model = Snippet
fields = ("content", "lexer", "rtl") fields = ("content", "lexer", "rtl")
def __init__(self, request, *args, **kwargs): def __init__(self, request: WSGIRequest, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.request = request self.request = request
@ -72,13 +74,13 @@ class SnippetForm(forms.ModelForm):
if "l" in request.GET and request.GET["l"] in LEXER_KEYS: if "l" in request.GET and request.GET["l"] in LEXER_KEYS:
self.fields["lexer"].initial = request.GET["l"] self.fields["lexer"].initial = request.GET["l"]
def clean_content(self): def clean_content(self) -> str:
content = self.cleaned_data.get("content", "") content = self.cleaned_data.get("content", "")
if not content.strip(): if not content.strip():
raise forms.ValidationError(_("This field is required.")) raise forms.ValidationError(_("This field is required."))
return content return content
def clean_expires(self): def clean_expires(self) -> Optional[datetime]:
""" """
Extract the 'expire_type' from the choice of expire choices. Extract the 'expire_type' from the choice of expire choices.
""" """
@ -87,7 +89,7 @@ class SnippetForm(forms.ModelForm):
self.cleaned_data["expire_type"] = expire_type self.cleaned_data["expire_type"] = expire_type
return expires return expires
def clean(self): def clean(self) -> Dict[str, Optional[Union[Type[object], None]]]:
""" """
The `title` field is a hidden honeypot field. If its filled, The `title` field is a hidden honeypot field. If its filled,
this is likely spam. this is likely spam.
@ -96,7 +98,9 @@ class SnippetForm(forms.ModelForm):
raise forms.ValidationError("This snippet was identified as Spam.") raise forms.ValidationError("This snippet was identified as Spam.")
return self.cleaned_data return self.cleaned_data
def save(self, parent=None, *args, **kwargs): def save(
self, parent: Optional[Snippet] = None, *args, **kwargs
) -> Snippet:
# Set parent snippet # Set parent snippet
self.instance.parent = parent self.instance.parent = parent

View file

@ -1,9 +1,11 @@
from io import StringIO
from logging import getLogger from logging import getLogger
from typing import Any, Iterator, Optional, Type
from django.apps import apps from django.apps import apps
from django.template.defaultfilters import escape, linebreaksbr from django.template.defaultfilters import escape, linebreaksbr
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.safestring import mark_safe from django.utils.safestring import SafeString, mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from pygments import highlight from pygments import highlight
from pygments.formatters.html import HtmlFormatter from pygments.formatters.html import HtmlFormatter
@ -34,7 +36,13 @@ class Highlighter(object):
return l[1] return l[1]
return fallback return fallback
def render(self, code_string, lexer_name, direction=None, **kwargs): def render(
self,
code_string: str,
lexer_name: str,
direction: Optional[str] = None,
**kwargs
) -> SafeString:
highlighted_string = self.highlight(code_string, lexer_name=lexer_name) highlighted_string = self.highlight(code_string, lexer_name=lexer_name)
context = { context = {
"highlighted": highlighted_string, "highlighted": highlighted_string,
@ -52,7 +60,7 @@ class PlainTextHighlighter(Highlighter):
template_name = "dpaste/highlight/text.html" template_name = "dpaste/highlight/text.html"
def highlight(self, code_string, **kwargs): def highlight(self, code_string: str, **kwargs) -> SafeString:
return linebreaksbr(code_string) return linebreaksbr(code_string)
@ -99,7 +107,7 @@ class RestructuredTextHighlighter(PlainTextHighlighter):
}, },
} }
def highlight(self, code_string, **kwargs): def highlight(self, code_string: str, **kwargs) -> SafeString:
from docutils.core import publish_parts from docutils.core import publish_parts
self.publish_args["source"] = code_string self.publish_args["source"] = code_string
@ -113,10 +121,10 @@ class RestructuredTextHighlighter(PlainTextHighlighter):
class NakedHtmlFormatter(HtmlFormatter): class NakedHtmlFormatter(HtmlFormatter):
"""Pygments HTML formatter with no further HTML tags.""" """Pygments HTML formatter with no further HTML tags."""
def wrap(self, source, outfile): def wrap(self, source: Iterator[Any], outfile: StringIO) -> Iterator[Any]:
return self._wrap_code(source) return self._wrap_code(source)
def _wrap_code(self, source): def _wrap_code(self, source: Iterator[Any]) -> None:
yield from source yield from source
@ -125,7 +133,7 @@ class PlainCodeHighlighter(Highlighter):
Plain Code. No highlighting but Pygments like span tags around each line. Plain Code. No highlighting but Pygments like span tags around each line.
""" """
def highlight(self, code_string, **kwargs): def highlight(self, code_string: str, **kwargs) -> str:
return "\n".join( return "\n".join(
[ [
'<span class="plain">{}</span>'.format(escape(l) or "&#8203;") '<span class="plain">{}</span>'.format(escape(l) or "&#8203;")
@ -144,7 +152,7 @@ class PygmentsHighlighter(Highlighter):
lexer = None lexer = None
lexer_fallback = PythonLexer() lexer_fallback = PythonLexer()
def highlight(self, code_string, lexer_name): def highlight(self, code_string: str, lexer_name: str) -> str:
if not self.lexer: if not self.lexer:
try: try:
self.lexer = get_lexer_by_name(lexer_name) self.lexer = get_lexer_by_name(lexer_name)
@ -155,18 +163,7 @@ class PygmentsHighlighter(Highlighter):
return highlight(code_string, self.lexer, self.formatter) return highlight(code_string, self.lexer, self.formatter)
class SolidityHighlighter(PygmentsHighlighter): def get_highlighter_class(lexer_name: str) -> Type[Highlighter]:
"""Solidity Specific Highlighter. This uses a 3rd party Pygments lexer."""
def __init__(self):
# SolidityLexer does not necessarily need to be installed
# since its imported here and not used later.
from pygments_lexer_solidity import SolidityLexer
self.lexer = SolidityLexer()
def get_highlighter_class(lexer_name):
""" """
Get Highlighter for lexer name. Get Highlighter for lexer name.

View file

@ -5,31 +5,78 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Snippet', name="Snippet",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('secret_id', models.CharField(max_length=255, unique=True, null=True, verbose_name='Secret ID', blank=True)), "id",
('content', models.TextField(verbose_name='Content')), models.AutoField(
('lexer', models.CharField(default=b'python', max_length=30, verbose_name='Lexer')), verbose_name="ID",
('published', models.DateTimeField(auto_now_add=True, verbose_name='Published')), serialize=False,
('expire_type', models.PositiveSmallIntegerField(default=1, verbose_name='Expire Type', choices=[(1, 'Expire by timestamp'), (2, 'Keep Forever'), (3, 'One-Time snippet')])), auto_created=True,
('expires', models.DateTimeField(null=True, verbose_name='Expires', blank=True)), primary_key=True,
('view_count', models.PositiveIntegerField(default=0, verbose_name='View count')), ),
('lft', models.PositiveIntegerField(editable=False, db_index=True)), ),
('rght', models.PositiveIntegerField(editable=False, db_index=True)), (
('tree_id', models.PositiveIntegerField(editable=False, db_index=True)), "secret_id",
('level', models.PositiveIntegerField(editable=False, db_index=True)), models.CharField(
('parent', models.ForeignKey(related_name='children', blank=True, to='dpaste.Snippet', null=True, on_delete=models.CASCADE)), max_length=255,
unique=True,
null=True,
verbose_name="Secret ID",
blank=True,
),
),
("content", models.TextField(verbose_name="Content")),
(
"lexer",
models.CharField(
default=b"python", max_length=30, verbose_name="Lexer"
),
),
(
"published",
models.DateTimeField(auto_now_add=True, verbose_name="Published"),
),
(
"expire_type",
models.PositiveSmallIntegerField(
default=1,
verbose_name="Expire Type",
choices=[
(1, "Expire by timestamp"),
(2, "Keep Forever"),
(3, "One-Time snippet"),
], ],
options={ ),
'ordering': ('-published',), ),
'db_table': 'dpaste_snippet', (
}, "expires",
models.DateTimeField(null=True, verbose_name="Expires", blank=True),
),
(
"view_count",
models.PositiveIntegerField(default=0, verbose_name="View count"),
),
("lft", models.PositiveIntegerField(editable=False, db_index=True)),
("rght", models.PositiveIntegerField(editable=False, db_index=True)),
("tree_id", models.PositiveIntegerField(editable=False, db_index=True)),
("level", models.PositiveIntegerField(editable=False, db_index=True)),
(
"parent",
models.ForeignKey(
related_name="children",
blank=True,
to="dpaste.Snippet",
null=True,
on_delete=models.CASCADE,
),
),
],
options={"ordering": ("-published",), "db_table": "dpaste_snippet",},
bases=(models.Model,), bases=(models.Model,),
), ),
] ]

View file

@ -7,24 +7,12 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dpaste', '0001_initial'), ("dpaste", "0001_initial"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="snippet", name="level",),
model_name='snippet', migrations.RemoveField(model_name="snippet", name="lft",),
name='level', migrations.RemoveField(model_name="snippet", name="rght",),
), migrations.RemoveField(model_name="snippet", name="tree_id",),
migrations.RemoveField(
model_name='snippet',
name='lft',
),
migrations.RemoveField(
model_name='snippet',
name='rght',
),
migrations.RemoveField(
model_name='snippet',
name='tree_id',
),
] ]

View file

@ -7,14 +7,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dpaste', '0002_auto_20170119_1038'), ("dpaste", "0002_auto_20170119_1038"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='snippet', model_name="snippet",
name='highlighted', name="highlighted",
field=models.TextField(default='', verbose_name='Highlighted Content'), field=models.TextField(default="", verbose_name="Highlighted Content"),
preserve_default=False, preserve_default=False,
), ),
] ]

View file

@ -7,13 +7,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dpaste', '0003_snippet_highlighted'), ("dpaste", "0003_snippet_highlighted"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='snippet', model_name="snippet",
name='lexer', name="lexer",
field=models.CharField(default='python', max_length=30, verbose_name='Lexer'), field=models.CharField(
default="python", max_length=30, verbose_name="Lexer"
),
), ),
] ]

View file

@ -6,12 +6,9 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dpaste', '0004_auto_20180107_1603'), ("dpaste", "0004_auto_20180107_1603"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="snippet", name="highlighted",),
model_name='snippet',
name='highlighted',
),
] ]

View file

@ -7,13 +7,20 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dpaste', '0005_remove_snippet_highlighted'), ("dpaste", "0005_remove_snippet_highlighted"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='snippet', model_name="snippet",
name='parent', name="parent",
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dpaste.Snippet', verbose_name='Parent Snippet'), field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="dpaste.Snippet",
verbose_name="Parent Snippet",
),
), ),
] ]

View file

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dpaste', '0006_auto_20180622_1051'), ("dpaste", "0006_auto_20180622_1051"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='snippet', model_name="snippet",
name='rtl', name="rtl",
field=models.BooleanField(default=False, verbose_name='Right-to-left'), field=models.BooleanField(default=False, verbose_name="Right-to-left"),
), ),
] ]

View file

@ -4,6 +4,7 @@ from random import SystemRandom
from django.apps import apps from django.apps import apps
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import SafeString
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dpaste import highlight from dpaste import highlight
@ -13,7 +14,7 @@ logger = getLogger(__file__)
R = SystemRandom() R = SystemRandom()
def generate_secret_id(length): def generate_secret_id(length: int) -> str:
if length > config.SLUG_LENGTH: if length > config.SLUG_LENGTH:
logger.warning( logger.warning(
"Slug creation triggered a duplicate, " "Slug creation triggered a duplicate, "
@ -75,18 +76,18 @@ class Snippet(models.Model):
ordering = ("-published",) ordering = ("-published",)
db_table = "dpaste_snippet" db_table = "dpaste_snippet"
def __str__(self): def __str__(self) -> str:
return self.secret_id return self.secret_id
def save(self, *args, **kwargs): def save(self, *args, **kwargs) -> None:
if not self.secret_id: if not self.secret_id:
self.secret_id = generate_secret_id(length=config.SLUG_LENGTH) self.secret_id = generate_secret_id(length=config.SLUG_LENGTH)
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self) -> str:
return reverse("snippet_details", kwargs={"snippet_id": self.secret_id}) return reverse("snippet_details", kwargs={"snippet_id": self.secret_id})
def highlight(self): def highlight(self) -> SafeString:
HighlighterClass = highlight.get_highlighter_class(self.lexer) HighlighterClass = highlight.get_highlighter_class(self.lexer)
return HighlighterClass().render( return HighlighterClass().render(
code_string=self.content, code_string=self.content,
@ -95,12 +96,12 @@ class Snippet(models.Model):
) )
@property @property
def lexer_name(self): def lexer_name(self) -> str:
"""Display name for this lexer.""" """Display name for this lexer."""
return highlight.Highlighter.get_lexer_display_name(self.lexer) return highlight.Highlighter.get_lexer_display_name(self.lexer)
@property @property
def remaining_views(self): def remaining_views(self) -> int:
if self.expire_type == self.EXPIRE_ONETIME: if self.expire_type == self.EXPIRE_ONETIME:
remaining = config.ONETIME_LIMIT - self.view_count remaining = config.ONETIME_LIMIT - self.view_count
return remaining > 0 and remaining or 0 return remaining > 0 and remaining or 0

View file

@ -1,8 +1,11 @@
import datetime import datetime
import difflib import difflib
import json import json
from typing import Any, Dict, Optional, Union
from django.apps import apps from django.apps import apps
from django.core.handlers.wsgi import WSGIRequest
from django.db.models.query import QuerySet
from django.http import ( from django.http import (
Http404, Http404,
HttpResponse, HttpResponse,
@ -11,9 +14,11 @@ from django.http import (
HttpResponseRedirect, HttpResponseRedirect,
) )
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.template.response import TemplateResponse
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.cache import add_never_cache_headers, patch_cache_control from django.utils.cache import add_never_cache_headers, patch_cache_control
from django.utils.datastructures import MultiValueDict
from django.utils.http import http_date from django.utils.http import http_date
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.views.generic import FormView from django.views.generic import FormView
@ -23,6 +28,7 @@ from pygments.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound from pygments.util import ClassNotFound
from dpaste import highlight from dpaste import highlight
from dpaste.apps import dpasteAppConfig
from dpaste.forms import SnippetForm, get_expire_values from dpaste.forms import SnippetForm, get_expire_values
from dpaste.highlight import PygmentsHighlighter from dpaste.highlight import PygmentsHighlighter
from dpaste.models import Snippet from dpaste.models import Snippet
@ -43,22 +49,26 @@ class SnippetView(FormView):
form_class = SnippetForm form_class = SnippetForm
template_name = "dpaste/new.html" template_name = "dpaste/new.html"
def get(self, request, *args, **kwargs): def get(self, request: WSGIRequest, *args, **kwargs) -> TemplateResponse:
response = super().get(request, *args, **kwargs) response = super().get(request, *args, **kwargs)
if config.CACHE_HEADER: if config.CACHE_HEADER:
patch_cache_control(response, max_age=config.CACHE_TIMEOUT) patch_cache_control(response, max_age=config.CACHE_TIMEOUT)
return response return response
def get_form_kwargs(self): def get_form_kwargs(
self,
) -> Dict[str, Optional[Union[MultiValueDict, WSGIRequest]]]:
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs.update({"request": self.request}) kwargs.update({"request": self.request})
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form: SnippetForm) -> HttpResponseRedirect:
snippet = form.save() snippet = form.save()
return HttpResponseRedirect(snippet.get_absolute_url()) return HttpResponseRedirect(snippet.get_absolute_url())
def get_context_data(self, **kwargs): def get_context_data(
self, **kwargs
) -> Dict[str, Union[SnippetForm, "SnippetView", str]]:
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx.update(config.extra_template_context) ctx.update(config.extra_template_context)
return ctx return ctx
@ -76,7 +86,9 @@ class SnippetDetailView(DetailView, FormView):
slug_url_kwarg = "snippet_id" slug_url_kwarg = "snippet_id"
slug_field = "secret_id" slug_field = "secret_id"
def post(self, request, *args, **kwargs): def post(
self, request: WSGIRequest, *args, **kwargs
) -> Union[HttpResponseRedirect, TemplateResponse]:
""" """
Delete a snippet. This is allowed by anybody as long as he knows the Delete a snippet. This is allowed by anybody as long as he knows the
snippet id. I got too many manual requests to do this, mostly for legal snippet id. I got too many manual requests to do this, mostly for legal
@ -95,7 +107,7 @@ class SnippetDetailView(DetailView, FormView):
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
def get(self, request, *args, **kwargs): def get(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
snippet = self.get_object() snippet = self.get_object()
# One-Time snippet get deleted if the view count matches our limit # One-Time snippet get deleted if the view count matches our limit
@ -126,7 +138,7 @@ class SnippetDetailView(DetailView, FormView):
return response return response
def get_initial(self): def get_initial(self) -> Dict[str, Union[str, bool]]:
snippet = self.get_object() snippet = self.get_object()
return { return {
"content": snippet.content, "content": snippet.content,
@ -134,16 +146,23 @@ class SnippetDetailView(DetailView, FormView):
"rtl": snippet.rtl, "rtl": snippet.rtl,
} }
def get_form_kwargs(self): def get_form_kwargs(
self,
) -> Dict[
str,
Optional[
Union[Dict[str, Union[str, bool]], MultiValueDict, WSGIRequest]
],
]:
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs.update({"request": self.request}) kwargs.update({"request": self.request})
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form: SnippetForm) -> HttpResponseRedirect:
snippet = form.save(parent=self.get_object()) snippet = form.save(parent=self.get_object())
return HttpResponseRedirect(snippet.get_absolute_url()) return HttpResponseRedirect(snippet.get_absolute_url())
def get_snippet_diff(self): def get_snippet_diff(self) -> None:
snippet = self.get_object() snippet = self.get_object()
if not snippet.parent_id: if not snippet.parent_id:
@ -165,7 +184,7 @@ class SnippetDetailView(DetailView, FormView):
# Remove blank lines # Remove blank lines
return highlighted return highlighted
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs) -> Dict[str, Any]:
self.object = self.get_object() self.object = self.get_object()
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
@ -187,26 +206,30 @@ class SnippetRawView(SnippetDetailView):
template_name = "dpaste/raw.html" template_name = "dpaste/raw.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
if not config.RAW_MODE_ENABLED: if not config.RAW_MODE_ENABLED:
return HttpResponseForbidden( return HttpResponseForbidden(
ugettext("This dpaste installation has Raw view mode disabled.") ugettext("This dpaste installation has Raw view mode disabled.")
) )
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def render_plain_text(self, context, **response_kwargs): def render_plain_text(
self, context: dpasteAppConfig, **response_kwargs
) -> HttpResponse:
snippet = self.get_object() snippet = self.get_object()
response = HttpResponse(snippet.content) response = HttpResponse(snippet.content)
response["Content-Type"] = "text/plain;charset=UTF-8" response["Content-Type"] = "text/plain;charset=UTF-8"
response["X-Content-Type-Options"] = "nosniff" response["X-Content-Type-Options"] = "nosniff"
return response return response
def render_to_response(self, context, **response_kwargs): def render_to_response(
self, context: Dict[str, Any], **response_kwargs
) -> HttpResponse:
if config.RAW_MODE_PLAIN_TEXT: if config.RAW_MODE_PLAIN_TEXT:
return self.render_plain_text(config, **response_kwargs) return self.render_plain_text(config, **response_kwargs)
return super().render_to_response(context, **response_kwargs) return super().render_to_response(context, **response_kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs) -> Dict[str, Any]:
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx.update(config.extra_template_context) ctx.update(config.extra_template_context)
return ctx return ctx
@ -220,16 +243,18 @@ class SnippetHistory(TemplateView):
template_name = "dpaste/history.html" template_name = "dpaste/history.html"
def get_user_snippets(self): def get_user_snippets(self) -> QuerySet:
snippet_id_list = self.request.session.get("snippet_list", []) snippet_id_list = self.request.session.get("snippet_list", [])
return Snippet.objects.filter(pk__in=snippet_id_list) return Snippet.objects.filter(pk__in=snippet_id_list)
def get(self, request, *args, **kwargs): def get(self, request: WSGIRequest, *args, **kwargs) -> TemplateResponse:
response = super().get(request, *args, **kwargs) response = super().get(request, *args, **kwargs)
add_never_cache_headers(response) add_never_cache_headers(response)
return response return response
def post(self, request, *args, **kwargs): def post(
self, request: WSGIRequest, *args, **kwargs
) -> HttpResponseRedirect:
""" """
Delete all user snippets at once. Delete all user snippets at once.
""" """
@ -240,7 +265,9 @@ class SnippetHistory(TemplateView):
url = "{0}#".format(reverse("snippet_history")) url = "{0}#".format(reverse("snippet_history"))
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
def get_context_data(self, **kwargs): def get_context_data(
self, **kwargs
) -> Dict[str, Union["SnippetHistory", QuerySet, str]]:
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx.update({"snippet_list": self.get_user_snippets()}) ctx.update({"snippet_list": self.get_user_snippets()})
ctx.update(config.extra_template_context) ctx.update(config.extra_template_context)
@ -257,14 +284,14 @@ class APIView(View):
API View API View
""" """
def _format_default(self, s): def _format_default(self, s: Snippet) -> str:
""" """
The default response is the snippet URL wrapped in quotes. The default response is the snippet URL wrapped in quotes.
""" """
base_url = config.get_base_url(request=self.request) base_url = config.get_base_url(request=self.request)
return f'"{base_url}{s.get_absolute_url()}"' return f'"{base_url}{s.get_absolute_url()}"'
def _format_url(self, s): def _format_url(self, s: Snippet) -> str:
""" """
The `url` format returns the snippet URL, The `url` format returns the snippet URL,
no quotes, but a linebreak at the end. no quotes, but a linebreak at the end.
@ -272,7 +299,7 @@ class APIView(View):
base_url = config.get_base_url(request=self.request) base_url = config.get_base_url(request=self.request)
return f"{base_url}{s.get_absolute_url()}\n" return f"{base_url}{s.get_absolute_url()}\n"
def _format_json(self, s): def _format_json(self, s: Snippet) -> str:
""" """
The `json` format export. The `json` format export.
""" """
@ -285,7 +312,7 @@ class APIView(View):
} }
) )
def post(self, request, *args, **kwargs): def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse:
content = request.POST.get("content", "") content = request.POST.get("content", "")
lexer = request.POST.get("lexer", highlight.LEXER_DEFAULT).strip() lexer = request.POST.get("lexer", highlight.LEXER_DEFAULT).strip()
filename = request.POST.get("filename", "").strip() filename = request.POST.get("filename", "").strip()
@ -357,7 +384,11 @@ class APIView(View):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def page_not_found(request, exception=None, template_name="dpaste/404.html"): def page_not_found(
request: WSGIRequest,
exception: Optional[Http404] = None,
template_name: str = "dpaste/404.html",
) -> HttpResponse:
context = {} context = {}
context.update(config.extra_template_context) context.update(config.extra_template_context)
response = render(request, template_name, context, status=404) response = render(request, template_name, context, status=404)