Black'ed the entire codebase

black dpaste/ --skip-string-normalization --exclude="migrations"
This commit is contained in:
Martin Mahner 2018-07-04 12:06:48 +02:00
parent 3b85c0e910
commit 00c6f058b9
15 changed files with 248 additions and 202 deletions

View file

@ -1,9 +1,7 @@
VERSION = (3, 0, 'a', 1) VERSION = (3, 0, 'a', 1)
__version__ = '{major}.{minor}{rest}'.format( __version__ = '{major}.{minor}{rest}'.format(
major=VERSION[0], major=VERSION[0], minor=VERSION[1], rest=''.join(str(i) for i in VERSION[2:])
minor=VERSION[1],
rest=''.join(str(i) for i in VERSION[2:])
) )
default_app_config = 'dpaste.apps.dpasteAppConfig' default_app_config = 'dpaste.apps.dpasteAppConfig'

View file

@ -96,8 +96,9 @@ class dpasteAppConfig(AppConfig):
from dpaste.highlight import ( from dpaste.highlight import (
PlainTextHighlighter, PlainTextHighlighter,
MarkdownHighlighter, MarkdownHighlighter,
RestructuredTextHighlighter RestructuredTextHighlighter,
) )
return [ return [
(self.PLAIN_TEXT_SYMBOL, 'Plain Text', PlainTextHighlighter), (self.PLAIN_TEXT_SYMBOL, 'Plain Text', PlainTextHighlighter),
('_markdown', 'Markdown', MarkdownHighlighter), ('_markdown', 'Markdown', MarkdownHighlighter),
@ -116,10 +117,8 @@ class dpasteAppConfig(AppConfig):
If the Highlight Class is not given, PygmentsHighlighter is used. If the Highlight Class is not given, PygmentsHighlighter is used.
""" """
from dpaste.highlight import ( from dpaste.highlight import PlainCodeHighlighter, SolidityHighlighter
PlainCodeHighlighter,
SolidityHighlighter
)
return [ return [
(self.PLAIN_CODE_SYMBOL, 'Plain Code', PlainCodeHighlighter), (self.PLAIN_CODE_SYMBOL, 'Plain Code', PlainCodeHighlighter),
('abap', 'ABAP'), ('abap', 'ABAP'),
@ -204,6 +203,7 @@ class dpasteAppConfig(AppConfig):
""" """
if apps.is_installed('django.contrib.sites'): if apps.is_installed('django.contrib.sites'):
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
site = get_current_site(request) site = get_current_site(request)
if site: if site:
return 'https://{0}'.format(site.domain) return 'https://{0}'.format(site.domain)

View file

@ -29,34 +29,27 @@ class SnippetForm(forms.ModelForm):
label=_('Content'), label=_('Content'),
widget=forms.Textarea(attrs={'placeholder': _('Awesome code goes here...')}), widget=forms.Textarea(attrs={'placeholder': _('Awesome code goes here...')}),
max_length=config.MAX_CONTENT_LENGTH, max_length=config.MAX_CONTENT_LENGTH,
strip=False strip=False,
) )
lexer = forms.ChoiceField( lexer = forms.ChoiceField(
label=_('Lexer'), label=_('Lexer'), initial=LEXER_DEFAULT, choices=LEXER_CHOICES
initial=LEXER_DEFAULT,
choices=LEXER_CHOICES
) )
expires = forms.ChoiceField( expires = forms.ChoiceField(
label=_('Expires'), label=_('Expires'), choices=config.EXPIRE_CHOICES, initial=config.EXPIRE_DEFAULT
choices=config.EXPIRE_CHOICES,
initial=config.EXPIRE_DEFAULT
) )
# Honeypot field # Honeypot field
title = forms.CharField( title = forms.CharField(
label=_('Title'), label=_('Title'),
required=False, required=False,
widget=forms.TextInput(attrs={'autocomplete': 'off'}) widget=forms.TextInput(attrs={'autocomplete': 'off'}),
) )
class Meta: class Meta:
model = Snippet model = Snippet
fields = ( fields = ('content', 'lexer')
'content',
'lexer',
)
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(SnippetForm, self).__init__(*args, **kwargs) super(SnippetForm, self).__init__(*args, **kwargs)

View file

@ -19,6 +19,7 @@ config = apps.get_app_config('dpaste')
# Highlight Code Snippets # Highlight Code Snippets
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Highlighter(object): class Highlighter(object):
template_name = 'dpaste/highlight/code.html' template_name = 'dpaste/highlight/code.html'
@ -47,6 +48,7 @@ class Highlighter(object):
class PlainTextHighlighter(Highlighter): class PlainTextHighlighter(Highlighter):
"""Plain Text. Just replace linebreaks.""" """Plain Text. Just replace linebreaks."""
template_name = 'dpaste/highlight/text.html' template_name = 'dpaste/highlight/text.html'
def highlight(self, code_string, **kwargs): def highlight(self, code_string, **kwargs):
@ -55,20 +57,33 @@ class PlainTextHighlighter(Highlighter):
class MarkdownHighlighter(PlainTextHighlighter): class MarkdownHighlighter(PlainTextHighlighter):
"""Markdown""" """Markdown"""
extensions = ('tables', 'fenced-code', 'footnotes', 'autolink,',
'strikethrough', 'underline', 'quote', 'superscript', extensions = (
'math') 'tables',
'fenced-code',
'footnotes',
'autolink,',
'strikethrough',
'underline',
'quote',
'superscript',
'math',
)
render_flags = ('skip-html',) render_flags = ('skip-html',)
def highlight(self, code_string, **kwargs): def highlight(self, code_string, **kwargs):
import misaka import misaka
return mark_safe(misaka.html(code_string,
extensions=self.extensions, return mark_safe(
render_flags=self.render_flags)) misaka.html(
code_string, extensions=self.extensions, render_flags=self.render_flags
)
)
class RestructuredTextHighlighter(PlainTextHighlighter): class RestructuredTextHighlighter(PlainTextHighlighter):
"""Restructured Text""" """Restructured Text"""
rst_part_name = 'html_body' rst_part_name = 'html_body'
publish_args = { publish_args = {
'writer_name': 'html5_polyglot', 'writer_name': 'html5_polyglot',
@ -78,11 +93,12 @@ class RestructuredTextHighlighter(PlainTextHighlighter):
'halt_level': 5, 'halt_level': 5,
'report_level': 2, 'report_level': 2,
'warning_stream': '/dev/null', 'warning_stream': '/dev/null',
} },
} }
def highlight(self, code_string, **kwargs): def highlight(self, code_string, **kwargs):
from docutils.core import publish_parts from docutils.core import publish_parts
self.publish_args['source'] = code_string self.publish_args['source'] = code_string
parts = publish_parts(**self.publish_args) parts = publish_parts(**self.publish_args)
return mark_safe(parts[self.rst_part_name]) return mark_safe(parts[self.rst_part_name])
@ -93,6 +109,7 @@ 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, outfile):
return self._wrap_code(source) return self._wrap_code(source)
@ -105,11 +122,14 @@ 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, **kwargs):
return '\n'.join([ return '\n'.join(
[
'<span class="plain">{}</span>'.format(escape(l) or '&#8203;') '<span class="plain">{}</span>'.format(escape(l) or '&#8203;')
for l in code_string.splitlines() for l in code_string.splitlines()
]) ]
)
class PygmentsHighlighter(Highlighter): class PygmentsHighlighter(Highlighter):
@ -117,6 +137,7 @@ class PygmentsHighlighter(Highlighter):
Highlight code string with Pygments. The lexer is automatically Highlight code string with Pygments. The lexer is automatically
determined by the lexer name. determined by the lexer name.
""" """
formatter = NakedHtmlFormatter() formatter = NakedHtmlFormatter()
lexer = None lexer = None
lexer_fallback = PythonLexer() lexer_fallback = PythonLexer()
@ -139,6 +160,7 @@ class SolidityHighlighter(PygmentsHighlighter):
# SolidityLexer does not necessarily need to be installed # SolidityLexer does not necessarily need to be installed
# since its imported here and not used later. # since its imported here and not used later.
from pygments_lexer_solidity import SolidityLexer from pygments_lexer_solidity import SolidityLexer
self.lexer = SolidityLexer() self.lexer = SolidityLexer()
@ -167,12 +189,13 @@ def get_highlighter_class(lexer_name):
# Generate a list of Form choices of all lexer. # Generate a list of Form choices of all lexer.
LEXER_CHOICES = ( LEXER_CHOICES = (
(_('Text'), [i[:2] for i in config.TEXT_FORMATTER]), (_('Text'), [i[:2] for i in config.TEXT_FORMATTER]),
(_('Code'), [i[:2] for i in config.CODE_FORMATTER]) (_('Code'), [i[:2] for i in config.CODE_FORMATTER]),
) )
# List of all Lexer Keys # List of all Lexer Keys
LEXER_KEYS = [i[0] for i in config.TEXT_FORMATTER] + \ LEXER_KEYS = [i[0] for i in config.TEXT_FORMATTER] + [
[i[0] for i in config.CODE_FORMATTER] i[0] for i in config.CODE_FORMATTER
]
# The default lexer which we fallback in case of # The default lexer which we fallback in case of
# an error or if not supplied in an API call. # an error or if not supplied in an API call.

View file

@ -8,14 +8,15 @@ class Command(BaseCommand):
help = "Purges snippets that are expired" help = "Purges snippets that are expired"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('--dry-run', action='store_true', dest='dry_run', parser.add_argument(
help='Don\'t do anything.'), '--dry-run', action='store_true', dest='dry_run', help='Don\'t do anything.'
),
def handle(self, *args, **options): def handle(self, *args, **options):
deleteable_snippets = Snippet.objects.filter( deleteable_snippets = Snippet.objects.filter(
expires__isnull=False, expires__isnull=False,
expire_type=Snippet.EXPIRE_TIME, expire_type=Snippet.EXPIRE_TIME,
expires__lte=timezone.now() expires__lte=timezone.now(),
) )
if len(deleteable_snippets) == 0: if len(deleteable_snippets) == 0:
self.stdout.write(u"No snippets to delete.") self.stdout.write(u"No snippets to delete.")

View file

@ -16,11 +16,14 @@ R = SystemRandom()
def generate_secret_id(length): def generate_secret_id(length):
if length > config.SLUG_LENGTH: if length > config.SLUG_LENGTH:
logger.warning('Slug creation triggered a duplicate, ' logger.warning(
'consider increasing the SLUG_LENGTH.') 'Slug creation triggered a duplicate, '
'consider increasing the SLUG_LENGTH.'
)
secret_id = ''.join([R.choice(config.SLUG_CHOICES) secret_id = ''.join(
for i in range(length or config.SLUG_LENGTH)]) [R.choice(config.SLUG_CHOICES) for i in range(length or config.SLUG_LENGTH)]
)
# Check if this slug already exists, if not, return this new slug # Check if this slug already exists, if not, return this new slug
try: try:
@ -30,7 +33,7 @@ def generate_secret_id(length):
# Otherwise create a new slug which is +1 character longer # Otherwise create a new slug which is +1 character longer
# than the previous one. # than the previous one.
return generate_secret_id(length=length+1) return generate_secret_id(length=length + 1)
@python_2_unicode_compatible @python_2_unicode_compatible
@ -45,21 +48,24 @@ class Snippet(models.Model):
) )
secret_id = models.CharField( secret_id = models.CharField(
_('Secret ID'), max_length=255, blank=True, null=True, unique=True) _('Secret ID'), max_length=255, blank=True, null=True, unique=True
)
content = models.TextField(_('Content')) content = models.TextField(_('Content'))
lexer = models.CharField( lexer = models.CharField(_('Lexer'), max_length=30, default=highlight.LEXER_DEFAULT)
_('Lexer'), max_length=30, default=highlight.LEXER_DEFAULT) published = models.DateTimeField(_('Published'), auto_now_add=True)
published = models.DateTimeField(
_('Published'), auto_now_add=True)
expire_type = models.PositiveSmallIntegerField( expire_type = models.PositiveSmallIntegerField(
_('Expire Type'), choices=EXPIRE_CHOICES, default=EXPIRE_CHOICES[0][0]) _('Expire Type'), choices=EXPIRE_CHOICES, default=EXPIRE_CHOICES[0][0]
expires = models.DateTimeField( )
_('Expires'), blank=True, null=True) expires = models.DateTimeField(_('Expires'), blank=True, null=True)
view_count = models.PositiveIntegerField( view_count = models.PositiveIntegerField(_('View count'), default=0)
_('View count'), default=0)
parent = models.ForeignKey( parent = models.ForeignKey(
'self', null=True, blank=True, verbose_name=_('Parent Snippet'), 'self',
related_name='children', on_delete=models.CASCADE) null=True,
blank=True,
verbose_name=_('Parent Snippet'),
related_name='children',
on_delete=models.CASCADE,
)
class Meta: class Meta:
ordering = ('-published',) ordering = ('-published',)

View file

@ -49,9 +49,7 @@ USE_I18N = True
USE_L10N = False USE_L10N = False
LANGUAGE_CODE = 'en' LANGUAGE_CODE = 'en'
LANGUAGES = ( LANGUAGES = (('en', 'English'),)
('en', 'English'),
)
# LOCALE_PATHS = ( # LOCALE_PATHS = (
# os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'locale')), # os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'locale')),
@ -63,9 +61,7 @@ LANGUAGES = (
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
STATICFILES_DIRS = ( STATICFILES_DIRS = (os.path.join(PROJECT_DIR, 'build'),)
os.path.join(PROJECT_DIR, 'build'),
)
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
@ -107,9 +103,9 @@ TEMPLATES = [
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.template.context_processors.i18n', 'django.template.context_processors.i18n',
], ]
},
}, },
}
] ]
INSTALLED_APPS = [ INSTALLED_APPS = [
@ -146,16 +142,12 @@ CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'filters': { 'filters': {'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}},
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': { 'handlers': {
'mail_admins': { 'mail_admins': {
'level': 'ERROR', 'level': 'ERROR',
'filters': ['require_debug_false'], 'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler' 'class': 'django.utils.log.AdminEmailHandler',
} }
}, },
'loggers': { 'loggers': {
@ -163,6 +155,6 @@ LOGGING = {
'handlers': ['mail_admins'], 'handlers': ['mail_admins'],
'level': 'ERROR', 'level': 'ERROR',
'propagate': True, 'propagate': True,
},
} }
},
} }

View file

@ -4,9 +4,4 @@ Settings for the test suite
from .base import * from .base import *
DATABASES = { DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}

View file

@ -11,7 +11,6 @@ config = apps.get_app_config('dpaste')
class SnippetAPITestCase(TestCase): class SnippetAPITestCase(TestCase):
def setUp(self): def setUp(self):
self.api_url = reverse('dpaste_api_create_snippet') self.api_url = reverse('dpaste_api_create_snippet')
self.client = Client(enforce_csrf_checks=True) self.client = Client(enforce_csrf_checks=True)
@ -84,7 +83,6 @@ class SnippetAPITestCase(TestCase):
self.assertTrue(content.startswith('http')) self.assertTrue(content.startswith('http'))
self.assertTrue(content.endswith('\n')) self.assertTrue(content.endswith('\n'))
def test_json_format(self): def test_json_format(self):
""" """
The 'new' url format is just the link with a linebreak. The 'new' url format is just the link with a linebreak.
@ -92,7 +90,7 @@ class SnippetAPITestCase(TestCase):
data = { data = {
'content': u"Hello Wörld.\n\tGood Bye", 'content': u"Hello Wörld.\n\tGood Bye",
'format': 'json', 'format': 'json',
'lexer': 'haskell' 'lexer': 'haskell',
} }
response = self.client.post(self.api_url, data) response = self.client.post(self.api_url, data)
@ -102,6 +100,7 @@ class SnippetAPITestCase(TestCase):
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
from json import loads from json import loads
json_data = loads(content) json_data = loads(content)
# Response is valid json, containing, content, lexer and url # Response is valid json, containing, content, lexer and url
@ -118,7 +117,7 @@ class SnippetAPITestCase(TestCase):
data = { data = {
'content': u"Hello Wörld.\n\tGood Bye", 'content': u"Hello Wörld.\n\tGood Bye",
'format': 'broken-format', 'format': 'broken-format',
'lexer': 'haskell' 'lexer': 'haskell',
} }
response = self.client.post(self.api_url, data) response = self.client.post(self.api_url, data)
@ -129,26 +128,25 @@ class SnippetAPITestCase(TestCase):
""" """
A broken lexer will fail loudly. A broken lexer will fail loudly.
""" """
data = { data = {'content': u"Hello Wörld.\n\tGood Bye", 'lexer': 'foobar'}
'content': u"Hello Wörld.\n\tGood Bye",
'lexer': 'foobar'
}
response = self.client.post(self.api_url, data) response = self.client.post(self.api_url, data)
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual(Snippet.objects.count(), 0) self.assertEqual(Snippet.objects.count(), 0)
def test_expire_choices_none_given(self): def test_expire_choices_none_given(self):
# No expire choice given will set a default expiration of one month # No expire choice given will set a default expiration of one month
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye"}) self.api_url, {'content': u"Hello Wörld.\n\tGood Bye"}
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertTrue(Snippet.objects.all()[0].expires) self.assertTrue(Snippet.objects.all()[0].expires)
def test_expire_choices_invalid_given(self): def test_expire_choices_invalid_given(self):
# A expire choice that does not exist returns a BadRequest # A expire choice that does not exist returns a BadRequest
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'foobar'}) self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'foobar'}
)
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual(Snippet.objects.count(), 0) self.assertEqual(Snippet.objects.count(), 0)
@ -156,37 +154,45 @@ class SnippetAPITestCase(TestCase):
Test all the different expiration choices. We dont actually test Test all the different expiration choices. We dont actually test
the deletion, since thats handled in the `test_snippet` section. the deletion, since thats handled in the `test_snippet` section.
""" """
def test_valid_expiration_choices_onetime(self): def test_valid_expiration_choices_onetime(self):
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'onetime'}) self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'onetime'}
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_ONETIME) self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_ONETIME)
def test_valid_expiration_choices_never(self): def test_valid_expiration_choices_never(self):
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'never'}) self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'never'}
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_KEEP) self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_KEEP)
def test_valid_expiration_choices_hour(self): def test_valid_expiration_choices_hour(self):
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600}) self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600}
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertTrue(Snippet.objects.all()[0].expires) self.assertTrue(Snippet.objects.all()[0].expires)
def test_valid_expiration_choices_week(self): def test_valid_expiration_choices_week(self):
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 7}) self.api_url,
{'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 7},
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertTrue(Snippet.objects.all()[0].expires) self.assertTrue(Snippet.objects.all()[0].expires)
def test_valid_expiration_choices_month(self): def test_valid_expiration_choices_month(self):
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 30}) self.api_url,
{'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 30},
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertTrue(Snippet.objects.all()[0].expires) self.assertTrue(Snippet.objects.all()[0].expires)
@ -195,22 +201,24 @@ class SnippetAPITestCase(TestCase):
""" """
No lexer and no filename given returns a BadRequest. No lexer and no filename given returns a BadRequest.
""" """
response = self.client.post(self.api_url, { response = self.client.post(
'content': u"Hello Wörld.\n\tGood Bye", self.api_url,
'lexer': '', {'content': u"Hello Wörld.\n\tGood Bye", 'lexer': '', 'filename': ''},
'filename': '' )
})
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
def test_filename_given(self): def test_filename_given(self):
""" """
No lexer and a Python filename will set a 'python' lexer. No lexer and a Python filename will set a 'python' lexer.
""" """
response = self.client.post(self.api_url, { response = self.client.post(
self.api_url,
{
'content': u"Hello Wörld.\n\tGood Bye", 'content': u"Hello Wörld.\n\tGood Bye",
'lexer': '', 'lexer': '',
'filename': 'helloworld.py' 'filename': 'helloworld.py',
}) },
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertEqual(Snippet.objects.all()[0].lexer, 'python') self.assertEqual(Snippet.objects.all()[0].lexer, 'python')
@ -219,11 +227,14 @@ class SnippetAPITestCase(TestCase):
""" """
A unknown filename will create a 'plain' code snippet. A unknown filename will create a 'plain' code snippet.
""" """
response = self.client.post(self.api_url, { response = self.client.post(
self.api_url,
{
'content': u"Hello Wörld.\n\tGood Bye", 'content': u"Hello Wörld.\n\tGood Bye",
'lexer': '', 'lexer': '',
'filename': 'helloworld.helloworld' 'filename': 'helloworld.helloworld',
}) },
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertEqual(Snippet.objects.all()[0].lexer, config.PLAIN_CODE_SYMBOL) self.assertEqual(Snippet.objects.all()[0].lexer, config.PLAIN_CODE_SYMBOL)
@ -232,11 +243,14 @@ class SnippetAPITestCase(TestCase):
""" """
A given lexer will overwrite whats the filename guessing. A given lexer will overwrite whats the filename guessing.
""" """
response = self.client.post(self.api_url, { response = self.client.post(
self.api_url,
{
'content': u"Hello Wörld.\n\tGood Bye", 'content': u"Hello Wörld.\n\tGood Bye",
'lexer': 'php', 'lexer': 'php',
'filename': 'helloworld.py' 'filename': 'helloworld.py',
}) },
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
self.assertEqual(Snippet.objects.all()[0].lexer, 'php') self.assertEqual(Snippet.objects.all()[0].lexer, 'php')
@ -248,5 +262,3 @@ class SnippetAPITestCase(TestCase):
content = ' one\n two\n three\n four' content = ' one\n two\n three\n four'
self.client.post(self.api_url, {'content': content}) self.client.post(self.api_url, {'content': content})
self.assertEqual(Snippet.objects.all()[0].content, content) self.assertEqual(Snippet.objects.all()[0].content, content)

View file

@ -4,12 +4,14 @@ from textwrap import dedent
from django.test import TestCase from django.test import TestCase
from dpaste.highlight import PlainCodeHighlighter, PygmentsHighlighter, \ from dpaste.highlight import (
RestructuredTextHighlighter PlainCodeHighlighter,
PygmentsHighlighter,
RestructuredTextHighlighter,
)
class HighlightAPITestCase(TestCase): class HighlightAPITestCase(TestCase):
def test_plain_code(self): def test_plain_code(self):
""" """
PLAIN_CODE is not run through Pygments, test it separately. PLAIN_CODE is not run through Pygments, test it separately.
@ -32,15 +34,13 @@ class HighlightAPITestCase(TestCase):
""" """
Whitespace on the first line is retained, also on subsequent lines. Whitespace on the first line is retained, also on subsequent lines.
""" """
input = (' vär=1\n' input = ' vär=1\n' ' vär=2\n' ' vär=3\n' ' vär=4'
' vär=2\n'
' vär=3\n'
' vär=4')
expected = ( expected = (
'<span class="plain"> vär=1</span>\n' '<span class="plain"> vär=1</span>\n'
'<span class="plain"> vär=2</span>\n' '<span class="plain"> vär=2</span>\n'
'<span class="plain"> vär=3</span>\n' '<span class="plain"> vär=3</span>\n'
'<span class="plain"> vär=4</span>') '<span class="plain"> vär=4</span>'
)
value = PlainCodeHighlighter().highlight(input) value = PlainCodeHighlighter().highlight(input)
self.assertEqual(value, expected) self.assertEqual(value, expected)
@ -67,15 +67,13 @@ class HighlightAPITestCase(TestCase):
""" """
Whitespace on the first line is retained, also on subsequent lines. Whitespace on the first line is retained, also on subsequent lines.
""" """
input = (' var\n' input = ' var\n' ' var\n' ' var\n' ' var'
' var\n'
' var\n'
' var')
expected = ( expected = (
' <span class="n">var</span>\n' ' <span class="n">var</span>\n'
' <span class="n">var</span>\n' ' <span class="n">var</span>\n'
' <span class="n">var</span>\n' ' <span class="n">var</span>\n'
' <span class="n">var</span>\n') ' <span class="n">var</span>\n'
)
value = PygmentsHighlighter().highlight(input, 'python') value = PygmentsHighlighter().highlight(input, 'python')
self.assertEqual(value, expected) self.assertEqual(value, expected)
@ -84,12 +82,14 @@ class HighlightAPITestCase(TestCase):
rst Syntax thats not valid must not raise an exception (SystemMessage) rst Syntax thats not valid must not raise an exception (SystemMessage)
""" """
# (SEVERE/4) Missing matching underline for section title overline. # (SEVERE/4) Missing matching underline for section title overline.
input = dedent(""" input = dedent(
"""
========================= =========================
Generate 15 random numbers Generate 15 random numbers
70 180 3 179 192 117 75 72 90 190 49 159 63 14 55 70 180 3 179 192 117 75 72 90 190 49 159 63 14 55
========================= =========================
""") """
)
try: try:
RestructuredTextHighlighter().highlight(input) RestructuredTextHighlighter().highlight(input)
except Exception as e: except Exception as e:

View file

@ -15,7 +15,6 @@ config = apps.get_app_config('dpaste')
class SnippetTestCase(TestCase): class SnippetTestCase(TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
self.new_url = reverse('snippet_new') self.new_url = reverse('snippet_new')
@ -132,7 +131,6 @@ class SnippetTestCase(TestCase):
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertEqual(Snippet.objects.count(), 0) self.assertEqual(Snippet.objects.count(), 0)
def test_snippet_notfound(self): def test_snippet_notfound(self):
url = reverse('snippet_details', kwargs={'snippet_id': 'abcd'}) url = reverse('snippet_details', kwargs={'snippet_id': 'abcd'})
response = self.client.get(url, follow=True) response = self.client.get(url, follow=True)
@ -203,8 +201,12 @@ class SnippetTestCase(TestCase):
def test_raw(self): def test_raw(self):
data = self.valid_form_data() data = self.valid_form_data()
self.client.post(self.new_url, data, follow=True) self.client.post(self.new_url, data, follow=True)
response = self.client.get(reverse('snippet_details_raw', kwargs={ response = self.client.get(
'snippet_id': Snippet.objects.all()[0].secret_id})) reverse(
'snippet_details_raw',
kwargs={'snippet_id': Snippet.objects.all()[0].secret_id},
)
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, data['content']) self.assertContains(response, data['content'])
@ -217,22 +219,23 @@ class SnippetTestCase(TestCase):
def test_xss_text_lexer(self): def test_xss_text_lexer(self):
# Simple 'text' lexer # Simple 'text' lexer
data = self.valid_form_data(content=self.XSS_ORIGINAL, data = self.valid_form_data(
lexer=config.PLAIN_TEXT_SYMBOL) content=self.XSS_ORIGINAL, lexer=config.PLAIN_TEXT_SYMBOL
)
response = self.client.post(self.new_url, data, follow=True) response = self.client.post(self.new_url, data, follow=True)
self.assertContains(response, self.XSS_ESCAPED) self.assertContains(response, self.XSS_ESCAPED)
def test_xss_code_lexer(self): def test_xss_code_lexer(self):
# Simple 'code' lexer # Simple 'code' lexer
data = self.valid_form_data(content=self.XSS_ORIGINAL, data = self.valid_form_data(
lexer=config.PLAIN_CODE_SYMBOL) content=self.XSS_ORIGINAL, lexer=config.PLAIN_CODE_SYMBOL
)
response = self.client.post(self.new_url, data, follow=True) response = self.client.post(self.new_url, data, follow=True)
self.assertContains(response, self.XSS_ESCAPED) self.assertContains(response, self.XSS_ESCAPED)
def test_xss_pygments_lexer(self): def test_xss_pygments_lexer(self):
# Pygments based lexer # Pygments based lexer
data = self.valid_form_data(content=self.XSS_ORIGINAL, data = self.valid_form_data(content=self.XSS_ORIGINAL, lexer='python')
lexer='python')
response = self.client.post(self.new_url, data, follow=True) response = self.client.post(self.new_url, data, follow=True)
self.assertContains(response, self.XSS_ESCAPED) self.assertContains(response, self.XSS_ESCAPED)
@ -253,8 +256,9 @@ class SnippetTestCase(TestCase):
def test_snippet_history_delete_all(self): def test_snippet_history_delete_all(self):
# Empty list, delete all raises no error # Empty list, delete all raises no error
response = self.client.post(reverse('snippet_history'), response = self.client.post(
{'delete': 1}, follow=True) reverse('snippet_history'), {'delete': 1}, follow=True
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 0) self.assertEqual(Snippet.objects.count(), 0)
@ -266,8 +270,9 @@ class SnippetTestCase(TestCase):
self.assertEqual(Snippet.objects.count(), 2) self.assertEqual(Snippet.objects.count(), 2)
# Delete all of them # Delete all of them
response = self.client.post(reverse('snippet_history'), response = self.client.post(
{'delete': 1}, follow=True) reverse('snippet_history'), {'delete': 1}, follow=True
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(Snippet.objects.count(), 0) self.assertEqual(Snippet.objects.count(), 0)
@ -315,7 +320,6 @@ class SnippetTestCase(TestCase):
PygmentsHighlighter().highlight('code', 'python') PygmentsHighlighter().highlight('code', 'python')
PygmentsHighlighter().highlight('code', 'doesnotexist') PygmentsHighlighter().highlight('code', 'doesnotexist')
def test_random_slug_generation(self): def test_random_slug_generation(self):
""" """
Set the max length of a slug to 1, so we wont have more than 60 Set the max length of a slug to 1, so we wont have more than 60
@ -325,8 +329,9 @@ class SnippetTestCase(TestCase):
""" """
for i in range(0, 100): for i in range(0, 100):
Snippet.objects.create(content='foobar') Snippet.objects.create(content='foobar')
slug_list = Snippet.objects.values_list( slug_list = Snippet.objects.values_list('secret_id', flat=True).order_by(
'secret_id', flat=True).order_by('published') 'published'
)
self.assertEqual(len(set(slug_list)), 100) self.assertEqual(len(set(slug_list)), 100)
def test_leading_white_is_retained_in_db(self): def test_leading_white_is_retained_in_db(self):

View file

@ -7,18 +7,21 @@ from .. import views
L = getattr(settings, 'DPASTE_SLUG_LENGTH', 4) L = getattr(settings, 'DPASTE_SLUG_LENGTH', 4)
urlpatterns = [ urlpatterns = [
url(r'^$', url(r'^$', views.SnippetView.as_view(), name='snippet_new'),
views.SnippetView.as_view(), name='snippet_new'), url(
r'^about/$',
url(r'^about/$', TemplateView.as_view(template_name='dpaste/about.html'),
TemplateView.as_view(template_name='dpaste/about.html'), name='dpaste_about'), name='dpaste_about',
),
url(r'^history/$', url(r'^history/$', views.SnippetHistory.as_view(), name='snippet_history'),
views.SnippetHistory.as_view(), name='snippet_history'), url(
r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/?$' % L,
url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/?$' % L, views.SnippetDetailView.as_view(),
views.SnippetDetailView.as_view(), name='snippet_details'), name='snippet_details',
),
url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/raw/?$' % L, url(
views.SnippetRawView.as_view(), name='snippet_details_raw'), r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/raw/?$' % L,
views.SnippetRawView.as_view(),
name='snippet_details_raw',
),
] ]

View file

@ -4,5 +4,5 @@ from django.views.decorators.csrf import csrf_exempt
from ..views import APIView from ..views import APIView
urlpatterns = [ urlpatterns = [
url(r'^api/$', csrf_exempt(APIView.as_view()), name='dpaste_api_create_snippet'), url(r'^api/$', csrf_exempt(APIView.as_view()), name='dpaste_api_create_snippet')
] ]

View file

@ -3,13 +3,19 @@ import difflib
import json import json
from django.apps import apps from django.apps import apps
from django.http import Http404, HttpResponse, HttpResponseBadRequest, \ from django.http import (
HttpResponseRedirect Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.views.defaults import page_not_found as django_page_not_found, \ from django.views.defaults import (
server_error as django_server_error page_not_found as django_page_not_found,
server_error as django_server_error,
)
from django.views.generic import FormView from django.views.generic import FormView
from django.views.generic.base import TemplateView, View from django.views.generic.base import TemplateView, View
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
@ -28,18 +34,18 @@ config = apps.get_app_config('dpaste')
# Snippet Handling # Snippet Handling
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class SnippetView(FormView): class SnippetView(FormView):
""" """
Create a new snippet. Create a new snippet.
""" """
form_class = SnippetForm form_class = SnippetForm
template_name = 'dpaste/new.html' template_name = 'dpaste/new.html'
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(SnippetView, self).get_form_kwargs() kwargs = super(SnippetView, self).get_form_kwargs()
kwargs.update({ kwargs.update({'request': self.request})
'request': self.request,
})
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
@ -52,6 +58,7 @@ class SnippetDetailView(SnippetView, DetailView):
Details list view of a snippet. Handles the actual view, reply and Details list view of a snippet. Handles the actual view, reply and
tree/diff view. tree/diff view.
""" """
queryset = Snippet.objects.all() queryset = Snippet.objects.all()
template_name = 'dpaste/details.html' template_name = 'dpaste/details.html'
slug_url_kwarg = 'snippet_id' slug_url_kwarg = 'snippet_id'
@ -78,8 +85,10 @@ class SnippetDetailView(SnippetView, DetailView):
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
if (snippet.expire_type == Snippet.EXPIRE_ONETIME and if (
snippet.view_count >= config.ONETIME_LIMIT): snippet.expire_type == Snippet.EXPIRE_ONETIME
and snippet.view_count >= config.ONETIME_LIMIT
):
snippet.delete() snippet.delete()
raise Http404() raise Http404()
@ -91,10 +100,7 @@ class SnippetDetailView(SnippetView, DetailView):
def get_initial(self): def get_initial(self):
snippet = self.get_object() snippet = self.get_object()
return { return {'content': snippet.content, 'lexer': snippet.lexer}
'content': snippet.content,
'lexer': snippet.lexer,
}
def form_valid(self, form): def form_valid(self, form):
snippet = form.save(parent=self.get_object()) snippet = form.save(parent=self.get_object())
@ -114,7 +120,7 @@ class SnippetDetailView(SnippetView, DetailView):
snippet.content.splitlines(), snippet.content.splitlines(),
ugettext('Previous Snippet'), ugettext('Previous Snippet'),
ugettext('Current Snippet'), ugettext('Current Snippet'),
n=1 n=1,
) )
diff_code = '\n'.join(d).strip() diff_code = '\n'.join(d).strip()
highlighted = PygmentsHighlighter().render(diff_code, 'diff') highlighted = PygmentsHighlighter().render(diff_code, 'diff')
@ -126,10 +132,12 @@ class SnippetDetailView(SnippetView, DetailView):
self.object = self.get_object() self.object = self.get_object()
ctx = super(SnippetDetailView, self).get_context_data(**kwargs) ctx = super(SnippetDetailView, self).get_context_data(**kwargs)
ctx.update({ ctx.update(
{
'wordwrap': self.object.lexer in highlight.LEXER_WORDWRAP, 'wordwrap': self.object.lexer in highlight.LEXER_WORDWRAP,
'diff': self.get_snippet_diff(), 'diff': self.get_snippet_diff(),
}) }
)
return ctx return ctx
@ -137,6 +145,7 @@ class SnippetRawView(SnippetDetailView):
""" """
Display the raw content of a snippet Display the raw content of a snippet
""" """
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
snippet = self.get_object() snippet = self.get_object()
response = HttpResponse(snippet.content) response = HttpResponse(snippet.content)
@ -150,6 +159,7 @@ class SnippetHistory(TemplateView):
Display the last `n` snippets created by this user (and saved in his Display the last `n` snippets created by this user (and saved in his
session). session).
""" """
template_name = 'dpaste/history.html' template_name = 'dpaste/history.html'
def get_user_snippets(self): def get_user_snippets(self):
@ -169,9 +179,7 @@ class SnippetHistory(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super(SnippetHistory, self).get_context_data(**kwargs) ctx = super(SnippetHistory, self).get_context_data(**kwargs)
ctx.update({ ctx.update({'snippet_list': self.get_user_snippets()})
'snippet_list': self.get_user_snippets(),
})
return ctx return ctx
@ -184,6 +192,7 @@ class APIView(View):
""" """
API View API View
""" """
def _format_default(self, s): def _format_default(self, s):
""" """
The default response is the snippet URL wrapped in quotes. The default response is the snippet URL wrapped in quotes.
@ -204,11 +213,13 @@ class APIView(View):
The `json` format export. The `json` format export.
""" """
base_url = config.get_base_url(request=self.request) base_url = config.get_base_url(request=self.request)
return json.dumps({ return json.dumps(
{
'url': '{url}{path}'.format(url=base_url, path=s.get_absolute_url()), 'url': '{url}{path}'.format(url=base_url, path=s.get_absolute_url()),
'content': s.content, 'content': s.content,
'lexer': s.lexer, 'lexer': s.lexer,
}) }
)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
content = request.POST.get('content', '') content = request.POST.get('content', '')
@ -222,13 +233,18 @@ class APIView(View):
# We need at least a lexer or a filename # We need at least a lexer or a filename
if not lexer and not filename: if not lexer and not filename:
return HttpResponseBadRequest('No lexer or filename given. Unable to ' return HttpResponseBadRequest(
'determine a highlight. Valid lexers are: %s' % ', '.join(highlight.LEXER_KEYS)) 'No lexer or filename given. Unable to '
'determine a highlight. Valid lexers are: %s'
% ', '.join(highlight.LEXER_KEYS)
)
# A lexer is given, check if its valid at all # A lexer is given, check if its valid at all
if lexer and lexer not in highlight.LEXER_KEYS: if lexer and lexer not in highlight.LEXER_KEYS:
return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % ( return HttpResponseBadRequest(
lexer, ', '.join(highlight.LEXER_KEYS))) 'Invalid lexer "%s" given. Valid lexers are: %s'
% (lexer, ', '.join(highlight.LEXER_KEYS))
)
# No lexer is given, but we have a filename, try to get the lexer # No lexer is given, but we have a filename, try to get the lexer
# out of it. In case Pygments cannot determine the lexer of the # out of it. In case Pygments cannot determine the lexer of the
@ -245,17 +261,16 @@ class APIView(View):
if expires not in expire_options: if expires not in expire_options:
return HttpResponseBadRequest( return HttpResponseBadRequest(
'Invalid expire choice "{}" given. Valid values are: {}'.format( 'Invalid expire choice "{}" given. Valid values are: {}'.format(
expires, ', '.join(expire_options))) expires, ', '.join(expire_options)
)
)
expires, expire_type = get_expire_values(expires) expires, expire_type = get_expire_values(expires)
else: else:
expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24) expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24)
expire_type = Snippet.EXPIRE_TIME expire_type = Snippet.EXPIRE_TIME
snippet = Snippet.objects.create( snippet = Snippet.objects.create(
content=content, content=content, lexer=lexer, expires=expires, expire_type=expire_type
lexer=lexer,
expires=expires,
expire_type=expire_type,
) )
# Custom formatter for the API response # Custom formatter for the API response
@ -272,6 +287,7 @@ class APIView(View):
# handle them here. # handle them here.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def page_not_found(request, exception=None, template_name='dpaste/404.html'): def page_not_found(request, exception=None, template_name='dpaste/404.html'):
return django_page_not_found(request, exception, template_name=template_name) return django_page_not_found(request, exception, template_name=template_name)

View file

@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
""" """
import os import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpaste.settings.local") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpaste.settings.local")
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()