mirror of
https://github.com/DarrenOfficial/dpaste.git
synced 2024-11-15 16:12:51 +11:00
309 lines
10 KiB
Python
309 lines
10 KiB
Python
from __future__ import unicode_literals
|
|
|
|
import datetime
|
|
import difflib
|
|
import json
|
|
|
|
from django.conf import settings
|
|
from django.db.models import Count
|
|
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.generic import FormView
|
|
from django.views.generic.base import TemplateView, View
|
|
from django.views.generic.detail import DetailView
|
|
from pygments.lexers import get_lexer_for_filename
|
|
from pygments.util import ClassNotFound
|
|
|
|
from dpaste import highlight
|
|
from dpaste.forms import EXPIRE_CHOICES, SnippetForm, get_expire_values
|
|
from dpaste.models import ONETIME_LIMIT, Snippet
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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,
|
|
})
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(SnippetView, self).get_context_data(**kwargs)
|
|
ctx.update({
|
|
'lexer_list': highlight.LEXER_LIST,
|
|
})
|
|
return ctx
|
|
|
|
def form_valid(self, form):
|
|
snippet = form.save()
|
|
return HttpResponseRedirect(snippet.get_absolute_url())
|
|
|
|
|
|
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'
|
|
slug_field = 'secret_id'
|
|
|
|
def post(self, *args, **kwargs):
|
|
"""
|
|
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.
|
|
"""
|
|
if 'delete' in self.request.POST:
|
|
snippet = get_object_or_404(Snippet, secret_id=self.kwargs['snippet_id'])
|
|
snippet.delete()
|
|
|
|
# Append `#` so #delete goes away in Firefox
|
|
url = '{0}#'.format(reverse('snippet_new'))
|
|
return HttpResponseRedirect(url)
|
|
|
|
return super(SnippetDetailView, self).post(*args, **kwargs)
|
|
|
|
def get(self, *args, **kwargs):
|
|
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 >= ONETIME_LIMIT:
|
|
snippet.delete()
|
|
raise Http404()
|
|
|
|
# Increase the view count of the snippet
|
|
snippet.view_count += 1
|
|
snippet.save()
|
|
|
|
return super(SnippetDetailView, self).get(*args, **kwargs)
|
|
|
|
def get_initial(self):
|
|
snippet = self.get_object()
|
|
return {
|
|
'content': snippet.content,
|
|
'lexer': snippet.lexer,
|
|
}
|
|
|
|
def form_valid(self, form):
|
|
snippet = form.save(parent=self.get_object())
|
|
return HttpResponseRedirect(snippet.get_absolute_url())
|
|
|
|
def get_snippet_diff(self):
|
|
snippet = self.get_object()
|
|
|
|
if not snippet.parent_id:
|
|
return None
|
|
|
|
if snippet.content == snippet.parent.content:
|
|
return None
|
|
|
|
d = difflib.unified_diff(
|
|
snippet.parent.content.splitlines(),
|
|
snippet.content.splitlines(),
|
|
ugettext('Previous Snippet'),
|
|
ugettext('Current Snippet'),
|
|
n=1
|
|
)
|
|
diff_code = '\n'.join(d).strip()
|
|
highlighted = highlight.pygmentize(diff_code, lexer_name='diff')
|
|
|
|
# Remove blank lines
|
|
return highlighted.replace('\n\n', '\n')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
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(),
|
|
})
|
|
return ctx
|
|
|
|
|
|
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)
|
|
response['Content-Type'] = 'text/plain;charset=UTF-8'
|
|
response['X-Content-Type-Options'] = 'nosniff'
|
|
return response
|
|
|
|
|
|
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):
|
|
snippet_id_list = self.request.session.get('snippet_list', [])
|
|
return Snippet.objects.filter(pk__in=snippet_id_list)
|
|
|
|
def post(self, *args, **kwargs):
|
|
"""
|
|
Delete all user snippets at once.
|
|
"""
|
|
if 'delete' in self.request.POST:
|
|
self.get_user_snippets().delete()
|
|
|
|
# Append `#` so #delete goes away in Firefox
|
|
url = '{0}#'.format(reverse('snippet_history'))
|
|
return HttpResponseRedirect(url)
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(SnippetHistory, self).get_context_data(**kwargs)
|
|
ctx.update({
|
|
'snippets_max': getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 10),
|
|
'snippet_list': self.get_user_snippets(),
|
|
})
|
|
return ctx
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Static pages
|
|
# -----------------------------------------------------------------------------
|
|
|
|
class AboutView(TemplateView):
|
|
"""
|
|
A rather static page, we need a view just to display a couple of
|
|
statistics.
|
|
"""
|
|
template_name = 'dpaste/about.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(AboutView, self).get_context_data(**kwargs)
|
|
ctx.update({
|
|
'total': Snippet.objects.count(),
|
|
'stats': Snippet.objects.values('lexer').annotate(
|
|
count=Count('lexer')).order_by('-count')[:5],
|
|
})
|
|
return ctx
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# API Handling
|
|
# -----------------------------------------------------------------------------
|
|
|
|
def _format_default(s):
|
|
"""The default response is the snippet URL wrapped in quotes."""
|
|
return u'"%s%s"' % (BASE_URL, s.get_absolute_url())
|
|
|
|
def _format_url(s):
|
|
"""The `url` format returns the snippet URL, no quotes, but a linebreak after."""
|
|
return u'%s%s\n' % (BASE_URL, s.get_absolute_url())
|
|
|
|
def _format_json(s):
|
|
"""The `json` format export."""
|
|
return json.dumps({
|
|
'url': u'%s%s' % (BASE_URL, s.get_absolute_url()),
|
|
'content': s.content,
|
|
'lexer': s.lexer,
|
|
})
|
|
|
|
|
|
BASE_URL = getattr(settings, 'DPASTE_BASE_URL', 'https://dpaste.de')
|
|
|
|
FORMAT_MAPPING = {
|
|
'default': _format_default,
|
|
'url': _format_url,
|
|
'json': _format_json,
|
|
}
|
|
|
|
|
|
class APIView(View):
|
|
"""
|
|
API View
|
|
"""
|
|
def post(self, request, *args, **kwargs):
|
|
content = request.POST.get('content', '')
|
|
lexer = request.POST.get('lexer', highlight.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.strip():
|
|
return HttpResponseBadRequest('No content given')
|
|
|
|
# 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))
|
|
|
|
# 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)))
|
|
|
|
# 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 = highlight.PLAIN_CODE
|
|
|
|
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
|
|
|
|
s = Snippet.objects.create(
|
|
content=content,
|
|
lexer=lexer,
|
|
expires=expires,
|
|
expire_type=expire_type,
|
|
)
|
|
s.save()
|
|
|
|
if not format in FORMAT_MAPPING:
|
|
response = _format_default(s)
|
|
else:
|
|
response = FORMAT_MAPPING[format](s)
|
|
|
|
return HttpResponse(response)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Custom 404 and 500 views. Its easier to integrate this as a app if we
|
|
# handle them here.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
def page_not_found(request, exception=None, template_name='dpaste/404.html'):
|
|
if not exception: # Django <1.8
|
|
return django_page_not_found(request, template_name=template_name)
|
|
return django_page_not_found(request, exception, template_name=template_name)
|
|
|
|
def server_error(request, template_name='dpaste/500.html'):
|
|
return django_server_error(request, template_name=template_name) # pragma: no cover
|