diff --git a/dpaste/__init__.py b/dpaste/__init__.py index 6a76256..965b81d 100644 --- a/dpaste/__init__.py +++ b/dpaste/__init__.py @@ -1,9 +1,7 @@ VERSION = (3, 0, 'a', 1) __version__ = '{major}.{minor}{rest}'.format( - major=VERSION[0], - minor=VERSION[1], - rest=''.join(str(i) for i in VERSION[2:]) + major=VERSION[0], minor=VERSION[1], rest=''.join(str(i) for i in VERSION[2:]) ) default_app_config = 'dpaste.apps.dpasteAppConfig' diff --git a/dpaste/apps.py b/dpaste/apps.py index 35303c2..171085f 100644 --- a/dpaste/apps.py +++ b/dpaste/apps.py @@ -96,10 +96,11 @@ class dpasteAppConfig(AppConfig): from dpaste.highlight import ( PlainTextHighlighter, MarkdownHighlighter, - RestructuredTextHighlighter + RestructuredTextHighlighter, ) + return [ - (self.PLAIN_TEXT_SYMBOL, 'Plain Text', PlainTextHighlighter), + (self.PLAIN_TEXT_SYMBOL, 'Plain Text', PlainTextHighlighter), ('_markdown', 'Markdown', MarkdownHighlighter), ('_rst', 'reStructuredText', RestructuredTextHighlighter), ] @@ -116,10 +117,8 @@ class dpasteAppConfig(AppConfig): If the Highlight Class is not given, PygmentsHighlighter is used. """ - from dpaste.highlight import ( - PlainCodeHighlighter, - SolidityHighlighter - ) + from dpaste.highlight import PlainCodeHighlighter, SolidityHighlighter + return [ (self.PLAIN_CODE_SYMBOL, 'Plain Code', PlainCodeHighlighter), ('abap', 'ABAP'), @@ -204,6 +203,7 @@ class dpasteAppConfig(AppConfig): """ if apps.is_installed('django.contrib.sites'): from django.contrib.sites.shortcuts import get_current_site + site = get_current_site(request) if site: return 'https://{0}'.format(site.domain) diff --git a/dpaste/forms.py b/dpaste/forms.py index 7c73d6a..4091a0b 100644 --- a/dpaste/forms.py +++ b/dpaste/forms.py @@ -29,34 +29,27 @@ class SnippetForm(forms.ModelForm): label=_('Content'), widget=forms.Textarea(attrs={'placeholder': _('Awesome code goes here...')}), max_length=config.MAX_CONTENT_LENGTH, - strip=False + strip=False, ) lexer = forms.ChoiceField( - label=_('Lexer'), - initial=LEXER_DEFAULT, - choices=LEXER_CHOICES + label=_('Lexer'), initial=LEXER_DEFAULT, choices=LEXER_CHOICES ) expires = forms.ChoiceField( - label=_('Expires'), - choices=config.EXPIRE_CHOICES, - initial=config.EXPIRE_DEFAULT + label=_('Expires'), choices=config.EXPIRE_CHOICES, initial=config.EXPIRE_DEFAULT ) # Honeypot field title = forms.CharField( label=_('Title'), required=False, - widget=forms.TextInput(attrs={'autocomplete': 'off'}) + widget=forms.TextInput(attrs={'autocomplete': 'off'}), ) class Meta: model = Snippet - fields = ( - 'content', - 'lexer', - ) + fields = ('content', 'lexer') def __init__(self, request, *args, **kwargs): super(SnippetForm, self).__init__(*args, **kwargs) diff --git a/dpaste/highlight.py b/dpaste/highlight.py index 3ad41ef..4e7a2b7 100644 --- a/dpaste/highlight.py +++ b/dpaste/highlight.py @@ -19,6 +19,7 @@ config = apps.get_app_config('dpaste') # Highlight Code Snippets # ----------------------------------------------------------------------------- + class Highlighter(object): template_name = 'dpaste/highlight/code.html' @@ -47,6 +48,7 @@ class Highlighter(object): class PlainTextHighlighter(Highlighter): """Plain Text. Just replace linebreaks.""" + template_name = 'dpaste/highlight/text.html' def highlight(self, code_string, **kwargs): @@ -55,20 +57,33 @@ class PlainTextHighlighter(Highlighter): class MarkdownHighlighter(PlainTextHighlighter): """Markdown""" - extensions = ('tables', 'fenced-code', 'footnotes', 'autolink,', - 'strikethrough', 'underline', 'quote', 'superscript', - 'math') + + extensions = ( + 'tables', + 'fenced-code', + 'footnotes', + 'autolink,', + 'strikethrough', + 'underline', + 'quote', + 'superscript', + 'math', + ) render_flags = ('skip-html',) def highlight(self, code_string, **kwargs): import misaka - return mark_safe(misaka.html(code_string, - extensions=self.extensions, - render_flags=self.render_flags)) + + return mark_safe( + misaka.html( + code_string, extensions=self.extensions, render_flags=self.render_flags + ) + ) class RestructuredTextHighlighter(PlainTextHighlighter): """Restructured Text""" + rst_part_name = 'html_body' publish_args = { 'writer_name': 'html5_polyglot', @@ -78,11 +93,12 @@ class RestructuredTextHighlighter(PlainTextHighlighter): 'halt_level': 5, 'report_level': 2, 'warning_stream': '/dev/null', - } + }, } def highlight(self, code_string, **kwargs): from docutils.core import publish_parts + self.publish_args['source'] = code_string parts = publish_parts(**self.publish_args) return mark_safe(parts[self.rst_part_name]) @@ -93,6 +109,7 @@ class RestructuredTextHighlighter(PlainTextHighlighter): class NakedHtmlFormatter(HtmlFormatter): """Pygments HTML formatter with no further HTML tags.""" + def wrap(self, source, outfile): return self._wrap_code(source) @@ -105,11 +122,14 @@ class PlainCodeHighlighter(Highlighter): """ Plain Code. No highlighting but Pygments like span tags around each line. """ + def highlight(self, code_string, **kwargs): - return '\n'.join([ - '{}'.format(escape(l) or '​') + return '\n'.join( + [ + '{}'.format(escape(l) or '​') for l in code_string.splitlines() - ]) + ] + ) class PygmentsHighlighter(Highlighter): @@ -117,6 +137,7 @@ class PygmentsHighlighter(Highlighter): Highlight code string with Pygments. The lexer is automatically determined by the lexer name. """ + formatter = NakedHtmlFormatter() lexer = None lexer_fallback = PythonLexer() @@ -139,6 +160,7 @@ class SolidityHighlighter(PygmentsHighlighter): # 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() @@ -167,12 +189,13 @@ def get_highlighter_class(lexer_name): # Generate a list of Form choices of all lexer. LEXER_CHOICES = ( (_('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 -LEXER_KEYS = [i[0] for i in config.TEXT_FORMATTER] + \ - [i[0] for i in config.CODE_FORMATTER] +LEXER_KEYS = [i[0] for i in config.TEXT_FORMATTER] + [ + i[0] for i in config.CODE_FORMATTER +] # The default lexer which we fallback in case of # an error or if not supplied in an API call. diff --git a/dpaste/management/commands/cleanup_snippets.py b/dpaste/management/commands/cleanup_snippets.py index b593034..50328e0 100644 --- a/dpaste/management/commands/cleanup_snippets.py +++ b/dpaste/management/commands/cleanup_snippets.py @@ -8,14 +8,15 @@ class Command(BaseCommand): help = "Purges snippets that are expired" def add_arguments(self, parser): - parser.add_argument('--dry-run', action='store_true', dest='dry_run', - help='Don\'t do anything.'), + parser.add_argument( + '--dry-run', action='store_true', dest='dry_run', help='Don\'t do anything.' + ), def handle(self, *args, **options): deleteable_snippets = Snippet.objects.filter( expires__isnull=False, expire_type=Snippet.EXPIRE_TIME, - expires__lte=timezone.now() + expires__lte=timezone.now(), ) if len(deleteable_snippets) == 0: self.stdout.write(u"No snippets to delete.") diff --git a/dpaste/models.py b/dpaste/models.py index 57c0234..c27a716 100644 --- a/dpaste/models.py +++ b/dpaste/models.py @@ -16,11 +16,14 @@ R = SystemRandom() def generate_secret_id(length): if length > config.SLUG_LENGTH: - logger.warning('Slug creation triggered a duplicate, ' - 'consider increasing the SLUG_LENGTH.') + logger.warning( + 'Slug creation triggered a duplicate, ' + 'consider increasing the SLUG_LENGTH.' + ) - secret_id = ''.join([R.choice(config.SLUG_CHOICES) - for i in range(length or config.SLUG_LENGTH)]) + secret_id = ''.join( + [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 try: @@ -30,7 +33,7 @@ def generate_secret_id(length): # Otherwise create a new slug which is +1 character longer # than the previous one. - return generate_secret_id(length=length+1) + return generate_secret_id(length=length + 1) @python_2_unicode_compatible @@ -45,21 +48,24 @@ class Snippet(models.Model): ) 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')) - lexer = models.CharField( - _('Lexer'), max_length=30, default=highlight.LEXER_DEFAULT) - published = models.DateTimeField( - _('Published'), auto_now_add=True) + lexer = models.CharField(_('Lexer'), max_length=30, default=highlight.LEXER_DEFAULT) + published = models.DateTimeField(_('Published'), auto_now_add=True) expire_type = models.PositiveSmallIntegerField( - _('Expire Type'), choices=EXPIRE_CHOICES, default=EXPIRE_CHOICES[0][0]) - expires = models.DateTimeField( - _('Expires'), blank=True, null=True) - view_count = models.PositiveIntegerField( - _('View count'), default=0) + _('Expire Type'), choices=EXPIRE_CHOICES, default=EXPIRE_CHOICES[0][0] + ) + expires = models.DateTimeField(_('Expires'), blank=True, null=True) + view_count = models.PositiveIntegerField(_('View count'), default=0) parent = models.ForeignKey( - 'self', null=True, blank=True, verbose_name=_('Parent Snippet'), - related_name='children', on_delete=models.CASCADE) + 'self', + null=True, + blank=True, + verbose_name=_('Parent Snippet'), + related_name='children', + on_delete=models.CASCADE, + ) class Meta: ordering = ('-published',) diff --git a/dpaste/settings/base.py b/dpaste/settings/base.py index 6f3079d..cca6712 100644 --- a/dpaste/settings/base.py +++ b/dpaste/settings/base.py @@ -49,9 +49,7 @@ USE_I18N = True USE_L10N = False LANGUAGE_CODE = 'en' -LANGUAGES = ( - ('en', 'English'), -) +LANGUAGES = (('en', 'English'),) # LOCALE_PATHS = ( # os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'locale')), @@ -63,9 +61,7 @@ LANGUAGES = ( STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' -STATICFILES_DIRS = ( - os.path.join(PROJECT_DIR, 'build'), -) +STATICFILES_DIRS = (os.path.join(PROJECT_DIR, 'build'),) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', @@ -107,9 +103,9 @@ TEMPLATES = [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.template.context_processors.i18n', - ], + ] }, - }, + } ] INSTALLED_APPS = [ @@ -146,16 +142,12 @@ CSP_STYLE_SRC = ("'self'", "'unsafe-inline'") LOGGING = { 'version': 1, 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, + 'filters': {'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}}, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' + 'class': 'django.utils.log.AdminEmailHandler', } }, 'loggers': { @@ -163,6 +155,6 @@ LOGGING = { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, - }, - } + } + }, } diff --git a/dpaste/settings/tests.py b/dpaste/settings/tests.py index 5581728..eb8b89b 100644 --- a/dpaste/settings/tests.py +++ b/dpaste/settings/tests.py @@ -4,9 +4,4 @@ Settings for the test suite from .base import * -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } -} +DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}} diff --git a/dpaste/tests/test_api.py b/dpaste/tests/test_api.py index 08cdf61..a8f6579 100644 --- a/dpaste/tests/test_api.py +++ b/dpaste/tests/test_api.py @@ -11,7 +11,6 @@ config = apps.get_app_config('dpaste') class SnippetAPITestCase(TestCase): - def setUp(self): self.api_url = reverse('dpaste_api_create_snippet') self.client = Client(enforce_csrf_checks=True) @@ -84,7 +83,6 @@ class SnippetAPITestCase(TestCase): self.assertTrue(content.startswith('http')) self.assertTrue(content.endswith('\n')) - def test_json_format(self): """ The 'new' url format is just the link with a linebreak. @@ -92,7 +90,7 @@ class SnippetAPITestCase(TestCase): data = { 'content': u"Hello Wörld.\n\tGood Bye", 'format': 'json', - 'lexer': 'haskell' + 'lexer': 'haskell', } response = self.client.post(self.api_url, data) @@ -102,6 +100,7 @@ class SnippetAPITestCase(TestCase): self.assertEqual(Snippet.objects.count(), 1) from json import loads + json_data = loads(content) # Response is valid json, containing, content, lexer and url @@ -118,7 +117,7 @@ class SnippetAPITestCase(TestCase): data = { 'content': u"Hello Wörld.\n\tGood Bye", 'format': 'broken-format', - 'lexer': 'haskell' + 'lexer': 'haskell', } response = self.client.post(self.api_url, data) @@ -129,26 +128,25 @@ class SnippetAPITestCase(TestCase): """ A broken lexer will fail loudly. """ - data = { - 'content': u"Hello Wörld.\n\tGood Bye", - 'lexer': 'foobar' - } + data = {'content': u"Hello Wörld.\n\tGood Bye", 'lexer': 'foobar'} response = self.client.post(self.api_url, data) self.assertEqual(response.status_code, 400) self.assertEqual(Snippet.objects.count(), 0) def test_expire_choices_none_given(self): # No expire choice given will set a default expiration of one month - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye"}) + response = self.client.post( + self.api_url, {'content': u"Hello Wörld.\n\tGood Bye"} + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertTrue(Snippet.objects.all()[0].expires) def test_expire_choices_invalid_given(self): # A expire choice that does not exist returns a BadRequest - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'foobar'}) + response = self.client.post( + self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'foobar'} + ) self.assertEqual(response.status_code, 400) self.assertEqual(Snippet.objects.count(), 0) @@ -156,37 +154,45 @@ class SnippetAPITestCase(TestCase): Test all the different expiration choices. We dont actually test the deletion, since thats handled in the `test_snippet` section. """ + def test_valid_expiration_choices_onetime(self): - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'onetime'}) + response = self.client.post( + self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'onetime'} + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_ONETIME) def test_valid_expiration_choices_never(self): - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'never'}) + response = self.client.post( + self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'never'} + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_KEEP) def test_valid_expiration_choices_hour(self): - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600}) + response = self.client.post( + self.api_url, {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600} + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertTrue(Snippet.objects.all()[0].expires) def test_valid_expiration_choices_week(self): - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 7}) + response = self.client.post( + self.api_url, + {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 7}, + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertTrue(Snippet.objects.all()[0].expires) def test_valid_expiration_choices_month(self): - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 30}) + response = self.client.post( + self.api_url, + {'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 30}, + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertTrue(Snippet.objects.all()[0].expires) @@ -195,22 +201,24 @@ class SnippetAPITestCase(TestCase): """ No lexer and no filename given returns a BadRequest. """ - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", - 'lexer': '', - 'filename': '' - }) + response = self.client.post( + self.api_url, + {'content': u"Hello Wörld.\n\tGood Bye", 'lexer': '', 'filename': ''}, + ) self.assertEqual(response.status_code, 400) def test_filename_given(self): """ No lexer and a Python filename will set a 'python' lexer. """ - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", - 'lexer': '', - 'filename': 'helloworld.py' - }) + response = self.client.post( + self.api_url, + { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': '', + 'filename': 'helloworld.py', + }, + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.all()[0].lexer, 'python') @@ -219,11 +227,14 @@ class SnippetAPITestCase(TestCase): """ A unknown filename will create a 'plain' code snippet. """ - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", - 'lexer': '', - 'filename': 'helloworld.helloworld' - }) + response = self.client.post( + self.api_url, + { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': '', + 'filename': 'helloworld.helloworld', + }, + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) 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. """ - response = self.client.post(self.api_url, { - 'content': u"Hello Wörld.\n\tGood Bye", - 'lexer': 'php', - 'filename': 'helloworld.py' - }) + response = self.client.post( + self.api_url, + { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': 'php', + 'filename': 'helloworld.py', + }, + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.all()[0].lexer, 'php') @@ -248,5 +262,3 @@ class SnippetAPITestCase(TestCase): content = ' one\n two\n three\n four' self.client.post(self.api_url, {'content': content}) self.assertEqual(Snippet.objects.all()[0].content, content) - - diff --git a/dpaste/tests/test_highlight.py b/dpaste/tests/test_highlight.py index 50234ff..f093536 100644 --- a/dpaste/tests/test_highlight.py +++ b/dpaste/tests/test_highlight.py @@ -4,12 +4,14 @@ from textwrap import dedent from django.test import TestCase -from dpaste.highlight import PlainCodeHighlighter, PygmentsHighlighter, \ - RestructuredTextHighlighter +from dpaste.highlight import ( + PlainCodeHighlighter, + PygmentsHighlighter, + RestructuredTextHighlighter, +) class HighlightAPITestCase(TestCase): - def test_plain_code(self): """ 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. """ - input = (' vär=1\n' - ' vär=2\n' - ' vär=3\n' - ' vär=4') + input = ' vär=1\n' ' vär=2\n' ' vär=3\n' ' vär=4' expected = ( ' vär=1\n' ' vär=2\n' ' vär=3\n' - ' vär=4') + ' vär=4' + ) value = PlainCodeHighlighter().highlight(input) self.assertEqual(value, expected) @@ -67,15 +67,13 @@ class HighlightAPITestCase(TestCase): """ Whitespace on the first line is retained, also on subsequent lines. """ - input = (' var\n' - ' var\n' - ' var\n' - ' var') + input = ' var\n' ' var\n' ' var\n' ' var' expected = ( ' var\n' ' var\n' ' var\n' - ' var\n') + ' var\n' + ) value = PygmentsHighlighter().highlight(input, 'python') self.assertEqual(value, expected) @@ -84,12 +82,14 @@ class HighlightAPITestCase(TestCase): rst Syntax thats not valid must not raise an exception (SystemMessage) """ # (SEVERE/4) Missing matching underline for section title overline. - input = dedent(""" + input = dedent( + """ ========================= Generate 15 random numbers 70 180 3 179 192 117 75 72 90 190 49 159 63 14 55 ========================= - """) + """ + ) try: RestructuredTextHighlighter().highlight(input) except Exception as e: diff --git a/dpaste/tests/test_snippet.py b/dpaste/tests/test_snippet.py index f16bde5..2e82b94 100644 --- a/dpaste/tests/test_snippet.py +++ b/dpaste/tests/test_snippet.py @@ -15,7 +15,6 @@ config = apps.get_app_config('dpaste') class SnippetTestCase(TestCase): - def setUp(self): self.client = Client() self.new_url = reverse('snippet_new') @@ -132,7 +131,6 @@ class SnippetTestCase(TestCase): self.assertEqual(response.status_code, 404) self.assertEqual(Snippet.objects.count(), 0) - def test_snippet_notfound(self): url = reverse('snippet_details', kwargs={'snippet_id': 'abcd'}) response = self.client.get(url, follow=True) @@ -203,8 +201,12 @@ class SnippetTestCase(TestCase): def test_raw(self): data = self.valid_form_data() self.client.post(self.new_url, data, follow=True) - response = self.client.get(reverse('snippet_details_raw', kwargs={ - 'snippet_id': Snippet.objects.all()[0].secret_id})) + response = self.client.get( + reverse( + 'snippet_details_raw', + kwargs={'snippet_id': Snippet.objects.all()[0].secret_id}, + ) + ) self.assertEqual(response.status_code, 200) self.assertContains(response, data['content']) @@ -217,22 +219,23 @@ class SnippetTestCase(TestCase): def test_xss_text_lexer(self): # Simple 'text' lexer - data = self.valid_form_data(content=self.XSS_ORIGINAL, - lexer=config.PLAIN_TEXT_SYMBOL) + data = self.valid_form_data( + content=self.XSS_ORIGINAL, lexer=config.PLAIN_TEXT_SYMBOL + ) response = self.client.post(self.new_url, data, follow=True) self.assertContains(response, self.XSS_ESCAPED) def test_xss_code_lexer(self): # Simple 'code' lexer - data = self.valid_form_data(content=self.XSS_ORIGINAL, - lexer=config.PLAIN_CODE_SYMBOL) + data = self.valid_form_data( + content=self.XSS_ORIGINAL, lexer=config.PLAIN_CODE_SYMBOL + ) response = self.client.post(self.new_url, data, follow=True) self.assertContains(response, self.XSS_ESCAPED) def test_xss_pygments_lexer(self): # Pygments based lexer - data = self.valid_form_data(content=self.XSS_ORIGINAL, - lexer='python') + data = self.valid_form_data(content=self.XSS_ORIGINAL, lexer='python') response = self.client.post(self.new_url, data, follow=True) self.assertContains(response, self.XSS_ESCAPED) @@ -253,8 +256,9 @@ class SnippetTestCase(TestCase): def test_snippet_history_delete_all(self): # Empty list, delete all raises no error - response = self.client.post(reverse('snippet_history'), - {'delete': 1}, follow=True) + response = self.client.post( + reverse('snippet_history'), {'delete': 1}, follow=True + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 0) @@ -266,8 +270,9 @@ class SnippetTestCase(TestCase): self.assertEqual(Snippet.objects.count(), 2) # Delete all of them - response = self.client.post(reverse('snippet_history'), - {'delete': 1}, follow=True) + response = self.client.post( + reverse('snippet_history'), {'delete': 1}, follow=True + ) self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 0) @@ -315,7 +320,6 @@ class SnippetTestCase(TestCase): PygmentsHighlighter().highlight('code', 'python') PygmentsHighlighter().highlight('code', 'doesnotexist') - def test_random_slug_generation(self): """ 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): Snippet.objects.create(content='foobar') - slug_list = Snippet.objects.values_list( - 'secret_id', flat=True).order_by('published') + slug_list = Snippet.objects.values_list('secret_id', flat=True).order_by( + 'published' + ) self.assertEqual(len(set(slug_list)), 100) def test_leading_white_is_retained_in_db(self): diff --git a/dpaste/urls/dpaste.py b/dpaste/urls/dpaste.py index b39564f..fb00e5d 100644 --- a/dpaste/urls/dpaste.py +++ b/dpaste/urls/dpaste.py @@ -7,18 +7,21 @@ from .. import views L = getattr(settings, 'DPASTE_SLUG_LENGTH', 4) urlpatterns = [ - url(r'^$', - views.SnippetView.as_view(), name='snippet_new'), - - url(r'^about/$', - TemplateView.as_view(template_name='dpaste/about.html'), name='dpaste_about'), - - url(r'^history/$', - views.SnippetHistory.as_view(), name='snippet_history'), - - url(r'^(?P[a-zA-Z0-9]{%d,})/?$' % L, - views.SnippetDetailView.as_view(), name='snippet_details'), - - url(r'^(?P[a-zA-Z0-9]{%d,})/raw/?$' % L, - views.SnippetRawView.as_view(), name='snippet_details_raw'), + url(r'^$', views.SnippetView.as_view(), name='snippet_new'), + url( + r'^about/$', + TemplateView.as_view(template_name='dpaste/about.html'), + name='dpaste_about', + ), + url(r'^history/$', views.SnippetHistory.as_view(), name='snippet_history'), + url( + r'^(?P[a-zA-Z0-9]{%d,})/?$' % L, + views.SnippetDetailView.as_view(), + name='snippet_details', + ), + url( + r'^(?P[a-zA-Z0-9]{%d,})/raw/?$' % L, + views.SnippetRawView.as_view(), + name='snippet_details_raw', + ), ] diff --git a/dpaste/urls/dpaste_api.py b/dpaste/urls/dpaste_api.py index 6bd6990..650428f 100644 --- a/dpaste/urls/dpaste_api.py +++ b/dpaste/urls/dpaste_api.py @@ -4,5 +4,5 @@ from django.views.decorators.csrf import csrf_exempt from ..views import APIView 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') ] diff --git a/dpaste/views.py b/dpaste/views.py index d348357..b036f5a 100644 --- a/dpaste/views.py +++ b/dpaste/views.py @@ -3,13 +3,19 @@ import difflib import json from django.apps import apps -from django.http import Http404, HttpResponse, HttpResponseBadRequest, \ - HttpResponseRedirect +from django.http import ( + Http404, + HttpResponse, + HttpResponseBadRequest, + HttpResponseRedirect, +) from django.shortcuts import get_object_or_404 from django.urls import reverse from django.utils.translation import ugettext -from django.views.defaults import page_not_found as django_page_not_found, \ - server_error as django_server_error +from django.views.defaults import ( + page_not_found as django_page_not_found, + server_error as django_server_error, +) from django.views.generic import FormView from django.views.generic.base import TemplateView, View from django.views.generic.detail import DetailView @@ -28,18 +34,18 @@ config = apps.get_app_config('dpaste') # Snippet Handling # ----------------------------------------------------------------------------- + class SnippetView(FormView): """ Create a new snippet. """ + form_class = SnippetForm template_name = 'dpaste/new.html' def get_form_kwargs(self): kwargs = super(SnippetView, self).get_form_kwargs() - kwargs.update({ - 'request': self.request, - }) + kwargs.update({'request': self.request}) return kwargs 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 tree/diff view. """ + queryset = Snippet.objects.all() template_name = 'dpaste/details.html' slug_url_kwarg = 'snippet_id' @@ -78,8 +85,10 @@ class SnippetDetailView(SnippetView, DetailView): snippet = self.get_object() # One-Time snippet get deleted if the view count matches our limit - if (snippet.expire_type == Snippet.EXPIRE_ONETIME and - snippet.view_count >= config.ONETIME_LIMIT): + if ( + snippet.expire_type == Snippet.EXPIRE_ONETIME + and snippet.view_count >= config.ONETIME_LIMIT + ): snippet.delete() raise Http404() @@ -91,10 +100,7 @@ class SnippetDetailView(SnippetView, DetailView): def get_initial(self): snippet = self.get_object() - return { - 'content': snippet.content, - 'lexer': snippet.lexer, - } + return {'content': snippet.content, 'lexer': snippet.lexer} def form_valid(self, form): snippet = form.save(parent=self.get_object()) @@ -114,7 +120,7 @@ class SnippetDetailView(SnippetView, DetailView): snippet.content.splitlines(), ugettext('Previous Snippet'), ugettext('Current Snippet'), - n=1 + n=1, ) diff_code = '\n'.join(d).strip() highlighted = PygmentsHighlighter().render(diff_code, 'diff') @@ -126,10 +132,12 @@ class SnippetDetailView(SnippetView, DetailView): self.object = self.get_object() ctx = super(SnippetDetailView, self).get_context_data(**kwargs) - ctx.update({ - 'wordwrap': self.object.lexer in highlight.LEXER_WORDWRAP, - 'diff': self.get_snippet_diff(), - }) + ctx.update( + { + 'wordwrap': self.object.lexer in highlight.LEXER_WORDWRAP, + 'diff': self.get_snippet_diff(), + } + ) return ctx @@ -137,6 +145,7 @@ class SnippetRawView(SnippetDetailView): """ Display the raw content of a snippet """ + def render_to_response(self, context, **response_kwargs): snippet = self.get_object() response = HttpResponse(snippet.content) @@ -150,6 +159,7 @@ class SnippetHistory(TemplateView): Display the last `n` snippets created by this user (and saved in his session). """ + template_name = 'dpaste/history.html' def get_user_snippets(self): @@ -169,9 +179,7 @@ class SnippetHistory(TemplateView): def get_context_data(self, **kwargs): ctx = super(SnippetHistory, self).get_context_data(**kwargs) - ctx.update({ - 'snippet_list': self.get_user_snippets(), - }) + ctx.update({'snippet_list': self.get_user_snippets()}) return ctx @@ -184,6 +192,7 @@ class APIView(View): """ API View """ + def _format_default(self, s): """ The default response is the snippet URL wrapped in quotes. @@ -204,11 +213,13 @@ class APIView(View): The `json` format export. """ base_url = config.get_base_url(request=self.request) - return json.dumps({ - 'url': '{url}{path}'.format(url=base_url, path=s.get_absolute_url()), - 'content': s.content, - 'lexer': s.lexer, - }) + return json.dumps( + { + 'url': '{url}{path}'.format(url=base_url, path=s.get_absolute_url()), + 'content': s.content, + 'lexer': s.lexer, + } + ) def post(self, request, *args, **kwargs): content = request.POST.get('content', '') @@ -222,13 +233,18 @@ class APIView(View): # We need at least a lexer or a filename if not lexer and not filename: - return HttpResponseBadRequest('No lexer or filename given. Unable to ' - 'determine a highlight. Valid lexers are: %s' % ', '.join(highlight.LEXER_KEYS)) + return HttpResponseBadRequest( + '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 if lexer and lexer not in highlight.LEXER_KEYS: - return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % ( - lexer, ', '.join(highlight.LEXER_KEYS))) + return HttpResponseBadRequest( + '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 # 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: return HttpResponseBadRequest( 'Invalid expire choice "{}" given. Valid values are: {}'.format( - expires, ', '.join(expire_options))) + expires, ', '.join(expire_options) + ) + ) expires, expire_type = get_expire_values(expires) else: expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24) expire_type = Snippet.EXPIRE_TIME snippet = Snippet.objects.create( - content=content, - lexer=lexer, - expires=expires, - expire_type=expire_type, + content=content, lexer=lexer, expires=expires, expire_type=expire_type ) # Custom formatter for the API response @@ -272,6 +287,7 @@ class APIView(View): # handle them here. # ----------------------------------------------------------------------------- + def page_not_found(request, exception=None, template_name='dpaste/404.html'): return django_page_not_found(request, exception, template_name=template_name) diff --git a/dpaste/wsgi.py b/dpaste/wsgi.py index 4470c5f..6602e2e 100644 --- a/dpaste/wsgi.py +++ b/dpaste/wsgi.py @@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ """ import os + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpaste.settings.local") from django.core.wsgi import get_wsgi_application + application = get_wsgi_application()