From 1c2e26c93033ac07b85e2e4894664db242e1e15b Mon Sep 17 00:00:00 2001 From: Martin Mahner Date: Thu, 24 Mar 2016 20:14:13 +0100 Subject: [PATCH] Make all views class-based. --- dpaste/templates/dpaste/snippet_form.html | 20 +- dpaste/urls/dpaste.py | 17 +- dpaste/urls/dpaste_api.py | 4 +- dpaste/views.py | 394 ++++++++++++---------- 4 files changed, 228 insertions(+), 207 deletions(-) diff --git a/dpaste/templates/dpaste/snippet_form.html b/dpaste/templates/dpaste/snippet_form.html index 361236a..631e8b6 100644 --- a/dpaste/templates/dpaste/snippet_form.html +++ b/dpaste/templates/dpaste/snippet_form.html @@ -2,34 +2,34 @@
{% csrf_token %} - {{ snippet_form.non_field_errors }} -
{{ snippet_form.title }}
+ {{ form.non_field_errors }} +
{{ form.title }}
- {{ snippet_form.content }} + {{ form.content }}
+ {% if form.lexer.errors %}control-group error{% endif %}">
- {{ snippet_form.lexer }} + {{ form.lexer }}
- {% for error in snippet_form.lexer.errors %} + {% for error in form.lexer.errors %} {{ error }} {% endfor %}
- {{ snippet_form.expires.errors }} + {{ form.expires.errors }}
- {{ snippet_form.expires }} + {{ form.expires }}
diff --git a/dpaste/urls/dpaste.py b/dpaste/urls/dpaste.py index 20713a5..b1a745b 100644 --- a/dpaste/urls/dpaste.py +++ b/dpaste/urls/dpaste.py @@ -6,14 +6,15 @@ from .. import views L = getattr(settings, 'DPASTE_SLUG_LENGTH', 4) urlpatterns = [ - url(r'^about/$', views.about, name='dpaste_about'), + url(r'^about/$', views.AboutView.as_view(), name='dpaste_about'), - url(r'^$', views.snippet_new, name='snippet_new'), - url(r'^diff/$', views.snippet_diff, name='snippet_diff'), - url(r'^history/$', views.snippet_history, name='snippet_history'), - url(r'^delete/$', views.snippet_delete, name='snippet_delete'), - url(r'^(?P[a-zA-Z0-9]{%d,})/?$' % L, views.snippet_details, name='snippet_details'), - url(r'^(?P[a-zA-Z0-9]{%d,})/delete/$' % L, views.snippet_delete, name='snippet_delete'), + url(r'^$', views.SnippetView.as_view(), name='snippet_new'), + url(r'^diff/$', views.SnippetDiffView.as_view(), name='snippet_diff'), + url(r'^history/$', views.SnippetHistory.as_view(), name='snippet_history'), + url(r'^delete/$', views.SnippetDeleteView.as_view(), name='snippet_delete'), + + url(r'^(?P[a-zA-Z0-9]{%d,})/?$' % L, views.SnippetDetailView.as_view(), name='snippet_details'), + url(r'^(?P[a-zA-Z0-9]{%d,})/delete/$' % L, views.SnippetDeleteView.as_view(), name='snippet_delete'), url(r'^(?P[a-zA-Z0-9]{%d,})/gist/$' % L, views.snippet_gist, name='snippet_gist'), - url(r'^(?P[a-zA-Z0-9]{%d,})/raw/?$' % L, views.snippet_details, {'template_name': 'dpaste/snippet_details_raw.html', 'is_raw': True}, name='snippet_details_raw'), + 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 df419e4..e3dcc47 100644 --- a/dpaste/urls/dpaste_api.py +++ b/dpaste/urls/dpaste_api.py @@ -1,7 +1,7 @@ from django.conf.urls import patterns, url -from ..views import snippet_api +from ..views import APIView urlpatterns = [ - url(r'^api/$', snippet_api, name='dpaste_api_create_snippet'), + url(r'^api/$', APIView.as_view(), name='dpaste_api_create_snippet'), ] diff --git a/dpaste/views.py b/dpaste/views.py index e1e6fb0..b1e6e46 100644 --- a/dpaste/views.py +++ b/dpaste/views.py @@ -9,12 +9,14 @@ from django.core.urlresolvers import reverse from django.db.models import Count from django.http import (Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect) -from django.shortcuts import get_object_or_404, render_to_response -from django.template.context import RequestContext +from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator from django.utils.translation import ugettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from django.views.defaults import page_not_found as django_page_not_found from django.views.defaults import server_error as django_server_error +from django.views.generic.base import View, TemplateView +from django.views.generic.detail import DetailView from pygments.lexers import get_lexer_for_filename from pygments.util import ClassNotFound @@ -33,183 +35,196 @@ template_globals = { # Snippet Handling # ----------------------------------------------------------------------------- -def snippet_new(request, template_name='dpaste/snippet_new.html'): +from django.views.generic import FormView + +class SnippetView(FormView): """ Create a new snippet. """ - if request.method == "POST": - snippet_form = SnippetForm(data=request.POST, request=request) - if snippet_form.is_valid(): - new_snippet = snippet_form.save() - url = new_snippet.get_absolute_url() - return HttpResponseRedirect(url) - else: - snippet_form = SnippetForm(request=request) + form_class = SnippetForm + template_name = 'dpaste/snippet_new.html' - template_context = { - 'snippet_form': snippet_form, - 'lexer_list': LEXER_LIST, - 'is_new': True, - } + def get_form_kwargs(self): + kwargs = super(SnippetView, self).get_form_kwargs() + kwargs.update({ + 'request': self.request, + }) + return kwargs - return render_to_response( - template_name, - template_context, - RequestContext(request, template_globals) - ) + def get_context_data(self, **kwargs): + ctx = super(SnippetView, self).get_context_data(**kwargs) + ctx.update(template_globals) + ctx.update({ + 'lexer_list': LEXER_LIST, + }) + return ctx + + def form_valid(self, form): + snippet = form.save() + return HttpResponseRedirect(snippet.get_absolute_url()) -def snippet_details(request, snippet_id, template_name='dpaste/snippet_details.html', is_raw=False): +class SnippetDetailView(SnippetView, DetailView): """ Details list view of a snippet. Handles the actual view, reply and tree/diff view. """ - snippet = get_object_or_404(Snippet, secret_id=snippet_id) + queryset = Snippet.objects.all() + template_name = 'dpaste/snippet_details.html' + slug_url_kwarg = 'snippet_id' + slug_field = 'secret_id' - # One-Time snippet get deleted if the view count matches our limit - if snippet.expire_type == Snippet.EXPIRE_ONETIME \ - and snippet.view_count >= ONETIME_LIMIT: - snippet.delete() - raise Http404() + def get(self, *args, **kwargs): + snippet = self.get_object() - # Increase the view count of the snippet - snippet.view_count += 1 - snippet.save() + # One-Time snippet get deleted if the view count matches our limit + if snippet.expire_type == Snippet.EXPIRE_ONETIME \ + and snippet.view_count >= ONETIME_LIMIT: + snippet.delete() + raise Http404() - tree = snippet.get_root() - tree = tree.get_descendants(include_self=True) + # Increase the view count of the snippet + snippet.view_count += 1 + snippet.save() - new_snippet_initial = { - 'content': snippet.content, - 'lexer': snippet.lexer, - } + return super(SnippetDetailView, self).get(*args, **kwargs) - if request.method == "POST": - snippet_form = SnippetForm( - data=request.POST, - request=request, - initial=new_snippet_initial) - if snippet_form.is_valid(): - new_snippet = snippet_form.save(parent=snippet) - url = new_snippet.get_absolute_url() - return HttpResponseRedirect(url) - else: - snippet_form = SnippetForm( - initial=new_snippet_initial, - request=request) + def get_initial(self): + snippet = self.get_object() + return { + 'content': snippet.content, + 'lexer': snippet.lexer, + } - template_context = { - 'snippet_form': snippet_form, - 'snippet': snippet, - 'lexers': LEXER_LIST, - 'lines': range(snippet.get_linecount()), - 'tree': tree, - 'wordwrap': snippet.lexer in LEXER_WORDWRAP and 'True' or 'False', - 'gist': getattr(settings, 'DPASTE_ENABLE_GIST', True), - } + def form_valid(self, form): + snippet = form.save(parent=self.get_object()) + return HttpResponseRedirect(snippet.get_absolute_url()) - response = render_to_response( - template_name, - template_context, - RequestContext(request, template_globals) - ) + def get_context_data(self, **kwargs): + self.object = snippet = self.get_object() + tree = snippet.get_root().get_descendants(include_self=True) - if is_raw: + ctx = super(SnippetDetailView, self).get_context_data(**kwargs) + ctx.update(template_globals) + ctx.update({ + 'lines': range(snippet.get_linecount()), + 'tree': tree, + 'wordwrap': snippet.lexer in LEXER_WORDWRAP and 'True' or 'False', + 'gist': getattr(settings, 'DPASTE_ENABLE_GIST', True), + }) + return ctx + + +class SnippetRawView(SnippetDetailView): + """ + Display the raw content of a snippet + """ + template_name = 'dpaste/snippet_details_raw.html' + + def render_to_response(self, context, **response_kwargs): + snippet = self.get_object() + response = HttpResponse(snippet.content) response['Content-Type'] = 'text/plain;charset=UTF-8' response['X-Content-Type-Options'] = 'nosniff' return response - else: - return response -def snippet_delete(request, snippet_id=None): +class SnippetDeleteView(View): """ 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 reasons and the chance to abuse this is not given anyway, since snippets always expire. """ - snippet_id = snippet_id or request.POST.get('snippet_id') - if not snippet_id: - raise Http404('No snippet id given') - snippet = get_object_or_404(Snippet, secret_id=snippet_id) - snippet.delete() - return HttpResponseRedirect(reverse('snippet_new')) + def dispatch(self, request, *args, **kwargs): + snippet_id = self.kwargs.get('snippet_id') or request.POST.get('snippet_id') + if not snippet_id: + raise Http404('No snippet id given') + snippet = get_object_or_404(Snippet, secret_id=snippet_id) + snippet.delete() + return HttpResponseRedirect(reverse('snippet_new')) -def snippet_history(request, template_name='dpaste/snippet_list.html'): +class SnippetHistory(TemplateView): """ Display the last `n` snippets created by this user (and saved in his session). """ - snippet_list = None - snippet_id_list = request.session.get('snippet_list', None) - if snippet_id_list: - snippet_list = Snippet.objects.filter(pk__in=snippet_id_list) + template_name = 'dpaste/snippet_list.html' - if 'delete-all' in request.GET: - if snippet_list: - for s in snippet_list: - s.delete() - return HttpResponseRedirect(reverse('snippet_history')) + def get(self, request, *args, **kwargs): + snippet_id_list = request.session.get('snippet_list', []) + self.snippet_list = Snippet.objects.filter(pk__in=snippet_id_list) - template_context = { - 'snippets_max': getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 10), - 'snippet_list': snippet_list, - } + if 'delete-all' in request.GET: + self.snippet_list.delete() + return HttpResponseRedirect(reverse('snippet_history')) + return super(SnippetHistory, self).get(request, *args, **kwargs) - return render_to_response( - template_name, - template_context, - RequestContext(request, template_globals) - ) + def get_context_data(self, **kwargs): + ctx = super(SnippetHistory, self).get_context_data(**kwargs) + ctx.update(template_globals) + ctx.update({ + 'snippets_max': getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 10), + 'snippet_list': self.snippet_list, + }) + return ctx -def snippet_diff(request, template_name='dpaste/snippet_diff.html'): +class SnippetDiffView(TemplateView): """ Display a diff between two given snippet secret ids. """ - if request.GET.get('a') and request.GET.get('a').isdigit() \ - and request.GET.get('b') and request.GET.get('b').isdigit(): - try: - fileA = Snippet.objects.get(pk=int(request.GET.get('a'))) - fileB = Snippet.objects.get(pk=int(request.GET.get('b'))) - except ObjectDoesNotExist: - return HttpResponseBadRequest(u'Selected file(s) does not exist.') - else: - return HttpResponseBadRequest(u'You must select two snippets.') + template_name = 'dpaste/snippet_diff.html' - class DiffText(object): - pass + def get(self, request, *args, **kwargs): + """ + Some validation around input files we will compare later. + """ + if request.GET.get('a') and request.GET.get('a').isdigit() \ + and request.GET.get('b') and request.GET.get('b').isdigit(): + try: + self.fileA = Snippet.objects.get(pk=int(request.GET.get('a'))) + self.fileB = Snippet.objects.get(pk=int(request.GET.get('b'))) + except ObjectDoesNotExist: + return HttpResponseBadRequest(u'Selected file(s) does not exist.') + else: + return HttpResponseBadRequest(u'You must select two snippets.') - diff = DiffText() + return super(SnippetDiffView, self).get(request, *args, **kwargs) - if fileA.content != fileB.content: - d = difflib.unified_diff( - fileA.content.splitlines(), - fileB.content.splitlines(), - 'Original', - 'Current', - lineterm='' - ) + def get_diff(self): + class DiffText(object): + pass - diff.content = '\n'.join(d).strip() - diff.lexer = 'diff' - else: - diff.content = _(u'No changes were made between this two files.') - diff.lexer = 'text' + diff = DiffText() - template_context = { - 'snippet': diff, - 'fileA': fileA, - 'fileB': fileB, - } + if self.fileA.content != self.fileB.content: + d = difflib.unified_diff( + self.fileA.content.splitlines(), + self.fileB.content.splitlines(), + 'Original', + 'Current', + lineterm='' + ) - return render_to_response( - template_name, - template_context, - RequestContext(request, template_globals) - ) + diff.content = '\n'.join(d).strip() + diff.lexer = 'diff' + else: + diff.content = _(u'No changes were made between this two files.') + diff.lexer = 'text' + + return diff + + def get_context_data(self, **kwargs): + ctx = super(SnippetDiffView, self).get_context_data(**kwargs) + ctx.update(template_globals) + ctx.update({ + 'snippet': self.get_diff(), + 'fileA': self.fileA, + 'fileB': self.fileB, + }) + return ctx def snippet_gist(request, snippet_id): # pragma: no cover @@ -247,22 +262,22 @@ def snippet_gist(request, snippet_id): # pragma: no cover # Static pages # ----------------------------------------------------------------------------- -def about(request, template_name='dpaste/about.html'): +class AboutView(TemplateView): """ A rather static page, we need a view just to display a couple of statistics. """ - template_context = { - 'total': Snippet.objects.count(), - 'stats': Snippet.objects.values('lexer').annotate( - count=Count('lexer')).order_by('-count')[:5], - } + template_name = 'dpaste/about.html' - return render_to_response( - template_name, - template_context, - RequestContext(request, template_globals) - ) + def get_context_data(self, **kwargs): + ctx = super(AboutView, self).get_context_data(**kwargs) + ctx.update(template_globals) + ctx.update({ + 'total': Snippet.objects.count(), + 'stats': Snippet.objects.values('lexer').annotate( + count=Count('lexer')).order_by('-count')[:5], + }) + return ctx # ----------------------------------------------------------------------------- @@ -293,61 +308,66 @@ FORMAT_MAPPING = { 'json': _format_json, } -@csrf_exempt -def snippet_api(request): - content = request.POST.get('content', '').strip() - lexer = request.POST.get('lexer', LEXER_DEFAULT).strip() - filename = request.POST.get('filename', '').strip() - expires = request.POST.get('expires', '').strip() - format = request.POST.get('format', 'default').strip() - if not content: - return HttpResponseBadRequest('No content given') +class APIView(View): + """ + API View + """ + @method_decorator(csrf_exempt) + def post(self, request, *args, **kwargs): + content = request.POST.get('content', '').strip() + lexer = request.POST.get('lexer', LEXER_DEFAULT).strip() + filename = request.POST.get('filename', '').strip() + expires = request.POST.get('expires', '').strip() + format = request.POST.get('format', 'default').strip() - # 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(LEXER_KEYS)) + if not content: + return HttpResponseBadRequest('No content given') - # A lexer is given, check if its valid at all - if lexer and lexer not in LEXER_KEYS: - return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % ( - lexer, ', '.join(LEXER_KEYS))) + # 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(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 filename, we fallback - # to 'plain' code. - if not lexer and filename: - try: - lexer_cls = get_lexer_for_filename(filename) - lexer = lexer_cls.aliases[0] - except (ClassNotFound, IndexError): - lexer = PLAIN_CODE + # A lexer is given, check if its valid at all + if lexer and lexer not in LEXER_KEYS: + return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % ( + lexer, ', '.join(LEXER_KEYS))) - if expires: - expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()] - if not expires in expire_options: - return HttpResponseBadRequest('Invalid expire choice "{}" given. ' - 'Valid values are: {}'.format(expires, ', '.join(expire_options))) - expires, expire_type = get_expire_values(expires) - else: - expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24 * 30) - expire_type = Snippet.EXPIRE_TIME + # 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 filename, we fallback + # to 'plain' code. + if not lexer and filename: + try: + lexer_cls = get_lexer_for_filename(filename) + lexer = lexer_cls.aliases[0] + except (ClassNotFound, IndexError): + lexer = PLAIN_CODE - s = Snippet.objects.create( - content=content, - lexer=lexer, - expires=expires, - expire_type=expire_type, - ) - s.save() + if expires: + expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()] + if not expires in expire_options: + return HttpResponseBadRequest('Invalid expire choice "{}" given. ' + 'Valid values are: {}'.format(expires, ', '.join(expire_options))) + expires, expire_type = get_expire_values(expires) + else: + expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24 * 30) + expire_type = Snippet.EXPIRE_TIME - if not format in FORMAT_MAPPING: - response = _format_default(s) - else: - response = FORMAT_MAPPING[format](s) + s = Snippet.objects.create( + content=content, + lexer=lexer, + expires=expires, + expire_type=expire_type, + ) + s.save() - return HttpResponse(response) + if not format in FORMAT_MAPPING: + response = _format_default(s) + else: + response = FORMAT_MAPPING[format](s) + + return HttpResponse(response) # -----------------------------------------------------------------------------