Make all views class-based.

This commit is contained in:
Martin Mahner 2016-03-24 20:14:13 +01:00
parent 82d49d0ed1
commit 1c2e26c930
4 changed files with 228 additions and 207 deletions

View file

@ -2,34 +2,34 @@
<form method="post" action="" class="form-horizontal"> <form method="post" action="" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{{ snippet_form.non_field_errors }} {{ form.non_field_errors }}
<div style="display: none;">{{ snippet_form.title }}</div> <div style="display: none;">{{ form.title }}</div>
<div class=" <div class="
control-group control-group
form-content form-content
superenter superenter
{% if is_new %}autofocus{% endif %} {% if not object %}autofocus{% endif %}
{% if snippet_form.content.errors %}error{% endif %} {% if form.content.errors %}error{% endif %}
"> ">
{{ snippet_form.content }} {{ form.content }}
</div> </div>
<div class="control-group form-options"> <div class="control-group form-options">
<div class="form-options-lexer <div class="form-options-lexer
{% if snippet_form.lexer.errors %}control-group error{% endif %}"> {% if form.lexer.errors %}control-group error{% endif %}">
<div class="input-append"> <div class="input-append">
{{ snippet_form.lexer }} {{ form.lexer }}
</div> </div>
{% for error in snippet_form.lexer.errors %} {% for error in form.lexer.errors %}
<span class="help-inline">{{ error }}</span> <span class="help-inline">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
<div class="form-options-expire"> <div class="form-options-expire">
{{ snippet_form.expires.errors }} {{ form.expires.errors }}
<div class="input-prepend"> <div class="input-prepend">
<span class="add-on"><i class="icon-trash" title="{% trans "Expire in" %}"></i></span> <span class="add-on"><i class="icon-trash" title="{% trans "Expire in" %}"></i></span>
{{ snippet_form.expires }} {{ form.expires }}
</div> </div>
</div> </div>
</div> </div>

View file

@ -6,14 +6,15 @@ from .. import views
L = getattr(settings, 'DPASTE_SLUG_LENGTH', 4) L = getattr(settings, 'DPASTE_SLUG_LENGTH', 4)
urlpatterns = [ 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'^$', views.SnippetView.as_view(), name='snippet_new'),
url(r'^diff/$', views.snippet_diff, name='snippet_diff'), url(r'^diff/$', views.SnippetDiffView.as_view(), name='snippet_diff'),
url(r'^history/$', views.snippet_history, name='snippet_history'), url(r'^history/$', views.SnippetHistory.as_view(), name='snippet_history'),
url(r'^delete/$', views.snippet_delete, name='snippet_delete'), url(r'^delete/$', views.SnippetDeleteView.as_view(), name='snippet_delete'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/?$' % L, views.snippet_details, name='snippet_details'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/delete/$' % L, views.snippet_delete, name='snippet_delete'), url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/?$' % L, views.SnippetDetailView.as_view(), name='snippet_details'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/delete/$' % L, views.SnippetDeleteView.as_view(), name='snippet_delete'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/gist/$' % L, views.snippet_gist, name='snippet_gist'), url(r'^(?P<snippet_id>[a-zA-Z0-9]{%d,})/gist/$' % L, views.snippet_gist, name='snippet_gist'),
url(r'^(?P<snippet_id>[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<snippet_id>[a-zA-Z0-9]{%d,})/raw/?$' % L, views.SnippetRawView.as_view(), name='snippet_details_raw'),
] ]

View file

@ -1,7 +1,7 @@
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from ..views import snippet_api from ..views import APIView
urlpatterns = [ urlpatterns = [
url(r'^api/$', snippet_api, name='dpaste_api_create_snippet'), url(r'^api/$', APIView.as_view(), name='dpaste_api_create_snippet'),
] ]

View file

@ -9,12 +9,14 @@ from django.core.urlresolvers import reverse
from django.db.models import Count from django.db.models import Count
from django.http import (Http404, HttpResponse, HttpResponseBadRequest, from django.http import (Http404, HttpResponse, HttpResponseBadRequest,
HttpResponseRedirect) HttpResponseRedirect)
from django.shortcuts import get_object_or_404, render_to_response from django.shortcuts import get_object_or_404
from django.template.context import RequestContext from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt 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 page_not_found as django_page_not_found
from django.views.defaults import server_error as django_server_error 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.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound from pygments.util import ClassNotFound
@ -33,183 +35,196 @@ template_globals = {
# Snippet Handling # 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. Create a new snippet.
""" """
if request.method == "POST": form_class = SnippetForm
snippet_form = SnippetForm(data=request.POST, request=request) template_name = 'dpaste/snippet_new.html'
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)
template_context = { def get_form_kwargs(self):
'snippet_form': snippet_form, kwargs = super(SnippetView, self).get_form_kwargs()
'lexer_list': LEXER_LIST, kwargs.update({
'is_new': True, 'request': self.request,
} })
return kwargs
return render_to_response( def get_context_data(self, **kwargs):
template_name, ctx = super(SnippetView, self).get_context_data(**kwargs)
template_context, ctx.update(template_globals)
RequestContext(request, 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 Details list view of a snippet. Handles the actual view, reply and
tree/diff view. 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 def get(self, *args, **kwargs):
if snippet.expire_type == Snippet.EXPIRE_ONETIME \ snippet = self.get_object()
and snippet.view_count >= ONETIME_LIMIT:
snippet.delete()
raise Http404()
# Increase the view count of the snippet # One-Time snippet get deleted if the view count matches our limit
snippet.view_count += 1 if snippet.expire_type == Snippet.EXPIRE_ONETIME \
snippet.save() and snippet.view_count >= ONETIME_LIMIT:
snippet.delete()
raise Http404()
tree = snippet.get_root() # Increase the view count of the snippet
tree = tree.get_descendants(include_self=True) snippet.view_count += 1
snippet.save()
new_snippet_initial = { return super(SnippetDetailView, self).get(*args, **kwargs)
'content': snippet.content,
'lexer': snippet.lexer,
}
if request.method == "POST": def get_initial(self):
snippet_form = SnippetForm( snippet = self.get_object()
data=request.POST, return {
request=request, 'content': snippet.content,
initial=new_snippet_initial) 'lexer': snippet.lexer,
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)
template_context = { def form_valid(self, form):
'snippet_form': snippet_form, snippet = form.save(parent=self.get_object())
'snippet': snippet, return HttpResponseRedirect(snippet.get_absolute_url())
'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),
}
response = render_to_response( def get_context_data(self, **kwargs):
template_name, self.object = snippet = self.get_object()
template_context, tree = snippet.get_root().get_descendants(include_self=True)
RequestContext(request, template_globals)
)
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['Content-Type'] = 'text/plain;charset=UTF-8'
response['X-Content-Type-Options'] = 'nosniff' response['X-Content-Type-Options'] = 'nosniff'
return response 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 Delete a snippet. This is allowed by anybody as long as he knows the
snippet id. I got too many manual requests to do this, mostly for legal snippet id. I got too many manual requests to do this, mostly for legal
reasons and the chance to abuse this is not given anyway, since snippets reasons and the chance to abuse this is not given anyway, since snippets
always expire. always expire.
""" """
snippet_id = snippet_id or request.POST.get('snippet_id') def dispatch(self, request, *args, **kwargs):
if not snippet_id: snippet_id = self.kwargs.get('snippet_id') or request.POST.get('snippet_id')
raise Http404('No snippet id given') if not snippet_id:
snippet = get_object_or_404(Snippet, secret_id=snippet_id) raise Http404('No snippet id given')
snippet.delete() snippet = get_object_or_404(Snippet, secret_id=snippet_id)
return HttpResponseRedirect(reverse('snippet_new')) 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 Display the last `n` snippets created by this user (and saved in his
session). session).
""" """
snippet_list = None template_name = 'dpaste/snippet_list.html'
snippet_id_list = request.session.get('snippet_list', None)
if snippet_id_list:
snippet_list = Snippet.objects.filter(pk__in=snippet_id_list)
if 'delete-all' in request.GET: def get(self, request, *args, **kwargs):
if snippet_list: snippet_id_list = request.session.get('snippet_list', [])
for s in snippet_list: self.snippet_list = Snippet.objects.filter(pk__in=snippet_id_list)
s.delete()
return HttpResponseRedirect(reverse('snippet_history'))
template_context = { if 'delete-all' in request.GET:
'snippets_max': getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 10), self.snippet_list.delete()
'snippet_list': snippet_list, return HttpResponseRedirect(reverse('snippet_history'))
} return super(SnippetHistory, self).get(request, *args, **kwargs)
return render_to_response( def get_context_data(self, **kwargs):
template_name, ctx = super(SnippetHistory, self).get_context_data(**kwargs)
template_context, ctx.update(template_globals)
RequestContext(request, 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. Display a diff between two given snippet secret ids.
""" """
if request.GET.get('a') and request.GET.get('a').isdigit() \ template_name = 'dpaste/snippet_diff.html'
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.')
class DiffText(object): def get(self, request, *args, **kwargs):
pass """
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: def get_diff(self):
d = difflib.unified_diff( class DiffText(object):
fileA.content.splitlines(), pass
fileB.content.splitlines(),
'Original',
'Current',
lineterm=''
)
diff.content = '\n'.join(d).strip() diff = DiffText()
diff.lexer = 'diff'
else:
diff.content = _(u'No changes were made between this two files.')
diff.lexer = 'text'
template_context = { if self.fileA.content != self.fileB.content:
'snippet': diff, d = difflib.unified_diff(
'fileA': fileA, self.fileA.content.splitlines(),
'fileB': fileB, self.fileB.content.splitlines(),
} 'Original',
'Current',
lineterm=''
)
return render_to_response( diff.content = '\n'.join(d).strip()
template_name, diff.lexer = 'diff'
template_context, else:
RequestContext(request, template_globals) 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 def snippet_gist(request, snippet_id): # pragma: no cover
@ -247,22 +262,22 @@ def snippet_gist(request, snippet_id): # pragma: no cover
# Static pages # 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 A rather static page, we need a view just to display a couple of
statistics. statistics.
""" """
template_context = { template_name = 'dpaste/about.html'
'total': Snippet.objects.count(),
'stats': Snippet.objects.values('lexer').annotate(
count=Count('lexer')).order_by('-count')[:5],
}
return render_to_response( def get_context_data(self, **kwargs):
template_name, ctx = super(AboutView, self).get_context_data(**kwargs)
template_context, ctx.update(template_globals)
RequestContext(request, 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, '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: class APIView(View):
return HttpResponseBadRequest('No content given') """
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 content:
if not lexer and not filename: return HttpResponseBadRequest('No content given')
return HttpResponseBadRequest('No lexer or filename given. Unable to '
'determine a highlight. Valid lexers are: %s' % ', '.join(LEXER_KEYS))
# A lexer is given, check if its valid at all # We need at least a lexer or a filename
if lexer and lexer not in LEXER_KEYS: if not lexer and not filename:
return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % ( return HttpResponseBadRequest('No lexer or filename given. Unable to '
lexer, ', '.join(LEXER_KEYS))) '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. # A lexer is given, check if its valid at all
# In case Pygments cannot determine the lexer of the filename, we fallback if lexer and lexer not in LEXER_KEYS:
# to 'plain' code. return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % (
if not lexer and filename: lexer, ', '.join(LEXER_KEYS)))
try:
lexer_cls = get_lexer_for_filename(filename)
lexer = lexer_cls.aliases[0]
except (ClassNotFound, IndexError):
lexer = PLAIN_CODE
if expires: # No lexer is given, but we have a filename, try to get the lexer out of it.
expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()] # In case Pygments cannot determine the lexer of the filename, we fallback
if not expires in expire_options: # to 'plain' code.
return HttpResponseBadRequest('Invalid expire choice "{}" given. ' if not lexer and filename:
'Valid values are: {}'.format(expires, ', '.join(expire_options))) try:
expires, expire_type = get_expire_values(expires) lexer_cls = get_lexer_for_filename(filename)
else: lexer = lexer_cls.aliases[0]
expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24 * 30) except (ClassNotFound, IndexError):
expire_type = Snippet.EXPIRE_TIME lexer = PLAIN_CODE
s = Snippet.objects.create( if expires:
content=content, expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()]
lexer=lexer, if not expires in expire_options:
expires=expires, return HttpResponseBadRequest('Invalid expire choice "{}" given. '
expire_type=expire_type, 'Valid values are: {}'.format(expires, ', '.join(expire_options)))
) expires, expire_type = get_expire_values(expires)
s.save() else:
expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24 * 30)
expire_type = Snippet.EXPIRE_TIME
if not format in FORMAT_MAPPING: s = Snippet.objects.create(
response = _format_default(s) content=content,
else: lexer=lexer,
response = FORMAT_MAPPING[format](s) 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)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------