mirror of
https://github.com/DarrenOfficial/dpaste.git
synced 2024-11-15 16:12:51 +11:00
First pass of Type Annotations
This commit is contained in:
parent
08f7d9c27f
commit
cf3ad14795
12 changed files with 204 additions and 123 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 "​")
|
'<span class="plain">{}</span>'.format(escape(l) or "​")
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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,),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue